<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-23815417</id><updated>2012-01-26T16:46:42.139Z</updated><title type='text'>Thoughts on XSLT</title><subtitle type='html'>Andrew Welch</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>54</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-23815417.post-6021615040647605792</id><published>2011-01-10T13:56:00.002Z</published><updated>2011-01-10T14:08:36.791Z</updated><title type='text'>Kernow 1.7 released</title><content type='html'>&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13px; color: rgb(34, 34, 34); line-height: 18px; "&gt;Kernow 1.7 is now available:&lt;br /&gt;&lt;ul style="padding-top: 0px; padding-right: 2.5em; padding-bottom: 0px; padding-left: 2.5em; margin-top: 0.5em; margin-right: 0px; margin-bottom: 0.5em; margin-left: 0px; line-height: 1.4; "&gt;&lt;li style="padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0.25em; margin-left: 0px; text-indent: 0px; "&gt;integrates LexEv (which now reports internal and parameter entities, along with CDATA sections, comments, numeric and entity refs, and the DOCTYPE)&lt;/li&gt;&lt;li style="padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0.25em; margin-left: 0px; text-indent: 0px; "&gt;On OSX and linux, it uses the "Nimbus" look and feel, on Windows it still uses native&lt;/li&gt;&lt;li style="padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0.25em; margin-left: 0px; text-indent: 0px; "&gt;includes the latest Saxon HE 9.3.0.2&lt;/li&gt;&lt;li style="padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0.25em; margin-left: 0px; text-indent: 0px; "&gt;directory transforms: input directory structure is now recreated in the output directory&lt;/li&gt;&lt;li style="padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0.25em; margin-left: 0px; text-indent: 0px; "&gt;files are correctly released, rather than held while Kernow is open&lt;/li&gt;&lt;li style="padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0.25em; margin-left: 0px; text-indent: 0px; "&gt;the caching entity resolver now correctly load all files from the cache dir first&lt;/li&gt;&lt;li style="padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0.25em; margin-left: 0px; text-indent: 0px; "&gt;updated to the latest Bounce version, which fixes some freezing in the sandboxes&lt;/li&gt;&lt;li style="padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0.25em; margin-left: 0px; text-indent: 0px; "&gt;includes Xerces 2.10 which partially supports XSD 1.1&lt;/li&gt;&lt;li style="padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0.25em; margin-left: 0px; text-indent: 0px; "&gt;fixed several small bugs&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Kernow 1.7 is released as a trial version which allows you to hit the run button 100 times, after which you will need to enter a code to continue.  The code can be purchased using the Help -&gt; Purchase menu.&lt;/div&gt;&lt;/span&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-6021615040647605792?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://kernowforsaxon.sf.net/' title='Kernow 1.7 released'/><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/6021615040647605792/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=6021615040647605792&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/6021615040647605792'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/6021615040647605792'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2011/01/kernow-17-released.html' title='Kernow 1.7 released'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-5013468596801668839</id><published>2010-07-19T14:45:00.002Z</published><updated>2010-07-19T14:51:26.031Z</updated><title type='text'>Kernow 1.7 beta available</title><content type='html'>Kernow 1.7 beta is now available: &lt;br /&gt;&lt;ul&gt;&lt;li&gt;integrates LexEv (which now reports internal and parameter entities, along with CDATA sections, comments, numeric and entity refs, and the DOCTYPE)&lt;/li&gt;&lt;li&gt;includes Saxon HE 9.2.1.1&lt;/li&gt;&lt;li&gt;directory transforms: input directory structure is now recreated in the output directory&lt;/li&gt;&lt;li&gt;files are correctly released, rather than held while Kernow is open&lt;/li&gt;&lt;li&gt;the caching entity resolver now correctly load all files from the cache dir first&lt;/li&gt;&lt;li&gt;updated to the latest Bounce version (enabling "folding" in the sandboxes)&lt;/li&gt;&lt;li&gt;includes Xerces 2.9.0 which partially supports XSD 1.1&lt;/li&gt;&lt;li&gt;fixed several small bugs&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-5013468596801668839?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='https://sourceforge.net/projects/kernowforsaxon/' title='Kernow 1.7 beta available'/><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/5013468596801668839/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=5013468596801668839&amp;isPopup=true' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/5013468596801668839'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/5013468596801668839'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2010/07/kernow-17-beta-available.html' title='Kernow 1.7 beta available'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-7627172438027645656</id><published>2009-11-17T10:10:00.004Z</published><updated>2009-11-17T10:20:46.914Z</updated><title type='text'>Mark Logic adds XSLT support</title><content type='html'>Mark Logic currently lacks any support for XSLT... but that's about to change: Norm Walsh has &lt;a href="http://norman.walsh.name/2009/11/12/nymug"&gt;announced upcoming support for it&lt;/a&gt; in version 5.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-7627172438027645656?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/7627172438027645656/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=7627172438027645656&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/7627172438027645656'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/7627172438027645656'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2009/11/mark-logic-adds-xslt-support.html' title='Mark Logic adds XSLT support'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-7530273833563764865</id><published>2009-09-14T12:54:00.002Z</published><updated>2009-09-14T13:06:55.378Z</updated><title type='text'>XML Schema 1.1 tutorials</title><content type='html'>Roger Costello has created a couple of good &lt;a href="http://www.xfront.com/xml-schema-1-1/"&gt;powerpoint presentations on XSD 1.1&lt;/a&gt;&lt;br /&gt;One tutorial is for developers, the other for managers which contains more general wordy descriptions of the benefits of 1.1&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-7530273833563764865?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://www.xfront.com/xml-schema-1-1/' title='XML Schema 1.1 tutorials'/><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/7530273833563764865/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=7530273833563764865&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/7530273833563764865'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/7530273833563764865'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2009/09/xml-schema-11-tutorials.html' title='XML Schema 1.1 tutorials'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-3055359810785425324</id><published>2008-08-22T14:56:00.005Z</published><updated>2012-01-26T15:49:10.705Z</updated><title type='text'>Some sample templates for use with LexEv</title><content type='html'>&lt;p&gt;If your XML has been parsed using &lt;a href="http://andrewjwelch.com/lexev"&gt;LexEv&lt;/a&gt;, here are some sample templates for handling the LexEv markup.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;To output an entity reference:&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-xsl"&gt;&lt;br /&gt;&amp;lt;xsl:template match="lexev:entity"&amp;gt;&lt;br /&gt; &amp;lt;xsl:value-of disable-output-escaping="yes" select="concat('&amp;amp;amp;', @name, ';')"/&amp;gt;   &lt;br /&gt;&amp;lt;/xsl:template&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;To process a CDATA section as markup:&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;xsl:template match="lexev:cdata"&amp;gt;&lt;br /&gt; &amp;lt;xsl:apply-templates/&amp;gt;   &lt;br /&gt;&amp;lt;/xsl:template&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;  &lt;br /&gt;&lt;p&gt;To output a DOCTYPE from the processing instructions:&lt;br /&gt;&lt;/p&gt;&lt;p&gt;In XSLT 1.0 the doctype-public and doctype-system attributes on &lt;code&gt;xsl:output&lt;/code&gt; are static and need to be known at compile time, which means I'm afraid you have to do this:&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;xsl:template match="/"&amp;gt;&lt;br /&gt;&amp;lt;xsl:value-of disable-output-escaping="yes"&lt;br /&gt; select="concat('&amp;amp;lt;!DOCTYPE ', name(/*), '&amp;amp;#xa;  PUBLIC &amp;amp;quot;',&lt;br /&gt;  processing-instruction('doctype-public'), '&amp;amp;quot; &amp;amp;quot;',&lt;br /&gt;  processing-instruction('doctype-system'), '&amp;amp;quot;&amp;amp;gt;')"/&amp;gt;&lt;br /&gt;&amp;lt;xsl:apply-templates/&amp;gt;&lt;br /&gt;&amp;lt;/xsl:template&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt; &lt;br /&gt;&lt;p&gt;In XSLT 2.0 you can use &lt;code&gt;xsl:result-document&lt;/code&gt; where the doctype-public and doctype-system are AVTs which mean their values can be determined at runtime:&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;xsl:template match="/"&amp;gt;&lt;br /&gt;&amp;lt;xsl:result-document&lt;br /&gt; doctype-public="{processing-instruction('doctype-public')}"&lt;br /&gt; doctype-system="{processing-instruction('doctype-system')}"&amp;gt;&lt;br /&gt; &amp;lt;xsl:apply-templates/&amp;gt;&lt;br /&gt;&amp;lt;/xsl:result-document&amp;gt;&lt;br /&gt;&amp;lt;/xsl:template&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-3055359810785425324?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/3055359810785425324/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=3055359810785425324&amp;isPopup=true' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/3055359810785425324'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/3055359810785425324'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2008/08/some-sample-templates-for-use-with.html' title='Some sample templates for use with LexEv'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-2947874638788408237</id><published>2008-08-21T15:34:00.004Z</published><updated>2008-08-21T16:06:53.374Z</updated><title type='text'>LexEv XMLReader - converts lexical events into markup</title><content type='html'>It's often a requirement to preserve entity references through to the output (which are usually lost during parsing) or to process the contents of CDATA sections as markup.  The &lt;a href="http://andrewjwelch.com/lexev/"&gt;&lt;span style="font-style: italic;"&gt;Lex&lt;/span&gt;ical &lt;span style="font-style: italic;"&gt;Ev&lt;/span&gt;ent XMLReader&lt;/a&gt;  wraps the standard XMLReader to convert lexical events into markup so that they can be processed.  Typical uses are:&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Converting cdata sections into markup: &lt;p&gt;&lt;br /&gt;&lt;code&gt;&amp;lt;![CDATA[ &amp;amp;lt;p&amp;amp;gt; a para &amp;amp;lt;p&amp;amp;gt; ]]&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;p&gt;to:&lt;/p&gt;&lt;p&gt;&lt;code&gt;&amp;lt;lexev:cdata&amp;gt; &amp;lt;p&amp;gt; a para &amp;lt;/p&amp;gt; &amp;lt;/lexev:cdata&amp;gt;&lt;/code&gt;&lt;/p&gt;   &lt;br /&gt;  &lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Preserving entity references:&lt;p&gt;&lt;br /&gt;&lt;code&gt;hello&amp;amp;mdash;world&lt;/code&gt;&lt;/p&gt;&lt;p&gt;is converted to:&lt;/p&gt;&lt;p&gt;&lt;code&gt;hello&amp;lt;lexev:entity name="mdash"&amp;gt;—&amp;lt;/lexev:entity&amp;gt;world&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Preserving the doctype declaration:&lt;p&gt;&lt;br /&gt;&lt;code&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"&lt;br /&gt;"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;p&gt;is converted to processing instructions:&lt;/p&gt;&lt;p&gt;&lt;code&gt;&amp;lt;?doctype-public -//W3C//DTD XHTML 1.0 Transitional//EN?&amp;gt;&lt;br /&gt;&amp;lt;?doctype-system http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd?&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Marking up comments: &lt;p&gt;&lt;br /&gt;&lt;code&gt;&amp;lt;!-- a comment --&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;p&gt;is converted to:&lt;/p&gt;&lt;p&gt;&lt;code&gt;&amp;lt;lexev:comment&amp;gt; a comment &amp;lt;/lexev:comment&amp;gt;&lt;span style="font-family:Georgia,serif;"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;To use LexEvXMLReader with Saxon:&lt;/p&gt;&lt;br /&gt;&lt;code&gt;java -cp saxon9.jar;&lt;b&gt;LexEvXMLReader.jar&lt;/b&gt; net.sf.saxon.Transform &lt;b&gt;-x:com.andrewjwelch.lexev.LexEvXMLReader&lt;/b&gt; input.xml&lt;br /&gt;stylesheet.xslt&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Make sure &lt;code&gt;&lt;b&gt;LexEvXMLReader.jar&lt;/b&gt;&lt;/code&gt; is on the classpath, and then tell Saxon to use it with the -x switch (copy and paste this line &lt;code&gt;&lt;b&gt;-x:com.andrewjwelch.lexev.LexEvXMLReader&lt;/b&gt;&lt;code&gt;)&lt;span style="font-family:Georgia,serif;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/code&gt;&lt;/code&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;code&gt;&lt;code&gt;&lt;span style="font-family:Georgia,serif;"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/code&gt;&lt;/p&gt;&lt;p&gt;To use LexEvXMLReader from Java:&lt;/p&gt;&lt;code&gt;XMLReader xmlReader = new LexEvXMLReader();&lt;/code&gt; &lt;br /&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;You can control the following features of LexEv:&lt;/p&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;enable/disable the marking up of entity references&lt;/li&gt;&lt;br /&gt;&lt;li&gt;enable/disable the marking up of CDATA sections&lt;/li&gt;&lt;br /&gt;&lt;li&gt;set the default namespace for the CDATA section markup&lt;/li&gt;&lt;br /&gt;&lt;li&gt;enable/disable the reporting of the DOCTYPE&lt;/li&gt;&lt;br /&gt;&lt;li&gt;enable/disable the marking up of comments&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;p&gt;You can set these through the API (if you are including LexEv in an application), or from the command line using the following system properties:&lt;/p&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;code&gt;com.andrewjwelch.lexev.inline-entities&lt;/code&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;code&gt;com.andrewjwelch.lexev.cdata&lt;/code&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;code&gt;com.andrewjwelch.doctype.cdataNamespace&lt;/code&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;code&gt;com.andrewjwelch.lexev.doctype&lt;/code&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;code&gt;com.andrewjwelch.lexev.comments&lt;/code&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;p&gt;For example to set a system property from the command line you would use: &lt;code&gt;-Dcom.andrewjwelch.lexev.comments=false&lt;/code&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;For support, suggestions and licensing, email lexev@andrewjwelch.com&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-2947874638788408237?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://andrewjwelch.com/lexev/' title='LexEv XMLReader - converts lexical events into markup'/><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/2947874638788408237/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=2947874638788408237&amp;isPopup=true' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2947874638788408237'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2947874638788408237'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2008/08/lexev-xmlreader-converts-lexical-events.html' title='LexEv XMLReader - converts lexical events into markup'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-2957718730635844125</id><published>2008-07-18T14:58:00.000Z</published><updated>2008-07-18T15:21:37.876Z</updated><title type='text'>Kernow 1.6.1</title><content type='html'>&lt;a href="http://kernowforsaxon.sf.net/"&gt;Kernow 1.6.1&lt;/a&gt; (beta) is now availble both as a &lt;a href="http://kernowforsaxon.sf.net/"&gt;download &lt;/a&gt;and via &lt;a href="http://kernowforsaxon.sourceforge.net/jws/Kernow.jnlp"&gt;web start&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Notable things in this release:&lt;br /&gt;&lt;br /&gt;- Line numbers on the editor panes in the sandboxes (thanks to a new version of &lt;a href="http://www.edankert.com/bounce/index.html"&gt;Bounce&lt;/a&gt;). You might not think so, but getting line numbers down the side of the editor pane is really involved.  It's like block indenting (pressing tab or shift-tab when a block of text is selected) in that it's very low level and requires a lot of coding.  Why it's not an intergral part of the editor pane I don't know... &lt;br /&gt;&lt;br /&gt;- Improved the syntax-checking-as-you-type and highlighting, and added the ability to disable it.&lt;br /&gt;&lt;br /&gt;- The output area is now also a JEditorPane using Bounce so it supports tag highlighting.  This might slow things down because now it's an HTML document where every addition is inserted at the end of the document, instead of just appending to a JTextArea... if this proves to be A Bad Thing I'll revert it back to a plain old text area with plain text.&lt;br /&gt;&lt;br /&gt;- You can now select which tabs are visible (in options -&gt; tabs) so if you never use certain tabs (like Batch or Schematron) you can remove them.&lt;br /&gt;&lt;br /&gt;- If you have Saxon SA you can use XML Schema 1.1 (options -&gt; validation)&lt;br /&gt;&lt;br /&gt;- Improved the parameters dialog to make it less fiddly to enter params&lt;br /&gt;&lt;br /&gt;- Slight graphical tweaks and likely other things that I've forgotten...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-2957718730635844125?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://kernowforsaxon.sf.net/' title='Kernow 1.6.1'/><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/2957718730635844125/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=2957718730635844125&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2957718730635844125'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2957718730635844125'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2008/07/kernow-161.html' title='Kernow 1.6.1'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-7950551531493839968</id><published>2008-07-17T11:11:00.001Z</published><updated>2008-07-17T11:36:56.654Z</updated><title type='text'>The Nimbus Look and Feel</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_ypDtgn03LEU/SH8usKbvi0I/AAAAAAAAACM/ceAeHx4-cwA/s1600-h/kernow-nimbus.png"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;" src="http://1.bp.blogspot.com/_ypDtgn03LEU/SH8usKbvi0I/AAAAAAAAACM/ceAeHx4-cwA/s400/kernow-nimbus.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5223945429022903106" /&gt;&lt;/a&gt;This is "Nimbus" - the new look and feel that comes with Java 6 Update 10.  This is a cross platform l&amp;f which means it should look the same on all platforms.  Kernow currently uses the "platform default" look and feel so it should look like a native app on the platform it's run on, but it's hard to make sure it looks right - often what looks ok on Windows will have obscured buttons on Linux... something I should've fixed but never did.&lt;br /&gt;&lt;br /&gt;Anyway, what do you think?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-7950551531493839968?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/7950551531493839968/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=7950551531493839968&amp;isPopup=true' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/7950551531493839968'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/7950551531493839968'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2008/07/this-is-nimbus-new-look-and-feel-that.html' title='The Nimbus Look and Feel'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_ypDtgn03LEU/SH8usKbvi0I/AAAAAAAAACM/ceAeHx4-cwA/s72-c/kernow-nimbus.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-2760403851453168673</id><published>2008-07-11T15:37:00.001Z</published><updated>2008-07-11T16:09:00.763Z</updated><title type='text'>Validating co-constrains in XML Schema 1.1 using xs:alternative</title><content type='html'>Rather than mess around with loads of assertions to check your co-constraints, XML Schema 1.1 introduces the &lt;code&gt;xs:alternative&lt;/code&gt; instruction which allows you to change the type used to validate the element based on some condition.  Instead of defining one type and then adding assertions to check the variations, just define one type per variation, then assign that type based on the condition.  &lt;br /&gt;&lt;br /&gt;To do this you first have to define a default type, then define types for each variation by restricting that type.  To choose between them, use &lt;code&gt;xs:alternative&lt;/code&gt; as a child of &lt;code&gt;xs:element&lt;/code&gt;.  Here's an example of a co-constraint - &lt;code&gt;this&lt;/code&gt; and &lt;code&gt;that&lt;/code&gt; are allowed based on the value of the &lt;code&gt;type&lt;/code&gt; attribute of &lt;code&gt;node&lt;/code&gt; - and how to validate it:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&amp;lt;root&gt;&lt;br /&gt;  &amp;lt;node type="A"&gt;&lt;br /&gt;    &amp;lt;this/&gt; &lt;br /&gt;  &amp;lt;/node&gt;&lt;br /&gt;  &amp;lt;node type="B"&gt;&lt;br /&gt;    &amp;lt;that/&gt; &lt;br /&gt;  &amp;lt;/node&gt;&lt;br /&gt;&amp;lt;/root&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;Here's the schema:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&amp;lt;xs:schema &lt;br /&gt;  xmlns:xs="http://www.w3.org/2001/XMLSchema" &lt;br /&gt;  elementFormDefault="qualified"&gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;xs:element name="root" type="root"/&gt;&lt;br /&gt;  &lt;b&gt;&amp;lt;xs:element name="node" type="node"&gt;&lt;br /&gt;    &amp;lt;xs:alternative type="node-type-A" test="@type = 'A'"/&gt;  &lt;br /&gt;    &amp;lt;xs:alternative type="node-type-B" test="@type = 'B'"/&gt;  &lt;br /&gt;  &amp;lt;/xs:element&gt;&lt;/b&gt;&lt;br /&gt;  &amp;lt;xs:element name="this"/&gt;&lt;br /&gt;  &amp;lt;xs:element name="that"/&gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;xs:complexType name="root"&gt;&lt;br /&gt;    &amp;lt;xs:sequence&gt;&lt;br /&gt;      &amp;lt;xs:element ref="node" maxOccurs="unbounded"/&gt;&lt;br /&gt;    &amp;lt;/xs:sequence&gt;&lt;br /&gt;  &amp;lt;/xs:complexType&gt;&lt;br /&gt;  &lt;br /&gt;  &lt;b&gt;&amp;lt;-- Base type --&gt;&lt;/b&gt;&lt;br /&gt;  &amp;lt;xs:complexType name="node"&gt;&lt;br /&gt;    &amp;lt;xs:sequence&gt;&lt;br /&gt;      &amp;lt;xs:any/&gt;  &lt;br /&gt;    &amp;lt;/xs:sequence&gt;&lt;br /&gt;    &amp;lt;xs:attribute name="type" type="allowed-node-types"/&gt;&lt;br /&gt;  &amp;lt;/xs:complexType&gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;xs:simpleType name="allowed-node-types"&gt;&lt;br /&gt;    &amp;lt;xs:restriction base="xs:string"&gt;&lt;br /&gt;      &amp;lt;xs:enumeration value="A"/&gt;&lt;br /&gt;      &amp;lt;xs:enumeration value="B"/&gt;&lt;br /&gt;    &amp;lt;/xs:restriction&gt;&lt;br /&gt;  &amp;lt;/xs:simpleType&gt;      &lt;br /&gt;  &lt;br /&gt;  &lt;b&gt;&amp;lt;-- Type A --&gt;&lt;/b&gt;&lt;br /&gt;  &amp;lt;xs:complexType name="node-type-A"&gt;&lt;br /&gt;    &amp;lt;xs:complexContent&gt;&lt;br /&gt;      &amp;lt;xs:restriction base="node"&gt;&lt;br /&gt;        &amp;lt;xs:sequence&gt;&lt;br /&gt;          &amp;lt;xs:element ref="this"/&gt;  &lt;br /&gt;        &amp;lt;/xs:sequence&gt;  &lt;br /&gt;      &amp;lt;/xs:restriction&gt;  &lt;br /&gt;    &amp;lt;/xs:complexContent&gt;&lt;br /&gt;  &amp;lt;/xs:complexType&gt;&lt;br /&gt;  &lt;br /&gt;  &lt;b&gt;&amp;lt;-- Type B --&gt;&lt;/b&gt;&lt;br /&gt;  &amp;lt;xs:complexType name="node-type-B"&gt;&lt;br /&gt;    &amp;lt;xs:complexContent&gt;&lt;br /&gt;      &amp;lt;xs:restriction base="node"&gt;&lt;br /&gt;        &amp;lt;xs:sequence&gt;&lt;br /&gt;          &amp;lt;xs:element ref="that"/&gt;  &lt;br /&gt;        &amp;lt;/xs:sequence&gt;  &lt;br /&gt;      &amp;lt;/xs:restriction&gt;  &lt;br /&gt;    &amp;lt;/xs:complexContent&gt;&lt;br /&gt;  &amp;lt;/xs:complexType&gt;  &lt;br /&gt;&amp;lt;/xs:schema&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;I really like this... schema 1.1 will be a joy to use.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-2760403851453168673?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/2760403851453168673/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=2760403851453168673&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2760403851453168673'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2760403851453168673'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2008/07/validating-co-constrains-in-xml-schema.html' title='Validating co-constrains in XML Schema 1.1 using xs:alternative'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-3464546988639212155</id><published>2008-06-16T14:45:00.001Z</published><updated>2008-06-16T15:10:07.759Z</updated><title type='text'>XML Schema co-occurrence constraint workaround</title><content type='html'>Here's a potential workaround for the co-constraint problem in XML Schema.&lt;br /&gt;&lt;br /&gt;Given some XML:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;elem type="typeA"&gt;&lt;br /&gt;  &amp;lt;typeA/&gt;&lt;br /&gt;&amp;lt;/elem&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;elem type="typeB"&gt;&lt;br /&gt;  &amp;lt;typeB/&gt;&lt;br /&gt;&amp;lt;/elem&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;...the problem is you can't constrain the contents of &lt;code&gt;&amp;lt;elem&gt;&lt;/code&gt; based on the value of the type attribute.&lt;br /&gt;&lt;br /&gt;You can do it though, if you add an xsi:type attribute to it to explicitly set its type:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;elem type="typeA" xsi:type="elem_typeA"&gt;&lt;br /&gt;  &amp;lt;typeA/&gt;&lt;br /&gt;&amp;lt;/elem&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;elem type="typeB" xsi:type="elem_typeB"&gt;&lt;br /&gt;  &amp;lt;typeB/&gt;&lt;br /&gt;&amp;lt;/elem&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;with suitable type definitions in the schema:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;xs:complexType name="elem_typeA"&gt;&lt;br /&gt;  &amp;lt;xs:sequence&gt;&lt;br /&gt;    &amp;lt;xs:element ref="typeA"/&gt;&lt;br /&gt;    ...&lt;br /&gt;&lt;br /&gt;&amp;lt;xs:complexType name="elem_typeB"&gt;&lt;br /&gt;  &amp;lt;xs:sequence&gt;&lt;br /&gt;    &amp;lt;xs:element ref="typeB"/&gt;&lt;br /&gt;    ... &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;...and when the XML is validated the relevant definition will be used.&lt;br /&gt;&lt;br /&gt;This technique is far from ideal as it involves modifying the source, but only in a way which doesn't break it for anyone else.  Given the various options for validating co-constraints, this could well be the most straightforward way (at least until 1.1 comes along).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-3464546988639212155?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/3464546988639212155/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=3464546988639212155&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/3464546988639212155'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/3464546988639212155'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2008/06/xml-schema-co-occurrence-constraint.html' title='XML Schema co-occurrence constraint workaround'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-278474760210246690</id><published>2008-04-25T13:48:00.001Z</published><updated>2008-04-27T09:58:49.321Z</updated><title type='text'>The Scrabble Reference</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.mobipocket.com/en/eBooks/eBookDetails.asp?BookID=78536&amp;amp;Origine=3931"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://2.bp.blogspot.com/_ypDtgn03LEU/SBHo2c9BFEI/AAAAAAAAABs/UmKpDZqVxi8/s320/scrabble-cover_small.gif" alt="" id="BLOGGER_PHOTO_ID_5193187867517588546" border="0" /&gt;&lt;/a&gt;The Scrabble Reference is an ebook I've created which allows Scrabble players to easily check if words are legal, to suggest longer words or sub-anagrams given a word and to show what words can be made given some letters (up to 15 letters).&lt;br /&gt;&lt;br /&gt;There are two versions, &lt;a href="http://www.mobipocket.com/en/eBooks/eBookDetails.asp?BookID=78536&amp;Origine=3931"&gt;TWL&lt;/a&gt; and &lt;a href="http://www.mobipocket.com/en/eBooks/eBookDetails.asp?BookID=81063&amp;Origine=3931"&gt;SOWPODS&lt;/a&gt; (TWL is used in the USA, Canada, Thailand and Israel and SOWPODS in the rest of the world)&lt;br /&gt;&lt;br /&gt;The ebook is in the Mobipocket format which deals with running the ebooks on all devices - PDAs, mobile phones, Blackberries etc so you just need to transform the input to suitable Mobipocket markup, compile the ebook and their client does the rest.&lt;br /&gt;&lt;br /&gt;I must admit to not being interested in Scrabble, but of course I am interested in XSLT, and given the list of words allowed in Scrabble I thought I should turn them into a product - one that you can run on your phone seems perfect.&lt;br /&gt;&lt;br /&gt;Generating the ebook was very straightforward - list of strings in, markup out...  XSLT 2.0 is ideal for the task.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-278474760210246690?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://www.mobipocket.com/en/eBooks/eBookDetails.asp?BookID=78536&amp;Origine=3931' title='The Scrabble Reference'/><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/278474760210246690/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=278474760210246690&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/278474760210246690'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/278474760210246690'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2008/04/scrabble-reference.html' title='The Scrabble Reference'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_ypDtgn03LEU/SBHo2c9BFEI/AAAAAAAAABs/UmKpDZqVxi8/s72-c/scrabble-cover_small.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-8905548350216111389</id><published>2008-04-25T11:18:00.000Z</published><updated>2008-04-25T11:45:32.892Z</updated><title type='text'>Relative paths and the document() function</title><content type='html'>A nice gotcha cropped up today on xsl-list...&lt;br /&gt;&lt;br /&gt;Relative paths passed to the document() function are resolved against either the XML or  the stylesheet depending on what is passed in: a node from the XML will mean the path is resolved against the XML, a string will mean it's resolved against the stylesheet.&lt;br /&gt;&lt;br /&gt;The gotcha is this - if you modify this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;document(@path)&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;to this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&amp;lt;xsl:variable name="path" select="@path" as="xs:string"/&gt;&lt;br /&gt;...&lt;br /&gt;document($path)&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;and &lt;code&gt;@path&lt;/code&gt; contains a relative path, then you could get a document not found error,  or worse if your XML and XSLT are in the same directory, you won't notice...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-8905548350216111389?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/8905548350216111389/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=8905548350216111389&amp;isPopup=true' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/8905548350216111389'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/8905548350216111389'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2008/04/relative-paths-and-document-function.html' title='Relative paths and the document() function'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-2588639905897464955</id><published>2008-02-29T16:23:00.001Z</published><updated>2008-02-29T16:34:31.525Z</updated><title type='text'>XML Schema - element with text and attributes</title><content type='html'>For some reason I always forget how to define an element that contains only text but also has attributes.  Perhaps it's because it's so verbose, or so non-intuitive for something so simple, who knows.  Either way it's something that needs to be committed to memory...&lt;br /&gt;&lt;br /&gt;So the element:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;foo bar="bar" baz="baz"/&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;is described using:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;xs:complexType name="foo"&gt;&lt;br /&gt;    &amp;lt;xs:simpleContent&gt;&lt;br /&gt;        &amp;lt;xs:extension base="xs:string"&gt;&lt;br /&gt;            &amp;lt;xs:attribute name="bar" type="xs:string"/&gt;&lt;br /&gt;            &amp;lt;xs:attribute name="baz" type="xs:string"/&gt;&lt;br /&gt;        &amp;lt;/xs:extension&gt;&lt;br /&gt;    &amp;lt;/xs:simpleContent&gt;                &lt;br /&gt;&amp;lt;/xs:complexType&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;nice!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-2588639905897464955?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/2588639905897464955/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=2588639905897464955&amp;isPopup=true' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2588639905897464955'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2588639905897464955'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2008/02/xml-schema-element-with-text-and.html' title='XML Schema - element with text and attributes'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-2251290802737929380</id><published>2008-02-27T16:10:00.000Z</published><updated>2008-02-27T16:17:34.931Z</updated><title type='text'>schema-aware.com</title><content type='html'>I've created a new website &lt;a href="http://schema-aware.com"&gt;schema-aware.com&lt;/a&gt; which is inteded to contain lots of examples of schema-aware XSLT and XQuery.  I've started it off with half a dozen or so and hope to add more as time goes on.&lt;br /&gt;&lt;br /&gt;I also intend to add a few articles about schema-aware transforms - how the run them from the command line, from Java, the various flags involved, how to write schemas to allow you to use the types in your XSLT etc...  My intentions are good, we'll have to see how much I actually do.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-2251290802737929380?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://schema-aware.com' title='schema-aware.com'/><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/2251290802737929380/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=2251290802737929380&amp;isPopup=true' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2251290802737929380'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2251290802737929380'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2008/02/schema-awarecom.html' title='schema-aware.com'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-5584053331799510270</id><published>2008-01-31T15:01:00.000Z</published><updated>2008-01-31T15:20:27.642Z</updated><title type='text'>Kernow 1.6 beta</title><content type='html'>I've uploaded a new version of Kernow (1.6) which contains the rather nice "XSLT Sandbox" tab.  This tab has the XML pane on the left, the XSLT pane on the right and a transform button... and that's it.  It's intended for anyone who wants to quickly try something out without the hassle of files, the command line or starting up a proper IDE.  It does error checking as you type and highlights any problems.&lt;br /&gt;&lt;br /&gt;It's available as the usual download from Sourceforge, or through Java Web Start.  If you already run the JWS version it should automatically update itself (any problems just re-install it).  I've finally figured out the temperamental errors with the JWS version - it turns out the ant jars included with Kernow were already signed by a previous version and so weren't being signed again, but because they were marked as "lazy" in the jnlp the JWS version would start anyway.  (You can tell if a jar has been signed by looking for *.SF and *.DSA in the META-INF directory.)&lt;br /&gt;&lt;br /&gt;The other improvement I'm pleased to have sorted out is that kernow.config (where all of the settings and combobox history are saved) is now stored in a directory called .kernow in your user.home (which is one up from My Documents in XP). Previously it would've been stored on the deskop for the JWS version which is really annoying - sorry about that.  As usual it was a 10 minute job, but just took a while to get around to.&lt;br /&gt;&lt;br /&gt;I've also separated out the SOAP and eXists extension functions into a separate package, so there's no longer the need for the largish eXist.jar, xmldb.jar and log4j.jar jars to be part of the download.&lt;br /&gt;&lt;br /&gt;I'll release a non-beta version in a few weeks if no bugs are reported, and I've got around to updating all of the documentation.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-5584053331799510270?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://kernowforsaxon.sf.net/' title='Kernow 1.6 beta'/><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/5584053331799510270/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=5584053331799510270&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/5584053331799510270'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/5584053331799510270'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2008/01/kernow-16-beta.html' title='Kernow 1.6 beta'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-5566597608500397751</id><published>2008-01-31T14:52:00.000Z</published><updated>2008-01-31T14:59:25.322Z</updated><title type='text'>Parsing XML into Java 5 Enums</title><content type='html'>Often when parsing XML into pojos I just resort to writing my own SAX based parser.  It can be long winded but I think gives you the greatest flexibility and control over how you get from the XML to objects you can process.&lt;br /&gt;&lt;br /&gt;One example is with Java 5's Enums, which are great.  Given the kind of XML fragment where currencies are represented using internal codes:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&amp;lt;currency refid="001"/&gt; &amp;lt;!-- 001 is Sterling --&gt;&lt;br /&gt;&amp;lt;currency refid="002"/&gt; &amp;lt;!-- 002 is Euros --&gt;&lt;br /&gt;&amp;lt;currency refid="003"/&gt; &amp;lt;!-- 003 is United States Dollars --&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;You can represent each currency element with an Enum, which contains extra fields for the additional information:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;public enum Currency {&lt;br /&gt;&lt;br /&gt;    GBP ("001", "GBP", "Sterling"),&lt;br /&gt;    USD ("002", "EUR", "Euros"),&lt;br /&gt;    USD ("003", "USD", "United States Dollar");    &lt;br /&gt;&lt;br /&gt;    private final String refId;&lt;br /&gt;    private final String code;&lt;br /&gt;    private final String desc;&lt;br /&gt;&lt;br /&gt;    Currency(String refId, String code, String desc) {&lt;br /&gt;        this.refId = refId;&lt;br /&gt;        this.code = code;&lt;br /&gt;        this.desc = desc;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String refId() {&lt;br /&gt;        return refId;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String code() {&lt;br /&gt;        return code;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String desc() {&lt;br /&gt;        return desc;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    // Returns the enum based on it's property rather than its name&lt;br /&gt;    // (This loop could possibly be replaced with a static map, but be aware&lt;br /&gt;    //  that static member variables are initialized *after* the enum and therefore&lt;br /&gt;    //  aren't available to the constructor, so you'd need a static block.  &lt;br /&gt;    public static Currency getTypeByRefId(String refId) {&lt;br /&gt;        for (Currency type : Currency.values()) {&lt;br /&gt;            if (type.refId().equals(refId)) {&lt;br /&gt;                return type;&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        throw new IllegalArgumentException("Don't have enum for: " + refId);&lt;br /&gt;    }    &lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Notice how each enum calls its own contructor with the 3 parameters - the refId, the code, and the description.  &lt;br /&gt;&lt;br /&gt;You parse the XML into the enum by calling &lt;code&gt;Currency.getTypeByRefId(String refId)&lt;/code&gt; passing in the @refid from the XML.  The benefit of using the Enum is that you can then do things like:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;if (currency.equals(Currency.GBP))&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;which is nice and clear, while at the same time being able to call &lt;code&gt;currency.refId()&lt;/code&gt; and &lt;code&gt;currency.desc()&lt;/code&gt; to get to the other values.  &lt;br /&gt;&lt;br /&gt;The drawback is that because static member variables are initialized after the enum, you can't create a HashMap and fill it for a faster lookup later (unless you use a static block).  Instead you have to loop through all known values() for the enum given a refId.  Although it feels wrong to loop, the worst case is only the size the of enum so I don't think it's too bad.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-5566597608500397751?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/5566597608500397751/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=5566597608500397751&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/5566597608500397751'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/5566597608500397751'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2008/01/parsing-xml-into-java-5-enums.html' title='Parsing XML into Java 5 Enums'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-2647632193311076501</id><published>2008-01-22T14:06:00.000Z</published><updated>2008-01-22T19:21:41.485Z</updated><title type='text'>Portability of a stylesheet across schema-aware and non-schema-aware processors</title><content type='html'>I came across this today, which I thought was really cool and worth a post.  It basically allows you to code a transform that is only schema-aware if a schema-aware processor is running it, otherwise it's just a standard transform.&lt;br /&gt;&lt;br /&gt;In this case I want to do input and output validation, so first I sort out the schemas:&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:import-schema schema-location="input.xsd" &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;namespace="http://www.foo.com" &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;use-when="system-property('xsl:is-schema-aware')='yes'"/&gt;&lt;br /&gt;  &lt;br /&gt;&amp;lt;xsl:import-schema schema-location="output.xsd"  &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;use-when="system-property('xsl:is-schema-aware')='yes'"/&gt;  &lt;br /&gt;&lt;br /&gt;Note the use-when...&lt;br /&gt;&lt;br /&gt;Next define two root matching templates, one for schema-aware, one for basic:&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="/" &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;use-when="system-property('xsl:is-schema-aware')='yes'" &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;priority="2"&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;lt;xsl:variable name="input" as="document-node()"&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;lt;xsl:document validation="strict"&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;lt;xsl:copy-of select="/"/&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;lt;/xsl:document&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;lt;/xsl:variable&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;lt;xsl:result-document validation="strict"&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;lt;xsl:apply-templates select="$input/the-root-elem"/&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;lt;/xsl:result-document&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="/"&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;lt;xsl:apply-templates select="the-root-elem"/&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;br /&gt;&amp;lt;xsl:template match="the-root-elem"&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;...&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;The root matching template for schema-aware processing uses xsl:document to validate the input, and xsl:result-document to validate the output.  Validation can also be controlled from outside the transform, but this way forces it on.&lt;br /&gt;&lt;br /&gt;I think this is great :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-2647632193311076501?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/2647632193311076501/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=2647632193311076501&amp;isPopup=true' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2647632193311076501'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2647632193311076501'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2008/01/portability-of-stylesheet-across-schema.html' title='Portability of a stylesheet across schema-aware and non-schema-aware processors'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-2855377856737708141</id><published>2008-01-22T13:47:00.001Z</published><updated>2009-07-05T08:06:58.303Z</updated><title type='text'>The identity transform for XSLT 2.0</title><content type='html'>I was looking at the standard identity transform the other day and realised that for nodes other than elements, the call to apply-templates is redundant. &lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="@*|node()"&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;lt;xsl:copy&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;lt;xsl:apply-templates select="@*|node()"/&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;lt;/xsl:copy&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;Also, although it might be intuitive to think that attributes have separate nodes for their name and value, they are in fact a single node that's copied in it's entirety by xsl:copy.&lt;br /&gt;&lt;br /&gt;I raised this on xsl-list and suggested seperating out the attribute into a template of its own with just xsl:copy for its body:&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="node()"&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;lt;xsl:copy&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;lt;xsl:apply-templates select="@*|node()"/&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;lt;/xsl:copy&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="@*"&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;lt;xsl:copy/&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Mike Kay suggested a more logical version would be:&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="element()"&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;lt;xsl:copy&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;lt;xsl:apply-templates select="@*,node()"/&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;lt;/xsl:copy&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="attribute()|text()|comment()|processing-instruction()"&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;lt;xsl:copy/&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;This turned out to be ideal for three reasons:&lt;br /&gt;&lt;br /&gt;- the comma between @* and node() will mean the selected nodes will be processed in that order, removing the sorting and deduplication that takes place with union |&lt;br /&gt;- apply-templates is only called when it will have an effect&lt;br /&gt;- it's clearer that attributes are leaf nodes&lt;br /&gt;&lt;br /&gt;So there it is... the identity transform for XSLT 2.0&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-2855377856737708141?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/2855377856737708141/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=2855377856737708141&amp;isPopup=true' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2855377856737708141'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2855377856737708141'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2008/01/indentity-transform-for-xslt-20.html' title='The identity transform for XSLT 2.0'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-345186737016129299</id><published>2007-09-28T13:04:00.000Z</published><updated>2007-09-28T13:07:52.202Z</updated><title type='text'>Kernow 1.5.2</title><content type='html'>I've just uploaded the non-beta version of Kernow 1.5.2.&lt;br /&gt;&lt;br /&gt;This version contains:&lt;br /&gt;&lt;br /&gt;- French and German translations  &lt;br /&gt;- XQuery syntax highlighting and checking as-you-type &lt;br /&gt;- Improved cancelling of Single File and Standalone tasks &lt;br /&gt;- icon and splash screen &lt;br /&gt;- An exe to launch it (for windows users)  &lt;br /&gt;- context menus  &lt;br /&gt;- comboboxes remember their selected index  &lt;br /&gt;- individual combobox entries can be removed by deleting the entry  &lt;br /&gt;- other small fixes&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-345186737016129299?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://kernowforsaxon.sourceforge.net/' title='Kernow 1.5.2'/><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/345186737016129299/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=345186737016129299&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/345186737016129299'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/345186737016129299'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/09/kernow-152.html' title='Kernow 1.5.2'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-8883359722728443102</id><published>2007-09-12T12:01:00.000Z</published><updated>2007-09-12T12:34:31.817Z</updated><title type='text'>Connecting to Oracle from XSLT</title><content type='html'>Today I generated a report by connecting directly to an Oracle database from XSLT, and thought I'd share the basic stylesheet.  I used &lt;a href="http://www.saxonica.com/documentation/sql-extension/intro.html"&gt;Saxon's SQL extension&lt;/a&gt;, which is available when saxon8-sql.jar is on the classpath.  As I was connecting to Oracle, I also needed to put ojdcb14.jar on the classpath.&lt;br /&gt;&lt;br /&gt;Here's the stylesheet in it's most basic form, formatted for display in this blog.&lt;br /&gt;&lt;br /&gt;The important things to note here are:&lt;br /&gt;&lt;br /&gt;- The sql prefix is bound to "&lt;code&gt;/net.sf.saxon.sql.SQLElementFactory&lt;/code&gt;"&lt;br /&gt;- The driver is "&lt;code&gt;oracle.jdbc.driver.OracleDriver&lt;/code&gt;"&lt;br /&gt;- The connection string format is "&lt;code&gt;jdbc:oracle:thin:@1.2.3.4:1234:sid&lt;/code&gt;" (note the colon between thin and @ - I missed that first time round) where the IP, port and sid  are placeholders for the real values&lt;br /&gt;- remember that &lt;code&gt;saxon8-sql.jar&lt;/code&gt; and &lt;code&gt;ojdbc14.jar&lt;/code&gt; needed to be on the classpath&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;xsl:stylesheet version="2.0"&lt;br /&gt; xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&lt;br /&gt; xmlns:xs="http://www.w3.org/2001/XMLSchema"&lt;br /&gt; xmlns:sql="/net.sf.saxon.sql.SQLElementFactory"&lt;br /&gt; exclude-result-prefixes="xs"&lt;br /&gt; extension-element-prefixes="sql"&gt;&lt;br /&gt; &lt;br /&gt;&amp;lt;xsl:output indent="yes"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:param name="driver" &lt;br /&gt;  select="'oracle.jdbc.driver.OracleDriver'" &lt;br /&gt;  as="xs:string"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:param name="database" &lt;br /&gt;  select="'jdbc:oracle:thin:@123.123.123.123:1234:sid'" &lt;br /&gt;  as="xs:string"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:param name="user" select="'un'" as="xs:string"/&gt;&lt;br /&gt;&amp;lt;xsl:param name="password" select="'pw'" as="xs:string"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:variable name="connection"&lt;br /&gt;  as="java:java.sql.Connection" &lt;br /&gt;  xmlns:java="http://saxon.sf.net/java-type"&gt;&lt;br /&gt; &lt;br /&gt;  &amp;lt;sql:connect driver="{$driver}" database="{$database}" &lt;br /&gt;    user="{$user}" password="{$password}"/&gt;&lt;br /&gt;&amp;lt;/xsl:variable&gt;&lt;br /&gt; &lt;br /&gt;&amp;lt;xsl:template match="/" name="main"&gt;&lt;br /&gt;  &amp;lt;root&gt;&lt;br /&gt;    &amp;lt;sql:query connection="$connection" &lt;br /&gt;      table="some_table" &lt;br /&gt;      column="*"&lt;br /&gt;      row-tag="row" &lt;br /&gt;      column-tag="col"/&gt;&lt;br /&gt;  &amp;lt;/root&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/xsl:stylesheet&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;The result of this transform outputs XML in the form:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;root&gt;&lt;br /&gt;  &amp;lt;row&gt;&lt;br /&gt;    &amp;lt;col&gt;data1&amp;lt;/col&gt;&lt;br /&gt;    &amp;lt;col&gt;data2&amp;lt;/col&gt;&lt;br /&gt;    &amp;lt;col&gt;data3&amp;lt;/col&gt;&lt;br /&gt;    &amp;lt;col&gt;data4&amp;lt;/col&gt;&lt;br /&gt;  &amp;lt;/row&gt;&lt;br /&gt;  ....&lt;br /&gt;&amp;lt;/root&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;where &lt;code&gt;&amp;lt;root&gt;&lt;/code&gt; is the wrapper element, and &lt;code&gt;&amp;lt;row&gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;col&gt;&lt;/code&gt; are the element names specified in the &lt;code&gt;&amp;lt;sql:query&gt;&lt;/code&gt; element.&lt;br /&gt;&lt;br /&gt;And that's it - connecting to an Oracle database from within XSLT.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-8883359722728443102?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/8883359722728443102/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=8883359722728443102&amp;isPopup=true' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/8883359722728443102'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/8883359722728443102'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/09/connecting-to-oracle-from-xslt.html' title='Connecting to Oracle from XSLT'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-158959017110751435</id><published>2007-09-03T15:59:00.000Z</published><updated>2007-09-03T16:29:31.326Z</updated><title type='text'>Kernow 1.5.2 beta b2 available</title><content type='html'>I've just uploaded a new version of Kernow.  This one was pretty much already available via Java Web Start,  this makes it available via the normal download route.&lt;br /&gt;&lt;br /&gt;New features/fixes:&lt;br /&gt;&lt;br /&gt;- Added syntax highlighting and checking as-you-type to the XQuery Sandbox tab. Syntax highlighting's provided using Bounce's XMLEditorKit - I'm hoping to use Netbeans' nbEditorKit in a future version which will add line numbers, code completion etc.  I've put together the checking-as-you-type and error highlighting using Saxon's error reporting.  This is really cool, so I'm planning on doing an equivalent "XSLT Sandbox" soon... perhaps using Netbeans RCP.  Not sure yet.&lt;br /&gt;&lt;br /&gt;- Added an icon and splashsceen.  These came about because JWS and the exe benefit from them.  Are they any good?  I'm not really a graphics person...&lt;br /&gt;&lt;br /&gt;- Kernow.jar is now a proper executable jar, so you can double click it to run Kernow (if you're on a mac for example)&lt;br /&gt;&lt;br /&gt;- It's all compiled using Java 1.5, again for mac users where 1.6 isn't supported yet.&lt;br /&gt;&lt;br /&gt;It's available here: &lt;a href="http://sourceforge.net/projects/kernowforsaxon"&gt;Kernow&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-158959017110751435?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/158959017110751435/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=158959017110751435&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/158959017110751435'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/158959017110751435'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/09/kernow-152-beta-b2-available.html' title='Kernow 1.5.2 beta b2 available'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-7912423256246298979</id><published>2007-08-30T15:16:00.000Z</published><updated>2007-08-30T15:30:11.533Z</updated><title type='text'>Kernow now available via Java Web Start</title><content type='html'>I've been playing around with making Kernow available through Java Web Start.  This should be the ideal way to run Kernow as it places a shortcut on your desktop (and in your start menu in Windows) and auto-updates whenever a new version is available.&lt;br /&gt;&lt;br /&gt;Reading around it seems Java Web Start has had mixed reviews.  Personally I really like it, perhaps because I'm using Java 1.6 and Netbeans 6 M10 which makes it all pretty straightforward (auto jar-signing is really helpful in M10).&lt;br /&gt;&lt;br /&gt;Give it a go, let me know what you think: &lt;a href="http://kernowforsaxon.sourceforge.net/jws.html"&gt;Kernow - Java Web Start&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-7912423256246298979?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://kernowforsaxon.sourceforge.net/jws.html' title='Kernow now available via Java Web Start'/><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/7912423256246298979/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=7912423256246298979&amp;isPopup=true' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/7912423256246298979'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/7912423256246298979'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/08/kernow-now-available-via-java-web-start.html' title='Kernow now available via Java Web Start'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-7393500625857233347</id><published>2007-08-17T14:18:00.000Z</published><updated>2007-08-17T14:29:53.411Z</updated><title type='text'>Using XQuery and the slash operator to quickly try out XPaths</title><content type='html'>This is the coolest thing I've seen in a while...  &lt;br /&gt;&lt;br /&gt;In XQuery you can constuct a node just by writing it, eg:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;node&gt;I'm a node&amp;lt;/node&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;and then you can use slash operator to apply an XPath to that node:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;node&gt;I'm a node&amp;lt;/node&gt;/data(.)&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;returns "I'm a node"&lt;br /&gt;&lt;br /&gt;The XML doesn't have to be limited to a single node - you can do:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;root&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;lt;node&gt;foo&amp;lt;/node&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;lt;node&gt;bar&amp;lt;/node&gt;&lt;br /&gt;&amp;lt;/root&gt;/node/data(.)&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;...to get "foo bar".&lt;br /&gt;&lt;br /&gt;Or:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;root&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;lt;node&gt;foo&amp;lt;/node&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;lt;node&gt;bar&amp;lt;/node&gt;&lt;br /&gt;&amp;lt;/root&gt;/node[1]&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;to get:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;node&gt;foo&amp;lt;/node&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Using this technique in combination with &lt;a href="http://kernowforsaxon.sf.net/"&gt;Kernow's XQuery Sandbox&lt;/a&gt; makes it straightforward to paste in some XML and start trying out some XPaths.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-7393500625857233347?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/7393500625857233347/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=7393500625857233347&amp;isPopup=true' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/7393500625857233347'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/7393500625857233347'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/08/using-xquery-and-slash-operator-to.html' title='Using XQuery and the slash operator to quickly try out XPaths'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-6665979842412983609</id><published>2007-08-16T14:59:00.000Z</published><updated>2007-08-16T15:44:44.282Z</updated><title type='text'>When a = b and a != b both return true...</title><content type='html'>In XPath = and != are set operators.  That is, they return true if any item on the left hand side returns true when compared with any item on the right hand side.  Or in other words:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;some x in $seqA, y in $seqB satisfies x op y&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;...where "op" is = or != (or &gt; or &amp;lt; etc)&lt;br /&gt;&lt;br /&gt;To demonstrate this take the two sets ('a', 'b') and ('b', 'c'):&lt;br /&gt;&lt;br /&gt;$seqA = $seqB returns true because both sets contains 'b'&lt;br /&gt;&lt;br /&gt;$seqA != $seqB returns true because setA contains 'a' which is not equal to 'c' in setB&lt;br /&gt;&lt;br /&gt;This catches me out a lot, even though I've been caught out before several times.  I really have to think hard about what it is exactly that I'm comparing, and still end up getting it wrong.&lt;br /&gt;&lt;br /&gt;A simple rules to follow is &lt;b&gt;"never use != where both sides are sequences of more than one item"&lt;/b&gt;.  99.9% of the time you won't need to, as much as it feels like the right thing to do.  &lt;br /&gt;&lt;br /&gt;Below are some of the most common operations on sequences, put together for a reference.&lt;br /&gt;&lt;br /&gt;The two sequences are ('a', 'b') and ('b', 'c'), which can be defined in XSLT as:&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:variable name="seqA" select="('a', 'b')" as="xs:string+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="seqB" select="('b', 'c')" as="xs:string+"/&gt;&lt;br /&gt;&lt;br /&gt;or in XQuery as:&lt;br /&gt;&lt;br /&gt;let $seqA := ('a', 'b')&lt;br /&gt;let $seqB := ('b', 'c')&lt;br /&gt;&lt;br /&gt;&lt;br/&gt;&lt;br /&gt;  &lt;div&gt;&lt;i&gt;Select all items in both sequences&lt;/i&gt;&lt;/div&gt;&lt;br /&gt;  &lt;div&gt;($seqA, $seqB)&lt;/div&gt;&lt;br /&gt;  &lt;div&gt;Result: a b b c&lt;/div&gt;&lt;br /&gt;&lt;br/&gt;&lt;br /&gt;  &lt;div&gt;&lt;i&gt;Select all items in both sequences, eliminating duplicates&lt;/i&gt;&lt;/div&gt;&lt;br /&gt;  &lt;div&gt;distinct-values(($seqA, $seqB))&lt;/div&gt;&lt;br /&gt;  &lt;div&gt;Result: a b c&lt;/div&gt;&lt;br /&gt;&lt;br/&gt;&lt;br /&gt;  &lt;div&gt;&lt;i&gt;Select all items that occur in $seq1 but not $seq2&lt;/i&gt;&lt;/div&gt;&lt;br /&gt;  &lt;div&gt;$seqA[not(. = $seqB)]&lt;/div&gt;&lt;br /&gt;  &lt;div&gt;Result: a&lt;/div&gt;&lt;br /&gt;&lt;br/&gt;&lt;br /&gt;  &lt;div&gt;&lt;i&gt;Select all items that occur in both sequences&lt;/i&gt;&lt;/div&gt;&lt;br /&gt;  &lt;div&gt;$seqA[. = $seqB]&lt;/div&gt;&lt;br /&gt;  &lt;div&gt;Result: b&lt;/div&gt;&lt;br /&gt;&lt;br/&gt;&lt;br /&gt;  &lt;div&gt;&lt;i&gt;Select all items that do not occur in both sequences&lt;/i&gt;&lt;/div&gt;&lt;br /&gt;  &lt;div&gt;($seqA[not(. = $seqB)],$seqB[not(. = $seqA)])&lt;br /&gt;            &lt;br&gt;or&lt;br&gt;&lt;br /&gt;            ($seqA, $seqB)[not(. = $seqA[. = $seqB])]&lt;br /&gt;          &lt;br /&gt;  &lt;/div&gt;&lt;br /&gt;  &lt;div&gt;Result: a c&lt;/div&gt;&lt;br /&gt;&lt;br/&gt;&lt;br /&gt;  &lt;div&gt;&lt;i&gt;Determine if both sequences are identical&lt;/i&gt;&lt;/div&gt;&lt;br /&gt;  &lt;div&gt;deep-equal($seqA, $seqB)&lt;/div&gt;&lt;br /&gt;  &lt;div&gt;Result: false&lt;/div&gt;&lt;br /&gt;&lt;br/&gt;&lt;br /&gt;  &lt;div&gt;&lt;i&gt;Test if all items in the sequence are different&lt;/i&gt;&lt;/div&gt;&lt;br /&gt;  &lt;div&gt;count(distinct-values($seqA)) eq count($seqA)&lt;/div&gt;&lt;br /&gt;  &lt;div&gt;Result: true&lt;/div&gt;&lt;br /&gt;&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-6665979842412983609?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/6665979842412983609/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=6665979842412983609&amp;isPopup=true' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/6665979842412983609'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/6665979842412983609'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/08/when-b-and-b-both-return-true.html' title='When a = b and a != b both return true...'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-2939328824060159474</id><published>2007-08-15T09:24:00.000Z</published><updated>2007-08-15T10:31:08.553Z</updated><title type='text'>The Worlds Fastest Sudoku Solution in XSLT 2.0 for the Worlds Hardest Sudoku - Al Escargot</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_ypDtgn03LEU/RsLIJuCKmpI/AAAAAAAAABE/GsS_ZPWtXdE/s1600-h/Al+escargot.jpg"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://1.bp.blogspot.com/_ypDtgn03LEU/RsLIJuCKmpI/AAAAAAAAABE/GsS_ZPWtXdE/s320/Al+escargot.jpg" alt="" id="BLOGGER_PHOTO_ID_5098857797438315154" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;I get a lot of traffic to this blog because of my Sudoku solver.  Google analytics tells me most of it lands on the &lt;a href="http://ajwelch.blogspot.com/2006/05/sudoku-solver-complete-and-shes-fast_02.html"&gt;original version&lt;/a&gt; that I wrote, and not the &lt;a href="http://www.andrewjwelch.com/code/xslt/sudoku/sudoku-solver.html"&gt;optimised version&lt;/a&gt; that's now the worlds fastest XSLT 2.0 solution - an issue which this post should hopefully rectify.&lt;br /&gt;&lt;br /&gt;The puzzle on the right is apparently the &lt;a href="http://zonkedyak.blogspot.com/2006/11/worlds-hardest-sudoku-puzzle-al.html"&gt;worlds hardest Sudoku puzzle&lt;/a&gt;.  If I run my solver "&lt;a href="http://www.andrewjwelch.com/code/xslt/sudoku/sudoku-solver.html"&gt;Sudoku.xslt&lt;/a&gt;" using &lt;a href="http://kernowforsaxon.sourceforge.net/"&gt;Kernow's performance testing feature&lt;/a&gt; I get this result:&lt;br /&gt;&lt;br /&gt;Ran 5 times&lt;br /&gt;Run 1: 328 ms&lt;br /&gt;Run 2: 344 ms&lt;br /&gt;Run 3: 328 ms&lt;br /&gt;Run 4: 391 ms&lt;br /&gt;Run 5: 328 ms&lt;br /&gt;Ignoring first 2 times&lt;br /&gt;Total Time (last 3 runs): 1 second 47 ms&lt;br /&gt;Average Time (last 3 runs): 349 ms&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;So on my machine the average execution time is 349ms, which is pretty good considering the original version would take minutes for several puzzles.  As far as I know this version will solve all puzzles in under a second on my machine (Core 2 duo E6600, 2gb).&lt;br /&gt;&lt;br /&gt;How does it do it?  This is taken from the &lt;a href="http://www.andrewjwelch.com/code/xslt/sudoku/sudoku-solver.html"&gt;web page&lt;/a&gt; where it's hosted:&lt;br /&gt;&lt;br /&gt;It accepts the puzzle as 81 comma separated integers in the range 0 to 9, with zero representing empty.  It works by continuously reducing the number of possible values for each cell, and only when the possible values can't be reduced any further it starts backtracking.&lt;br /&gt;&lt;br /&gt;The first phase attempts to populate as many cells of the board based on the start values. For each empty cell it works out the possible values using the "Naked Single", "Hidden Single" and "Naked Tuples" techniques in that order (read here for more on the techniques). Cells where only one possible value exists are populated and then the second phase begins.&lt;br /&gt;&lt;br /&gt;The second phase follows this process:&lt;br /&gt;&lt;br /&gt;   * Find all empty cells and get all the possible values for each cell (using Naked Single and Hidden Single techniques)&lt;br /&gt;   * Sort the cells by least possible values first&lt;br /&gt;   * Populate the cells with only one possible value&lt;br /&gt;   * If more there's more than one value, go through them one by one&lt;br /&gt;   * Repeat&lt;br /&gt;&lt;br /&gt;This is how it solves the Al Esgargot: A slightly modified version of the solution gives this output with the $verbose parameter set to true.  As you can see it's found that it can insert a 1 at position 66 using the static analysis of the puzzle (position 66 is middle-right of the bottom-left group).  Next it's decided that there are two possible values 4 and 6 at index 12 (middle-right cell of top-left group), so it tries 4 and continues.  With that 4 in place it's found that there's only one possible value at index 39, a 3, so it inserts that and continues.  It will keep reducing the possible values based on the current state of the board, inserting the only possible values or trying each one when there are many, until either there are no possible values for an empty cell, or the puzzle is solved.&lt;br /&gt;&lt;br /&gt;(the solution is shown below)&lt;br /&gt;&lt;br /&gt;Populated single value cell at index 66 with 1&lt;br /&gt;Trying 4 out of a possible 4 6 at index 12&lt;br /&gt;Only one value 3 for index 39&lt;br /&gt;Trying 5 out of a possible 5 7 at index 10&lt;br /&gt;Trying 1 out of a possible 1 9 at index 13&lt;br /&gt;Only one value 9 for index 15&lt;br /&gt;Trying 6 out of a possible 6 7 at index 16&lt;br /&gt;Only one value 7 for index 17&lt;br /&gt;Trying 2 out of a possible 2 4 at index 7&lt;br /&gt;Trying 6 out of a possible 6 8 at index 2&lt;br /&gt;Only one value 8 for index 3&lt;br /&gt;Only one value 2 for index 48&lt;br /&gt;Only one value 6 for index 57&lt;br /&gt;Only one value 5 for index 60&lt;br /&gt;Only one value 9 for index 63&lt;br /&gt;Only one value 5 for index 74&lt;br /&gt;Only one value 1 for index 78&lt;br /&gt;Only one value 3 for index 69&lt;br /&gt;Only one value 5 for index 71&lt;br /&gt;Only one value 6 for index 81&lt;br /&gt;Only one value 4 for index 36&lt;br /&gt;! Cannot go any further !&lt;br /&gt;Trying 8 out of a possible 8 at index 2&lt;br /&gt;Only one value 6 for index 3&lt;br /&gt;Trying 4 out of a possible 4 5 at index 4&lt;br /&gt;Only one value 3 for index 9&lt;br /&gt;Only one value 3 for index 23&lt;br /&gt;Only one value 4 for index 26&lt;br /&gt;Only one value 3 for index 53&lt;br /&gt;Only one value 5 for index 54&lt;br /&gt;Only one value 4 for index 36&lt;br /&gt;Only one value 6 for index 44&lt;br /&gt;Only one value 8 for index 48&lt;br /&gt;Only one value 2 for index 57&lt;br /&gt;Only one value 8 for index 73&lt;br /&gt;Only one value 9 for index 47&lt;br /&gt;Only one value 7 for index 50&lt;br /&gt;Only one value 6 for index 60&lt;br /&gt;Only one value 9 for index 64&lt;br /&gt;! Cannot go any further !&lt;br /&gt;Trying 5 out of a possible 5 at index 4&lt;br /&gt;Only one value 9 for index 47&lt;br /&gt;Only one value 2 for index 67&lt;br /&gt;Only one value 7 for index 49&lt;br /&gt;Only one value 9 for index 64&lt;br /&gt;Only one value 2 for index 80&lt;br /&gt;Only one value 1 for index 32&lt;br /&gt;Only one value 2 for index 48&lt;br /&gt;Only one value 5 for index 50&lt;br /&gt;Only one value 8 for index 58&lt;br /&gt;Only one value 8 for index 73&lt;br /&gt;! Cannot go any further !&lt;br /&gt;Trying 4 out of a possible 4 at index 7&lt;br /&gt;Only one value 3 for index 9&lt;br /&gt;Only one value 4 for index 23&lt;br /&gt;Only one value 3 for index 24&lt;br /&gt;Only one value 2 for index 26&lt;br /&gt;Only one value 3 for index 53&lt;br /&gt;Only one value 5 for index 54&lt;br /&gt;Only one value 8 for index 4&lt;br /&gt;Trying 2 out of a possible 2 6 at index 2&lt;br /&gt;Only one value 6 for index 3&lt;br /&gt;Trying 7 out of a possible 7 8 at index 19&lt;br /&gt;Only one value 8 for index 20&lt;br /&gt;Only one value 7 for index 29&lt;br /&gt;Only one value 9 for index 40&lt;br /&gt;Only one value 7 for index 50&lt;br /&gt;Only one value 6 for index 44&lt;br /&gt;Only one value 2 for index 49&lt;br /&gt;Only one value 2 for index 28&lt;br /&gt;Only one value 8 for index 48&lt;br /&gt;Only one value 2 for index 57&lt;br /&gt;Only one value 5 for index 67&lt;br /&gt;Only one value 7 for index 58&lt;br /&gt;Only one value 8 for index 61&lt;br /&gt;Only one value 3 for index 68&lt;br /&gt;Only one value 2 for index 70&lt;br /&gt;! Cannot go any further !&lt;br /&gt;Trying 8 out of a possible 8 at index 19&lt;br /&gt;Only one value 7 for index 20&lt;br /&gt;Only one value 8 for index 29&lt;br /&gt;Only one value 9 for index 47&lt;br /&gt;Only one value 2 for index 48&lt;br /&gt;Only one value 8 for index 57&lt;br /&gt;Only one value 4 for index 37&lt;br /&gt;Only one value 9 for index 40&lt;br /&gt;Only one value 7 for index 49&lt;br /&gt;! Cannot go any further !&lt;br /&gt;Trying 6 out of a possible 6 at index 2&lt;br /&gt;Only one value 2 for index 3&lt;br /&gt;Only one value 6 for index 57&lt;br /&gt;Trying 7 out of a possible 7 8 at index 19&lt;br /&gt;Only one value 8 for index 20&lt;br /&gt;Trying 2 out of a possible 2 4 at index 28&lt;br /&gt;Only one value 7 for index 29&lt;br /&gt;Only one value 9 for index 47&lt;br /&gt;Only one value 2 for index 49&lt;br /&gt;Only one value 9 for index 40&lt;br /&gt;Only one value 6 for index 44&lt;br /&gt;Only one value 7 for index 50&lt;br /&gt;Only one value 9 for index 59&lt;br /&gt;! Cannot go any further !&lt;br /&gt;Trying 4 out of a possible 4 at index 28&lt;br /&gt;Only one value 9 for index 37&lt;br /&gt;Only one value 4 for index 44&lt;br /&gt;Only one value 6 for index 42&lt;br /&gt;Trying 2 out of a possible 2 7 at index 29&lt;br /&gt;Only one value 7 for index 47&lt;br /&gt;Only one value 2 for index 49&lt;br /&gt;Only one value 7 for index 32&lt;br /&gt;Only one value 9 for index 50&lt;br /&gt;! Cannot go any further !&lt;br /&gt;Trying 7 out of a possible 7 at index 29&lt;br /&gt;Only one value 1 for index 32&lt;br /&gt;Only one value 2 for index 33&lt;br /&gt;Only one value 2 for index 47&lt;br /&gt;Trying 7 out of a possible 7 9 at index 49&lt;br /&gt;Only one value 9 for index 50&lt;br /&gt;Only one value 6 for index 77&lt;br /&gt;Only one value 1 for index 78&lt;br /&gt;Only one value 5 for index 80&lt;br /&gt;Only one value 5 for index 56&lt;br /&gt;Only one value 8 for index 60&lt;br /&gt;Only one value 2 for index 61&lt;br /&gt;Only one value 8 for index 70&lt;br /&gt;Only one value 6 for index 71&lt;br /&gt;Only one value 9 for index 74&lt;br /&gt;Only one value 2 for index 64&lt;br /&gt;Only one value 4 for index 81&lt;br /&gt;Only one value 4 for index 58&lt;br /&gt;Only one value 9 for index 67&lt;br /&gt;Only one value 8 for index 73&lt;br /&gt;Only one value 2 for index 76&lt;br /&gt;Done!&lt;br /&gt;&lt;br /&gt;1, 6, 2,&amp;#160;&amp;#160;&amp;#160;8, 5, 7,&amp;#160;&amp;#160;&amp;#160;4, 9, 3,  &lt;br /&gt;5, 3, 4,&amp;#160;&amp;#160;&amp;#160;1, 2, 9,&amp;#160;&amp;#160;&amp;#160;6, 7, 8,  &lt;br /&gt;7, 8, 9,&amp;#160;&amp;#160;&amp;#160;6, 4, 3,&amp;#160;&amp;#160;&amp;#160;5, 2, 1,  &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;4, 7, 5,&amp;#160;&amp;#160;&amp;#160;3, 1, 2,&amp;#160;&amp;#160;&amp;#160;9, 8, 6,  &lt;br /&gt;9, 1, 3,&amp;#160;&amp;#160;&amp;#160;5, 8, 6,&amp;#160;&amp;#160;&amp;#160;7, 4, 2,  &lt;br /&gt;6, 2, 8,&amp;#160;&amp;#160;&amp;#160;7, 9, 4,&amp;#160;&amp;#160;&amp;#160;1, 3, 5,  &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;3, 5, 6,&amp;#160;&amp;#160;&amp;#160;4, 7, 8,&amp;#160;&amp;#160;&amp;#160;2, 1, 9,  &lt;br /&gt;2, 4, 1,&amp;#160;&amp;#160;&amp;#160;9, 3, 5,&amp;#160;&amp;#160;&amp;#160;8, 6, 7,  &lt;br /&gt;8, 9, 7,&amp;#160;&amp;#160;&amp;#160;2, 6, 1,&amp;#160;&amp;#160;&amp;#160;3, 5, 4,  &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;If you have a solution that can statically detect more cells to fill using different techniques than I have, or has a better strategy than simply backtracking when there's more than one value, then I'd be interested to know it works. &lt;br /&gt;&lt;br /&gt;I'm pretty sure the XSLT is as good as it can be, but if you think it can be improved  in any way then let me know.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-2939328824060159474?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://andrewjwelch.com/code/xslt/sudoku/sudoku-solver.html' title='The Worlds Fastest Sudoku Solution in XSLT 2.0 for the Worlds Hardest Sudoku - Al Escargot'/><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/2939328824060159474/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=2939328824060159474&amp;isPopup=true' title='11 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2939328824060159474'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2939328824060159474'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/08/worlds-fastest-sudoku-solution-in-xslt.html' title='The Worlds Fastest Sudoku Solution in XSLT 2.0 for the Worlds Hardest Sudoku - Al Escargot'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_ypDtgn03LEU/RsLIJuCKmpI/AAAAAAAAABE/GsS_ZPWtXdE/s72-c/Al+escargot.jpg' height='72' width='72'/><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-4554652156325501454</id><published>2007-07-23T10:23:00.000Z</published><updated>2007-07-23T10:40:23.128Z</updated><title type='text'>Combining XSLT 2.0's Grouping with eXist</title><content type='html'>I work a lot with large XML datasets that are arranged as thousands of 1 - 10mb XML files.  I spend most of my days writing transforms and transformation pipelines to process these files, which is where &lt;a href="http://kernowforsaxon.sourceforge.net/"&gt;Kernow&lt;/a&gt; came from.  I also like messing around with &lt;a href="http://exist.sourceforge.net/"&gt;eXist&lt;/a&gt; (I'm yet to use it commercially, but I hope to one day) and enjoying the speed a native XML database gives you.&lt;div id="content"&gt;&lt;div class="entry"&gt;&lt;br /&gt;    &lt;div class="para"&gt;Requirements that regularly come up are to generate indexes and reports for the dataset.  This is nice and simple using XSLT 2.0's grouping but require the whole dataset to be in memory, unless you use &lt;a href="http://ajwelch.blogspot.com/2006/11/using-collection-and-saxondiscard.html"&gt;saxon:discard-document()&lt;/a&gt;.  It can also be quite slow, if only because you have to read GB's from disk and parse the whole of each and every XML input file to just get the snippet that you're interested in (such as the title, or say all of the &lt;xref&gt; elements). &lt;/xref&gt;&lt;/div&gt;&lt;br /&gt;    &lt;div class="para"&gt;Conversely, XQuery doesn't suffer from the dataset size but lacks XSLT 2.0's grouping features.  It's perfectly possible (although a bit involved - you could say "a bit XSLT 1.0") to recreate the grouping in XQuery, but it's just so much nicer in XSLT 2.0.  So to get the best of both, you can use eXist's &lt;a href="http://exist.sourceforge.net/devguide.html#N1021D"&gt;fanstastic REST style interface&lt;/a&gt; to select the parts of the XML you're interested in, and then use XSLT 2.0's for-each-group to arrange the results.&lt;/div&gt;     &lt;br /&gt;   &lt;div class="para"&gt;In the example stylesheet below I create an index by getting the &amp;lt;title&gt; for each XML document, and then grouping the titles by their first letter, then sorting by title itself. I use eXist to get the &amp;lt;title&gt; element, then XSLT 2.0 to do the sorting and grouping.&lt;/div&gt;&lt;br /&gt;  &lt;div class="para"&gt;I have an instance of eXist running on my local machine and fully populated with the XML dataset.  The function fn:eXist() takes the collection I'm interested in and the XQuery to execute against that collection, constructs the correct URI for the REST interface and calls doc() with that URI.  The result is a &lt;a href="http://exist.sourceforge.net/devguide.html#N1029D"&gt;proprietary XML format&lt;/a&gt; containing each tuple that I then group using xsl:for-each-group.  It's worth noting the -1 value for the &lt;code&gt;_howmany&lt;/code&gt; parameter on the query - without this it defaults to 10.&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="xmlverb-default"&gt;&amp;lt;&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;stylesheet&lt;/span&gt;&lt;span class="xmlverb-ns-name"&gt;&lt;br /&gt;&amp;#xa; xmlns:xsl&lt;/span&gt;="&lt;span class="xmlverb-ns-uri"&gt;http://www.w3.org/1999/XSL/Transform&lt;/span&gt;"&lt;span class="xmlverb-ns-name"&gt;&lt;br /&gt;&amp;#xa; xmlns:fn&lt;/span&gt;="&lt;span class="xmlverb-ns-uri"&gt;fn&lt;/span&gt;"&lt;span class="xmlverb-ns-name"&gt;&lt;br /&gt;&amp;#xa; xmlns:exist&lt;/span&gt;="&lt;span class="xmlverb-ns-uri"&gt;http://exist.sourceforge.net/NS/exist&lt;/span&gt;"&lt;br /&gt;&amp;#xa; &lt;span class="xmlverb-attr-name"&gt;version&lt;/span&gt;="&lt;span class="xmlverb-attr-content"&gt;2.0&lt;/span&gt;"&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#xa;&lt;br /&gt;&lt;/span&gt;&amp;lt;&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;output&lt;/span&gt; &lt;span class="xmlverb-attr-name"&gt;indent&lt;/span&gt;="&lt;span class="xmlverb-attr-content"&gt;yes&lt;/span&gt;" /&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#xa;&lt;br /&gt;&lt;/span&gt;&amp;lt;&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;param&lt;/span&gt; &lt;span class="xmlverb-attr-name"&gt;name&lt;/span&gt;="&lt;span class="xmlverb-attr-content"&gt;db-uri&lt;/span&gt;" &lt;span class="xmlverb-attr-name"&gt;select&lt;/span&gt;="&lt;span class="xmlverb-attr-content"&gt;'http://localhost:8080/exist/rest'&lt;/span&gt;" /&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#xa;&lt;br /&gt;&lt;/span&gt;&amp;lt;&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;function&lt;/span&gt; &lt;span class="xmlverb-attr-name"&gt;name&lt;/span&gt;="&lt;span class="xmlverb-attr-content"&gt;fn:eXist&lt;/span&gt;"&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;/span&gt;&amp;lt;&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;param&lt;/span&gt; &lt;span class="xmlverb-attr-name"&gt;name&lt;/span&gt;="&lt;span class="xmlverb-attr-content"&gt;collection&lt;/span&gt;" /&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;/span&gt;&amp;lt;&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;param&lt;/span&gt; &lt;span class="xmlverb-attr-name"&gt;name&lt;/span&gt;="&lt;span class="xmlverb-attr-content"&gt;query&lt;/span&gt;" /&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;/span&gt;&amp;lt;&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;sequence&lt;/span&gt; &lt;span class="xmlverb-attr-name"&gt;select&lt;/span&gt;="&lt;span class="xmlverb-attr-content"&gt;doc(concat($db-uri, $collection, '?_query=', $query, '&amp;amp;amp;_start=1&amp;amp;amp;_howmany=-1'))/exist:result/node()&lt;/span&gt;" /&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&lt;/span&gt;&amp;lt;/&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;function&lt;/span&gt;&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#xa;&lt;br /&gt;&lt;/span&gt;&amp;lt;&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;template&lt;/span&gt; &lt;span class="xmlverb-attr-name"&gt;match&lt;/span&gt;="&lt;span class="xmlverb-attr-content"&gt;/&lt;/span&gt;"&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;/span&gt;&amp;lt;&lt;span class="xmlverb-element-name"&gt;div&lt;/span&gt;&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;/span&gt;&amp;lt;&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;for-each-group&lt;/span&gt; &lt;span class="xmlverb-attr-name"&gt;select&lt;/span&gt;="&lt;span class="xmlverb-attr-content"&gt;fn:eXist('/db/mycomp/myproject', '/doc/head/title')&lt;/span&gt;" &lt;span class="xmlverb-attr-name"&gt;group-by&lt;/span&gt;="&lt;span class="xmlverb-attr-content"&gt;substring(., 1, 1)&lt;/span&gt;"&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;/span&gt;&amp;lt;&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;sort&lt;/span&gt; &lt;span class="xmlverb-attr-name"&gt;select&lt;/span&gt;="&lt;span class="xmlverb-attr-content"&gt;.&lt;/span&gt;" /&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;/span&gt;&amp;lt;&lt;span class="xmlverb-element-name"&gt;div&lt;/span&gt;&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;/span&gt;&amp;lt;&lt;span class="xmlverb-element-name"&gt;div&lt;/span&gt;&amp;gt;&amp;lt;&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;value-of&lt;/span&gt; &lt;span class="xmlverb-attr-name"&gt;select&lt;/span&gt;="&lt;span class="xmlverb-attr-content"&gt;current-grouping-key()&lt;/span&gt;" /&amp;gt;&amp;lt;/&lt;span class="xmlverb-element-name"&gt;div&lt;/span&gt;&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;/span&gt;&amp;lt;&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;for-each&lt;/span&gt; &lt;span class="xmlverb-attr-name"&gt;select&lt;/span&gt;="&lt;span class="xmlverb-attr-content"&gt;current-group()&lt;/span&gt;"&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;/span&gt;&amp;lt;&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;sort&lt;/span&gt; &lt;span class="xmlverb-attr-name"&gt;select&lt;/span&gt;="&lt;span class="xmlverb-attr-content"&gt;.&lt;/span&gt;" /&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;/span&gt;&amp;lt;&lt;span class="xmlverb-element-name"&gt;div&lt;/span&gt;&amp;gt;&amp;lt;&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;value-of&lt;/span&gt; &lt;span class="xmlverb-attr-name"&gt;select&lt;/span&gt;="&lt;span class="xmlverb-attr-content"&gt;.&lt;/span&gt;" /&amp;gt;&amp;lt;/&lt;span class="xmlverb-element-name"&gt;div&lt;/span&gt;&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;/span&gt;&amp;lt;/&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;for-each&lt;/span&gt;&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;/span&gt;&amp;lt;/&lt;span class="xmlverb-element-name"&gt;div&lt;/span&gt;&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;/span&gt;&amp;lt;/&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;for-each-group&lt;/span&gt;&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&lt;/span&gt;&amp;lt;/&lt;span class="xmlverb-element-name"&gt;div&lt;/span&gt;&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&lt;/span&gt;&amp;lt;/&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;template&lt;/span&gt;&amp;gt;&lt;span class="xmlverb-text"&gt;&amp;#xa;&lt;br /&gt;&amp;#xa;&lt;br /&gt;&lt;/span&gt;&amp;lt;/&lt;span class="xmlverb-element-nsprefix"&gt;xsl&lt;/span&gt;:&lt;span class="xmlverb-element-name"&gt;stylesheet&lt;/span&gt;&amp;gt;&amp;#xa;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;It's as simple as that... what would normally take minutes takes seconds (once the database setup is done).  If you haven't used eXist yet I highly recommend it.&lt;br /&gt;&lt;/div&gt;    &lt;br /&gt;&lt;div&gt;This article is &lt;a href="http://andrewjwelch.com/code/xslt/eXist/Combine-with-eXist.html"&gt;repeated here&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-4554652156325501454?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/4554652156325501454/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=4554652156325501454&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/4554652156325501454'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/4554652156325501454'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/07/combining-xslt-20s-grouping-with-exist.html' title='Combining XSLT 2.0&apos;s Grouping with eXist'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-2637733168077463174</id><published>2007-07-11T12:42:00.000Z</published><updated>2007-07-11T13:02:54.950Z</updated><title type='text'>CSV to XML transform updated</title><content type='html'>I've posted a new version of the &lt;a href="http://andrewjwelch.com/code/xslt/csv/csv-to-xml_v2.html"&gt;CSV to XML transform&lt;/a&gt;. This version handles nested quotes correctly - the previous version would generate extra tokens either side of the quoted value.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-2637733168077463174?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://andrewjwelch.com/code/xslt/csv/csv-to-xml_v2.html' title='CSV to XML transform updated'/><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/2637733168077463174/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=2637733168077463174&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2637733168077463174'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2637733168077463174'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/07/csv-to-xml-transform-updated.html' title='CSV to XML transform updated'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-6981445030494352208</id><published>2007-06-29T09:26:00.000Z</published><updated>2007-06-29T09:49:05.460Z</updated><title type='text'>Kernow 1.5.0.9 [beta] available</title><content type='html'>I've made a new beta version of Kernow available:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;XSLT directory transforms are now multi-threaded, with the size of the thread pool configurable in the options.  For systems with multi-core processors and enough available memory this can really improve directory transform time.  &lt;/li&gt;&lt;li&gt;Single File / Standalone transforms now have a "Performance Testing" feature where the transforms are run repeatedly and the average time displayed. &lt;/li&gt;&lt;li&gt;The panels can now be resized for the XQuery Sandbox tab.  The fixed size XQuery Sandbox made it pretty useless before - at least now you can see more than four lines!&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Other minor bug fixes&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;There's loads still to do, but I'm trying to stick to the "release early, release often" policy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-6981445030494352208?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://kernowforsaxon.sourceforge.net' title='Kernow 1.5.0.9 [beta] available'/><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/6981445030494352208/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=6981445030494352208&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/6981445030494352208'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/6981445030494352208'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/06/kernow-1509-beta-available.html' title='Kernow 1.5.0.9 [beta] available'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-5134266872414857103</id><published>2007-06-18T14:19:00.000Z</published><updated>2007-06-18T14:29:56.711Z</updated><title type='text'>UK Postcode Googlemaps mashup</title><content type='html'>Everyone else has done a mashup so I thought I should do one too.  My &lt;a href="http://andrewjwelch.com/code/other/uk-postcode-mashup.html"&gt;mashup&lt;/a&gt; combines GoogleMaps with StreetMap.co.uk's GridConvert to get the nearest postcode for a given longitude and latitude.  I had to use some server side PHP to get around cross domain scripting issues and to scrape the postcode from result HTML, but other than that it's all through the GoogleMaps API.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-5134266872414857103?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://andrewjwelch.com/code/other/uk-postcode-mashup.html' title='UK Postcode Googlemaps mashup'/><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/5134266872414857103/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=5134266872414857103&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/5134266872414857103'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/5134266872414857103'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/06/uk-postcode-googlemaps-mashup.html' title='UK Postcode Googlemaps mashup'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-2953463373348898809</id><published>2007-06-15T10:50:00.000Z</published><updated>2007-06-16T12:48:18.078Z</updated><title type='text'>Sukoku Solver  - much improved.</title><content type='html'>I've just uploaded the latest version of my &lt;a href="http://andrewjwelch.com/code/xslt/sudoku/sudoku-solver.html"&gt;Sudoku Solver&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I wrote the initial version of this in &lt;a href="http://ajwelch.blogspot.com/2006/03/sudoku-solution-in-xslt-20.html"&gt;March 2006&lt;/a&gt;, and have since been in competition with &lt;a href="http://dnovatchev.spaces.live.com/"&gt;Dimitre Novatchev&lt;/a&gt; to have the fastest solution.  Dimitre's held that title for while, but with this version I think I may have won it back.&lt;br /&gt;&lt;br /&gt;The additions here are Naked Tuple discovery in the first phase, and then repeatedly re-ordering the cells by least-number-of-possible-values as each cell is populated in the second phase.  Sounds obvious but its something I missed first time around.&lt;br /&gt;&lt;br /&gt;On my machine this latest version solves the vast majority of puzzles (including &lt;a href="http://zonkedyak.blogspot.com/2006/11/worlds-hardest-sudoku-puzzle-al.html"&gt;AI Escargot&lt;/a&gt;) in under a second, with the occasional one taking just under two seconds.  The previous version would take up to a minute on some puzzles, so I'm pleased with improvement.&lt;br /&gt;&lt;br /&gt;Hopefully Dimitre will do some comparisons using his latest version, and then tell me the good news :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-2953463373348898809?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/2953463373348898809/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=2953463373348898809&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2953463373348898809'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2953463373348898809'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/06/sukoku-solver-much-improved.html' title='Sukoku Solver  - much improved.'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-2426179224863215807</id><published>2007-06-15T10:46:00.000Z</published><updated>2007-06-15T10:50:01.772Z</updated><title type='text'>My new homepage</title><content type='html'>I'd like to draw attention to my new homepage: &lt;a href="http://andrewjwelch.com"&gt;andrewjwelch.com&lt;/a&gt;.  I've created this as a better place to store the code samples that were posted in this blog, and to link to my open source projects.&lt;br /&gt;&lt;br /&gt;I'll try to keep news and opinions to this blog, and code samples on the homepage.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-2426179224863215807?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/2426179224863215807/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=2426179224863215807&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2426179224863215807'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/2426179224863215807'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/06/my-new-homepage.html' title='My new homepage'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-527039818813260022</id><published>2007-06-08T13:21:00.000Z</published><updated>2007-06-08T13:31:33.810Z</updated><title type='text'>A long drawn out redundancy...</title><content type='html'>I've been informed that I will be made redundant... in October!  That's a long 4 1/2 months away.  In the mean time I'm meant to be performing maintenance and handover tasks (4 months of handover ?!?!) while the redundancy + retention carrot is tentatively dangled to keep us here until then...  &lt;br /&gt;&lt;br /&gt;So if you can see that far ahead and need a Java/XML/XSLT bod to fill a high paying contract, let me know :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-527039818813260022?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/527039818813260022/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=527039818813260022&amp;isPopup=true' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/527039818813260022'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/527039818813260022'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/06/long-drawn-out-redundancy.html' title='A long drawn out redundancy...'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-1763779099559359801</id><published>2007-03-30T10:58:00.000Z</published><updated>2007-03-30T11:23:09.875Z</updated><title type='text'>Er, the real "hardest Sudoku"</title><content type='html'>Following the comment from Malcom below, I've run my Sudoku solver against the real "&lt;a href="http://zonkedyak.blogspot.com/2006/11/worlds-hardest-sudoku-puzzle-al.html"&gt;AL Escargot&lt;/a&gt;"... &lt;br /&gt;&lt;br /&gt;Using Saxon 8.9.0.3b from the command line with the -3 option (to discount java startup time) the average time across three runs is 2213ms... which isn't bad at all.&lt;br /&gt;&lt;br /&gt;This is when I just solve the puzzle starting at the top-left - if I turn on the ordering feature where empty cells are processing by least number of possibilities first, then the time dramatically increases to ~58 seconds, which shows why Sudoku is &lt;a href="http://en.wikipedia.org/wiki/NP-complete"&gt;NP-complete&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;If you're interested the solution it produced is:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;1, 6, 2,&amp;nbsp;&amp;nbsp;&amp;nbsp;8, 5, 7,&amp;nbsp;&amp;nbsp;&amp;nbsp;4, 9, 3&lt;br /&gt;5, 3, 4,&amp;nbsp;&amp;nbsp;&amp;nbsp;1, 2, 9,&amp;nbsp;&amp;nbsp;&amp;nbsp;6, 7, 8&lt;br /&gt;7, 8, 9,&amp;nbsp;&amp;nbsp;&amp;nbsp;6, 4, 3,&amp;nbsp;&amp;nbsp;&amp;nbsp;5, 2, 1&lt;br /&gt;&lt;br /&gt;4, 7, 5,&amp;nbsp;&amp;nbsp;&amp;nbsp;3, 1, 2,&amp;nbsp;&amp;nbsp;&amp;nbsp;9, 8, 6&lt;br /&gt;9, 1, 3,&amp;nbsp;&amp;nbsp;&amp;nbsp;5, 8, 6,&amp;nbsp;&amp;nbsp;&amp;nbsp;7, 4, 2&lt;br /&gt;6, 2, 8,&amp;nbsp;&amp;nbsp;&amp;nbsp;7, 9, 4,&amp;nbsp;&amp;nbsp;&amp;nbsp;1, 3, 5&lt;br /&gt;&lt;br /&gt;3, 5, 6,&amp;nbsp;&amp;nbsp;&amp;nbsp;4, 7, 8,&amp;nbsp;&amp;nbsp;&amp;nbsp;2, 1, 9,&lt;br /&gt;2, 4, 1,&amp;nbsp;&amp;nbsp;&amp;nbsp;9, 3, 5,&amp;nbsp;&amp;nbsp;&amp;nbsp;8, 6, 7,&lt;br /&gt;8, 9, 7,&amp;nbsp;&amp;nbsp;&amp;nbsp;2, 6, 1,&amp;nbsp;&amp;nbsp;&amp;nbsp;3, 5, 4,&lt;br /&gt;&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-1763779099559359801?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/1763779099559359801/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=1763779099559359801&amp;isPopup=true' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/1763779099559359801'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/1763779099559359801'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/03/er-real-hardest-sudoku.html' title='Er, the &lt;i&gt;real&lt;/i&gt; &quot;hardest Sudoku&quot;'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-7594115620193070481</id><published>2007-03-29T15:17:00.000Z</published><updated>2007-03-29T15:23:51.055Z</updated><title type='text'>The Worlds Hardest Sudoku</title><content type='html'>A mathematician claims to have &lt;a href="http://www.usatoday.com/news/offbeat/2006-11-06-sudoku_x.htm"&gt;penned the world's hardest Sudoku puzzle&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Running it through my &lt;a href="http://ajwelch.blogspot.com/2006/05/sudoku-solver-complete-and-shes-fast_02.html"&gt;Sudoku Solver&lt;/a&gt; it took ~24 seconds on my work machine (most puzzles are sub 1 second on the same machine) so I would think it must be reasonably hard.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-7594115620193070481?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/7594115620193070481/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=7594115620193070481&amp;isPopup=true' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/7594115620193070481'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/7594115620193070481'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/03/worlds-hardest-sudoku.html' title='The Worlds Hardest Sudoku'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-5061745304595743761</id><published>2007-02-23T10:33:00.000Z</published><updated>2008-02-06T14:32:51.302Z</updated><title type='text'>A CSV to XML converter in XSLT 2.0</title><content type='html'>&lt;p style="font-weight:bold;"&gt;* Note: This transform has been updated &lt;a href="http://andrewjwelch.com/code/xslt/csv/csv-to-xml_v2.html"&gt;http://andrewjwelch.com/code/xslt/csv/csv-to-xml_v2.html&lt;/a&gt;&lt;/p&gt;&lt;br /&gt;I wrote a rudimentary csv to XML converter a while back which broke when the csv contained quoted values,  eg &lt;code&gt;foo, "foo, bar", bar&lt;/code&gt;  Dealing with these quotes is surprisingly hard,  especially when you take into account quotes are escaped by doubling them.&lt;br /&gt;&lt;br /&gt;I raised it on &lt;a href="http://www.biglist.com/lists/xsl-list/archives/200702/msg00599.html"&gt;xsl-list&lt;/a&gt;, and &lt;a href="http://abel.metacarpus.com/"&gt;Abel Braaksma&lt;/a&gt; came up with a genious solution - the technique is to use both sides of analyze-string - read &lt;a href="http://www.biglist.com/lists/xsl-list/archives/200702/msg00629.html"&gt;his post&lt;/a&gt; for the best explanation.&lt;br /&gt;&lt;br /&gt;To keep the transform generic I've used an attribute instead of an element for the column names to cope with names that aren't valid QNames (for example ones that contain a space) - for my own use would add a function to convert names to valid QNames and then change &amp;lt;elem name="{...}"&gt; to &amp;lt;xsl:element name="{fn:getQName(...)}"&gt; as it generates nicer XML.  I've also modified the non-matching-substring side of analyze-string to only return tokens that contain values.&lt;br /&gt;&lt;br /&gt;So this sample input:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;Col 1, Col 2, Col 3&lt;br /&gt;foo, "foo,bar", "foo:""bar"""&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;...creates this output:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;root&gt;&lt;br /&gt;&amp;lt;row&gt;&lt;br /&gt;  &amp;lt;elem name="Col 1"&gt;foo&amp;lt;/elem&gt;&lt;br /&gt;  &amp;lt;elem name="Col 2"&gt;foo,bar&amp;lt;/elem&gt;&lt;br /&gt;  &amp;lt;elem name="Col 3"&gt;foo:"bar"&amp;lt;/elem&gt;&lt;br /&gt;&amp;lt;/row&gt;&lt;br /&gt;&amp;lt;/root&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here's the finished transform:&lt;br /&gt;&lt;div style="overflow: auto;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;xsl:stylesheet version="2.0"&lt;br /&gt;xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&lt;br /&gt;xmlns:xs="http://www.w3.org/2001/XMLSchema"&lt;br /&gt;xmlns:fn="fn"&lt;br /&gt;exclude-result-prefixes="xs fn"&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:output indent="yes" encoding="US-ASCII"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:param name="pathToCSV" select="'file:///c:/csv.csv'"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:getTokens" as="xs:string+"&gt;&lt;br /&gt;&amp;lt;xsl:param name="str" as="xs:string"/&gt;&lt;br /&gt;&amp;lt;xsl:analyze-string regex='("[^"]*")+' select="$str"&gt;&lt;br /&gt;   &amp;lt;xsl:matching-substring&gt;&lt;br /&gt;       &amp;lt;xsl:sequence select='replace(., "^""|""$|("")""", "$1")'/&gt;&lt;br /&gt;   &amp;lt;/xsl:matching-substring&gt;&lt;br /&gt;   &amp;lt;xsl:non-matching-substring&gt;&lt;br /&gt;       &amp;lt;xsl:for-each select="tokenize(., '\s*,\s*')"&gt;&lt;br /&gt;           &amp;lt;xsl:sequence select="."/&gt;&lt;br /&gt;       &amp;lt;/xsl:for-each&gt;&lt;br /&gt;   &amp;lt;/xsl:non-matching-substring&gt;&lt;br /&gt;&amp;lt;/xsl:analyze-string&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="/" name="main"&gt;&lt;br /&gt;&amp;lt;xsl:choose&gt;&lt;br /&gt;    &amp;lt;xsl:when test="unparsed-text-available($pathToCSV)"&gt;&lt;br /&gt;        &amp;lt;xsl:variable name="csv" select="unparsed-text($pathToCSV)"/&gt;&lt;br /&gt;        &amp;lt;xsl:variable name="lines" select="tokenize($csv, '&amp;amp;#xa;')" as="xs:string+"/&gt;&lt;br /&gt;        &amp;lt;xsl:variable name="elemNames" select="fn:getTokens($lines[1])" as="xs:string+"/&gt;&lt;br /&gt;        &amp;lt;root&gt;&lt;br /&gt;            &amp;lt;xsl:for-each select="$lines[position() &gt; 1]"&gt;&lt;br /&gt;                &amp;lt;row&gt;&lt;br /&gt;                    &amp;lt;xsl:variable name="lineItems" select="fn:getTokens(.)" as="xs:string+"/&gt;&lt;br /&gt;&lt;br /&gt;                    &amp;lt;xsl:for-each select="$elemNames"&gt;&lt;br /&gt;                        &amp;lt;xsl:variable name="pos" select="position()"/&gt;&lt;br /&gt;                        &amp;lt;elem name="{.}"&gt;&lt;br /&gt;                            &amp;lt;xsl:value-of select="$lineItems[$pos]"/&gt;&lt;br /&gt;                        &amp;lt;/elem&gt;&lt;br /&gt;                    &amp;lt;/xsl:for-each&gt;&lt;br /&gt;                &amp;lt;/row&gt;&lt;br /&gt;            &amp;lt;/xsl:for-each&gt;&lt;br /&gt;        &amp;lt;/root&gt;&lt;br /&gt;    &amp;lt;/xsl:when&gt;&lt;br /&gt;    &amp;lt;xsl:otherwise&gt;&lt;br /&gt;        &amp;lt;xsl:text&gt;Cannot locate : &amp;lt;/xsl:text&gt;&lt;br /&gt;        &amp;lt;xsl:value-of select="$pathToCSV"/&gt;&lt;br /&gt;    &amp;lt;/xsl:otherwise&gt;&lt;br /&gt;&amp;lt;/xsl:choose&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/xsl:stylesheet&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-5061745304595743761?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/5061745304595743761/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=5061745304595743761&amp;isPopup=true' title='16 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/5061745304595743761'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/5061745304595743761'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/02/csv-to-xml-converter-in-xslt-20.html' title='A CSV to XML converter in XSLT 2.0'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>16</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-481461527844075500</id><published>2007-02-05T13:48:00.000Z</published><updated>2007-02-05T15:03:17.547Z</updated><title type='text'>A Soap Extension Function</title><content type='html'>&lt;p&gt;Recently I had to write a client to retrieve XML from a web service that required authentication.  All this meant was that the credentials needed to be in the soap header, eg:&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;&amp;lt;soapenv:Envelope&gt;&lt;br /&gt;&amp;lt;soapenv:Header&gt;&lt;br /&gt;&amp;lt;c:AuthHeader&gt;&lt;br /&gt;&amp;lt;c:Username&gt;foo&amp;lt;/c:Username&gt;&lt;br /&gt;&amp;lt;c:Password&gt;bar&amp;lt;/c:Password&gt;&lt;br /&gt;&amp;lt;/c:AuthHeader&gt;&lt;br /&gt;&amp;lt;/soapenv:Header&gt;&lt;br /&gt;&amp;lt;soapenv:Body&gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;Anyone that's coded any Java web service clients will know how hard it is to get the methods generated for you to add this soap header to your calls... its a nightmare.  It varies between generation tool and the specification level you're coding to.  What makes it worse is the whole reason you go through this pain is to send XML down the wire and get XML back.  It would be much nicer if you could just make the call in XSLT....&lt;br /&gt;&lt;br /&gt;I've written the following extension function to do just that.  It accepts a soap request and an endpoint, makes the request and returns the soap response.  It leaves the "complexity" of creating the request and processing to response to the XSLT, where it's pretty straightforward.  Here's the java:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;package net.sf.kernow.soapextension;&lt;br /&gt;&lt;br /&gt;import java.io.BufferedReader;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.io.InputStreamReader;&lt;br /&gt;import java.io.OutputStream;&lt;br /&gt;import java.io.StringWriter;&lt;br /&gt;import java.io.UnsupportedEncodingException;&lt;br /&gt;import java.net.HttpURLConnection;&lt;br /&gt;import java.net.MalformedURLException;&lt;br /&gt;import java.net.ProtocolException;&lt;br /&gt;import java.net.URL;&lt;br /&gt;import java.net.URLConnection;&lt;br /&gt;import javax.xml.transform.Transformer;&lt;br /&gt;import javax.xml.transform.TransformerConfigurationException;&lt;br /&gt;import javax.xml.transform.TransformerException;&lt;br /&gt;import javax.xml.transform.TransformerFactory;&lt;br /&gt;import javax.xml.transform.stream.StreamResult;&lt;br /&gt;import net.sf.saxon.om.NodeInfo;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* Enables the calling of SOAP based web services from XSLT.&lt;br /&gt;* @author Andrew Welch&lt;br /&gt;*/&lt;br /&gt;public class SOAPExtension {&lt;br /&gt;&lt;br /&gt;public static String soapRequest(NodeInfo requestXML, String endpoint) {&lt;br /&gt; String result = makeCall(transformToString(requestXML), endpoint);&lt;br /&gt; return result;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static String soapRequest(String requestXML, String endpoint) {&lt;br /&gt; String result = makeCall(requestXML, endpoint);&lt;br /&gt; return result;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;private static String transformToString(NodeInfo sourceXML) {&lt;br /&gt;&lt;br /&gt; StringWriter sw = new StringWriter();&lt;br /&gt;&lt;br /&gt; try {&lt;br /&gt;     TransformerFactory tFactory = new net.sf.saxon.TransformerFactoryImpl();&lt;br /&gt;     Transformer transformer = tFactory.newTransformer();&lt;br /&gt;     transformer.transform(sourceXML, new StreamResult(sw));&lt;br /&gt; } catch (TransformerConfigurationException ex) {&lt;br /&gt;     ex.printStackTrace();&lt;br /&gt; } catch (TransformerException ex) {&lt;br /&gt;     ex.printStackTrace();&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; return sw.toString();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;private static String makeCall(String requestXML, String endpoint) {&lt;br /&gt; &lt;br /&gt; String SOAPUrl = endpoint;&lt;br /&gt; StringBuffer responseBuf = new StringBuffer();&lt;br /&gt;&lt;br /&gt; try {    &lt;br /&gt;     // Create the connection to the endpoint&lt;br /&gt;     URL url = new URL(SOAPUrl);&lt;br /&gt;     URLConnection connection = url.openConnection();&lt;br /&gt;     HttpURLConnection httpConn = (HttpURLConnection) connection;&lt;br /&gt;                  &lt;br /&gt;     byte[] b = requestXML.getBytes("UTF-8");&lt;br /&gt;&lt;br /&gt;     // Set the appropriate HTTP parameters.&lt;br /&gt;     httpConn.setRequestProperty( "Content-Length", String.valueOf(b.length));&lt;br /&gt;     httpConn.setRequestProperty("Content-Type","text/xml; charset=utf-8");&lt;br /&gt;&lt;br /&gt;     httpConn.setRequestMethod("POST");&lt;br /&gt;     httpConn.setDoOutput(true);&lt;br /&gt;     httpConn.setDoInput(true);&lt;br /&gt;&lt;br /&gt;     // Send the the request&lt;br /&gt;     OutputStream out = httpConn.getOutputStream();&lt;br /&gt;     out.write(b);&lt;br /&gt;     out.close();&lt;br /&gt;&lt;br /&gt;     // Read the response and write it to the response buffer.    &lt;br /&gt;     InputStreamReader isr = new InputStreamReader(httpConn.getInputStream());&lt;br /&gt;     BufferedReader in = new BufferedReader(isr);&lt;br /&gt;&lt;br /&gt;     String line;&lt;br /&gt;     do {&lt;br /&gt;         line = in.readLine();&lt;br /&gt;         if (line != null) {&lt;br /&gt;             responseBuf.append(line);&lt;br /&gt;         }&lt;br /&gt;     } while (line != null);&lt;br /&gt;&lt;br /&gt;     in.close();&lt;br /&gt;&lt;br /&gt; } catch (ProtocolException ex) {&lt;br /&gt;     ex.printStackTrace();&lt;br /&gt; } catch (MalformedURLException ex) {&lt;br /&gt;     ex.printStackTrace();&lt;br /&gt; } catch (UnsupportedEncodingException ex) {&lt;br /&gt;     ex.printStackTrace();&lt;br /&gt; } catch (IOException ex) {&lt;br /&gt;     ex.printStackTrace();&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; return responseBuf.toString();&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;I've put the extension function in the "net.sf.kernow.soapextension" package and called it SOAPExtension (it will be in the 1.5 version of Kernow when I eventually release it). Now the XSLT to make and process the requests:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;xsl:stylesheet version="2.0"&lt;br /&gt;xmlns:soap="net.sf.kernow.soapextension.SOAPExtension"&lt;br /&gt;xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&lt;br /&gt;xmlns:saxon="http://saxon.sf.net/"&lt;br /&gt;xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"&lt;br /&gt;xmlns:xs="http://www.w3.org/2001/XMLSchema"&lt;br /&gt;exclude-result-prefixes="xs"&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:param name="endpoint" select="'http://somewebservice'"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:variable name="request"&gt;&lt;br /&gt;&amp;lt;soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://somewebservice/"&gt;&lt;br /&gt;&amp;lt;soapenv:Body&gt;&lt;br /&gt;&amp;lt;ws:getSomething&gt;&lt;br /&gt;  &amp;lt;urn&gt;name123&amp;lt;/urn&gt;&lt;br /&gt;&amp;lt;/ws:getSomething&gt;&lt;br /&gt;&amp;lt;/soapenv:Body&gt;&lt;br /&gt;&amp;lt;/soapenv:Envelope&gt;&lt;br /&gt;&amp;lt;/xsl:variable&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="/" name="main"&gt;&lt;br /&gt;&amp;lt;xsl:apply-templates select="saxon:parse(soap:soapRequest($request, $endpoint))" mode="process-SOAP-message"/&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="/" mode="process-SOAP-message"&gt;&lt;br /&gt;&amp;lt;xsl:apply-templates select="saxon:parse(soapenv:Envelope/soapenv:Body/*/return/node())" mode="process-response-payload"/&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="/" mode="process-response-payload"&gt;&lt;br /&gt;&amp;lt;xsl:apply-templates/&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/xsl:stylesheet&gt;&lt;br /&gt;&lt;/pre&gt;There are a couple of thing to notice - firstly you call soapRequest() with the message as a document node, and the endpoint as a string.  The extension will also accept the message as a string, but that would just request the extra step of saxon:serialize($request).&lt;br /&gt;&lt;br /&gt;Secondly you need to use saxon:parse to parse the response string into XML.  Applying templates to saxon:parse() will search for the root matching template, so to avoid endless loops different modes are used to separate the various root matching templates.&lt;br /&gt;&lt;br /&gt;The template in the mode "process-SOAP-message" deals with processing the soap response, so the root element here would be &lt;soapenv:envelope&gt;, so in order to get to the actual payload (and to treat it as a document in its own right)  I use:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;saxon:parse(soapenv:Envelope/soapenv:Body/*/return/node())&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;...and a third root matching template in the mode "process-response-payload" (the actual path may vary for your payload).  In this template you deal with actual response, so you can apply-templates to it, write it to disk etc&lt;br /&gt;&lt;br /&gt;And that's it, it really is as simple as that.  The S in SOAP can mean Simple :)&lt;/soapenv:envelope&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-481461527844075500?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/481461527844075500/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=481461527844075500&amp;isPopup=true' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/481461527844075500'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/481461527844075500'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/02/soap-extension-function.html' title='A Soap Extension Function'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-100014058363146498</id><published>2007-02-04T13:11:00.000Z</published><updated>2007-02-04T15:57:18.493Z</updated><title type='text'>Testing XSLT - CheckXML</title><content type='html'>It's well known that XSLT isn't easily unit-testable, and there isn't currently a standard way of testing transforms for correctness.  I've long thought that the only way to do this is to run the transform using a given input and check the result, and through that infer correctness in the transform.&lt;br /&gt;&lt;p&gt;&lt;br /&gt;I wrote a stylesheet to do this in XSLT 2.0 with heavy use of extensions (to perform the transforms and execute the XPaths) which was nice from an academic standpoint, but it soon became clear that this would be more useful as a Java app runnable from Ant.&lt;br /&gt;&lt;p&gt;&lt;br /&gt;This little project grew into a way of checking any XML file (to check a transform it runs the transform first and then checks the result).  I'm provisionally calling this "CheckXML" and it's still early days but I think it's got the potential to be something really good. &lt;br /&gt;&lt;br /&gt;&lt;p&gt;CheckXML will allow you perform various checks on an XML file - XML Schema, XPath 2.0, XSLT 2.0, XQuery and Relax NG.   This allows users to augment schema checks with XPath/XSLT checks to fully check the correctness of an XML file.  A sample CheckXML configuration file would be:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;checkXML&gt;&lt;br /&gt;&amp;#160;&amp;lt;xml src="SampleXML.xml"&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;lt;check&gt;SampleXSD.xsd&amp;lt;/check&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;lt;check&gt;count(distinct-values(//@id)) = count(//id)&amp;lt;/check&gt;&lt;br /&gt;&amp;#160;&amp;#160;&amp;lt;check&gt;SampleXSLT.xslt&amp;lt;/check&gt;                           &lt;br /&gt;&amp;#160;&amp;lt;/xml&gt;&lt;br /&gt;&amp;lt;/checkXML&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;Here the  XML file "SampleXML.xml" first has the XML Schema SampleXSD.xsd applied to it, the then the XPath in the second check and finally the XSLT check.  The CheckXML app will run each check and look for a result of "true" - any value that isn't true will be reported as a fail.  This is the crucial point - it offloads the reponsbility of a creating a useful error to the check writer, and because the check write has the full power of XSLT/XPath/XQuery the error message can be as detailed as necessary (XSD/RNG check will return the error message if the XML isn't valid).&lt;br /&gt;&lt;p&gt;For example, modifying the XPath above to return a helpful message could be like this:&lt;br /&gt;&lt;br/&gt;&lt;code&gt;&amp;lt;check&gt;if (count(distinct-values(//@id)) = count(//@id))  then 'true' else concat('The following id is not unique: ', distinct-values(for $x in //@id return $x[count(//@id[. = $x]) &gt; 1]))&amp;lt;/check&gt;&lt;/code&gt;&lt;br/&gt;&lt;br /&gt;This would output "The following id is not unique: 123" if you had two @id's with the value "123".  The XPath's can get pretty complicated pretty quickly, which is why most of them should be moved into XSLT files, but it still might be more convenient to just use the XPath in the check config file itself.  As the check writer has full control over the return message, it can be as simple or complicated as needed.&lt;br /&gt;&lt;p&gt;The CheckConfig files can have multiple &amp;lt;xml&gt; elements (&amp;lt;transform&gt; elements for checking transforms) with each one having as many &amp;lt;check&gt;'s as required.  A CheckSuite will point to multiple CheckConfig files.  CheckXML will be callable from Ant, with any fails causing the Ant build to fail (with all details of the fail in the logs).  I'm also planning a GUI with a nice green/red progress bar :) and a CheckConfig editor, but that's down the line.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-100014058363146498?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/100014058363146498/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=100014058363146498&amp;isPopup=true' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/100014058363146498'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/100014058363146498'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2007/02/testing-xslt-checkxml.html' title='Testing XSLT - CheckXML'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-116308052974778098</id><published>2006-11-09T13:22:00.000Z</published><updated>2006-11-09T13:55:31.293Z</updated><title type='text'>Using collection() and saxon:discard-document() to create reports</title><content type='html'>You can process directories of XML using the collection() function, and keep memory usage constant by using the Saxon extension saxon:discard-document()&lt;br /&gt;&lt;br /&gt;&lt;div style="font-family:courier new; font-size: 10pt"&gt;&lt;br /&gt;&amp;lt;xsl:for-each select="for $x in collection('file:///c:/xmlDir?select=*.xml;recurse=yes;on-error=ignore') return saxon:discard-document($x)"&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;You have to be careful that Saxon doesn't optimize out the call to saxon:discard-document() - this basic outer xsl:for-each works well and has become boilerplate code for whenever I start a new report.&lt;/div&gt;&lt;br /&gt;&lt;div&gt;This technique allows you to do things that would otherwise not be feasible with XSLT, and would take longer in another language.  For example finding, grouping and sorting all links in your collection of XML files. Coding the XSLT takes minutes and running it takes time proportional to your dataset size, but the restriction of system memory has gone.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-116308052974778098?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/116308052974778098/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=116308052974778098&amp;isPopup=true' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/116308052974778098'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/116308052974778098'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2006/11/using-collection-and-saxondiscard.html' title='Using collection() and saxon:discard-document() to create reports'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-115739195540277739</id><published>2006-09-04T17:45:00.000Z</published><updated>2006-10-01T11:53:47.396Z</updated><title type='text'>Table Normalization in XSLT 2.0</title><content type='html'>&lt;p&gt;Some time ago I wrote a 1.0 stylesheet that normalizes a table - it's currently available on &lt;a href="http://www.dpawson.co.uk/xsl/sect2/N7450.html#d9845e757"&gt;Dave Pawson's FAQ&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;p&gt;The problem of table normalization is to remove colspans and rowspans from a table placing a copy of the content in each cell.&lt;br /&gt;&lt;br /&gt;&lt;p&gt;This table:&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: courier new; font-size: 10pt;"&gt;&lt;br /&gt;+-----------+-----------+&lt;br /&gt;| a         | b         |&lt;br /&gt;|           +-----+-----+&lt;br /&gt;|           | c   | d   |&lt;br /&gt;+-----------+-----+     |&lt;br /&gt;| e               |     |&lt;br /&gt;+-----+-----+-----+     |&lt;br /&gt;| f   | g   | h   |     |&lt;br /&gt;+-----+-----+-----+-----+&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Would become this:&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;pre style="font-family: courier; font-size: 10pt;"&gt;&lt;br /&gt;+-----+-----+-----+-----+&lt;br /&gt;| a   | a   | b   | b   |&lt;br /&gt;+-----+-----+-----+-----+&lt;br /&gt;| a   | a   | c   | d   |&lt;br /&gt;+-----+-----+-----+-----+&lt;br /&gt;| e   | e   | e   | d   |&lt;br /&gt;+-----+-----+-----+-----+&lt;br /&gt;| f   | g   | h   | d   |&lt;br /&gt;+-----+-----+-----+-----+&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;The 1.0 solution I wrote used a neat recursive template that maintained pointers to the previous and current rows to deal with the tricky rowspans (colspans are the easy part).  In rewriting the transform in 2.0 I've simplified it a great deal, but sadly not with any new 2.0 only feature, just with a better algorithm.  Here's the transform:&lt;br /&gt;&lt;br /&gt;&lt;p style="font-family: courier; font-size: 10pt;"&gt;&lt;br /&gt;&amp;lt;xsl:stylesheet version="2.0"&lt;br /&gt;xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&lt;br /&gt;xmlns:xs="http://www.w3.org/2001/XMLSchema"&lt;br /&gt;exclude-result-prefixes="xs"&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:output method="xhtml" indent="yes"&lt;br /&gt;omit-xml-declaration="yes"&lt;br /&gt;encoding="UTF-8" /&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:variable name="table_with_no_colspans"&gt;&lt;br /&gt; &amp;lt;xsl:apply-templates mode="colspan"/&gt;&lt;br /&gt;&amp;lt;/xsl:variable&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:variable name="table_with_no_rowspans"&gt;&lt;br /&gt; &amp;lt;xsl:for-each select="$table_with_no_colspans"&gt;&lt;br /&gt;  &amp;lt;xsl:apply-templates mode="rowspan"/&gt;&lt;br /&gt; &amp;lt;/xsl:for-each&gt;&lt;br /&gt;&amp;lt;/xsl:variable&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="/"&gt;&lt;br /&gt; &amp;lt;xsl:apply-templates select="$table_with_no_rowspans" mode="final"/&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="@*|*" mode="#all"&gt;&lt;br /&gt; &amp;lt;xsl:copy&gt;&lt;br /&gt;  &amp;lt;xsl:apply-templates select="@*|*" mode="#current"/&gt;&lt;br /&gt; &amp;lt;/xsl:copy&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="td" mode="colspan"&gt;&lt;br /&gt; &amp;lt;xsl:choose&gt;&lt;br /&gt;  &amp;lt;xsl:when test="@colspan"&gt;&lt;br /&gt;   &amp;lt;xsl:variable name="this" select="." as="element()"/&gt;&lt;br /&gt;   &amp;lt;xsl:for-each select="1 to @colspan"&gt;&lt;br /&gt;    &amp;lt;td&gt;&lt;br /&gt;     &amp;lt;xsl:copy-of select="$this/@*[not(name() = 'colspan')][not(name() = 'width')]"/&gt;&lt;br /&gt;     &amp;lt;xsl:copy-of select="$this/node()"/&gt;&lt;br /&gt;    &amp;lt;/td&gt;&lt;br /&gt;   &amp;lt;/xsl:for-each&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:otherwise&gt;&lt;br /&gt;   &amp;lt;xsl:copy-of select="."/&gt;&lt;br /&gt;  &amp;lt;/xsl:otherwise&gt;&lt;br /&gt; &amp;lt;/xsl:choose&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="tbody" mode="rowspan"&gt;&lt;br /&gt; &amp;lt;xsl:copy&gt;&lt;br /&gt;  &amp;lt;xsl:copy-of select="tr[1]" /&gt;&lt;br /&gt;  &amp;lt;xsl:apply-templates select="tr[2]" mode="rowspan"&gt;&lt;br /&gt;   &amp;lt;xsl:with-param name="previousRow" select="tr[1]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:apply-templates&gt;&lt;br /&gt; &amp;lt;/xsl:copy&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="tr" mode="rowspan"&gt;&lt;br /&gt; &amp;lt;xsl:param name="previousRow"/&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:variable name="currentRow" select="."/&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:variable name="normalizedTDs"&gt;&lt;br /&gt;  &amp;lt;xsl:for-each select="$previousRow/td"&gt;&lt;br /&gt;   &amp;lt;xsl:choose&gt;&lt;br /&gt;    &amp;lt;xsl:when test="@rowspan &gt; 1"&gt;&lt;br /&gt;     &amp;lt;xsl:copy&gt;&lt;br /&gt;      &amp;lt;xsl:attribute name="rowspan"&gt;&lt;br /&gt;                   &amp;lt;xsl:value-of select="@rowspan - 1" /&gt;&lt;br /&gt;                  &amp;lt;/xsl:attribute&gt;&lt;br /&gt;&lt;br /&gt;      &amp;lt;xsl:copy-of select="@*[not(name() = 'rowspan')]"/&gt;&lt;br /&gt;      &amp;lt;xsl:copy-of select="node()"/&gt;&lt;br /&gt;     &amp;lt;/xsl:copy&gt;&lt;br /&gt;    &amp;lt;/xsl:when&gt;&lt;br /&gt;    &amp;lt;xsl:otherwise&gt;&lt;br /&gt;     &amp;lt;xsl:copy-of select="$currentRow/td[1 + count(current()/preceding-sibling::td[not(@rowspan) or (@rowspan = 1)])]"/&gt;&lt;br /&gt;    &amp;lt;/xsl:otherwise&gt;&lt;br /&gt;   &amp;lt;/xsl:choose&gt;&lt;br /&gt;  &amp;lt;/xsl:for-each&gt;&lt;br /&gt; &amp;lt;/xsl:variable&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:variable name="newRow" as="element(tr)"&gt;&lt;br /&gt;  &amp;lt;xsl:copy&gt;&lt;br /&gt;   &amp;lt;xsl:copy-of select="$currentRow/@*"/&gt;&lt;br /&gt;   &amp;lt;xsl:copy-of select="$normalizedTDs"/&gt;&lt;br /&gt;  &amp;lt;/xsl:copy&gt;&lt;br /&gt; &amp;lt;/xsl:variable&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:copy-of select="$newRow"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:apply-templates select="following-sibling::tr[1]" mode="rowspan"&gt;&lt;br /&gt;  &amp;lt;xsl:with-param name="previousRow" select="$newRow"/&gt;&lt;br /&gt; &amp;lt;/xsl:apply-templates&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="td" mode="final"&gt;&lt;br /&gt; &amp;lt;xsl:choose&gt;&lt;br /&gt;  &amp;lt;xsl:when test="@rowspan"&gt;&lt;br /&gt;   &amp;lt;xsl:copy&gt;&lt;br /&gt;    &amp;lt;xsl:copy-of select="@*[not(name() = 'rowspan')]"/&gt;&lt;br /&gt;    &amp;lt;xsl:copy-of select="node()"/&gt;&lt;br /&gt;   &amp;lt;/xsl:copy&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:otherwise&gt;&lt;br /&gt;   &amp;lt;xsl:copy-of select="."/&gt;&lt;br /&gt;  &amp;lt;/xsl:otherwise&gt;&lt;br /&gt; &amp;lt;/xsl:choose&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/xsl:stylesheet&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-115739195540277739?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/115739195540277739/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=115739195540277739&amp;isPopup=true' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/115739195540277739'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/115739195540277739'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2006/09/table-normalization-in-xslt-20.html' title='Table Normalization in XSLT 2.0'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-115227792535179342</id><published>2006-07-07T13:05:00.000Z</published><updated>2006-07-07T13:18:57.306Z</updated><title type='text'>Kernow 1.4 released</title><content type='html'>&lt;p&gt;I've finally got around to releasing a new version of Kernow.&lt;br /&gt;&lt;p&gt;This version adds a few new features and I've also fixed a few annoying bugs:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Added optional caching URI and entity resolvers (including an entity resolver for documents referenced through the collection() function) &lt;br /&gt;&lt;li&gt;Added directory validation using SaxonSA, Xerces or JAXP (choice of XSD or RNG where possible).  &lt;br /&gt;&lt;li&gt;Added the ability to run schema aware XQueries  &lt;br /&gt;&lt;li&gt;Added default, lax and strict validation for schema aware transforms and queries  &lt;br /&gt;&lt;li&gt;Added the ability to choose the type of files to process in a directory (eg, xml, xhtml, or well-formed html)  &lt;br /&gt;&lt;li&gt;Fixed a number of small bugs, such as the progress bar occasionally throwing an NPE.&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-115227792535179342?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://sourceforge.net/projects/kernowforsaxon/' title='Kernow 1.4 released'/><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/115227792535179342/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=115227792535179342&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/115227792535179342'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/115227792535179342'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2006/07/kernow-14-released.html' title='Kernow 1.4 released'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-115028668042446169</id><published>2006-06-14T12:02:00.000Z</published><updated>2006-06-14T12:04:48.196Z</updated><title type='text'>Saxon 8.7.3 released</title><content type='html'>Mike Kay has released Saxon 8.7.3&lt;br /&gt;&lt;br /&gt;Saxon-B:  &lt;a href="http://saxon.sf.net/"&gt;http://saxon.sf.net/&lt;/a&gt;&lt;br /&gt;Saxon-SA: &lt;a href="http://www.saxonica.com/"&gt;http://www.saxonica.com/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;"This maintenance release clears 35 documented bugs, and on .NET it adds the&lt;br /&gt;capability to process a DOM Document "in situ" rather than by first copying&lt;br /&gt;it to create a Saxon tree.&lt;br /&gt;&lt;br /&gt;(Saxon 8.7.2, in case you missed it, was a rebuild of Saxon-SA on .NET with&lt;br /&gt;no changed functionality.)"&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-115028668042446169?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://www.saxonica.com/' title='Saxon 8.7.3 released'/><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/115028668042446169/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=115028668042446169&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/115028668042446169'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/115028668042446169'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2006/06/saxon-873-released.html' title='Saxon 8.7.3 released'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-115012334073736794</id><published>2006-06-12T14:33:00.000Z</published><updated>2006-06-12T14:42:20.960Z</updated><title type='text'>A Caching EntityResolver for the collection() function</title><content type='html'>&lt;p&gt;XSLT 2.0 allows you to write a "standalone stylesheet", that is, a transform with no input XML.  Instead you must provide the name of the template where execution should begin.  The stylesheet can then use the document() or doc() functions to process individual files, and the collection() function to process directories of XML.  I use the standalone XSLT model a lot where you have multiple input files and a single output file, for example generating a report or creating an index page.&lt;br /&gt;&lt;br /&gt;&lt;p&gt;I recently ran into a problem though, where a few thousand of several thousand input files referenced entity files hosted at the w3.org.  This only became apparent when the transforms started failing for no reason.  It turns out that the network admins where I work had turned off the proxy, so each and every entity file was being fetched from the w3c - where making 500 requests in 10 minutes mean you get a 24 hour ban...&lt;br /&gt;&lt;br /&gt;&lt;p&gt;In a standard transform catching and caching requests for entity files is easy - you write a custom EntityResolver and tell the XMLReader to use that.  For the collection() function however it's a lot more complicated.  In Saxon 8.7.1, the URI used in the collection() function is resolved using the StandardCollectionURIResolver().  You can use your own one of these by calling setCollectionURIResolver() on the Configuration, and then do what you like within that class.  To create a CollectionURIResolver() that caches entities files, it's pretty much a case of copying whats in the StandardCollectionURIResolver() and then modifying it to suit your needs. &lt;br /&gt;&lt;br /&gt;&lt;p&gt;I've added this functionality to the next release of Kernow, so that standalone transforms and XQueries have the option of a caching EntityResolver.  I get the impression it's early days here, and this will become a common enough requirement that a setter will be exposed for an EntityResolver on the XMLReader used by the collection() function, making this kind of task much easier in the future.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-115012334073736794?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/115012334073736794/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=115012334073736794&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/115012334073736794'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/115012334073736794'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2006/06/caching-entityresolver-for-collection.html' title='A Caching EntityResolver for the collection() function'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-114856832218730903</id><published>2006-05-25T14:32:00.000Z</published><updated>2006-05-25T14:58:29.780Z</updated><title type='text'>Character encoding, character references and Windows-1252</title><content type='html'>Consider this XML:&lt;br /&gt;&lt;div style="font-family:courier new;font-size:smaller"&gt;&lt;br /&gt;&amp;lt;?xml version="1.0" encoding="Windows-1252" ?&gt;&lt;br /&gt;&amp;lt;foo&gt;&amp;_#150;0x96&amp;lt;/foo&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Here there is the character reference #150 and the actual character 0x96 that character reference #150 resolves to.  The underscore is used to prevent the character reference being resolved by the browser, and the string '0x96' is used instead of the actual character 0x96 because blogger complains otherwise (stick with me...) Note the encoding used in the prolog.&lt;br /&gt;&lt;br /&gt;Transforming that XML with this stylesheet:&lt;br /&gt;&lt;div style="font-family:courier new;font-size:smaller"&gt;&lt;br /&gt;&amp;lt;xsl:stylesheet version="1.0"&lt;br /&gt;xmlns:xsl="http://www.w3.org/1999/XSL/Transform";&gt;&lt;br /&gt;&amp;lt;xsl:output encoding="US-ASCII"/&gt;&lt;br /&gt;&amp;lt;xsl:template match="/"&gt;&lt;br /&gt;  &amp;lt;xsl:copy-of select="."/&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&amp;lt;/xsl:stylesheet&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Gives this result:&lt;br /&gt;&lt;div style="font-family:courier new;font-size:smaller"&gt;&lt;br /&gt;&amp;lt;foo&gt;&amp;_#150;&amp;_#8211;&amp;lt;/foo&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Here you can see the character reference has been written back out as the same character, however character 0x96 has become 0x2013 (8211 in decimal).  Character 150 is the unicode character "START OF GUARDED AREA" in the non-displayed C1 control character range, but in the Windows-1252 encoding it's mapped to to the displayable character 0x2013 "en-dash" (a short dash).  Microsoft squeezed more characters into the single byte range by replacing non-displayed control characters with more useful displayable characters, but mistakenly went on to label files encoded in this way as ISO-8859-1 in some MS Office applications.  In ISO-8859-1 the characters in the C0 and C1 ranges are the non-displayable control characters, but this mis-labelling was so widespread that parsers began detecting this situation and silently switching the read encoding to Windows-1252.  &lt;br /&gt;&lt;br /&gt;This problem surfaces when serving XHTML to an XHTML browser.  While browsers are reading files using their HTML parsers, any file mis-labelled as ISO-8859-1 that contains characters in the C0 or C1 ranges will still auto-magically display the characters in those ranges, as the forgiving parsers auto-switch the read encoding.  However, when an XHTML file is served (using the correct mime type eg "application/xhtml+xml") XHTML browsers such as Firefox will parse the file using its stricter XML parser - and all the characters in the C0 and C1 ranges will remain as those non-displayed characters.  The auto-switch won't take place and characters such as 0x96 (en-dash) that were once displayed will just disappear.&lt;br /&gt;&lt;br /&gt;This problem only occurs when an XML file is saved in Windows-1252 but is labelled as something else, usually IS0-8859-1.  The most common culprit is Notepad, where a user has edited and saved an XML file without realising/caring that Notepad is unaware of the XML prolog.&lt;br /&gt;&lt;br /&gt;So back to the example above:&lt;br /&gt;&lt;div style="font-family:courier new;font-size:smaller"&gt;&lt;br /&gt;&amp;lt;?xml version="1.0" encoding="Windows-1252" ?&gt;&lt;br /&gt;&amp;lt;foo&gt;&amp;_#150;0x96&amp;lt;/foo&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;The main point to realise here is that character references (the #150 in the example) are always resolved using Unicode codepoints, regardless of the specified encoding.  Actual characters in the file will be read using the specified encoding.  Therefore the #150 is resolved to 0x96 (its Unicode codepoint), while the actual character 0x96 in the source becomes 0x2013 (#8211) as specified in the Windows-1252 encoding.&lt;br /&gt;&lt;br /&gt;The result of the transformation demonstrates this when serialised using the US-ASCII encoding (so all bytes above 127 will be written out as character references) looks like this:&lt;br /&gt;&lt;div style="font-family:courier new;font-size:smaller"&gt;&lt;br /&gt;&amp;lt;foo&gt;&amp;_#150;&amp;_#8211;&amp;lt;/foo&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;A great example I think :)&lt;br /&gt;&lt;br /&gt;Wikipedia has lots more information: http://en.wikipedia.org/wiki/ISO_8859-1&lt;br /&gt;&lt;br /&gt;*There are two versions of ISO-8859-1 - The ISO and IANA versions. The ISO versions doesn't contain the C0 and C1 control characters, the IANA version does contain them. The XML recommendation uses the IANA version.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-114856832218730903?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/114856832218730903/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=114856832218730903&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114856832218730903'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114856832218730903'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2006/05/character-encoding-character.html' title='Character encoding, character references and Windows-1252'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-114701489277421335</id><published>2006-05-07T14:50:00.000Z</published><updated>2006-05-07T15:14:53.650Z</updated><title type='text'>Kernow 1.3 released</title><content type='html'>I've uploaded a new version Kernow that contains Saxon 8.7.1 with a few minor bugs fixed.  These mainly centered around paths - the systemId of the source XML and base uri used by xsl:result-document.&lt;br /&gt;&lt;br /&gt;The latter was pretty straightforward, the base uri used by xsl:result-document is set using Controller.setBaseOutputURI() - this value is now set to either the uri of the stylesheet, or the output file/directory (depending if one is supplied).  This makes sense, as if you are outputting to a given file/directory, you would like the output from your xsl:result-document instructions to be relative to that (in my view, anyway).  If you haven't supplied an output file/directory and you're outputting to the Kernow output window, then it should be resovled against the stylesheet. &lt;br /&gt;&lt;br /&gt;Setting the systemId of the TransformerHandler to be that of the XML file seemed wrong to me at first, as you would expect it to be used by the stylesheet.  After thinking about it a little, it makes some sense: the TransformerHandler knows the systemId of the stylesheet from when it was created - it doesn't know the location of the source XML as that arrives as a series of SAX events - therefore it's possible to set it through setSystemId.  Perhaps someone knows for sure...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-114701489277421335?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/114701489277421335/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=114701489277421335&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114701489277421335'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114701489277421335'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2006/05/kernow-13-released.html' title='Kernow 1.3 released'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-114657714808295150</id><published>2006-05-02T13:37:00.000Z</published><updated>2007-06-17T11:48:58.503Z</updated><title type='text'>Sudoku Solver complete - and shes fast</title><content type='html'>(NOTE: I've improved on this version &lt;a href="http://ajwelch.blogspot.com/2007/06/sukoku-solver-much-improved.html"&gt;here&lt;/a&gt;)&lt;br /&gt;&lt;br /&gt;I finally got around to finishing the Sudoku solver after several weeks of other things getting the way.&lt;br /&gt;&lt;br /&gt;I've improved the heuristics that check for the allowed values for a given cell.  It now checks the row and column "friends" - the other rows and columns in a group (eg columns 1 and 2 when the cell is in column 3, or columns 1 and 3 when the cell is in 2).  This is quite straightforward for a human and is done almost without noticing, but its reasonably involved to code.&lt;br /&gt;&lt;br /&gt;The other change was to recursively insert all the values into cells with only one possible value, and then re-evaluate the allowed values with those cells inserted.  This takes care of the situation where there are initially two possible values, but after inserting a definite value elsewhere there is only one possible values left. &lt;br /&gt;&lt;br /&gt;Ultimately all this reduces the number of times the transform has to guess - on certain boards it would take a long time to solve purely because it was guessing wrongly early on, and then have to go through all the permutations before working its way back to that early point and trying the next number.  Now there should be the maximum number of values in place before it has to guess (if at all) so that it doesn't waste time backtracking.&lt;br /&gt;&lt;br /&gt;Here it is.  To try it out run it using Saxon with "-it main" set, or run it against a dummy input XML.  The are a few sample boards included, to try them out change the main template to point the variable containing the board, or cut and paste it's contents into the param at the top of the stylesheet.  &lt;br /&gt;&lt;br /&gt;If you find a board that this stylesheet can't process quickly, please let me know.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-family:courier new;font-size:8pt"&gt;&lt;br /&gt;&amp;lt;xsl:stylesheet version="2.0"&lt;br /&gt;xmlns:xs="http://www.w3.org/2001/XMLSchema"&lt;br /&gt;xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&lt;br /&gt;xmlns:saxon="http://saxon.sf.net/"&lt;br /&gt;xmlns:fn="sudoku"&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:param name="board" select="(&lt;br /&gt;1,0,0,  3,0,0,  6,0,0,&lt;br /&gt;0,2,0,  5,0,0,  0,0,4,&lt;br /&gt;0,0,9,  0,0,0,  5,2,0,&lt;br /&gt;&lt;br /&gt;0,0,0,  9,6,3,  0,0,0,&lt;br /&gt;7,1,6,  0,0,0,  0,0,0,&lt;br /&gt;0,0,0,  0,8,0,  0,4,0,&lt;br /&gt;&lt;br /&gt;9,0,0,  0,0,5,  3,0,7,&lt;br /&gt;8,0,0,  4,0,6,  0,0,0,&lt;br /&gt;3,5,0,  0,0,0,  0,0,1&lt;br /&gt;)" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:param name="verbose" select="false()" as="xs:boolean"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:variable name="rowStarts" select="(1, 10, 19, 28, 37, 46, 55, 64, 73)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="topLeftGroup"     select="(1, 2, 3,     10, 11, 12,  19, 20, 21)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="topGroup"         select="(4, 5, 6,     13, 14, 15,  22, 23, 24)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="topRightGroup"    select="(7, 8, 9,     16, 17, 18,  25, 26, 27)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="midLeftGroup"     select="(28, 29, 30,  37, 38, 39,  46, 47, 48)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="center"           select="(31, 32, 33,  40, 41, 42,  49, 50, 51)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="midRightGroup"    select="(34, 35, 36,  43, 44, 45,  52, 53, 54)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="bottomLeftGroup"  select="(55, 56, 57,  64, 65, 66,  73, 74, 75)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="bottomGroup"      select="(58, 59, 60,  67, 68, 69,  76, 77, 78)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="bottomRightGroup" select="(61, 62, 63,  70, 71, 72,  79, 80, 81)" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:getRowStart" as="xs:integer+" saxon:memo-function="yes"&gt;&lt;br /&gt; &amp;lt;xsl:param name="index" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:sequence select="xs:integer(floor(($index - 1) div 9) * 9) + 1"/&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:getRowNumber" as="xs:integer+" saxon:memo-function="yes"&gt;&lt;br /&gt; &amp;lt;xsl:param name="index" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:sequence select="(($index - 1) idiv 9) + 1"/&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:getColNumber" as="xs:integer+" saxon:memo-function="yes"&gt;&lt;br /&gt; &amp;lt;xsl:param name="index" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:sequence select="(($index - 1) mod 9) + 1"/&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:getRow" as="xs:integer+"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="index" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:sequence select="subsequence($board, fn:getRowStart($index), 9)"/&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:getCol" as="xs:integer+"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="index" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:variable name="gap" select="($index - 1) mod 9" as="xs:integer"/&gt;&lt;br /&gt; &amp;lt;xsl:variable name="colIndexes" select="for $x in $rowStarts return $x + $gap" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:sequence select="for $x in $colIndexes return $board[$x]"/&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:getGroup" as="xs:integer+"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="index" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:choose&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $topLeftGroup"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="for $x in $topLeftGroup return $board[$x]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $topGroup"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="for $x in $topGroup return $board[$x]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $topRightGroup"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="for $x in $topRightGroup return $board[$x]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $midLeftGroup"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="for $x in $midLeftGroup return $board[$x]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $center"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="for $x in $center return $board[$x]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $midRightGroup"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="for $x in $midRightGroup return $board[$x]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $bottomLeftGroup"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="for $x in $bottomLeftGroup return $board[$x]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $bottomGroup"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="for $x in $bottomGroup return $board[$x]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $bottomRightGroup"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="for $x in $bottomRightGroup return $board[$x]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt; &amp;lt;/xsl:choose&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:getOtherValuesInTriple" as="xs:integer+" saxon:memo-function="yes"&gt;&lt;br /&gt; &amp;lt;xsl:param name="num" as="xs:integer"/&gt;&lt;br /&gt; &amp;lt;xsl:sequence select="(xs:integer(((($num -1) idiv 3) * 3) + 1) to xs:integer(((($num - 1) idiv 3) * 3) + 3))[. != $num]"/&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:getRowFriends" as="xs:integer+"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="index" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:sequence select="for $x in fn:getOtherValuesInTriple($index) return $board[$x]"/&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:getColFriends" as="xs:integer+"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="index" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:sequence select="for $x in fn:getOtherValuesInTriple(fn:getRowNumber($index))&lt;br /&gt;            return&lt;br /&gt;             $board[(($x * 9) - 8) + (($index - 1) mod 9)]"/&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:reducePossibleValues" as="xs:integer*"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="index" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="possibleValues" as="xs:integer+"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:variable name="rowFriends" select="fn:getRowFriends($board, $index)" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:variable name="colFriends" select="fn:getColFriends($board, $index)" as="xs:integer+"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:variable name="otherRow1" select="fn:getRow($board, 9 * fn:getOtherValuesInTriple(fn:getRowNumber($index))[1])" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:variable name="otherRow2" select="fn:getRow($board, 9 * fn:getOtherValuesInTriple(fn:getRowNumber($index))[2])" as="xs:integer+"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:variable name="otherCol1" select="fn:getCol($board, fn:getOtherValuesInTriple($index)[1])" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:variable name="otherCol2" select="fn:getCol($board, fn:getOtherValuesInTriple($index)[2])" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:variable name="reducedValues" as="xs:integer*"&gt;&lt;br /&gt;  &amp;lt;xsl:for-each select="$possibleValues"&gt;&lt;br /&gt;   &amp;lt;xsl:if test=". = $otherCol1 and . = $otherCol2"&gt;&lt;br /&gt;    &amp;lt;xsl:choose&gt;&lt;br /&gt;     &amp;lt;xsl:when test="not($colFriends = 0)"&gt;&lt;br /&gt;      &amp;lt;xsl:sequence select="."/&gt;&lt;br /&gt;     &amp;lt;/xsl:when&gt;&lt;br /&gt;     &amp;lt;xsl:when test="$colFriends != 0"&gt;&lt;br /&gt;      &amp;lt;xsl:choose&gt;&lt;br /&gt;       &amp;lt;xsl:when test="$colFriends[1] = 0"&gt;&lt;br /&gt;        &amp;lt;xsl:if test=". = $otherRow1"&gt;&lt;br /&gt;         &amp;lt;xsl:sequence select="."/&gt;&lt;br /&gt;        &amp;lt;/xsl:if&gt;&lt;br /&gt;       &amp;lt;/xsl:when&gt;&lt;br /&gt;       &amp;lt;xsl:when test="$colFriends[2] = 0"&gt;&lt;br /&gt;        &amp;lt;xsl:if test=". = $otherRow2"&gt;&lt;br /&gt;         &amp;lt;xsl:sequence select="."/&gt;&lt;br /&gt;        &amp;lt;/xsl:if&gt;&lt;br /&gt;       &amp;lt;/xsl:when&gt;&lt;br /&gt;      &amp;lt;/xsl:choose&gt;&lt;br /&gt;     &amp;lt;/xsl:when&gt;&lt;br /&gt;    &amp;lt;/xsl:choose&gt;&lt;br /&gt;   &amp;lt;/xsl:if&gt;&lt;br /&gt;   &lt;br /&gt;   &amp;lt;xsl:if test=". = $otherRow1 and . = $otherRow2"&gt;&lt;br /&gt;    &amp;lt;xsl:choose&gt;&lt;br /&gt;     &amp;lt;xsl:when test="not($rowFriends = 0)"&gt;&lt;br /&gt;      &amp;lt;xsl:sequence select="."/&gt;&lt;br /&gt;     &amp;lt;/xsl:when&gt;&lt;br /&gt;     &amp;lt;xsl:when test="$rowFriends != 0"&gt;&lt;br /&gt;      &amp;lt;xsl:choose&gt;&lt;br /&gt;       &amp;lt;xsl:when test="$rowFriends[1] = 0"&gt;&lt;br /&gt;        &amp;lt;xsl:if test=". = $otherCol1"&gt;&lt;br /&gt;         &amp;lt;xsl:sequence select="."/&gt;&lt;br /&gt;        &amp;lt;/xsl:if&gt;&lt;br /&gt;       &amp;lt;/xsl:when&gt;&lt;br /&gt;       &amp;lt;xsl:when test="$rowFriends[2] = 0"&gt;&lt;br /&gt;        &amp;lt;xsl:if test=". = $otherCol2"&gt;&lt;br /&gt;         &amp;lt;xsl:sequence select="."/&gt;&lt;br /&gt;        &amp;lt;/xsl:if&gt;&lt;br /&gt;       &amp;lt;/xsl:when&gt;&lt;br /&gt;      &amp;lt;/xsl:choose&gt;&lt;br /&gt;     &amp;lt;/xsl:when&gt;&lt;br /&gt;    &amp;lt;/xsl:choose&gt;&lt;br /&gt;   &amp;lt;/xsl:if&gt;&lt;br /&gt;  &amp;lt;/xsl:for-each&gt;&lt;br /&gt; &amp;lt;/xsl:variable&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:sequence select="if (count($reducedValues) = 1) then $reducedValues else $possibleValues"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:getAllowedValues" as="xs:integer*"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="index" as="xs:integer+"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:variable name="possibleValues" select="(1 to 9)[not(. = (fn:getRow($board, $index), fn:getCol($board, $index), fn:getGroup($board, $index)))]" as="xs:integer*"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:choose&gt;&lt;br /&gt;  &amp;lt;xsl:when test="count($possibleValues) &gt; 1"&gt;&lt;br /&gt;      &amp;lt;xsl:variable name="reducedValues" select="fn:reducePossibleValues($board, $index, $possibleValues)" as="xs:integer*"/&gt;&lt;br /&gt;   &lt;br /&gt;   &amp;lt;xsl:sequence select="$reducedValues"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:otherwise&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$possibleValues"/&gt;&lt;br /&gt;  &amp;lt;/xsl:otherwise&gt;&lt;br /&gt; &amp;lt;/xsl:choose&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:tryValues" as="xs:integer*"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="emptyCells" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="possibleValues" as="xs:integer+"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:variable name="index" select="$emptyCells[1]" as="xs:integer"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:variable name="newBoard" select="(subsequence($board, 1, $index - 1), $possibleValues[1], subsequence($board, $index + 1))" as="xs:integer+"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:if test="$verbose"&gt;&lt;br /&gt;  &amp;lt;xsl:message&gt;&lt;br /&gt;   &amp;lt;xsl:value-of select="concat('Trying ', $possibleValues[1], ' out of a possible ', string-join(for $i in $possibleValues return xs:string($i), ' '), ' at index ', $index)"/&gt;&lt;br /&gt;  &amp;lt;/xsl:message&gt;&lt;br /&gt; &amp;lt;/xsl:if&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:variable name="result" select="fn:populateValues($newBoard, subsequence($emptyCells, 2))" as="xs:integer*"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:sequence select="if (empty($result)) then&lt;br /&gt;                               if (count($possibleValues) &gt; 1) then&lt;br /&gt;                                 fn:tryValues($board, $emptyCells, subsequence($possibleValues, 2))&lt;br /&gt;                               else&lt;br /&gt;                                 ()&lt;br /&gt;                             else&lt;br /&gt;                               $result"/&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:populateValues" as="xs:integer*"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="emptyCells" as="xs:integer*"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:choose&gt;&lt;br /&gt;  &amp;lt;xsl:when test="empty($emptyCells)"&gt;&lt;br /&gt;   &amp;lt;xsl:message&gt;Done!&amp;lt;/xsl:message&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$board"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:otherwise&gt;&lt;br /&gt;   &amp;lt;xsl:variable name="index" select="$emptyCells[1]" as="xs:integer"/&gt;&lt;br /&gt;   &amp;lt;xsl:variable name="possibleValues" select="fn:getAllowedValues($board, $index)" as="xs:integer*"/&gt;&lt;br /&gt;   &amp;lt;xsl:choose&gt;&lt;br /&gt;    &amp;lt;xsl:when test="count($possibleValues) = 0"&gt;&lt;br /&gt;     &amp;lt;xsl:if test="$verbose"&gt;&lt;br /&gt;      &amp;lt;xsl:message&gt;! Cannot go any further !&amp;lt;/xsl:message&gt;&lt;br /&gt;     &amp;lt;/xsl:if&gt;&lt;br /&gt;     &amp;lt;xsl:sequence select="()"/&gt;&lt;br /&gt;    &amp;lt;/xsl:when&gt;&lt;br /&gt;    &amp;lt;xsl:when test="count($possibleValues) &gt; 1"&gt;&lt;br /&gt;     &amp;lt;xsl:sequence select="fn:tryValues($board, $emptyCells, $possibleValues)"/&gt;&lt;br /&gt;    &amp;lt;/xsl:when&gt;&lt;br /&gt;    &amp;lt;xsl:when test="count($possibleValues) = 1"&gt;&lt;br /&gt;     &amp;lt;xsl:variable name="newBoard" select="(subsequence($board, 1, $index - 1), $possibleValues[1], subsequence($board, $index + 1))" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt;     &amp;lt;xsl:if test="$verbose"&gt;&lt;br /&gt;      &amp;lt;xsl:message&gt;&lt;br /&gt;       &amp;lt;xsl:value-of&gt;&lt;br /&gt;        &amp;lt;xsl:text&gt;Only one value &amp;lt;/xsl:text&gt;&lt;br /&gt;        &amp;lt;xsl:value-of select="$possibleValues[1]"/&gt;&lt;br /&gt;        &amp;lt;xsl:text&gt; for index &amp;lt;/xsl:text&gt;&lt;br /&gt;        &amp;lt;xsl:value-of select="$index"/&gt;&lt;br /&gt;       &amp;lt;/xsl:value-of&gt;&lt;br /&gt;      &amp;lt;/xsl:message&gt;&lt;br /&gt;     &amp;lt;/xsl:if&gt;&lt;br /&gt;&lt;br /&gt;     &amp;lt;xsl:sequence select="fn:populateValues($newBoard, subsequence($emptyCells, 2))"/&gt;&lt;br /&gt;    &amp;lt;/xsl:when&gt;&lt;br /&gt;   &amp;lt;/xsl:choose&gt;&lt;br /&gt;  &amp;lt;/xsl:otherwise&gt;&lt;br /&gt; &amp;lt;/xsl:choose&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:populateSingleValueCells" as="xs:integer+"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="emptyCellsXML" as="element()*"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:for-each select="1 to 81"&gt;&lt;br /&gt;  &amp;lt;xsl:variable name="pos" select="." as="xs:integer"/&gt;&lt;br /&gt;  &amp;lt;xsl:choose&gt;&lt;br /&gt;   &amp;lt;xsl:when test="$emptyCellsXML[@index = $pos]/@possibleValues = 1"&gt;&lt;br /&gt;    &amp;lt;xsl:sequence select="$emptyCellsXML[@index = $pos]/@values"/&gt;&lt;br /&gt;   &amp;lt;/xsl:when&gt;&lt;br /&gt;   &amp;lt;xsl:otherwise&gt;&lt;br /&gt;    &amp;lt;xsl:sequence select="$board[$pos]"/&gt;&lt;br /&gt;   &amp;lt;/xsl:otherwise&gt;&lt;br /&gt;  &amp;lt;/xsl:choose&gt;&lt;br /&gt; &amp;lt;/xsl:for-each&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:populateSingleValueCells" as="xs:integer+"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:variable name="emptyCells" select="for $x in (1 to 81) return $x[$board[$x] = 0]" as="xs:integer*"/&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:variable name="emptyCellsXML" as="element()*"&gt;&lt;br /&gt;  &amp;lt;xsl:for-each select="$emptyCells"&gt;&lt;br /&gt;   &amp;lt;xsl:variable name="allowedValues" select="fn:getAllowedValues($board, .)" as="xs:integer*"/&gt;&lt;br /&gt;   &amp;lt;cell index="{.}" possibleValues="{count($allowedValues)}" values="{$allowedValues}"/&gt;&lt;br /&gt;  &amp;lt;/xsl:for-each&gt;&lt;br /&gt; &amp;lt;/xsl:variable&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:choose&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$emptyCellsXML/@possibleValues = 1"&gt;&lt;br /&gt;   &amp;lt;xsl:variable name="newBoard" select="fn:populateSingleValueCells($board, $emptyCellsXML)" as="xs:integer+"/&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="fn:populateSingleValueCells($newBoard)"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:otherwise&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$board"/&gt;&lt;br /&gt;  &amp;lt;/xsl:otherwise&gt;&lt;br /&gt; &amp;lt;/xsl:choose&gt;&lt;br /&gt; &lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:solveSudoku" as="xs:integer+"&gt;&lt;br /&gt; &amp;lt;xsl:param name="startBoard" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:variable name="board" select="fn:populateSingleValueCells($startBoard)" as="xs:integer+"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:variable name="emptyCells" select="for $x in (1 to 81) return $x[$board[$x] = 0]" as="xs:integer*"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:variable name="emptyCellsXML" as="element()*"&gt;&lt;br /&gt;  &amp;lt;xsl:for-each select="$emptyCells"&gt;&lt;br /&gt;   &amp;lt;xsl:variable name="allowedValues" select="fn:getAllowedValues($board, .)" as="xs:integer*"/&gt;&lt;br /&gt;   &amp;lt;cell index="{.}" possibleValues="{count($allowedValues)}" values="{$allowedValues}"/&gt;&lt;br /&gt;  &amp;lt;/xsl:for-each&gt;&lt;br /&gt; &amp;lt;/xsl:variable&gt; &lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:variable name="emptyCellsOrdered" as="xs:integer*"&gt;&lt;br /&gt;  &amp;lt;xsl:for-each select="$emptyCellsXML"&gt;&lt;br /&gt;   &amp;lt;xsl:sort select="@possibleValues" data-type="number" order="ascending"/&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="@index"/&gt;&lt;br /&gt;  &amp;lt;/xsl:for-each&gt;&lt;br /&gt; &amp;lt;/xsl:variable&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:variable name="endBoard" select="fn:populateValues($board, $emptyCells)" as="xs:integer*"/&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:choose&gt;&lt;br /&gt;  &amp;lt;xsl:when test="empty($endBoard)"&gt;&lt;br /&gt;   &amp;lt;xsl:message&gt;! Invalid board - The starting board is not correct&amp;lt;/xsl:message&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$startBoard"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:otherwise&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$endBoard"/&gt;&lt;br /&gt;  &amp;lt;/xsl:otherwise&gt;&lt;br /&gt; &amp;lt;/xsl:choose&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="/tt" name="maintt"&gt;&lt;br /&gt; &amp;lt;div&gt;&lt;br /&gt;  &amp;lt;xsl:variable name="emptyCellsXML" as="element()*"&gt;&lt;br /&gt;   &amp;lt;xsl:for-each select="for $x in (1 to 81) return $x[$board[$x] = 0]"&gt;&lt;br /&gt;    &amp;lt;cell index="{.}" possibleValues="{count(fn:getAllowedValues($board, .))}" values="{fn:getAllowedValues($board, .)}"/&gt;&lt;br /&gt;   &amp;lt;/xsl:for-each&gt;&lt;br /&gt;  &amp;lt;/xsl:variable&gt;&lt;br /&gt; &lt;br /&gt;  &amp;lt;xsl:for-each select="$emptyCellsXML"&gt;&lt;br /&gt;   &amp;lt;xsl:sort select="@possibleValues" data-type="number" order="ascending"/&gt;&lt;br /&gt;   &amp;lt;xsl:copy-of select="."/&gt;&lt;br /&gt;  &amp;lt;/xsl:for-each&gt;&lt;br /&gt; &amp;lt;/div&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="/" name="main"&gt;&lt;br /&gt; &amp;lt;xsl:call-template name="drawResult"&gt;&lt;br /&gt;  &amp;lt;xsl:with-param name="board" select="fn:solveSudoku($board)"/&gt;&lt;br /&gt; &amp;lt;/xsl:call-template&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template name="drawResult"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;html&gt;&lt;br /&gt;  &amp;lt;head&gt;&lt;br /&gt;   &amp;lt;title&gt;Sudoku - XSLT&amp;lt;/title&gt;&lt;br /&gt;   &amp;lt;style&gt;&lt;br /&gt;    table { border-collapse: collapse;&lt;br /&gt;    border: 1px solid black; }&lt;br /&gt;    td { padding: 10px; }&lt;br /&gt;    .norm { border-left: 1px solid #CCC;&lt;br /&gt;      border-top: 1px solid #CCC;}&lt;br /&gt;    .csep { border-left: 1px solid black; }&lt;br /&gt;    .rsep { border-top: 1px solid black; }&lt;br /&gt;   &amp;lt;/style&gt;&lt;br /&gt;  &amp;lt;/head&gt;&lt;br /&gt;  &amp;lt;body&gt;&lt;br /&gt;   &amp;lt;xsl:call-template name="drawBoard"&gt;&lt;br /&gt;    &amp;lt;xsl:with-param name="board" select="$board"/&gt;&lt;br /&gt;   &amp;lt;/xsl:call-template&gt;&lt;br /&gt;  &amp;lt;/body&gt;&lt;br /&gt; &amp;lt;/html&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template name="drawBoard"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;table&gt;&lt;br /&gt;  &amp;lt;xsl:for-each select="1 to 9"&gt;&lt;br /&gt;   &amp;lt;xsl:variable name="i" select="."/&gt;&lt;br /&gt;   &amp;lt;tr&gt;&lt;br /&gt;    &amp;lt;xsl:for-each select="1 to 9"&gt;&lt;br /&gt;     &amp;lt;xsl:variable name="pos" select="(($i - 1) * 9) + ."/&gt;&lt;br /&gt;     &amp;lt;td class="{if (position() mod 3 = 1) then 'csep' else ('norm')} {if ($i mod 3 = 1) then 'rsep' else ('norm')}"&gt;&lt;br /&gt;      &amp;lt;xsl:value-of select="$board[$pos]"/&gt;&lt;br /&gt;     &amp;lt;/td&gt;&lt;br /&gt;    &amp;lt;/xsl:for-each&gt;&lt;br /&gt;   &amp;lt;/tr&gt;&lt;br /&gt;  &amp;lt;/xsl:for-each&gt;&lt;br /&gt; &amp;lt;/table&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template name="drawBasicBoard"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:for-each select="1 to 9"&gt;&lt;br /&gt;  &amp;lt;xsl:variable name="i" select="."/&gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;xsl:for-each select="1 to 9"&gt;&lt;br /&gt;   &amp;lt;xsl:variable name="pos" select="(($i - 1) * 9) + ."/&gt;&lt;br /&gt;   &amp;lt;xsl:value-of select="$board[$pos]"/&gt;&lt;br /&gt;   &amp;lt;xsl:value-of select="if ($pos mod 3 = 0) then ',   ' else ', '"/&gt;&lt;br /&gt;  &amp;lt;/xsl:for-each&gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;xsl:value-of select="if ($i mod 3 = 0) then '&amp;#xa;&amp;#xa;&amp;#xa;' else '&amp;#xa;'"/&gt;&lt;br /&gt; &amp;lt;/xsl:for-each&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Easy board, 32 existing numbers --&gt;&lt;br /&gt;&amp;lt;xsl:variable name="testBoard1" select="(&lt;br /&gt;0,2,0,  0,0,0,  0,3,6,&lt;br /&gt;0,0,7,  4,0,0,  0,9,0,&lt;br /&gt;0,0,5,  6,0,0,  0,4,8,&lt;br /&gt;&lt;br /&gt;0,0,0,  9,3,0,  0,1,2,&lt;br /&gt;2,9,0,  0,0,0,  0,7,5,&lt;br /&gt;1,5,0,  0,8,2,  0,0,0,&lt;br /&gt;&lt;br /&gt;6,7,0,  0,0,9,  1,0,0,&lt;br /&gt;0,3,0,  0,0,7,  6,0,0,&lt;br /&gt;4,8,0,  0,0,0,  0,2,0&lt;br /&gt;)" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Hard board, 24 existing numbers --&gt;&lt;br /&gt;&amp;lt;xsl:variable name="testBoard2" select="(&lt;br /&gt;1,0,0,  5,6,0,  0,0,0,&lt;br /&gt;9,0,0,  0,0,0,  2,0,8,&lt;br /&gt;0,0,0,  0,0,0,  7,0,0,&lt;br /&gt;&lt;br /&gt;0,8,0,  9,0,7,  0,0,2,&lt;br /&gt;2,0,0,  0,0,0,  0,0,1,&lt;br /&gt;6,0,0,  3,0,2,  0,4,0,&lt;br /&gt;&lt;br /&gt;0,0,5,  0,0,0,  0,0,0,&lt;br /&gt;4,0,3,  0,0,0,  0,0,9,&lt;br /&gt;0,0,0,  0,4,1,  0,0,6&lt;br /&gt;)" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Extremely Hard board --&gt;&lt;br /&gt;&amp;lt;xsl:variable name="testBoard3" select="(&lt;br /&gt;4,0,6,  7,8,0,  2,0,0,&lt;br /&gt;0,5,7,  0,0,0,  0,0,0,&lt;br /&gt;8,0,0,  0,0,0,  4,0,0,&lt;br /&gt;&lt;br /&gt;0,0,0,  0,5,0,  0,9,0,&lt;br /&gt;9,1,2,  0,0,0,  0,0,0,&lt;br /&gt;0,4,0,  0,1,0,  0,0,3,&lt;br /&gt;&lt;br /&gt;0,0,4,  3,0,0,  0,2,0,&lt;br /&gt;7,0,3,  0,0,6,  0,0,0,&lt;br /&gt;0,0,0,  5,0,7,  6,0,8&lt;br /&gt;)" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Fiendish board --&gt;&lt;br /&gt;&amp;lt;xsl:variable name="testBoard4" select="(&lt;br /&gt;0,0,0,  0,0,5,  0,0,0,&lt;br /&gt;0,0,0,  0,2,0,  9,0,0,&lt;br /&gt;0,8,4,  9,0,0,  7,0,0,&lt;br /&gt;&lt;br /&gt;2,0,0,  0,9,0,  4,0,0,&lt;br /&gt;0,3,0,  6,0,2,  0,8,0,&lt;br /&gt;0,0,7,  0,3,0,  0,0,6,&lt;br /&gt;&lt;br /&gt;0,0,2,  0,0,9,  8,1,0,&lt;br /&gt;0,0,6,  0,4,0,  0,0,0,&lt;br /&gt;0,0,0,  5,0,0,  0,0,0&lt;br /&gt;)" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Fiendish 2 board --&gt;&lt;br /&gt;&amp;lt;xsl:variable name="testBoard5" select="(&lt;br /&gt; 0,0,0,  0,0,5,  1,0,0,&lt;br /&gt; 0,3,5,  0,0,0,  0,4,0,&lt;br /&gt; 8,0,0,  4,0,0,  0,2,0,&lt;br /&gt; &lt;br /&gt; 9,0,0,  0,3,0,  5,0,0,&lt;br /&gt; 0,0,0,  2,0,8,  0,0,0,&lt;br /&gt; 0,0,7,  0,9,0,  0,0,8,&lt;br /&gt; &lt;br /&gt; 0,5,0,  0,0,9,  0,0,2,&lt;br /&gt; 0,4,0,  0,0,0,  9,8,0,&lt;br /&gt; 0,0,1,  7,0,0,  0,0,0&lt;br /&gt;)" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:variable name="testBoard_Fiendish_060323" select="(&lt;br /&gt; 5,3,0,  0,9,0,  0,0,6,&lt;br /&gt; 0,0,0,  1,5,0,  0,0,3,&lt;br /&gt; 0,0,2,  0,0,0,  8,0,0,&lt;br /&gt; &lt;br /&gt; 0,0,0,  0,0,0,  0,8,0,&lt;br /&gt; 1,7,0,  0,4,0,  0,2,9,&lt;br /&gt; 0,9,0,  0,0,0,  0,0,0,&lt;br /&gt; &lt;br /&gt; 0,0,6,  0,0,0,  3,0,0,&lt;br /&gt; 4,0,0,  0,1,2,  0,0,0,&lt;br /&gt; 3,0,0,  0,6,0,  0,5,4&lt;br /&gt;)" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Fiendish 060403--&gt;&lt;br /&gt;&amp;lt;xsl:variable name="testBoard38" select="(&lt;br /&gt; 5,0,0, 0,0,1, 0,0,2,&lt;br /&gt; 0,6,0, 0,7,0, 0,0,0,&lt;br /&gt; 0,0,0, 6,0,0, 8,0,0,&lt;br /&gt; &lt;br /&gt; 0,0,3, 0,0,5, 0,0,8,&lt;br /&gt; 0,9,0, 0,1,0, 0,2,0,&lt;br /&gt; 2,0,0, 3,0,0, 7,0,0,&lt;br /&gt; &lt;br /&gt; 0,0,1, 0,0,2, 0,0,0,&lt;br /&gt; 0,0,0, 0,6,0, 0,9,0,&lt;br /&gt; 8,0,0, 7,0,0, 0,0,5&lt;br /&gt;)" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Failing board, an erroneous 9 has been added at index 1 --&gt;&lt;br /&gt;&amp;lt;xsl:variable name="testBoard_Fail" select="(&lt;br /&gt;9,2,0,  0,0,0,  0,3,6,&lt;br /&gt;0,0,7,  4,0,0,  0,9,0,&lt;br /&gt;0,0,5,  6,0,0,  0,4,8,&lt;br /&gt;&lt;br /&gt;0,0,0,  9,3,0,  0,1,2,&lt;br /&gt;2,9,0,  0,0,0,  0,7,5,&lt;br /&gt;1,5,0,  0,8,2,  0,0,0,&lt;br /&gt;&lt;br /&gt;6,7,0,  0,0,9,  1,0,0,&lt;br /&gt;0,3,0,  0,0,7,  6,0,0,&lt;br /&gt;4,8,0,  0,0,0,  0,2,0&lt;br /&gt;)" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/xsl:stylesheet&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-114657714808295150?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/114657714808295150/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=114657714808295150&amp;isPopup=true' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114657714808295150'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114657714808295150'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2006/05/sudoku-solver-complete-and-shes-fast_02.html' title='Sudoku Solver complete - and shes fast'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-114346840508047187</id><published>2006-03-27T13:47:00.000Z</published><updated>2006-03-31T09:26:03.550Z</updated><title type='text'>VTD-XML</title><content type='html'>Ian Jones pointed me to an article on &lt;a href="http://www.javaworld.com/"&gt;javaworld&lt;/a&gt; about &lt;a href="http://www.javaworld.com/javaworld/jw-03-2006/jw-0327-simplify.html"&gt;VTD-XML&lt;/a&gt;, which is apparently "A new option that overcomes the problems of DOM and SAX".&lt;br /&gt;&lt;br /&gt;It all sounds promising, although the article is light on code examples and heavy on comparisons, so I'll reserve judgement until I've used it.  It does seem to concentrate on speed without mentioning conformity, which is not a good sign.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-114346840508047187?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/114346840508047187/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=114346840508047187&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114346840508047187'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114346840508047187'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2006/03/vtd-xml.html' title='VTD-XML'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-114322813119827771</id><published>2006-03-24T19:13:00.000Z</published><updated>2006-03-24T19:22:11.450Z</updated><title type='text'></title><content type='html'>Well when I said "blisteringly fast" I should have said "very fast, but give it a week or two and we'll get faster"...&lt;br /&gt;&lt;br /&gt;I've since found a way of improving the performance of my solution... and I think Dimitre has improved his.  Watch this space, because neither is quite ready yet.  I'll stick my neck out and say once it's done, mine should solve any board pretty much instantly. (I'm going to regret saying that, because mine probably won't &lt;span style="font-style:italic;"&gt;but Dimitre's will...&lt;/span&gt;)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-114322813119827771?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/114322813119827771/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=114322813119827771&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114322813119827771'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114322813119827771'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2006/03/well-when-i-said-blisteringly-fast-i.html' title=''/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-114287470751273204</id><published>2006-03-20T17:11:00.000Z</published><updated>2006-03-31T09:31:25.396Z</updated><title type='text'>Dimitre's tuned Sudoku solution</title><content type='html'>Never one to settle for second place Dimitre set about performance tuning his Sudoku solving stylesheet... with dramatic effect.&lt;br /&gt;&lt;br /&gt;His latest effort is *blisteringly* fast... &lt;br /&gt;&lt;br /&gt;With this board, known as "Fiendish 2" his solution takes just 7.1 seconds.  To put that into perspective, my performance tuned implementation takes well over 5 minutes!  I'll have to find out why the time is so bad for me...&lt;br /&gt;&lt;br /&gt;Here's the "Fiendish 2" board, in the format Dimite's solution requires:&lt;br /&gt; &lt;br /&gt;&lt;span style="font-family:courier new; font-size:smaller"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;board&gt;&lt;br /&gt; &amp;lt;row&gt;0,0,0,0,0,5,1,0,0&amp;lt;/row&gt;&lt;br /&gt; &amp;lt;row&gt;0,3,5,0,0,0,0,4,0&amp;lt;/row&gt;&lt;br /&gt; &amp;lt;row&gt;8,0,0,4,0,0,0,2,0&amp;lt;/row&gt;&lt;br /&gt; &amp;lt;row&gt;9,0,0,0,3,0,5,0,0&amp;lt;/row&gt;&lt;br /&gt; &amp;lt;row&gt;0,0,0,2,0,8,0,0,0&amp;lt;/row&gt;&lt;br /&gt; &amp;lt;row&gt;0,0,7,0,9,0,0,0,8&amp;lt;/row&gt;&lt;br /&gt; &amp;lt;row&gt;0,5,0,0,0,9,0,0,2&amp;lt;/row&gt;&lt;br /&gt; &amp;lt;row&gt;0,4,0,0,0,0,9,8,0&amp;lt;/row&gt;&lt;br /&gt; &amp;lt;row&gt;0,0,1,7,0,0,0,0,0&amp;lt;/row&gt;&lt;br /&gt;&amp;lt;/board&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Dimitre's stylesheet:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new; font-size:smaller"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;xsl:stylesheet version="2.0"&lt;br /&gt; xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&lt;br /&gt; xmlns:xs="http://www.w3.org/2001/XMLSchema"&lt;br /&gt; xmlns:f="http://fxsl.sf.net/"&lt;br /&gt; exclude-result-prefixes="f xs"&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:output omit-xml-declaration="yes" indent="yes"/&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:template match="/"&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;xsl:variable name="vFixed" select="f:cellsGroup('Fixed', /*/*)"/&gt;&lt;br /&gt;  &amp;lt;xsl:variable name="vEmpty" select="f:cellsGroup('Empty', /*/*)"/&gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;xsl:variable name="vallFillers" as="element()*"&gt;&lt;br /&gt;   &amp;lt;xsl:for-each select=&lt;br /&gt;    "f:makeFillers(f:cellsGroup('Fixed', /*/*), f:cellsGroup('Empty', /*/*))"&gt;&lt;br /&gt;     &amp;lt;xsl:sort select="@row"/&gt;&lt;br /&gt;     &amp;lt;xsl:sort select="@col"/&gt;&lt;br /&gt;&lt;br /&gt;     &amp;lt;xsl:sequence select="."/&gt;&lt;br /&gt;   &amp;lt;/xsl:for-each&gt;&lt;br /&gt;  &amp;lt;/xsl:variable&gt;&lt;br /&gt;&lt;br /&gt;  &lt;br /&gt;   &amp;lt;xsl:sequence&lt;br /&gt;     select="f:sudoku($vFixed, $vEmpty, $vallFillers)"/&gt;&lt;br /&gt; &amp;lt;/xsl:template&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:variable name="vAllVals" select="1 to 9" as="xs:integer+"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:function name="f:cellsGroup" as="element()*"&gt;&lt;br /&gt;   &amp;lt;xsl:param name="pgrpType" as="xs:string"/&gt;&lt;br /&gt;   &amp;lt;xsl:param name="pRows" as="element()*"/&gt;&lt;br /&gt;     &amp;lt;xsl:sequence select=&lt;br /&gt;      "for $i in 1 to count($pRows),&lt;br /&gt;            $vRow in $pRows[$i],&lt;br /&gt;                $vNum in tokenize($vRow, ',')&lt;br /&gt;                           [if($pgrpType='Fixed')&lt;br /&gt;                               then . ne '0'&lt;br /&gt;                               else . eq '0'&lt;br /&gt;                           ][if($pgrpType='Empty')&lt;br /&gt;                               then 1&lt;br /&gt;                               else true()],&lt;br /&gt;                $k in index-of(tokenize($vRow, ','),$vNum)&lt;br /&gt;               return&lt;br /&gt;                 f:makeCell($i,$k, xs:integer($vNum))&lt;br /&gt;      "/&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:function name="f:makeCell" as="element()"&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pnRow" as="xs:integer"/&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pnCol" as="xs:integer"/&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pnVal" as="xs:integer"/&gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;cell row="{$pnRow}" col="{$pnCol}" val="{$pnVal}"/&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:function name="f:sudoku" as="item()*"&gt;&lt;br /&gt;   &amp;lt;xsl:param name="pFixedCells" as="element()*"/&gt;&lt;br /&gt;   &amp;lt;xsl:param name="pEmptyCells" as="element()*"/&gt;&lt;br /&gt;   &amp;lt;xsl:param name="pallFillers" as="element()*"/&gt;&lt;br /&gt;   &lt;br /&gt;&amp;lt;!--&lt;br /&gt;   &amp;lt;xsl:message&gt;&lt;br /&gt;  f:sudoku:&lt;br /&gt;     Fixed: &amp;lt;xsl:sequence select="count($pFixedCells)"/&gt;&lt;br /&gt;     Empty: &amp;lt;xsl:sequence select="count($pEmptyCells)"/&gt;&lt;br /&gt;   &amp;lt;/xsl:message&gt;&lt;br /&gt;--&gt;&lt;br /&gt;   &amp;lt;xsl:choose&gt;&lt;br /&gt;    &amp;lt;xsl:when test="empty($pEmptyCells)"&gt;&lt;br /&gt;       &amp;lt;xsl:sequence select="f:printBoard($pFixedCells)"/&gt;&lt;br /&gt;    &amp;lt;/xsl:when&gt;&lt;br /&gt;    &amp;lt;xsl:otherwise&gt;&lt;br /&gt;   &amp;lt;xsl:if test="f:canContinue($pFixedCells, $pEmptyCells, $pallFillers)"&gt;&lt;br /&gt;       &amp;lt;xsl:variable name="vBestCells" as="element()*"&lt;br /&gt;         select="f:bestCellsToTry($pEmptyCells,$pallFillers)"/&gt;&lt;br /&gt;    &amp;lt;xsl:variable name="vBestCell" select="$vBestCells[1]"/&gt;&lt;br /&gt;       &amp;lt;xsl:variable name="vcellFillers" as="element()+"&lt;br /&gt;         select="f:cellFillers($pallFillers,$vBestCell)"/&gt;&lt;br /&gt;&lt;br /&gt;           &amp;lt;xsl:sequence select=&lt;br /&gt;             "f:tryFillers($pFixedCells,$pEmptyCells, $pallFillers,&lt;br /&gt;                           $vcellFillers,$vBestCell)"&lt;br /&gt;             /&gt;&lt;br /&gt;      &amp;lt;/xsl:if&gt;&lt;br /&gt;    &amp;lt;/xsl:otherwise&gt;&lt;br /&gt;   &amp;lt;/xsl:choose&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:function name="f:canContinue" as="xs:boolean"&gt;&lt;br /&gt;   &amp;lt;xsl:param name="pFixedCells" as="element()*"/&gt;&lt;br /&gt;   &amp;lt;xsl:param name="pEmptyCells" as="element()*"/&gt;&lt;br /&gt;   &amp;lt;xsl:param name="pallFillers" as="element()*"/&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select=&lt;br /&gt;      "empty($pEmptyCells[empty(f:cellFillers($pallFillers,.))])"&lt;br /&gt;   /&gt;&lt;br /&gt;&amp;lt;!-- --&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:function name="f:cellFillers" as="element()*"&gt;&lt;br /&gt;   &amp;lt;xsl:param name="pallFillers" as="element()*"/&gt;&lt;br /&gt;   &amp;lt;xsl:param name="pemptyCell" as="element()"/&gt;&lt;br /&gt;   &lt;br /&gt;   &amp;lt;xsl:sequence select="$pallFillers[@row eq $pemptyCell/@row&lt;br /&gt;                                     and&lt;br /&gt;                                      @col eq $pemptyCell/@col&lt;br /&gt;                                     ]"/&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:function name="f:bestCellsToTry" as="element()*"&gt;&lt;br /&gt;   &amp;lt;xsl:param name="pEmptyCells" as="element()*"/&gt;&lt;br /&gt;   &amp;lt;xsl:param name="pallFillers" as="element()*"/&gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;xsl:for-each select="$pEmptyCells"&gt;&lt;br /&gt;     &amp;lt;xsl:sort select="count(f:cellFillers($pallFillers,.))" order="ascending"/&gt;&lt;br /&gt;     &lt;br /&gt;     &amp;lt;xsl:sequence select="."/&gt;&lt;br /&gt;   &amp;lt;/xsl:for-each&gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;xsl:variable name="vbestRow" as="element()+"&gt;&lt;br /&gt;    &amp;lt;xsl:for-each-group select="$pEmptyCells" group-by="@row"&gt;&lt;br /&gt;       &amp;lt;xsl:sort select="count(current-group())" order="ascending"/&gt;&lt;br /&gt;       &amp;lt;xsl:sequence select=&lt;br /&gt;           "if(position() = 1)&lt;br /&gt;               then current-group()&lt;br /&gt;               else ()"/&gt;&lt;br /&gt;    &amp;lt;/xsl:for-each-group&gt;&lt;br /&gt;   &amp;lt;/xsl:variable&gt;&lt;br /&gt;  &amp;lt;!--  Output the resulting cell --&gt;&lt;br /&gt; &amp;lt;xsl:for-each-group select="$vbestRow"&lt;br /&gt;       group-by="count(f:column($pEmptyCells, current()/@col))"&gt;&lt;br /&gt;    &amp;lt;xsl:sort select="current-grouping-key()" order="ascending"/&gt;&lt;br /&gt; &lt;br /&gt;    &amp;lt;xsl:sequence select="."/&gt;&lt;br /&gt;  &amp;lt;/xsl:for-each-group&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:function name="f:makeFillers" as="element()*"&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pFixedCells" as="element()*"/&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pEmptyCells" as="element()*"/&gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;xsl:sequence select="$pEmptyCells/f:makeFillersForCell($pFixedCells,.)"/&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:function name="f:makeFillersForCell" as="element()*"&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pFixedCells" as="element()*"/&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pEmptyCell" as="element()"/&gt;&lt;br /&gt;  &lt;br /&gt;    &amp;lt;xsl:for-each select="$vAllVals"&gt;&lt;br /&gt;      &amp;lt;xsl:if test="not(. = f:column($pFixedCells,$pEmptyCell/@col)/@val)&lt;br /&gt;                  and&lt;br /&gt;                    not(. = f:row($pFixedCells,$pEmptyCell/@row)/@val)&lt;br /&gt;                  and&lt;br /&gt;                    not(. = f:region($pFixedCells, $pEmptyCell)/@val)&lt;br /&gt;                   "&gt;&lt;br /&gt;      &amp;lt;xsl:sequence select="f:makeCell($pEmptyCell/@row,$pEmptyCell/@col,.)"/&gt;&lt;br /&gt;      &amp;lt;/xsl:if&gt;&lt;br /&gt;    &amp;lt;/xsl:for-each&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:function name="f:tryFillers" as="item()*"&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pFixedCells" as="element()*"/&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pEmptyCells" as="element()*"/&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pallFillers" as="element()*"/&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pFillers" as="element()*"/&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pBestCell" as="element()"/&gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;xsl:if test="exists($pFillers)"&gt;&lt;br /&gt;   &amp;lt;xsl:variable name="vtheFiller" select="$pFillers[1]"/&gt;&lt;br /&gt;&amp;lt;!--&lt;br /&gt;   &amp;lt;xsl:message&gt;&lt;br /&gt;     Trying filler: &amp;lt;xsl:sequence select="$vtheFiller"/&gt;&lt;br /&gt;   &amp;lt;/xsl:message&gt;&lt;br /&gt;--&gt;&lt;br /&gt;   &amp;lt;xsl:variable name="vreducedFilllers" as="element()*"&lt;br /&gt;    select="f:reduceAllFillers($pallFillers, $pBestCell,$vtheFiller)"/&gt;&lt;br /&gt;&amp;lt;!--&lt;br /&gt;   &amp;lt;xsl:variable name="vmadeFilllers" as="element()*"&lt;br /&gt;    select="f:makeFillers(($pFixedCells,$vtheFiller),$pEmptyCells[not(. is $pBestCell)])"/&gt;&lt;br /&gt;   &amp;lt;xsl:if test="count($vmadeFilllers) != count($vreducedFilllers)"&gt;&lt;br /&gt;      &amp;lt;xsl:message&gt;&lt;br /&gt;           Problem:&lt;br /&gt;    count($vmadeFilllers) = &amp;lt;xsl:value-of select="count($vmadeFilllers)"/&gt;&lt;br /&gt;    count($vreducedFilllers) = &amp;lt;xsl:value-of select="count($vreducedFilllers)"/&gt;&lt;br /&gt;    &lt;br /&gt;    madeFillers:&lt;br /&gt;    &amp;lt;xsl:copy-of select="$vmadeFilllers"/&gt;&lt;br /&gt;    &lt;br /&gt;    reducedFillers:&lt;br /&gt;    &amp;lt;xsl:copy-of select="$vreducedFilllers"/&gt;&lt;br /&gt;      &amp;lt;/xsl:message&gt;&lt;br /&gt;   &amp;lt;/xsl:if&gt;&lt;br /&gt;--&gt;&lt;br /&gt;   &amp;lt;xsl:variable name="vSolution" select=&lt;br /&gt;   "f:sudoku(($pFixedCells,$vtheFiller),$pEmptyCells[not(. is $pBestCell)],&lt;br /&gt;             f:reduceAllFillers($pallFillers, $pBestCell,$vtheFiller)&lt;br /&gt;             (: f:makeFillers(($pFixedCells,$vtheFiller),$pEmptyCells[not(. is $pBestCell)]) :)&lt;br /&gt;             )&lt;br /&gt;   "/&gt;&lt;br /&gt;   &lt;br /&gt;     &amp;lt;xsl:choose&gt;&lt;br /&gt;       &amp;lt;xsl:when test="exists($vSolution)"&gt;&lt;br /&gt;     &amp;lt;xsl:sequence select="$vSolution"/&gt;&lt;br /&gt;       &amp;lt;/xsl:when&gt;&lt;br /&gt;       &amp;lt;xsl:otherwise&gt;&lt;br /&gt;         &amp;lt;xsl:sequence select=&lt;br /&gt;         "f:tryFillers($pFixedCells,$pEmptyCells, $pallFillers,&lt;br /&gt;                       $pFillers[position() gt 1],$pBestCell)"/&gt;&lt;br /&gt;       &amp;lt;/xsl:otherwise&gt;&lt;br /&gt;     &amp;lt;/xsl:choose&gt;&lt;br /&gt;  &amp;lt;/xsl:if&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:function name="f:reduceAllFillers" as="element()*"&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pallFillers" as="element()*"/&gt;&lt;br /&gt;  &amp;lt;xsl:param name="ppickedCell" as="element()"/&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pthisFiller" as="element()"/&gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;xsl:sequence select=&lt;br /&gt;   "$pallFillers[(@row ne $ppickedCell/@row&lt;br /&gt;                or&lt;br /&gt;                 @col ne $ppickedCell/@col&lt;br /&gt;                  )&lt;br /&gt;                and&lt;br /&gt;                 (&lt;br /&gt;                 if(@row = $ppickedCell/@row&lt;br /&gt;                    or&lt;br /&gt;                     @col = $ppickedCell/@col&lt;br /&gt;                    or&lt;br /&gt;                      (&lt;br /&gt;                       ((@row - 1) idiv 3 eq ($ppickedCell/@row -1) idiv 3)&lt;br /&gt;                      and&lt;br /&gt;                        ((@col - 1) idiv 3 eq ($ppickedCell/@col -1) idiv 3)&lt;br /&gt;                       )&lt;br /&gt;                     )&lt;br /&gt;                     then&lt;br /&gt;                       @val ne $pthisFiller/@val&lt;br /&gt;                     else&lt;br /&gt;                       true()&lt;br /&gt;                  )&lt;br /&gt;                 ]"&lt;br /&gt;   /&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:function name="f:column" as="element()*"&gt;&lt;br /&gt;   &amp;lt;xsl:param name="pCells" as="element()*"/&gt;&lt;br /&gt;   &amp;lt;xsl:param name="pColno" as="xs:integer"/&gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$pCells[@col = $pColno]"/&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:function name="f:row" as="element()*"&gt;&lt;br /&gt;   &amp;lt;xsl:param name="pCells" as="element()*"/&gt;&lt;br /&gt;   &amp;lt;xsl:param name="pRowno" as="xs:integer"/&gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$pCells[@row = $pRowno]"/&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:function name="f:region" as="element()*"&gt;&lt;br /&gt;   &amp;lt;xsl:param name="pCells" as="element()*"/&gt;&lt;br /&gt;   &amp;lt;xsl:param name="paCell" as="element()"/&gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;xsl:variable name="vregRowStart" as="xs:integer"&lt;br /&gt;        select="3*(($paCell/@row -1) idiv 3) +1"/&gt;&lt;br /&gt;   &amp;lt;xsl:variable name="vregColStart" as="xs:integer"&lt;br /&gt;        select="3*(($paCell/@col -1) idiv 3) +1"/&gt;&lt;br /&gt;        &lt;br /&gt;   &amp;lt;xsl:sequence select=&lt;br /&gt;      "$pCells[xs:integer(@row) ge $vregRowStart and xs:integer(@row) lt ($vregRowStart +3)&lt;br /&gt;        and&lt;br /&gt;          xs:integer(@col) ge $vregColStart and xs:integer(@col) lt ($vregColStart +3)&lt;br /&gt;              ]"/&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:function name="f:printBoard"&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pCells" as="element()+"/&gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;xsl:for-each-group select="$pCells" group-by="@row"&gt;&lt;br /&gt;    &amp;lt;xsl:sort select="current-grouping-key()"/&gt;&lt;br /&gt;    &amp;lt;row&gt;&lt;br /&gt;       &amp;lt;xsl:for-each select="current-group()"&gt;&lt;br /&gt;         &amp;lt;xsl:sort select="@col"/&gt;&lt;br /&gt;         &amp;lt;xsl:value-of select=&lt;br /&gt;          "concat(@val, if(position() ne last()) then ', ' else ())"&lt;br /&gt;          /&gt;&lt;br /&gt;       &amp;lt;/xsl:for-each&gt;&lt;br /&gt;    &amp;lt;/row&gt;&lt;br /&gt;  &amp;lt;/xsl:for-each-group&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt;&amp;lt;/xsl:stylesheet&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-114287470751273204?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/114287470751273204/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=114287470751273204&amp;isPopup=true' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114287470751273204'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114287470751273204'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2006/03/dimitres-tuned-sudoku-solution.html' title='Dimitre&apos;s tuned Sudoku solution'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-114253103403944340</id><published>2006-03-16T17:33:00.000Z</published><updated>2006-03-31T09:26:59.890Z</updated><title type='text'>Dimitre Novatchev's Sudoku solution</title><content type='html'>The Sudoku solving stylesheet craze has been catching on...&lt;br /&gt;&lt;br /&gt;Dimitre Novatchev, creator of &lt;a href="http://fxsl.sourceforge.net/"&gt;FXSL&lt;/a&gt; and xsl-list regular, has written a stylesheet (in typical Dimitre style) which attacks the problem from a slightly different angle.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;font-size:smaller"&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;xsl:stylesheet version="2.0"&lt;br /&gt; xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&lt;br /&gt; xmlns:xs="http://www.w3.org/2001/XMLSchema"&lt;br /&gt; xmlns:f="http://fxsl.sf.net/"&lt;br /&gt; exclude-result-prefixes="f xs"&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:output omit-xml-declaration="yes" indent="yes"/&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:template match="/"&gt;&lt;br /&gt;  &amp;lt;xsl:sequence&lt;br /&gt;    select="f:sudoku(f:cellsGroup('Fixed', /*/*),&lt;br /&gt;f:cellsGroup('Empty', /*/*))"/&gt;&lt;br /&gt; &amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:variable name="vAllVals" select="1 to 9" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:function name="f:cellsGroup" as="element()*"&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pgrpType" as="xs:string"/&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pRows" as="element()*"/&gt;&lt;br /&gt;    &amp;lt;xsl:sequence select=&lt;br /&gt;     "for $i in 1 to count($pRows),&lt;br /&gt;           $vRow in $pRows[$i],&lt;br /&gt;               $vNum in tokenize($vRow, ',')&lt;br /&gt;                          [if($pgrpType='Fixed')&lt;br /&gt;                              then . ne '0'&lt;br /&gt;                              else . eq '0'&lt;br /&gt;                          ][if($pgrpType='Empty')&lt;br /&gt;                              then 1&lt;br /&gt;                              else true()],&lt;br /&gt;               $k in index-of(tokenize($vRow, ','),$vNum)&lt;br /&gt;              return&lt;br /&gt;                f:makeCell($i,$k, xs:integer($vNum))&lt;br /&gt;     "/&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:function name="f:makeCell" as="element()"&gt;&lt;br /&gt; &amp;lt;xsl:param name="pnRow" as="xs:integer"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="pnCol" as="xs:integer"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="pnVal" as="xs:integer"/&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;cell row="{$pnRow}" col="{$pnCol}" val="{$pnVal}"/&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:function name="f:sudoku" as="item()*"&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pFixedCells" as="element()*"/&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pEmptyCells" as="element()*"/&gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;xsl:choose&gt;&lt;br /&gt;   &amp;lt;xsl:when test="empty($pEmptyCells)"&gt;&lt;br /&gt;      &amp;lt;xsl:sequence select="f:printBoard($pFixedCells)"/&gt;&lt;br /&gt;   &amp;lt;/xsl:when&gt;&lt;br /&gt;   &amp;lt;xsl:otherwise&gt;&lt;br /&gt;      &amp;lt;xsl:variable name="vBestCells" as="element()*"&lt;br /&gt;        select="f:bestCellsToTry($pEmptyCells)"/&gt;&lt;br /&gt;         &amp;lt;xsl:if test="empty($vBestCells[empty(f:fillers($pFixedCells,.))])"&gt;&lt;br /&gt;            &amp;lt;xsl:variable name="vBestCell" select="$vBestCells[1]"/&gt;&lt;br /&gt;            &amp;lt;xsl:variable name="vFillers" as="element()+"&lt;br /&gt;                select="f:fillers($pFixedCells,$vBestCell)"/&gt;&lt;br /&gt;&lt;br /&gt;          &amp;lt;xsl:sequence select=&lt;br /&gt;            "f:tryFillers($pFixedCells,$pEmptyCells, $vFillers,$vBestCell)"&lt;br /&gt;            /&gt;&lt;br /&gt;       &amp;lt;/xsl:if&gt;&lt;br /&gt;   &amp;lt;/xsl:otherwise&gt;&lt;br /&gt;  &amp;lt;/xsl:choose&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:function name="f:bestCellsToTry" as="element()*"&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pEmptyCells" as="element()*"/&gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;xsl:variable name="vbestRow" as="element()+"&gt;&lt;br /&gt;          &amp;lt;xsl:for-each-group select="$pEmptyCells" group-by="@row"&gt;&lt;br /&gt;             &amp;lt;xsl:sort select="count(current-group())" order="ascending"/&gt;&lt;br /&gt;             &amp;lt;xsl:sequence select=&lt;br /&gt;                 "if(position() = 1)&lt;br /&gt;                     then current-group()&lt;br /&gt;                     else ()"/&gt;&lt;br /&gt;          &amp;lt;/xsl:for-each-group&gt;&lt;br /&gt;  &amp;lt;/xsl:variable&gt;&lt;br /&gt; &amp;lt;!--  Output the resulting cell --&gt;&lt;br /&gt;       &amp;lt;xsl:for-each-group select="$vbestRow"&lt;br /&gt;             group-by="count(f:column($pEmptyCells, current()/@col))"&gt;&lt;br /&gt;          &amp;lt;xsl:sort select="current-grouping-key()" order="ascending"/&gt;&lt;br /&gt;&lt;br /&gt;          &amp;lt;xsl:sequence select="."/&gt;&lt;br /&gt;        &amp;lt;/xsl:for-each-group&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:function name="f:fillers" as="element()*"&gt;&lt;br /&gt; &amp;lt;xsl:param name="pFixedCells" as="element()*"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="pEmptyCell" as="element()"/&gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;xsl:for-each select="$vAllVals"&gt;&lt;br /&gt;     &amp;lt;xsl:if test="not(. = f:column($pFixedCells,$pEmptyCell/@col)/@val)&lt;br /&gt;                 and&lt;br /&gt;                   not(. = f:row($pFixedCells,$pEmptyCell/@row)/@val)&lt;br /&gt;                 and&lt;br /&gt;                   not(. = f:region($pFixedCells, $pEmptyCell)/@val)&lt;br /&gt;                  "&gt;&lt;br /&gt;         &amp;lt;xsl:sequence&lt;br /&gt;select="f:makeCell($pEmptyCell/@row,$pEmptyCell/@col,.)"/&gt;&lt;br /&gt;     &amp;lt;/xsl:if&gt;&lt;br /&gt;   &amp;lt;/xsl:for-each&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:function name="f:tryFillers" as="item()*"&gt;&lt;br /&gt; &amp;lt;xsl:param name="pFixedCells" as="element()*"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="pEmptyCells" as="element()*"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="pFillers" as="element()*"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="pBestCell" as="element()"/&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:if test="exists($pFillers)"&gt;&lt;br /&gt;  &amp;lt;xsl:variable name="vtheFiller" select="$pFillers[1]"/&gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;xsl:variable name="vSolution" select=&lt;br /&gt;  "f:sudoku(($pFixedCells,$vtheFiller),$pEmptyCells[not(. is $pBestCell)])&lt;br /&gt;  "/&gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;xsl:choose&gt;&lt;br /&gt;      &amp;lt;xsl:when test="exists($vSolution)"&gt;&lt;br /&gt;          &amp;lt;xsl:sequence select="$vSolution"/&gt;&lt;br /&gt;      &amp;lt;/xsl:when&gt;&lt;br /&gt;      &amp;lt;xsl:otherwise&gt;&lt;br /&gt;        &amp;lt;xsl:sequence select=&lt;br /&gt;        "f:tryFillers($pFixedCells,$pEmptyCells, $pFillers[position()&lt;br /&gt;gt 1],$pBestCell)"/&gt;&lt;br /&gt;      &amp;lt;/xsl:otherwise&gt;&lt;br /&gt;    &amp;lt;/xsl:choose&gt;&lt;br /&gt; &amp;lt;/xsl:if&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:function name="f:column" as="element()*"&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pCells" as="element()*"/&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pColno" as="xs:integer"/&gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;xsl:sequence select="$pCells[@col = $pColno]"/&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:function name="f:row" as="element()*"&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pCells" as="element()*"/&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pRowno" as="xs:integer"/&gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;xsl:sequence select="$pCells[@row = $pRowno]"/&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:function name="f:region" as="element()*"&gt;&lt;br /&gt;  &amp;lt;xsl:param name="pCells" as="element()*"/&gt;&lt;br /&gt;  &amp;lt;xsl:param name="paCell" as="element()"/&gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;xsl:variable name="vregRowStart" as="xs:integer"&lt;br /&gt;       select="3*(($paCell/@row -1) idiv 3) +1"/&gt;&lt;br /&gt;  &amp;lt;xsl:variable name="vregColStart" as="xs:integer"&lt;br /&gt;       select="3*(($paCell/@col -1) idiv 3) +1"/&gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;xsl:sequence select=&lt;br /&gt;     "$pCells[xs:integer(@row) ge $vregRowStart and xs:integer(@row)&lt;br /&gt;lt ($vregRowStart +3)&lt;br /&gt;                                   and&lt;br /&gt;                                           xs:integer(@col) ge $vregColStart and xs:integer(@col) lt&lt;br /&gt;($vregColStart +3)&lt;br /&gt;             ]"/&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:function name="f:printBoard"&gt;&lt;br /&gt; &amp;lt;xsl:param name="pCells" as="element()+"/&gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;xsl:for-each-group select="$pCells" group-by="@row"&gt;&lt;br /&gt;   &amp;lt;xsl:sort select="current-grouping-key()"/&gt;&lt;br /&gt;   &amp;lt;row&gt;&lt;br /&gt;      &amp;lt;xsl:for-each select="current-group()"&gt;&lt;br /&gt;        &amp;lt;xsl:sort select="@col"/&gt;&lt;br /&gt;        &amp;lt;xsl:value-of select=&lt;br /&gt;         "concat(@val, if(position() ne last()) then ', ' else ())"&lt;br /&gt;         /&gt;&lt;br /&gt;      &amp;lt;/xsl:for-each&gt;&lt;br /&gt;   &amp;lt;/row&gt;&lt;br /&gt; &amp;lt;/xsl:for-each-group&gt;&lt;br /&gt; &amp;lt;/xsl:function&gt;&lt;br /&gt;&amp;lt;/xsl:stylesheet&gt;&lt;/pre&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-114253103403944340?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/114253103403944340/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=114253103403944340&amp;isPopup=true' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114253103403944340'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114253103403944340'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2006/03/dimitre-novatchevs-sudoku-solution.html' title='Dimitre Novatchev&apos;s Sudoku solution'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-114243591021088461</id><published>2006-03-15T15:08:00.000Z</published><updated>2006-03-31T09:24:47.500Z</updated><title type='text'>xq2xml</title><content type='html'>David Carlisle is currently undertaking a project to convert XQuery expressions into other languages, by first converting the query to XML and then on from there.&lt;br /&gt;&lt;br /&gt;The "xq2xml" page can be found at &lt;a href="http://monet.nag.co.uk/xq2xml/index.html"&gt;http://monet.nag.co.uk/xq2xml/index.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;His notes on the differences between XQuery and XSLT can be found at &lt;a href="http://monet.nag.co.uk/xq2xml/xq2xslnotes.html"&gt;http://monet.nag.co.uk/xq2xml/xq2xslnotes.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;It's all very interesting reading, but I kept asking myself "why?" so I mailed him asking if there's a real world application behind all this...&lt;br /&gt;&lt;br /&gt;David's reply:  "is 'teach myself xquery' a real world application?"&lt;br /&gt;&lt;br /&gt;I guess the "because it's there" mentality applies to programming too :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-114243591021088461?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://monet.nag.co.uk/xq2xml/index.html' title='xq2xml'/><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/114243591021088461/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=114243591021088461&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114243591021088461'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114243591021088461'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2006/03/xq2xml.html' title='xq2xml'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-114237147411194368</id><published>2006-03-14T20:55:00.000Z</published><updated>2006-03-31T09:30:31.423Z</updated><title type='text'>David Carlisle's XQuery conversion of the Sudoku stylesheet</title><content type='html'>After posting the Sudoku stylesheet to xsl-list, David Carlisle mailed me back off-list with the XQuery equivalent, and very fine it is too.&lt;br /&gt;&lt;br /&gt;I did start doing some performance comparisons between this and the XSLT 2.0 version, but when the first few results were produced I remembered Saxon compiles much of it into the same internal represenation...&lt;br /&gt;&lt;br /&gt;&lt;div style="font-family:courier new;font-size:smaller"&gt;&lt;pre&gt;&lt;br /&gt;declare namespace fn = "sudoku";&lt;br /&gt;&lt;br /&gt;declare variable  $board as xs:integer+ := (&lt;br /&gt;1,0,0,  3,0,0,  6,0,0,&lt;br /&gt;0,2,0,  5,0,0,  0,0,4,&lt;br /&gt;0,0,9,  0,0,0,  5,2,0,&lt;br /&gt;&lt;br /&gt;0,0,0,  9,6,3,  0,0,0,&lt;br /&gt;7,1,6,  0,0,0,  0,0,0,&lt;br /&gt;0,0,0,  0,8,0,  0,4,0,&lt;br /&gt;&lt;br /&gt;9,0,0,  0,0,5,  3,0,7,&lt;br /&gt;8,0,0,  4,0,6,  0,0,0,&lt;br /&gt;3,5,0,  0,0,0,  0,0,1);&lt;br /&gt;&lt;br /&gt;declare variable  $rowStarts as xs:integer+  := (1, 10, 19, 28, 37, 46, 55, 64,73);&lt;br /&gt;&lt;br /&gt;declare variable  $groups as xs:integer+  := (&lt;br /&gt;1,1,1,  2,2,2,  3,3,3,&lt;br /&gt;1,1,1,  2,2,2,  3,3,3,&lt;br /&gt;1,1,1,  2,2,2,  3,3,3,&lt;br /&gt;&lt;br /&gt;4,4,4,  5,5,5,  6,6,6,&lt;br /&gt;4,4,4,  5,5,5,  6,6,6,&lt;br /&gt;4,4,4,  5,5,5,  6,6,6,&lt;br /&gt;&lt;br /&gt;7,7,7,  8,8,8,  9,9,9,&lt;br /&gt;7,7,7,  8,8,8,  9,9,9,&lt;br /&gt;7,7,7,  8,8,8,  9,9,9&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;declare function fn:getRow ($board as xs:integer+, $index as xs:integer) as xs:integer+ {&lt;br /&gt; let $rowStart := floor(($index - 1) div 9) * 9&lt;br /&gt; return&lt;br /&gt;  $board[position() &gt; $rowStart and position() &amp;lt;= $rowStart + 9]&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;declare  function fn:getCol ($board as xs:integer+, $index as xs:integer)  as xs:integer+ {&lt;br /&gt; let $gap := ($index - 1) mod 9,&lt;br /&gt;    $colIndexes := for $x in $rowStarts return $x + $gap&lt;br /&gt;      return&lt;br /&gt;  $board[position() = $colIndexes]};&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;declare  function fn:getGroup ($board as xs:integer+, $index as xs:integer)  as xs:integer+ {&lt;br /&gt; let $group :=    $groups[$index]&lt;br /&gt; return&lt;br /&gt;   $board[for $x in position()  return $groups[$x]= $group]&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;declare  function fn:getAllowedValues ($board as xs:integer+, $index as xs:integer)  as xs:integer* {&lt;br /&gt; let $existingValues := (fn:getRow($board,&lt;br /&gt;$index), fn:getCol($board, $index), fn:getGroup($board, $index))&lt;br /&gt;return&lt;br /&gt; for $x in (1 to 9) return&lt;br /&gt;                               if (not($x = $existingValues))&lt;br /&gt;                                 then $x&lt;br /&gt;                           else ()&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;declare  function  fn:tryValues($board as xs:integer+,&lt;br /&gt;                               $emptyCells as xs:integer+,&lt;br /&gt;                               $possibleValues as xs:integer+) as xs:integer* {&lt;br /&gt; let $index as xs:integer := $emptyCells[1],&lt;br /&gt;    $newBoard as xs:integer+ := ($board[position() &amp;lt;$index],&lt;br /&gt;                            $possibleValues[1], $board[position() &gt; $index]),&lt;br /&gt;    $result as xs:integer* := fn:populateValues($newBoard, $emptyCells[position() != 1])&lt;br /&gt;   return&lt;br /&gt;    if (empty($result)) then&lt;br /&gt;     if (count($possibleValues) &gt; 1) then&lt;br /&gt;       fn:tryValues($board, $emptyCells, $possibleValues[position() != 1])&lt;br /&gt;     else&lt;br /&gt;       ()&lt;br /&gt;   else&lt;br /&gt;    $result&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;declare  function  fn:populateValues($board as xs:integer+,&lt;br /&gt;                                    $emptyCells as xs:integer*) as xs:integer*{&lt;br /&gt;&lt;br /&gt; if (not(empty($emptyCells)))&lt;br /&gt;       then&lt;br /&gt;  let $index as xs:integer :=$emptyCells[1],&lt;br /&gt;      $possibleValues as xs:integer* := distinct-values(fn:getAllowedValues($board, $index))&lt;br /&gt;    return&lt;br /&gt;      if (count($possibleValues) &gt; 1)&lt;br /&gt;  then&lt;br /&gt;      fn:tryValues($board, $emptyCells, $possibleValues)&lt;br /&gt;  else if (count($possibleValues) = 1)&lt;br /&gt;      then&lt;br /&gt; let $newBoard as xs:integer+ :=($board[position() &amp;lt;&lt;br /&gt;$index], $possibleValues[1], $board[position() &gt; $index])&lt;br /&gt;  return&lt;br /&gt;   fn:populateValues($newBoard, $emptyCells[position() != 1])&lt;br /&gt;    else ()&lt;br /&gt;else&lt;br /&gt; $board&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;declare  function  fn:solveSudoku ($startBoard as xs:integer+) as  xs:integer+{&lt;br /&gt; let $emptyCells as xs:integer* :=for $x in (1 to 81) return if&lt;br /&gt;($startBoard[$x] = 0) then $x else (),&lt;br /&gt;     $endBoard as xs:integer* :=fn:populateValues($startBoard,$emptyCells)&lt;br /&gt;    return&lt;br /&gt;  if (empty($endBoard))&lt;br /&gt;  then&lt;br /&gt;  $startBoard&lt;br /&gt;  else&lt;br /&gt;   $endBoard};&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;declare  function  fn:drawResult ($board as xs:integer+) as  element(){&lt;br /&gt;       &amp;lt;html&gt;&lt;br /&gt;               &amp;lt;head&gt;&lt;br /&gt;                       &amp;lt;title&gt;Sudoku - XSLT&amp;lt;/title&gt;&lt;br /&gt;                       &amp;lt;style&gt;&lt;br /&gt;                         table {{ border-collapse: collapse;&lt;br /&gt;                                 border: 1px solid black; }}&lt;br /&gt;                         td {{ padding: 10px; }}&lt;br /&gt;                         .norm {{ border-left: 1px solid #CCC;&lt;br /&gt;                                 border-top: 1px solid #CCC;}}&lt;br /&gt;                         .csep {{ border-left: 1px solid black; }}&lt;br /&gt;                         .rsep {{ border-top: 1px solid black; }}&lt;br /&gt;                       &amp;lt;/style&gt;&lt;br /&gt;               &amp;lt;/head&gt;&lt;br /&gt;               &amp;lt;body&gt;&lt;br /&gt;         {fn:drawBoard($board)}&lt;br /&gt;               &amp;lt;/body&gt;&lt;br /&gt;       &amp;lt;/html&gt;&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;declare  function  fn:drawBoard ($board as xs:integer+) as  element(){&lt;br /&gt;       &amp;lt;table&gt;&lt;br /&gt;           { for $i in 1 to 9&lt;br /&gt;             return&lt;br /&gt;                               &amp;lt;tr&gt;&lt;br /&gt; {for $j at $p in 1 to 9&lt;br /&gt; let $pos := (($i - 1) * 9) + $j&lt;br /&gt;  return&lt;br /&gt; &amp;lt;td class="{if ($p mod 3 = 1) then 'csep' else ('norm')}&lt;br /&gt;{if ($i mod 3 = 1) then 'rsep' else ('norm')}"&gt;&lt;br /&gt;       {$board[$pos]}&lt;br /&gt;  &amp;lt;/td&gt;&lt;br /&gt;}&lt;br /&gt;                               &amp;lt;/tr&gt;&lt;br /&gt;         }&lt;br /&gt;       &amp;lt;/table&gt;&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;fn:drawResult(fn:solveSudoku($board))&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-114237147411194368?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/114237147411194368/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=114237147411194368&amp;isPopup=true' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114237147411194368'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114237147411194368'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2006/03/david-carlisles-xquery-conversion-of.html' title='David Carlisle&apos;s XQuery conversion of the Sudoku stylesheet'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-114200989779937717</id><published>2006-03-10T15:39:00.000Z</published><updated>2006-03-31T09:29:14.126Z</updated><title type='text'>A Sudoku solution in XSLT 2.0</title><content type='html'>&lt;span style="font-size:85%;"&gt;&lt;span style="font-family: courier new;"&gt;&lt;span style="font-size:100%;"&gt;&lt;span style="font-family: arial;"&gt;&lt;br /&gt;Here's a stylesheet I've written to solve a Sudoku puzzle.  It came about when some friends and I were talking about Sudoku over lunch, and one them pointed at me and said "aah I bet XSLT can't do that!" .... which laid down the challenge.&lt;br /&gt;&lt;br /&gt;After studying Michael Kay's Knights Tour stylesheet (in the samples directory of the Saxon download) for some time I was able to get going on this.  This uses the same recursive technique, trying a particular "route" and then backtracking to the point where a different route can be taken when the last one failed.  It uses an intelligent brute force technique (if those aren't mutually exclusive...) - it finds all of the available values for a cell, and then tries each one in turn until the puzzle is solved.&lt;br /&gt;&lt;br /&gt;Once the stylesheet was written, it was quite interesting to try out different approaches to get the best performance.  For example, the same friend (who reckons he's some kind of Sudoku expert) says that once you have the center squares its just a matter of time before you solve the rest of the board.  So instead of just processing the "empty" squares (the zero values here) in the order they appear from 1 to 81, I processed the center squares first followed by the rest.  This led to quite an improvement in performance...&lt;br /&gt;&lt;br /&gt;It didn't make much sense to sense to then process the empty cells in the top-left group as they aren't affect by the center group, so I modified it to process the center group, then the top-middle group, followed by the rest.  This has given the best performance so far...&lt;br /&gt;&lt;br /&gt;Anyway, here it is.  There are some test boards at the bottom of the stylesheet so just change the parameter value in the main template to use one of them.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family: courier new;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;xsl:stylesheet version="2.0"&lt;br /&gt;xmlns:xs="http://www.w3.org/2001/XMLSchema"&lt;br /&gt;xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&lt;br /&gt;xmlns:fn="sudoku"&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:param name="board" select="(&lt;br /&gt;1,0,0,  3,0,0,  6,0,0,&lt;br /&gt;0,2,0,  5,0,0,  0,0,4,&lt;br /&gt;0,0,9,  0,0,0,  5,2,0,&lt;br /&gt;&lt;br /&gt;0,0,0,  9,6,3,  0,0,0,&lt;br /&gt;7,1,6,  0,0,0,  0,0,0,&lt;br /&gt;0,0,0,  0,8,0,  0,4,0,&lt;br /&gt;&lt;br /&gt;9,0,0,  0,0,5,  3,0,7,&lt;br /&gt;8,0,0,  4,0,6,  0,0,0,&lt;br /&gt;3,5,0,  0,0,0,  0,0,1&lt;br /&gt;)" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:param name="verbose" select="false()" as="xs:boolean"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:variable name="rowStarts" select="(1, 10, 19, 28, 37, 46, 55, 64, 73)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="topLeftGroup"     select="(1, 2, 3,     10, 11, 12,  19, 20, 21)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="topGroup"         select="(4, 5, 6,     13, 14, 15,  22, 23, 24)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="topRightGroup"    select="(7, 8, 9,     16, 17, 18,  25, 26, 27)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="midLeftGroup"     select="(28, 29, 30,  37, 38, 39,  46, 47, 48)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="center"           select="(31, 32, 33,  40, 41, 42,  49, 50, 51)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="midRightGroup"    select="(34, 35, 36,  43, 44, 45,  52, 53, 54)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="bottomLeftGroup"  select="(55, 56, 57,  64, 65, 66,  73, 74, 75)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="bottomGroup"      select="(58, 59, 60,  67, 68, 69,  76, 77, 78)" as="xs:integer+"/&gt;&lt;br /&gt;&amp;lt;xsl:variable name="bottomRightGroup" select="(61, 62, 63,  70, 71, 72,  79, 80, 81)" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:getRow" as="xs:integer+"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="index" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:variable name="rowStart" select="floor(($index - 1) div 9) * 9"/&gt;&lt;br /&gt; &amp;lt;xsl:sequence select="$board[position() &gt; $rowStart and position() &amp;lt;= $rowStart + 9]"/&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:getCol" as="xs:integer+"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="index" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:variable name="gap" select="($index - 1) mod 9"/&gt;&lt;br /&gt; &amp;lt;xsl:variable name="colIndexes" select="for $x in $rowStarts return $x + $gap" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:sequence select="$board[some $x in position() satisfies $x = $colIndexes]"/&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:getGroup" as="xs:integer+"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="index" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:choose&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $topLeftGroup"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$board[some $x in position() satisfies $x = $topLeftGroup]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $topGroup"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$board[some $x in position() satisfies $x = $topGroup]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $topRightGroup"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$board[some $x in position() satisfies $x = $topRightGroup]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $midLeftGroup"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$board[some $x in position() satisfies $x = $midLeftGroup]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $center"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$board[some $x in position() satisfies $x = $center]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $midRightGroup"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$board[some $x in position() satisfies $x = $midRightGroup]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $bottomLeftGroup"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$board[some $x in position() satisfies $x = $bottomLeftGroup]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $bottomGroup"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$board[some $x in position() satisfies $x = $bottomGroup]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:when test="$index = $bottomRightGroup"&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$board[some $x in position() satisfies $x = $bottomRightGroup]"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt; &amp;lt;/xsl:choose&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:getAllowedValues" as="xs:integer*"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="index" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:variable name="existingValues" select="(fn:getRow($board, $index), fn:getCol($board, $index), fn:getGroup($board, $index))" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:sequence select="for $x in (1 to 9) return&lt;br /&gt;                               if (not($x = $existingValues))&lt;br /&gt;                                 then $x&lt;br /&gt;                               else ()"/&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:tryValues" as="xs:integer*"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="emptyCells" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="possibleValues" as="xs:integer+"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:variable name="index" select="$emptyCells[1]" as="xs:integer"/&gt;&lt;br /&gt; &amp;lt;xsl:variable name="newBoard" select="($board[position() &amp;lt; $index], $possibleValues[1], $board[position() &gt; $index])" as="xs:integer+"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:if test="$verbose"&gt;&lt;br /&gt;  &amp;lt;xsl:message&gt;Trying &lt;br /&gt;   &amp;lt;xsl:value-of select="$possibleValues[1]"/&gt; out of a possible &lt;br /&gt;   &amp;lt;xsl:value-of select="$possibleValues"/&gt; at index &lt;br /&gt;   &amp;lt;xsl:value-of select="$index"/&gt;&lt;br /&gt;  &amp;lt;/xsl:message&gt;&lt;br /&gt; &amp;lt;/xsl:if&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:variable name="result" select="fn:populateValues($newBoard, $emptyCells[position() != 1])" as="xs:integer*"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:sequence select="if (empty($result)) then&lt;br /&gt;                               if (count($possibleValues) &gt; 1) then&lt;br /&gt;                                 fn:tryValues($board, $emptyCells, $possibleValues[position() != 1])&lt;br /&gt;                               else&lt;br /&gt;                                 ()&lt;br /&gt;                             else&lt;br /&gt;                               $result"/&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:populateValues" as="xs:integer*"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;xsl:param name="emptyCells" as="xs:integer*"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:choose&gt;&lt;br /&gt;  &amp;lt;xsl:when test="not(empty($emptyCells))"&gt;&lt;br /&gt;   &amp;lt;xsl:variable name="index" select="$emptyCells[1]" as="xs:integer"/&gt;&lt;br /&gt;   &amp;lt;xsl:variable name="possibleValues" select="distinct-values(fn:getAllowedValues($board, $index))" as="xs:integer*"/&gt;&lt;br /&gt;   &amp;lt;xsl:choose&gt;&lt;br /&gt;    &amp;lt;xsl:when test="count($possibleValues) &gt; 1"&gt;&lt;br /&gt;     &amp;lt;xsl:sequence select="fn:tryValues($board, $emptyCells, $possibleValues)"/&gt;&lt;br /&gt;    &amp;lt;/xsl:when&gt;&lt;br /&gt;    &amp;lt;xsl:when test="count($possibleValues) = 1"&gt;&lt;br /&gt;     &amp;lt;xsl:variable name="newBoard" select="($board[position() &amp;lt; $index], $possibleValues[1], $board[position() &gt; $index])" as="xs:integer+"/&gt;&lt;br /&gt;     &lt;br /&gt;     &amp;lt;xsl:if test="$verbose"&gt;&lt;br /&gt;      &amp;lt;xsl:message&gt;Only one value&lt;br /&gt;       &amp;lt;xsl:value-of select="$possibleValues[1]"/&gt; for index&lt;br /&gt;       &amp;lt;xsl:value-of select="$index"/&gt;&lt;br /&gt;      &amp;lt;/xsl:message&gt;&lt;br /&gt;     &amp;lt;/xsl:if&gt;&lt;br /&gt;     &lt;br /&gt;     &amp;lt;xsl:sequence select="fn:populateValues($newBoard, $emptyCells[position() != 1])"/&gt;&lt;br /&gt;    &amp;lt;/xsl:when&gt;&lt;br /&gt;    &amp;lt;xsl:otherwise&gt;&lt;br /&gt;     &amp;lt;xsl:if test="$verbose"&gt;&lt;br /&gt;      &amp;lt;xsl:message&gt;! Cannot go any further !&amp;lt;/xsl:message&gt;&lt;br /&gt;     &amp;lt;/xsl:if&gt;&lt;br /&gt;     &amp;lt;xsl:sequence select="()"/&gt;&lt;br /&gt;    &amp;lt;/xsl:otherwise&gt;&lt;br /&gt;   &amp;lt;/xsl:choose&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:otherwise&gt;&lt;br /&gt;   &amp;lt;xsl:message&gt;Done!&amp;lt;/xsl:message&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$board"/&gt;&lt;br /&gt;  &amp;lt;/xsl:otherwise&gt;&lt;br /&gt; &amp;lt;/xsl:choose&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="fn:solveSudoku" as="xs:integer+"&gt;&lt;br /&gt; &amp;lt;xsl:param name="startBoard" as="xs:integer+"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;!-- First process the center cells, then the top, then the rest of the board.&lt;br /&gt;      This should give better performance than starting top-left and working from there. --&gt;&lt;br /&gt; &amp;lt;xsl:variable name="theRest" select="for $x in 1 to 81 return $x[not($x = $center)][not($x = $topGroup)]" as="xs:integer+"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:variable name="emptyCells" select="for $x in ($center, $topGroup, $theRest) return if ($startBoard[$x] = 0) then $x else ()" as="xs:integer*"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:variable name="endBoard" select="fn:populateValues($startBoard, $emptyCells)" as="xs:integer*"/&gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;xsl:choose&gt;&lt;br /&gt;  &amp;lt;xsl:when test="empty($endBoard)"&gt;&lt;br /&gt;   &amp;lt;xsl:message&gt;! Invalid board - The starting board is not correct&amp;lt;/xsl:message&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$startBoard"/&gt;&lt;br /&gt;  &amp;lt;/xsl:when&gt;&lt;br /&gt;  &amp;lt;xsl:otherwise&gt;&lt;br /&gt;   &amp;lt;xsl:sequence select="$endBoard"/&gt;&lt;br /&gt;  &amp;lt;/xsl:otherwise&gt;&lt;br /&gt; &amp;lt;/xsl:choose&gt;&lt;br /&gt;&amp;lt;/xsl:function&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="/" name="main"&gt;&lt;br /&gt; &amp;lt;xsl:call-template name="drawResult"&gt;&lt;br /&gt;  &amp;lt;xsl:with-param name="board" select="fn:solveSudoku($board)"/&gt;&lt;br /&gt; &amp;lt;/xsl:call-template&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template name="drawResult"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;html&gt;&lt;br /&gt;  &amp;lt;head&gt;&lt;br /&gt;   &amp;lt;title&gt;Sudoku - XSLT&amp;lt;/title&gt;&lt;br /&gt;   &amp;lt;style&gt;&lt;br /&gt;    table { border-collapse: collapse;&lt;br /&gt;    border: 1px solid black; }&lt;br /&gt;    td { padding: 10px; }&lt;br /&gt;    .norm { border-left: 1px solid #CCC;&lt;br /&gt;      border-top: 1px solid #CCC;}&lt;br /&gt;    .csep { border-left: 1px solid black; }&lt;br /&gt;    .rsep { border-top: 1px solid black; }&lt;br /&gt;   &amp;lt;/style&gt;&lt;br /&gt;  &amp;lt;/head&gt;&lt;br /&gt;  &amp;lt;body&gt;&lt;br /&gt;   &amp;lt;xsl:call-template name="drawBoard"&gt;&lt;br /&gt;    &amp;lt;xsl:with-param name="board" select="$board"/&gt;&lt;br /&gt;   &amp;lt;/xsl:call-template&gt;&lt;br /&gt;  &amp;lt;/body&gt;&lt;br /&gt; &amp;lt;/html&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template name="drawBoard"&gt;&lt;br /&gt; &amp;lt;xsl:param name="board" as="xs:integer+"/&gt;&lt;br /&gt; &amp;lt;table&gt;&lt;br /&gt;  &amp;lt;xsl:for-each select="1 to 9"&gt;&lt;br /&gt;   &amp;lt;xsl:variable name="i" select="."/&gt;&lt;br /&gt;   &amp;lt;tr&gt;&lt;br /&gt;    &amp;lt;xsl:for-each select="1 to 9"&gt;&lt;br /&gt;     &amp;lt;xsl:variable name="pos" select="(($i - 1) * 9) + ."/&gt;&lt;br /&gt;     &amp;lt;td class="{if (position() mod 3 = 1) then 'csep' else ('norm')} {if ($i mod 3 = 1) then 'rsep' else ('norm')}"&gt;&lt;br /&gt;      &amp;lt;xsl:value-of select="$board[$pos]"/&gt;&lt;br /&gt;     &amp;lt;/td&gt;&lt;br /&gt;    &amp;lt;/xsl:for-each&gt;&lt;br /&gt;   &amp;lt;/tr&gt;&lt;br /&gt;  &amp;lt;/xsl:for-each&gt;&lt;br /&gt; &amp;lt;/table&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Easy board, 32 existing numbers --&gt;&lt;br /&gt;&amp;lt;xsl:variable name="testBoard1" select="(&lt;br /&gt;0,2,0,  0,0,0,  0,3,6,&lt;br /&gt;0,0,7,  4,0,0,  0,9,0,&lt;br /&gt;0,0,5,  6,0,0,  0,4,8,&lt;br /&gt;&lt;br /&gt;0,0,0,  9,3,0,  0,1,2,&lt;br /&gt;2,9,0,  0,0,0,  0,7,5,&lt;br /&gt;1,5,0,  0,8,2,  0,0,0,&lt;br /&gt;&lt;br /&gt;6,7,0,  0,0,9,  1,0,0,&lt;br /&gt;0,3,0,  0,0,7,  6,0,0,&lt;br /&gt;4,8,0,  0,0,0,  0,2,0&lt;br /&gt;)" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Hard board, 24 existing numbers --&gt;&lt;br /&gt;&amp;lt;xsl:variable name="testBoard2" select="(&lt;br /&gt;1,0,0,  5,6,0,  0,0,0,&lt;br /&gt;9,0,0,  0,0,0,  2,0,8,&lt;br /&gt;0,0,0,  0,0,0,  7,0,0,&lt;br /&gt;&lt;br /&gt;0,8,0,  9,0,7,  0,0,2,&lt;br /&gt;2,0,0,  0,0,0,  0,0,1,&lt;br /&gt;6,0,0,  3,0,2,  0,4,0,&lt;br /&gt;&lt;br /&gt;0,0,5,  0,0,0,  0,0,0,&lt;br /&gt;4,0,3,  0,0,0,  0,0,9,&lt;br /&gt;0,0,0,  0,4,1,  0,0,6&lt;br /&gt;)" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Extremely Hard board --&gt;&lt;br /&gt;&amp;lt;xsl:variable name="testBoard3" select="(&lt;br /&gt;4,0,6,  7,8,0,  2,0,0,&lt;br /&gt;0,5,7,  0,0,0,  0,0,0,&lt;br /&gt;8,0,0,  0,0,0,  4,0,0,&lt;br /&gt;&lt;br /&gt;0,0,0,  0,5,0,  0,9,0,&lt;br /&gt;9,1,2,  0,0,0,  0,0,0,&lt;br /&gt;0,4,0,  0,1,0,  0,0,3,&lt;br /&gt;&lt;br /&gt;0,0,4,  3,0,0,  0,2,0,&lt;br /&gt;7,0,3,  0,0,6,  0,0,0,&lt;br /&gt;0,0,0,  5,0,7,  6,0,8&lt;br /&gt;)" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!-- Failing board, an erroneous 9 has been added at index 1 --&gt;&lt;br /&gt;&amp;lt;xsl:variable name="testBoard_Fail" select="(&lt;br /&gt;9,2,0,  0,0,0,  0,3,6,&lt;br /&gt;0,0,7,  4,0,0,  0,9,0,&lt;br /&gt;0,0,5,  6,0,0,  0,4,8,&lt;br /&gt;&lt;br /&gt;0,0,0,  9,3,0,  0,1,2,&lt;br /&gt;2,9,0,  0,0,0,  0,7,5,&lt;br /&gt;1,5,0,  0,8,2,  0,0,0,&lt;br /&gt;&lt;br /&gt;6,7,0,  0,0,9,  1,0,0,&lt;br /&gt;0,3,0,  0,0,7,  6,0,0,&lt;br /&gt;4,8,0,  0,0,0,  0,2,0&lt;br /&gt;)" as="xs:integer+"/&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/xsl:stylesheet&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-114200989779937717?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/114200989779937717/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=114200989779937717&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114200989779937717'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114200989779937717'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2006/03/sudoku-solution-in-xslt-20.html' title='A Sudoku solution in XSLT 2.0'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-114200488278782315</id><published>2006-03-10T15:25:00.000Z</published><updated>2006-03-31T09:28:17.886Z</updated><title type='text'>Kernow 1.2</title><content type='html'>I've released Kernow 1.2 which, after fixing a broken zip, is available from &lt;a href="http://www.blogger.com/%20http://sourceforge.net/projects/easytransformer/"&gt;http://sourceforge.net/projects/easytransformer/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;(It used to be called EasyTransformer, but since it nows supports XQuery and I really didn't like the name Easy.... EZ... eee zee...  etc a name change was long overdue).&lt;br /&gt;&lt;br /&gt;Kernow is an open source graphical front end for Saxon, the XSLT and XQuery processor.  It's written in Java, with the Swing UI created using Matisse - the new UI editor in Netbeans 5.  For me eclipse still wins on the IDE front (as it's just better for everyday usage), but Matisse is really special so it's worth putting up with Netbeans just for that.&lt;br /&gt;&lt;br /&gt;Kernow basically gives you file choosers for the files involved in the transform, parameter and named template discovery, a caching entity resovler and improved directory transforms through compiling the stylesheet.   It also stores all of the files involved and makes them selectable through combo boxes to reduce the amout of setup time when you get going.  Hopefully it's useful to someone...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-114200488278782315?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/114200488278782315/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=114200488278782315&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114200488278782315'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114200488278782315'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2006/03/kernow-12.html' title='Kernow 1.2'/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23815417.post-114200225923613292</id><published>2006-03-10T14:48:00.000Z</published><updated>2006-03-10T14:50:59.246Z</updated><title type='text'></title><content type='html'>The first post.  I've set this blog up as a place to link through to some of the things I'm doing...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23815417-114200225923613292?l=ajwelch.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ajwelch.blogspot.com/feeds/114200225923613292/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23815417&amp;postID=114200225923613292&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114200225923613292'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23815417/posts/default/114200225923613292'/><link rel='alternate' type='text/html' href='http://ajwelch.blogspot.com/2006/03/first-post.html' title=''/><author><name>Andrew Welch</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
