TurboPower LockBox 3.5.0 Released!

I have just now released TurboPower LockBox 3.5.0. The code repository has been migrated/forked to Google Code.

With 3.4.0 as the baseline, the 3.5.0 version delivers:

  1. Migrated code repository from SourceForge to GoogleCode
  2. Renamed unit suffix from uTPlb_ to TPLB3.
  3. Replaced most references to AnsiString, to Utf8String
  4. Added support for XE6, Win32 and Win62
  5. Removed support for all other compilers (other than XE6 and D2010). The return of support for other compilers will be coming soon.
  6. Removed the automated installer.

Get the source here ….

https://tplockbox.googlecode.com/svn/trunk

Posted in Delphi | Comments Off on TurboPower LockBox 3.5.0 Released!

Introducing Apache 2.4 modules for Delphi 2010

I now have the pleasure of introducing to you the apache-24-with-delphi project (https://code.google.com/p/apache-24-with-delphi).

Following a request from a commenter off my previous post, I have posted a solution to problem of how to develop Apache 2.4 modules using Delphi 2010, to a new Google Code Project Subversion repository.

Features include:

  • The ability to have multiple handlers in one module. Each handler, of course, can have its own separate configuration in the Apache configuration file. You cannot do this with Embarcadero’s solution.
  • An easy way to define and leverage custom Apache directives. Apache directives are commands and data that are configured in the Apache configuration file.
Posted in Delphi | Comments Off on Introducing Apache 2.4 modules for Delphi 2010

Apache 2.4 and Delphi

I have translated the Apache 2.4 headers into Delphi, and created a demo web app in Delphi that works with Apache 2.4.7 – …. well almost. There remains one persistent stubborn bug in the demo. Possibly I have the calling conventions wrong.

Any way, if any Delphi developer is interested in writing for modern Apache servers and would like to collaborate with me, post a comment here, or email me. I need help cracking this bug.

Basically, Apache correctly loads the module, and calls the module’s RegisterHooks subroutine. But shortly after returning from RegisterHooks(), the server crashes.

Update
It’s done. I now have a fully functionally demo web-site, written in Delphi 2010 as an Apache module, that plugs into Apache 2.4.7 . Now, I wonder if Embarcadero would be interested in taking ownership of my solution? ….

Posted in Delphi | Comments Off on Apache 2.4 and Delphi

Introducing the SBD Dependency Injection Framework

Happy
From today the Delphi community can leverage the power of the SBD Dependency Injection Framework. Oh happy developer!, you can find the source code at …

… and download with a Subversion client.

The SBD Dependency Injection Framework is … well … a Dependency Injection Framework for Delphi. It consists of two units accompanied by a demo program and fluff. The framework supports all versions of Delphi from 2010 onwards and all platforms and is available for use via the popular aqnd permissive MPL 2.0 license.

Features

For the sake of brevity, I will list only the main features of the framework.

Service oriented

The framework is Service oriented. In this context “Service” means a service accessed via an interface pointer with a defined API. Central to the framework is the Service Provider. The service provider is a service container for it’s clients. The service provider is also a service.

Usage is Attribute oriented

Yes, that’s right – the lazy developer can use attributes to specify automatic injection points. In other Injection Frameworks, the developer would have to write code support constructor injection. In SBD Injection Framework, auto-magical code-less injection of services occurs before the constructor is called, and is driven by the [Injection] attribute.

Example

First let’s start with a couple of service definitions …

type
  IBankAccount = interface
  ['{some guid}']
    function Credit( Amount: currency; const Payee: string): boolean;
    // Attempt to pay someone (Payee) something (Amount) from my bank account.
    // Return True if payment was made.
    end;

  IProfitAndLossAccount = interface
  ['{some other guid}']
    function DistributeDividend( GrossDividendToPayOut: double): boolean;
    // Distribute the dividend to all the share-holders.
    end;

Our task is to distribute a company’s profit to it’s shareholders, by implementing an instance of the IProfitAndLossAccount. We will assume that some-one else has already written a suitable IBankAccount service. Here is how we do it ….

type
  TProfitAndLossAccount = class( TInterfacedObject, IProfitAndLossAccount)
  private
    [Injection] FBankAccount: IBankAccount;
    function DistributeDividend( GrossDividendToPayOut: double): boolean;
  public
    [configuration] constructor ServiceModeCreate;
  end;

constructor TProfitAndLossAccount.ServiceModeCreate;
begin
end;  // Empty constructor! 

function TProfitAndLossAccount.DistributeDividend( GrossDividendToPayOut: double): boolean;
begin
  // The two shareholders are Jack and Jill
  result := FBankAccount.Credit( GrossDividendToPayOut / 2, 'Jack') and
            FBankAccount.Credit( GrossDividendToPayOut / 2, 'Jill') and
end;

Notice there is no constructor logic for the dependent service FBankAccount. A suitable service instance is inserted by the framework. If we have a service provider (ServiceProvider: IServiceProvider),

var
  PandL: IProfitAndLossAccount;
begin
if ServiceProvider.Gn.Acquire<IProfitAndLossAccount>( PandL) then
  PanL.DistributeDividend( 10000.0);
end;

Service implementation discrimination

If we have two different service definitions that just happen to use the same interface pointer type, but serve different purposes (not just different implementations serving the same purpose), the we can discriminate between the two with a “Configuration string”. This is what it looks like …

type
  TProfitAndLossAccount = class( TInterfacedObject, IProfitAndLossAccount)
  private
    [Injection] FBankAccount: IBankAccount;
    function DistributeDividend( GrossDividendToPayOut: double): boolean;
  public
    [configuration('accounting')] constructor ServiceModeCreate;
  end;

Now this …

if ServiceProvider.Gn.Acquire<IProfitAndLossAccount>( PandL, 'accounting') then
  PanL.DistributeDividend( 10000.0);

will use the above service implementation, but …

if ServiceProvider.Gn.Acquire<IProfitAndLossAccount>( PandL, 'something-else') then
  PanL.DistributeDividend( 10000.0);
... will not.

Competing service array versus co-operative service array

You can register an array (“Service file”) of alternate implementations of the one service definition (Interface pointer type, guid + config string). In other frameworks you can only register one implementation per service definition, if you can do multiple, then the service file can only be used competitively (that is to say the client asks for a service instance, and the provider gives one). SBD Dependency Injection framework can offer a file of service implementations either competitively or co-operatively.

Imagine a task of compressing a message. The service definition is “compress this data”. There is lots of ways to compress data. You might have many algorithms at your disposal in the form of registered services. You pick one, acquire an instance from the service provider and solve your problem. That is an example of a competitive service array.

Competing versus Cooperating
But what about the reverse problem of de-compression, where a standard has not been followed. You need to find the correct compressor to correctly decompress it. As the selection of compressor was not recorded, you really need to get all the decompressor services in stock to examine the compressed data, just to see which one is the one to use. Now you are using an array of service implementations in a co-operative fashion.

Some hard problems can be simplified by leveraging a co-operative array of services. SBD Injection framework, makes acquisition and usage of co-operative services easy and almost code-free. When you acquire co-operative services, instead of one selected service being injected, you get a collection of them. At service registration time, you can define how the collection is made and how it is added to – or just use IInterfaceList.

Documentation

The documentation will be in form of a demo program, some wiki pages of text (like this blog post), and some Help Insight ///<summary> notes. This is a work-in-progress. At the moment, I have done about 30% of the demo.

No singletons

The framework does not define singletons, nor require you to use them. Nice!

But how does it compare to the DI in Delphi-Spring?

I don’t know. Perhaps you can tell me?

Posted in Delphi | Tagged | Comments Off on Introducing the SBD Dependency Injection Framework

A Generalised and Comprehensive Solution to CSV to XML and XML to CSV Transformations

Originally posted FRIDAY, FEBRUARY 17. 2012
This is a repost of an entry from my previous, but now defunct blog. The original URL was http://pascaliburnus.seanbdurkin.id.au/index.php?/archives/17-A-Generalised-and-Comprehensive-Solution-to-CSV-to-XML-and-XML-to-CSV-Transformations.html, and the entry can still be seen in its original rendering via the Way Back Machine


Abstract:This entry provides a generalised and comprehensive solution to the conversion of Comma Separated Values (CSV) file to XML. CSV is a very common file format for tabular data. It is clear from the frequency of related questions on StackOverflow, that there is a strong public demand for a generalised solution to CSV to XML and XML to CSV conversion. I believe that this entry marks the first published solution to the said transformations, which defines the model of CSV that it applies to, is generalised to work with all conformant CSV, in the XML domain defines the schema of the XML representation of the CSV file, and is error free. A feature of this solution is that the XML representation is constrained by an XML Schema. This is as opposed to other possible transformations which might derive the XML node names from the CSV header values, which in a generalised sense are unknown.

A quick example

csv data

Colour," Make", model
Lime ,"GMH","23 "
Black,Ford,14

xml equivalent

<?xml version="1.0" encoding="UTF-8"?>
<xcsv:comma-separated-single-line-values
  xmlns:xcsv="http://www.seanbdurkin.id.au/xslt/xcsv.xsd"
  xcsv-version="1.0">
   <xcsv:notice xml:lang="en">The xcsv format was developed by Sean B. Durkin&#133;www.seanbdurkin.id.au</xcsv:notice>
   <xcsv:row>
      <xcsv:value>Colour</xcsv:value>
      <xcsv:value> Make</xcsv:value>
      <xcsv:value> model</xcsv:value>
   </xcsv:row>
   <xcsv:row>
      <xcsv:value>Lime </xcsv:value>
      <xcsv:value>GMH</xcsv:value>
      <xcsv:value>23 </xcsv:value>
   </xcsv:row>
   <xcsv:row>
      <xcsv:value>Black</xcsv:value>
      <xcsv:value>Ford</xcsv:value>
      <xcsv:value>14</xcsv:value>
   </xcsv:row>
</xcsv:comma-separated-single-line-values>

Statement of copying permission

Excluding any code written by Andrew Welch, readers are permitted to copy the style-sheets and schema listed in this post, without let or hinderance.

What is CSV?

For the purposes of defining this pair of transformations, I will define the CSV format. My definition is based on RFC 4180 October 2005 “Common Format and MIME Type for CSV Files” but with some minor changes to suite the needs of XSLT based transformations.

  • The CSV format is a text stream encoded in either UTF-8 or UTF-16. If encoded in UTF-16, it must be prefixed by a BOM. A BOM is optional for UTF-8. No other encoding is valid. In any case, the csv file must be correctly readable by the XSLT 2.0 function unparsed-text()
  • A CSV stream is a contiguous sequence of text lines, also known as rows.
  • Lines are separated by line terminators. The last line can be terminated by either a line terminator or the end of the stream. If the last sequence of characters is a line terminator, then this line terminator does not signify the separtion of the previous line and a following empty line, but rather it follows and terminates the last line. Refer to sections 2.1 and 2.2 of RFC 4180 for examples.
  • Valid line terminators are:
  • The sequence U+00000D (CR) U+00000A (LF) or
  • the character U+00000A (LF).

Other line terminators which might exist in some file-systems or recognised as line terminators by XML 1.1, are not recognised. Counter-examples include:

  • The sequence U+00000A (LF) U+00000D (CR),
  • the character U+00000D (CR),
  • U+000085 (NEL) or
  • U+002028 (LS)
  • The first line is the header row. Subsequent rows are data rows. The values of the header row are respectively positioned headings for the values of the data rows. In contrast to RFC 4180, there is always exactly one header row.
  • Headings are not required to be unique, but they cannot be empty or consist of entirely of white-space. For this purpose, white-space is defined as per XML 1.1 .
  • The heading row has at least one heading (cell in the header row).
  • Each row is a list of cells. Cells are encoded string values, and are separated by the comma (,) character.
  • There is at least one cell per row. If the line is empty of characters, it signifies a row with one cell, whose value is empty.
  • Two output options are available for transforming to xml format: One encapsulated with node >xcsv:comma-separated-single-line-values/< and the other >xcsv:comma-separated-multi-line-values/<
  • In the case of >xcsv:comma-separated-single-line-values/< output, cells may not contain a line terminator (LF or the CR LF pattern), but may contain a CR not followed by an LF, or may contain NEL or LS. If content developers require multi-line cells, it is recommended that the LS character be used as a content-line separator within cells.
  • Cells are either encoded as quoted values or unquoted values.
  • An unquoted value represents the same value as its encoding string. An unquoted value may not contain either the comma (,) character or the quote (“) character.
  • A quoted value represents the same value as its encoding string, except that it is delimited on both sides by quote (“) marks (which are part of the encoding, not the value), and any value-bearing quote marks are escaped in the representation as a double occurance of the quote character (“).
  • White space, not as a line separator, is always significant and a part of the cell value.
  • There is no requirement for the count of cells in any data row to match the count of cells in the header. In other words, ragged csv is permissible.

Specification of the schema of the XML representation of CSV

The XML schema for the XML representation of CSV is as follows. It can be referenced in binary form or downloaded as indicated in the xs:schema targetNamespace.

(Click the word Schema below to expand, if you need to read.)

Schema
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xcsv="http://www.seanbdurkin.id.au/xslt/xcsv.xsd"
    elementFormDefault="qualified"
    targetNamespace="http://www.seanbdurkin.id.au/xslt/xcsv.xsd"
    version="1.0">

  <xs:import
      namespace="http://www.w3.org/XML/1998/namespace" 
      schemaLocation="xml.xsd"/>

  <xs:element name="comma-separated-single-line-values">
   <xs:annotation><xs:documentation xml:lang="en">
    This schema describes an XML representation of a subset of csv content.
    The format described by this schema, here-after referred to as "xcsv"
    is part of a generalised solution to the problem of converting
    general csv files into suitable XML, and the reverse transform.

    The restrictions on the csv content (in relation to
	xcsv:comma-separated-single-line-values) are:
      <strong> The csv file is encoded either in UTF-8 or UTF16. If UTF-16, a BOM
        is required.
      </strong> The cell values of the csv may not contain the CR or LF characters.
        Essentially, we are restricted to single-line values.

	A multi-line version follows.
	
    The xcsv format was developed by Sean B. Durkin&#x85;www.seanbdurkin.id.au
   </xs:documentation></xs:annotation>

   <xs:complexType>
    <xs:sequence>

     <xs:element ref="xcsv:notice" minOccurs="0" maxOccurs="1"/>
     <xs:element name="row" minOccurs="0" maxOccurs="unbounded">
      <xs:annotation><xs:documentation xml:lang="en">
       A row element represents a "row" or "line" in the csv file. Rows contain values.
       </xs:documentation>
       <xs:appinfo>
        <example>
         <csv-line>apple,"banana","red, white and blue","quote this("")"</csv-line>
         <row>
          <value>apple</value>
          <value>banana</value>
          <value>red, white and blue</value>
          <value>quote this(")</value>
         </row>
        </example>
       </xs:appinfo>
      </xs:annotation> 
        <xs:choice minOccurs="1" maxOccurs="unbounded">
       <xs:annotation><xs:documentation xml:lang="en">
         Empty rows are not possible in csv. We must have at least one value or one error.
       </xs:documentation></xs:annotation>
       <xs:element name="value">
        <xs:annotation><xs:documentation xml:lang="en">
         A value element represents a decoded (model) csv "value" or "cell".
         If the encoded value in the lexical csv was of a quoted form, then
         the element content here is the decoded or model form. In other words,
         the delimiting double-quote marks are striped out and the internal
         escaped double-quotes are de-escaped.
        </xs:documentation></xs:annotation>
        <xs:simpleType>
         <xs:restriction base="xs:string">
          <xs:pattern value="[^\n]*"/>
          <xs:whiteSpace value="preserve"/>
          <xs:annotation><xs:documentation xml:lang="en">
           Cell values must fit this pattern because of the single-line restriction
           that we placed on the csv values.
          </xs:documentation></xs:annotation>
         </xs:restriction>
        </xs:simpleType>
       </xs:element>
       <xs:group ref="xcsv:errorGroup">
        <xs:annotation><xs:documentation xml:lang="en">
          An error can be recorded here as a child of row, if there was an encoding
          error in the csv for that row.
        </xs:documentation></xs:annotation>
       </xs:group>
        </xs:choice>
     </xs:element>

     <xs:group ref="xcsv:errorGroup">
      <xs:annotation><xs:documentation xml:lang="en">
       An error can be recorded here as a child of the comma-separated-values element,
       if there was an i/o error in the transformational process. For example:
        CSV file not found.
      </xs:documentation></xs:annotation>
     </xs:group>

    </xs:sequence>

    <xs:attribute name="xcsv-version" type="xs:decimal"
        fixed="1.0" use="required"/>
   </xs:complexType>
  </xs:element>

  <xs:element name="comma-separated-multiline-values">
   <xs:annotation><xs:documentation xml:lang="en">
    Similar to xcsv:comma-separated-multi-line-values but allows multi-line values.
   </xs:documentation></xs:annotation>

   <xs:complexType>
    <xs:sequence>

     <xs:element ref="xcsv:notice" minOccurs="0" maxOccurs="1"/>
     <xs:element name="row" minOccurs="0" maxOccurs="unbounded">
       <xs:choice minOccurs="1" maxOccurs="unbounded">
       <xs:element name="value">
        <xs:simpleType>
         <xs:restriction base="xs:string">
          <xs:whiteSpace value="preserve"/>
         </xs:restriction>
        </xs:simpleType>
       </xs:element>
       <xs:group ref="xcsv:errorGroup">
       </xs:group>
        </xs:choice>
     </xs:element>

     <xs:group ref="xcsv:errorGroup">
     </xs:group>

    </xs:sequence>

    <xs:attribute name="xcsv-version" type="xs:decimal"
        fixed="1.0" use="required"/>
   </xs:complexType>
  </xs:element>

 <xs:element name="notice" type="xcsv:notice-en" />
      <xs:annotation><xs:documentation xml:lang="en">
       This is an optional element below comma-separated-single-line-values or
        comma-separated-multiline-values that looks like the example.
       </xs:documentation>
      <xs:appinfo>
       <example>
        <notice xml:lang="en">The xcsv format was developed by Sean B. Durkin&#x85;www.seanbdurkin.id.au</notice>
       </example>
      </xs:appinfo></xs:annotation>
      <xs:complexType name="notice-en">
        <xs:simpleContent>
          <xs:extension base="xcsv:notice-content-en">
           <xs:attribute ref="xml:lang" use="required" fixed="en" />
          </xs:extension>
        </xs:simpleContent>
      </xs:complexType>
      <xs:simpleType name="notice-content-en">
       <xs:restriction base="xs:string">
         <xs:enumeration value="The xcsv format was developed by Sean B. Durkin&#x85;www.seanbdurkin.id.au"/>
       </xs:restriction>
      </xs:simpleType>
 <xs:element />

   <xs:group name="errorGroup">
      <xs:annotation><xs:documentation xml:lang="en">
       This is an error node/message in one or more languages.
      </xs:documentation>
      <xs:appinfo>
       <example>
        <error error-code="2">
         <message xml:lang="en">Quoted value not terminated.</message>
         <message xml:lang="ru">процитированная строка не закрыта.</message>
         <error-data>"</error-data>
        </error>
       </example> 
       <example>
        <error error-code="3">
         <message xml:lang="en">Quoted value incorrectly terminated.</message>
         <message xml:lang="ru">процитированная строка закрыта неправильно.</message>
        </error>
       </example>
      </xs:appinfo> 
      </xs:annotation>
   <xs:element name="error">
    <xs:element name="message" minOccurs="1" maxOccurs="unbounded" type="xcsv:string-with-lang" />
      <xs:annotation><xs:documentation xml:lang="en">
       Although there can be multiple messages, there should only be at most one per language.
      </xs:documentation></xs:annotation>
    <xs:element name="error-data" minOccurs="0" maxOccurs="1" >
     <xs:simpleContent>
      <xs:restriction base="xs:string">
       <xs:whiteSpace value="preserve"/>
      </xs:restriction>
     </xs:simpleContent>
    </xs:element>
    <xs:attribute name="error-code" type="xs:positiveInteger" default="1" />
      <xs:annotation><xs:documentation xml:lang="en">
       Each different kind of error should be associated with a unique error code.
       A map for the error codes is outside the scope of this schema, except to say the following:
         <strong> one (1) means a general or uncategorised error. (Try to avoid this!)
      </xs:documentation></xs:annotation>
   </xs:element>
  </xs:group>

  <xs:complexType name="string-with-lang">
      <xs:annotation><xs:documentation xml:lang="en">
       This is an element with text content in some language as indicated
       by the xml:lang attribute.
      </xs:documentation></xs:annotation>
   <xs:simpleContent>
    <xs:extension base="xs:string">
     <xs:attribute ref="xml:lang" use="required" default="en" />
    </xs:extension>
   </xs:simpleContent>
  </xs:complexType>


</xs:schema> 

Rules for Transformation

When transforming from CSV to XML:

  • The transformation should only output UTF-8 encoded XML.
  • The transformation should check for validity of the CSV. If invalid, an element should be written directly underneath the element. Refer to the schema.
  • The schema does not require that the xcsv:comma-separated-single-line-values or xcsv:comma-separated-multiline-values elements be the document element, but if transforming one CSV file to one XML file, this should be made the case.

XSLT solutions

Here is a style-sheet to convert from csv (single line values case only) to xcsv format (output type is root node). A significant part of this style-sheet is derived from or copied from Andrew Welch’s solution. A binary copy is located at ‘http://www.seanbdurkin.id.au/xslt/csv-to-xml.xslt’. The style-sheet takes two parameters: url-of-csv and lang. url-of-csv is the filename or reference to the csv file to be converted. ‘lang’, a parameter defaulting to ‘en’ specifies the human language of error messages. If non-english language is chosen, the transform needs to be supported by a language file (param ‘url-of-messages’) with the localised error messages.

I plan to make a style sheet for the multi-line csv case in a future post, as well as the inverse transforms (xcsv to csv). If you are interested, either watch this blog, or take it as an exercise for the reader to design these style-sheets. If you design such style-sheets, let me know in the comments for this post.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE constants[
  <!ENTITY notice "The xcsv format was developed by Sean B. Durkin&#x85;www.seanbdurkin.id.au">
  ]>  
<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:fn="http://www.w3.org/2005/xpath-functions" 
  xmlns:local="http://www.seanbdurkin.id.au/xslt/csv-to-xml.xslt" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  xmlns:xcsv="http://www.seanbdurkin.id.au/xslt/xcsv.xsd"
  version="2.0"
  exclude-result-prefixes="xsl xs fn local">
<xsl:output indent="yes" encoding="UTF-8" method="xml"/>
<xsl:import-schema schema-location="http://www.seanbdurkin.id.au/xslt/xcsv.xsd"
                   use-when="system-property('xsl:is-schema-aware')='yes'" />
				   
<!-- Read Me ! -->
<!-- ************************************************************************</strong> -->
<!-- A significant part of this style-sheet is derived from or copied from     --> 
<!-- Andrew Welch's solution. Refer: http://andrewjwelch.com/code/xslt/csv/csv-to-xml_v2.html -->
<!-- Modifications have been made by me (Sean B. Durkin) in order to meet the design         -->
<!-- goals as described here:                                                                -->
<!-- http://pascaliburnus.seanbdurkin.id.au/index.php?/archives/17-A-Generalised-and-Compreh -->
<!--ensive-Solution-to-CSV-to-XML-and-XML-to-CSV-Transformations.html				         -->

<!-- Stylesheet parameters -->
<!-- ************************************************************************<strong> -->
<xsl:param name="url-of-csv" as="xs:string" />
<xsl:param name="lang" as="xs:string" select="'en'" />
<xsl:param name="url-of-messages" as="xs:string" />

<!-- Configurable constants -->
<!-- ************************************************************************</strong> -->
  <xsl:variable name="quote" as="xs:string">"</xsl:variable>
  <xsl:variable name="error-messages-i18n">
   <xcsv:error error-code="1">
    <xcsv:message xml:lang="en">Uncategorised error.</xcsv:message>
   </xcsv:error>
   <xcsv:error error-code="2">
    <xcsv:message xml:lang="en">Quoted value not terminated.</xcsv:message>
   </xcsv:error>
   <xcsv:error error-code="3">
    <xcsv:message xml:lang="en">Quoted value incorrectly terminated.</xcsv:message>
   </xcsv:error>
   <xcsv:error error-code="5">
    <xcsv:message xml:lang="en">Could not open CSV resource.</xcsv:message>
   </xcsv:error>
  </xsl:variable> 
  
<!-- Non-configurable constants -->
<!-- ************************************************************************<strong> -->
 <xsl:variable name="error-messages">
   <xsl:apply-templates select="$error-messages-i18n" mode="messages" />
  </xsl:variable>
    
<xsl:template match="@*|node()" mode="messages" >
 <xsl:copy>
  <xsl:apply-templates select="@*|node()" mode="messages" />
 </xsl:copy>
</xsl:template>

<xsl:template match="xcsv:message[
      not(@xml:lang=$lang) and
     (not(@xml:lang='en') or ../xcsv:message[@xml:lang=$lang])]" mode="messages" />
	 


<xsl:function name="local:unparsed-text-lines" as="xs:string+">
 <xsl:param name="href" as="xs:string" />
 <xsl:sequence use-when="function-available('unparsed-text-lines')" select="fn:unparsed-text-lines($href)" />
 <xsl:sequence use-when="not(function-available('unparsed-text-lines'))" select="tokenize(unparsed-text($href), '\r\n|\r|\n')[not(position()=last() and .='')]" />
</xsl:function>

<xsl:function name="local:error-node" as="node()">
 <xsl:param name="error-code" as="xs:integer" />
 <xsl:param name="data" as="xs:string" />
 <xcsv:error error-code="{$error-code}">
  <xcsv:message
    xml:lang="{$error-messages/xcsv:error[@error-code=$error-code]/xcsv:message/@xml:lang}">
    <xsl:value-of select="$error-messages/xcsv:error[@error-code=$error-code]/xcsv:message"/>
  </xcsv:message>
  <xcsv:error-data><xsl:value-of select="$data"/></xcsv:error-data>
 </xcsv:error>
</xsl:function>

<xsl:function name="local:csv-to-xml" as="node()+">
 <xsl:param name="href" as="xs:string" />
 <xcsv:comma-separated-single-line-values xcsv-version="1.0">
  <xcsv:notice xml:lang="en">&notice;</xcsv:notice>
  <xsl:choose>
   <xsl:when test="fn:unparsed-text-available($href)">
    <xsl:for-each select="local:unparsed-text-lines($href)">
	 <xcsv:row>
     <xsl:analyze-string select="fn:concat(., ',')" regex='(("[^"]*")+|[^,"]*),'>
      <xsl:matching-substring>
	   <xcsv:value>
	    <xsl:choose>
         <xsl:when test="fn:starts-with( fn:regex-group(1), $quote)">
          <xsl:value-of select='fn:replace(fn:regex-group(1), "^""|""$|("")""", "$1" )' />
	     </xsl:when>
	     <xsl:otherwise>
          <xsl:value-of select='regex-group(1)' />
	     </xsl:otherwise>
	    </xsl:choose>
	   </xcsv:value>
      </xsl:matching-substring>
      <xsl:non-matching-substring>
       <xsl:copy-of select="local:error-node(3,.)"/>
       <!-- Quoted value incorrectly terminated. -->
      </xsl:non-matching-substring>
     </xsl:analyze-string>
	 </xcsv:row>
    </xsl:for-each>
   </xsl:when>
   <xsl:otherwise>
    <xsl:copy-of select="local:error-node(5,$href)"/>
    <!-- Could not open CSV resource. -->
   </xsl:otherwise>
  </xsl:choose>
 </xcsv:comma-separated-single-line-values>
</xsl:function>

<xsl:template match="/" name="local:main">
 <xsl:copy-of select="local:csv-to-xml($url-of-csv)" />
</xsl:template>


</xsl:stylesheet>

Unit testing

Please refer to http://codereview.stackexchange.com/questions/10180/please-review-my-xml-schema-for-an-xml-representation-of-csv. The Use cases can be used for unit testing.

Case Study

Let’s say we have a csv file like this:

1st Name,2nd Name
Sean B.,Durkin
"Chris ""Nemora""",Durkin

… and we want to convert it to an xml format like this …

<?xml version="1.0" encoding="UTF-8"?>
<people>
   <person>
      <first-name>Sean B.</first-name>
      <last-name>Durkin</last-name>
   </person>
   <person>
      <first-name>Chris "Nemora"</first-name>
      <last-name>Durkin</last-name>
   </person>
</people>

Assumptions

We will assume:

  • The csv file is encoded in utf-8 without BOM.
  • The csv file uses CR.LF for its end of line marker.
  • The csv file may or may not have an CR.LF at the end of the last line (right after “Durkin”).
  • The aforementioned xcsv style-sheet has been set-up as a standard library, whose location is known by your XSLT engine (probably via your configured OASIS catalog for the XSLT engine).
  • The location of the csv file has been passed in to parameter ‘url-of-csv’

So the solution is …

<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:fn="http://www.w3.org/2005/xpath-functions" 
  xmlns:xcsvt="http://www.seanbdurkin.id.au/xslt/csv-to-xml.xslt" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  xmlns:xcsv="http://www.seanbdurkin.id.au/xslt/xcsv.xsd"
  version="2.0"
  exclude-result-prefixes="xsl xs fn xcsvt xcsv">
<xsl:import href="csv-to-xml.xslt" />
<xsl:output indent="yes" encoding="UTF-8" method="xml"/>
<xsl:import-schema schema-location="http://www.seanbdurkin.id.au/xslt/xcsv.xsd"
                   use-when="system-property('xsl:is-schema-aware')='yes'" />
<xsl:param name="url-of-csv" as="xs:string" />

<xsl:import-schema schema-location="http://www.seanbdurkin.id.au/xslt/xcsv.xsd"
                   use-when="system-property('xsl:is-schema-aware')='yes'" />


<xsl:template match="/">
  <xsl:variable name="phase-1-result">
    <xsl:call-template name="xcsvt:main" /><!-- phase 1 -->
  </xsl:variable>
  <xsl:apply-templates select="$phase-1-result" mode="phase-2"/>
 <people>
   <xsl:apply-templates select="
     $phase-1-result/
	 xcsv:comma-separated-single-line-values/
	 (xcsv:row[position() ge 2])" mode="phase-2"/>
 </people>
</xsl:template>

<xsl:template match="@*|</strong>" mode="phase-2" />

<xsl:template match="xcsv:row" mode="phase-2">
 <person>
  <first-name><xsl:value-of select="xcsv:value[1]" /></first-name>
  <last-name> <xsl:value-of select="xcsv:value[2]" /></last-name>
 </person>
</xsl:template>

</xsl:stylesheet>				   

Posted in XSLT 2.0 | Comments Off on A Generalised and Comprehensive Solution to CSV to XML and XML to CSV Transformations

How to extract a time-sheet from a Windows event log

Let’s say you are required to keep a time-sheet for services performed, and for the most part, the hours of services performed corresponds to when you are logged into a specific Win7+ machine. The best way to keep a time-sheet is with pencil and paper, or a spread-sheet, or the corporate time-sheet keeping system. But what if there is a problem with your normal record-keeping system? Is there a way to leverage the windows event log without an enormous amount of pain?
Timesheet
Yes, there is! Now for the good oil …

Step One

Open the Event Viewer (%windir%\system32\eventvwr.msc /s)

Step Two

[aside]
caption = What does the query do?
alignment = right
collapse_state = collapsed
corners = round
hr_style = sbd-swish
bg_colour = #E0D584
width = 300px

This query selects events from

  • the “Security” Log (the Path= part).
  • It selects events from the named computer (the Computer= part);
  • informational (as opposed to errors, warnings etc (Level=4 or Level=0 part);
  • with a keyword “Audit Success” (the band part);
  • not too old (TimeCreated… part); and
  • either any kind of log-out (EventID’s 4634 or 4647);
  • or a manual (as opposed to programmatic) log-in (EventId=4624 is log-in; LogonType=2 is the manual part).

[/aside]
Create a custom view (Action | Create Custom View...). Select the XML tab, and check the checkbox “Edit query manually”. Enter in the following query:

<QueryList>
  <Query Id="0" Path="Security">
    <Select Path="Security">*
	  [System[(Computer='E91W7CLI64') and (Level=4 or Level=0) and (band(Keywords,9007199254740992)) and TimeCreated[timediff(@SystemTime) &lt;= 9002000000]]]
	  [System[(EventID=4634 or EventID=4647)] or
	   (System[EventID=4624] and EventData/Data[@Name='LogonType']=2)]
	</Select>
  </Query>
</QueryList>

You will have to tweak two parameters in the above query, for your circumstances. The tweaks are:

  1. Change Computer='E91W7CLI64' to your local machine name. (hint: right-click properties on your Computer icon to get the name)
  2. Change the number part of the TimeCreated[timediff(@SystemTime) <= 9002000000 expression to how-ever far back you want to observe. This particular number equates to about 60 days. Adjust proportionally as required.

Name the filter as you please. I call mine “Logins”. This view observes successful manual log-ins and log-outs for the named computer for the specified time-back until now.

Step Three

Right-click on the filter and select Save All Events in Custom View As.... Select XML as the output format. The exported file now contains all your manual log-in session data.

Step Four

Transform the exported file with this XSLT script. I have tested this with the Saxon XSLT processor (Community edition).

<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:ev="http://schemas.microsoft.com/win/2004/08/events/event"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:fn="http://www.w3.org/2005/xpath-functions"
  xmlns:l="http://www.seanbdurkin.id.au"
  exclude-result-prefixes="xsl xs fn ev">
<xsl:output omit-xml-declaration="yes" encoding="utf-8" indent="yes"/>
<xsl:strip-space elements="*" />
      
<xsl:template match="Events">
 <l:sean-work-log>
  <xsl:variable name="sessions"> 
    <xsl:for-each-group select="ev:Event" group-by="ev:EventData/ev:Data[@Name='TargetLogonId']">
	  <xsl:if test="count(current-group()) eq 2">
	    <xsl:variable name="start"    as="xs:dateTime" select="xs:dateTime( current-group()[2]/ev:System/ev:TimeCreated/@SystemTime)" />
	    <xsl:variable name="end"      as="xs:dateTime" select="xs:dateTime( current-group()[1]/ev:System/ev:TimeCreated/@SystemTime)" />
	    <xsl:variable name="duration" as="xs:duration" select="$end - $start" />
	    <l:session start="{$start}" end="{$end}"
			   x-duration="{$duration}" 
			   day="{fn:format-date( xs:date( $start),'[FNn] [D] [MNn] [Y]')}"
			   duration="{fn:days-from-duration($duration) * 24 + fn:hours-from-duration($duration)} hours and {fn:minutes-from-duration($duration)} minutes" />
	  </xsl:if>
    </xsl:for-each-group>
  </xsl:variable>
 <l:work-days from="{fn:format-date( fn:min( for $d in $sessions/l:session/@start return xs:date( xs:dateTime($d))),'[FNn] [D] [MNn] [Y]')}"
              to="{  fn:format-date( fn:max( for $d in $sessions/l:session/@start return xs:date( xs:dateTime($d))),'[FNn] [D] [MNn] [Y]')}"> 
  <xsl:for-each-group select="$sessions/l:session" group-by="xs:date( xs:dateTime( @start))">
   <xsl:sort  select="xs:date( current-grouping-key())" order="descending" data-type="number" />
   <xsl:variable name="hours" select="fn:sum( for $x in current-group()/@x-duration return fn:minutes-from-duration( $x)) div 60" />
   <xsl:if test="fn:round($hours) ne 0">
     <l:work-day
       day="{fn:format-date( current-grouping-key(),'[FNn] [D] [MNn] [Y]')}"
       x-day="{current-grouping-key()}"
	   round-hours="{fn:round($hours)} hours" 
	   x-hours="{$hours}" />
   </xsl:if>	   
  </xsl:for-each-group>
 </l:work-days>  
 <l:log-sessions>
  <xsl:copy-of select="$sessions" />
 </l:log-sessions>
 </l:sean-work-log>
</xsl:template>
	  
</xsl:stylesheet>
        

Step Five

Read the output document and transcribe to your work-sheet. The output has two sections: A day-by=day summary and a list of log-in sessions. In the day-by-day summary, if the total for the day is less than half an hour, it will be filtered out. Hours for each day are rounded to the nearest hour. If you have different rounding rules, adjust the transform in step four as required.

Posted in XSLT 2.0 | Comments Off on How to extract a time-sheet from a Windows event log

HostMonster down-time

My web-server host is Host Monster. Every now and then (maybe once per couple of months), the server goes down. It appears to be a reboot, because it is down for about 15 minutes, and then there is 5 minutes where the Apache webserver is up, but the MySQL server is down.

What disappoints me is that I have never recieved an email or other communication from HostMonster to give advance warning of a down-time, and after the fact, they never report any such incidents to me. How many times has my server been rebooted? How much down-time has it had? I will never know, because this information is not available through the host control panel, and they never tell you.

Just today, my server went down for 30 minutes, including the e-mail server. I opened a chat to HostMonster support. After a 30 minute wait time, this was the response…

[Initial Question] Provider: HostMonster - My Domain is: "seanbdurkin.id.au" My webserver is down. Also I can't log in to control panel. It appears the server for the control panel is down too.
(5:25) [System] Customer has entered chat and is waiting for an agent.
(6:4) [ChatSnipedManually] {"staffId":"229"}
(6:8) [Jean C.] Please verify the last 4 characters of the account password or the last 4 digits of the credit card on file and I'll look into that for you. Please rate your chat experience! We appreciate your feedback.
(6:9) [Sean B. Durkin] (redacted)
(6:10) [Jean C.] Thank you, give me a moment to look into that for you. We appreciate your patience.
(6:11) [Sean B. Durkin] The website is back up now. But it was down for 30 minutes or so.
(6:14) [Jean C.] sorry about that
(6:14) [Jean C.] We will keep an eye on the server for you
(6:14) [Sean B. Durkin] Is there any way I can get advance notice of down-time?
(6:17) [Sean B. Durkin] Hello
(6:19) [Sean B. Durkin] I guess that means no. Thanks for your time.
(6:19) [ChatClosedByCustomer] {}
(6:19) [System] Chat closed by customer request.

How can I improve on my website’s up-time? Any suggestions anyone?

Posted in Web hosting | Comments Off on HostMonster down-time

About the “SBD Aside” plugin for WordPress

[aside]
caption = Open me!
alignment = right
width = 200px
collapse_state = collapsed
corners = round
hr_style = sbd-swish
bg_colour = #DBE3A6

This is the side-bar. You can put content here that is tangential to the main article, rather like the dramatical use of asides.
[/aside]
The SBD Aside plugin is a WordPress plugin that allows bloggers to easily create small side-bars for there entries, called asides. I recommend against using this plugin with the WordPress Visual Editor, as I have not tested it with the visual editor, and will not support the use of the plugin through the Visual Editor.

Look at the side-bar on the top right of this page. Press the pull-down button to see the content of the aside. Click the pull-up button to hide the content and make more room for the main article.

Features

Nice features of SBD Aside include:
[aside]
caption = No code traps!
alignment = right
width = 180px
collapse_state = expanded
corners = sharp
hr_style = sbd-regular
bg_colour = #F7F72D

Unlike most other filter plugins, there is no content that the blogger is blocked from.
[/aside]

  1. HTML 5 Support. SBD Aside uses the HTML 5 semantic tag <aside> to play well with search engines and content aggregators.
  2. Choose left or right placement on the page.
  3. Choose expanded (“open”) style, or collapsible.
  4. collapsible ones like on the top right of this page can be collapsed or expanded by the reader as required.
  5. Choose round corners or square cut.
  6. Choose background colour.
  7. Choose width in pixels or percent.
  8. Choose from one of two caption underline styles.
  9. Display a caption
  10. All of the above options/settings are optional. Define your own defaults in the plugin’s setting page.
  11. The styling for the aside is compact, with a small font and low margins. This minimises space disruption to the main article.

How to use

Inserting an Aside

Its easy. Just type [aside] on the start of a line; some content; then type [/aside] on a fresh line like so…

[aside]
caption = What you get
alignment = left
width = 300px
collapse_state = expanded
corners = round
hr_style = sbd-swish
bg_colour = aqua

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
[/aside]

![aside]
  caption = What you get
  alignment = left
  width = 300px
  collapse_state = expanded
  bg_colour = aqua

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
![/aside]

Options

To specify an option put, within the [aside][/aside] pair an option line. An option line starts with two space, and then proceeds with option=value like so…

![aside]
  caption = This is a caption
![/aside]

Available options include:

Option name Range of values Factory default Effect
caption Any one-line text The head-line for the aside
alignment left or right right placement on the page
width like 200px or 30% 200px Width of the aside
collapse_state expanded or collapsed collapsed Fixed-open or collapsable
corners round or sharp round Rounded corners or square-cut
hr_style sbd-regular or sbd-swish sbd-swish The style of the underline under the caption
bg_colour Any html colour like #9ae6d4 #9ae6d4 Background colour of the aside box

Main Aside content

The main aside content starts from the first line between the [aside][/aside] that does not follow the option line pattern.

What about when I want to write [aside] as literal content?

If you want to write [aside] or [/aside] literally then just prefix it by an exclamation mark like so…

!![aside] is how the SBD Aside is demarcated to begin.

If the word “[aside] is not at the start of the line, then no problem. It does not need to be so escaped.
Hey but what if I want to write ![aside] literally at the start of a line? If you need to write [aside] or [/aside] literally at the start of a line, prefixed by any number of exclamation marks, just add one more exclamation mark, or just don’t put it at the start of a line. These escaping rules apply both within and external to asides.

Defaults for options

None of the options are mandatory. If absent in your blog entry text, they will take default values. The blogger can specify his own defaults through WordPress settings.

How to install

  1. Your website host must have PHP version 5.3 or better. WordPress must be 3.5 or better. Check your PHP version and WordPress version.
  2. Deactivate any other plugin that uses the `[aside]` markup keyword.
  3. Upload `sbd-aside` folder and its 3 files (`sbd-aside.php`,`sbd-aside.js`,`sbd-aside.css` plus image files) to the `/wp-content/plugins/` directory
  4. Activate the plugin through the ‘Plugins’ menu in WordPress
  5. Go to the WordPress settings for SBD Aside, and configure the defaults as you prefer.
Posted in WordPress | Tagged , , | Comments Off on About the “SBD Aside” plugin for WordPress

Short-Circuit boolean evaluation in XPath

In this post, I explore short-circuit boolean evaluation in XPath.

Let’s start with this reference task:

Let $seq be a possibly empty sequence of integer. The task is to compute a boolean which is true if the first item is the maximum integer and false otherwise. For this purpose, deem the case of an empty sequence NOT have the property that the first item is the maximum. Now there many possible solutions to this task, but for argument’s sake, let’s restrict our solutions to the form of …

index-of( $seq, max( $seq))[1]
    eq 1

… or something derived from this.

The problem with the using above as in in XPath 2.0 occurs with an edge case. If $seq is empty, this will return an error. So what we really want is this imaginary XPath 2.0 expression..

exists( $seq) short-circuit-and
              index-of( $seq, max( $seq))[1] eq 1

where ‘short-circuit-and’ is an imaginary XPath 2.0 operator which does short-circuit boolean evaluation from left to right. When XPath 1.0 compatibility mode is set to true, we can use the ‘and’ operator for this purpose. Sadly we cannot safely do so when the comp ability mode is set to false. In the default case of compatibility mode equals false, the above expression returns an implementation dependent result. For the edge case, some processors may return false and other processors may return error. In other words, XPath 2.0 does not implement short-circuit boolean evaluation.

Here is how we can achieve the effect of short-circuit boolean evaluation in XPath 2.0

if (exists( $seq))
 then index-of( $seq, max( $seq))[1] eq 1
 else false()

Here are the implementation patterns for short-circuit boolean evaluation in XPath 2.0 (and probably the future XPath 3.0).

Short-circuit AND

expression-a short-circuit-and expression-b 
  ==>  if (expression-a) then (expression-b) or false()

Short-circuit OR

expression-a short-circuit-or expression-b 
  ==>  if (expression-a) then true() else expression-b
Posted in XSLT 3.0 | Comments Off on Short-Circuit boolean evaluation in XPath

What the Knight’s Tour might look like in XSLT 3.0?

At the time of writing XSL Transformations (XSLT) Version 3.0 (W3C Working Draft 10 July 2012) is still a working draft. I can’t test this, but this is my estimate of what a solution for the Knight’s tour might look like in XSLT 3.0  .d

First we build a generic package for solving any kind of tour over the chess board. Here it is…
(Updated 31-Oct-2012)

<br />&lt;xsl:package xsl:version="3.0"<br />  xmlns:xsl=&amp;amp;quot;http://www.w3.org/1999/XSL/Transform&amp;amp;quot;<br />  xmlns:xs=&amp;amp;quot;http://www.w3.org/2001/XMLSchema&amp;amp;quot;<br />  xmlns:fn=&amp;amp;quot;http://www.w3.org/2005/xpath-functions&amp;amp;quot;<br />  xmlns:tour=&amp;amp;quot;http://www.seanbdurkin.id.au/tour&amp;amp;quot;<br />  name=&amp;amp;quot;tour:tours&amp;amp;quot;&amp;amp;gt;<br />&amp;amp;lt;xsl:stylesheet&amp;amp;gt;<br />  &amp;amp;lt;xsl:function name=&amp;amp;quot;tour:manufacture-square&amp;amp;quot;<br />       as=&amp;amp;quot;element(square)&amp;amp;quot; visibility=&amp;amp;quot;public&amp;amp;quot;&amp;amp;gt;<br />    &amp;amp;lt;xsl:param name=&amp;amp;quot;rank&amp;amp;quot; as=&amp;amp;quot;xs:integer&amp;amp;quot; /&amp;amp;gt;<br />    &amp;amp;lt;xsl:param name=&amp;amp;quot;file&amp;amp;quot; as=&amp;amp;quot;xs:integer&amp;amp;quot; /&amp;amp;gt;<br />    &amp;amp;lt;square file=&amp;amp;quot;$file&amp;amp;quot; rank=&amp;amp;quot;$rank&amp;amp;quot; /&amp;amp;gt;<br />  &amp;amp;lt;/xsl:function&amp;amp;gt;<br /><br />  &amp;amp;lt;xsl:function name=&amp;amp;quot;tour:on-board&amp;amp;quot; as=&amp;amp;quot;xs:boolean&amp;amp;quot; visibility=&amp;amp;quot;public&amp;amp;quot;&amp;amp;gt;<br />    &amp;amp;lt;xsl:param name=&amp;amp;quot;rank&amp;amp;quot; as=&amp;amp;quot;xs:integer&amp;amp;quot; /&amp;amp;gt;<br />    &amp;amp;lt;xsl:param name=&amp;amp;quot;file&amp;amp;quot; as=&amp;amp;quot;xs:integer&amp;amp;quot; /&amp;amp;gt;<br />    &amp;amp;lt;xsl:copy-of select=&amp;amp;quot;($rank ge 1) and ($rank le 8) and<br />                         ($file ge 1) and ($file le 8)&amp;amp;quot; /&amp;amp;gt;<br />  &amp;amp;lt;/xsl:function&amp;amp;gt;<br /><br />  &amp;amp;lt;xsl:function name=&amp;amp;quot;tour:solve-tour&amp;amp;quot; as=&amp;amp;quot;item()*&amp;amp;quot; visibility=&amp;amp;quot;public&amp;amp;quot;&amp;amp;gt;<br />    &amp;amp;lt;!-- Solves the tour for any specified piece. --&amp;amp;gt;<br />    &amp;amp;lt;!-- Outputs either a full solution of 64 squares, of if fail,<br />         a copy of the $state input. --&amp;amp;gt;<br />    &amp;amp;lt;xsl:param name=&amp;amp;quot;state&amp;amp;quot; as=&amp;amp;quot;item()+&amp;amp;quot; /&amp;amp;gt;<br />    &amp;amp;lt;xsl:variable name=&amp;amp;quot;compute-possible-moves&amp;amp;quot;<br />      select=&amp;amp;quot;$state[. instance of function(*)]&amp;amp;quot;<br />	  as=&amp;amp;quot;function(element(square)) as element(square)*&amp;amp;quot;&amp;amp;gt;<br />    &amp;amp;lt;xsl:variable name=&amp;amp;quot;way-points&amp;amp;quot; select=&amp;amp;quot;$state/self::square&amp;amp;quot; /&amp;amp;gt;<br />    &amp;amp;lt;xsl:choose&amp;amp;gt;<br />      &amp;amp;lt;xsl:when test=&amp;amp;quot;count($way-points) eq 64&amp;amp;quot;&amp;amp;gt;<br />        &amp;amp;lt;xsl:sequence =&amp;amp;quot;$state&amp;amp;quot; /&amp;amp;gt;<br />      &amp;amp;lt;/xsl:when&amp;amp;gt;<br />      &amp;amp;lt;xsl:otherwise&amp;amp;gt;<br />        &amp;amp;lt;xsl:sequence select=&amp;amp;quot;<br />	  let $try-move := function( $state as item()*, $move as item()) as item()*)<br />	        {<br />	         if $state/self::square[@file=$move/@file]<br />	                               [@rank=$move/@rank]<br />	           then $state<br />	           else tour:solve-tour( ( $state, $move) )<br />                },<br />              $possible-moves := $compute-possible-moves( $way-points[last()])<br />	      return if empty( $possible-moves) then $state<br />                     else fn:fold-left( $try-move, $state, $possible-moves)&amp;amp;quot; /&amp;amp;gt;<br />      &amp;amp;lt;/xsl:otherwise&amp;amp;gt;<br />    &amp;amp;lt;/xsl:choose&amp;amp;gt;<br />  &amp;amp;lt;/xsl:variable&amp;amp;gt;&amp;amp;lt;/xsl:function&amp;amp;gt;<br />&amp;amp;lt;/xsl:stylesheet&amp;amp;gt;<br /><br />&amp;amp;lt;xsl:expose component=&amp;amp;quot;function&amp;amp;quot;<br />  names=&amp;amp;quot;tour:manufacture-square tour:on-board tour:solve-tour&amp;amp;quot;<br />  visibility=&amp;amp;quot;public&amp;amp;quot; /&amp;amp;gt;<br /><br />&amp;amp;lt;/xsl:package&amp;amp;gt;<br />
The Knight's Tour

And now for the style-sheet to solve the Knight’s tour…

&amp;lt;xsl:stylesheet version=&amp;quot;3.0&amp;quot;
  xmlns:xsl=&amp;quot;http://www.w3.org/1999/XSL/Transform&amp;quot;
  xmlns:xs=&amp;quot;http://www.w3.org/2001/XMLSchema&amp;quot;
  xmlns:fn=&amp;quot;http://www.w3.org/2005/xpath-functions&amp;quot;
  xmlns:tour=&amp;quot;http://www.seanbdurkin.id.au/tour&amp;quot;
  exclude-result-prefixes=&amp;quot;xsl fn xs tour&amp;quot;&amp;gt;
&amp;lt;xsl:use-package name=&amp;quot;tour:tours&amp;quot; /&amp;gt;
&amp;lt;xsl:output indent=&amp;quot;yes&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; omit-xml-declaration=&amp;quot;yes&amp;quot; /&amp;gt;
&amp;lt;xsl:mode on-no-match=&amp;quot;shallow-copy&amp;quot; streamable=&amp;quot;yes&amp;quot;/&amp;gt;

&amp;lt;xsl:template match=&amp;quot;knight[square]&amp;quot;&amp;gt;
  &amp;lt;xsl:variable name=&amp;quot;error&amp;quot;&amp;gt;
    &amp;lt;error&amp;gt;Failed to find solution to Knight's Tour.&amp;lt;/error&amp;gt;
  &amp;lt;/xsl:variable&amp;gt;
  &amp;lt;xsl:copy&amp;gt;
    &amp;lt;xsl:copy-of select=&amp;quot;
    let $final-state := tour:solve-tour((
    function( $piece-position as element(square)) as element(square)*
      { (: This function defines a knight's move. :)
	    let $r0 := number( $piece-position/@rank),
	    let $f0 := number( $piece-position/@file),
	    for $r in -2..2, $f in -2..2 return
		  if (abs($r) + abs($f) eq 3) and
		     tour:on-board($r+$r0, $f+$f0) then
		    tour:manufacture-square($r+$r0, $f+$f0)
	      else ()
	  }
      , current()/square)),
     $solution := $final-state/self::square
    return if count($solution) eq 64 then $solution
           else $error/*&amp;quot; /&amp;gt;
  &amp;lt;/xsl:copy&amp;gt;
&amp;lt;/xsl:template&amp;gt;

&amp;lt;!-- Add templates for other piece types if you want to solve
     their tours too. Solve by calling tour:solve-tour() .    --&amp;gt;

&amp;lt;/xsl:stylesheet&amp;gt;

So an input like this…

&amp;lt;tt&amp;gt;
 &amp;lt;knight&amp;gt;
   &amp;lt;square file=&amp;quot;1&amp;quot; rank=&amp;quot;1&amp;quot; /&amp;gt;
 &amp;lt;/knight&amp;gt;
&amp;lt;/tt&amp;gt;

…should be transformed in something like this…

&amp;lt;tt&amp;gt;
 &amp;lt;knight&amp;gt;
   &amp;lt;square file=&amp;quot;1&amp;quot; rank=&amp;quot;1&amp;quot; /&amp;gt;
   &amp;lt;square file=&amp;quot;2&amp;quot; rank=&amp;quot;3&amp;quot; /&amp;gt;
   &amp;lt;square file=&amp;quot;1&amp;quot; rank=&amp;quot;5&amp;quot; /&amp;gt;
   ... etc for 64 squares.
 &amp;lt;/knight&amp;gt;
&amp;lt;/tt&amp;gt;
Posted in XSLT 3.0 | Tagged , | Comments Off on What the Knight’s Tour might look like in XSLT 3.0?