Thursday 3 April 2014

XSLT: processing instructions

A recent requirement entailed the round-tripping of elements to processing instructions and then back again to their original elements using a second transformation. This presented a few challanges.

The elements concerned were off the form


<err:Warning 
foo="1234567" 
bar="abcd/efgh/jklm">content that records "some message"</err:Warning>

The first part of the round trip, is to take the elements that were all in a single namespace, and recast them as PI's. The attributes are taken through as name-value pairs and the content is taken through as a name value pair of the form content={content}.


<xsl:template match="err:*">
       <xsl:processing-instruction name="err-{local-name()}">
       <xsl:for-each select="@*">
   <xsl:value-of select="name()"/>
   <xsl:text>="</xsl:text>
   <xsl:value-of select="."/>
   <xsl:text>" </xsl:text>
    </xsl:for-each>
    <xsl:text>content="</xsl:text>
    <xsl:value-of select='.'/>
    <xsl:text>"</xsl:text>
 </xsl:processing-instruction>
</xsl:template>

Running this transformation in Saxon gives the end result of:


<?err-Warning foo="1234567" bar="abcd/efgh/jklm" content="content that records "some message""?>

It must be remembered that PIs do not have attributes, although the result looks to all intents and purposes as an attribute, they are not and you cannot target a PI attribute with an xpath as it does not exists! Therefore, it is a little more of a challange to get this back to their original attributes. In XSLT 2 we can use regular expresions to recreate the attributes.

Firstly, filter out the content={value} name value pair, then use the remaining string as the source for an analyze-string and a regualr expression of ([\c]+)="(.*?") where the second group targets the end quote of the name-value pair. This end quote then needs removing with the translate() or replace() function


<xsl:template match="processing-instruction()[starts-with(name(),'err-')]">
 <xsl:variable name="PI" select="substring-before(.,'content=')" />
 <xsl:variable name="PIname" select="substring-after(name(), 'err-')"/>
 <xsl:variable name="content" select="substring-after(., 'content="')"/>
 <xsl:element name="err:{$PIname}">
  <xsl:analyze-string select="$PI" regex='([\c]+)="(.*?")'>
    <xsl:matching-substring>
    <xsl:attribute name="{regex-group(1)}">
     <xsl:value-of select='translate(regex-group(2),"""", "")'/>
    </xsl:attribute>
    </xsl:matching-substring>
  </xsl:analyze-string>
  <!-- remove the last double quote from the content  -->
  <xsl:value-of select='replace($content,"""$", "")' />
 </xsl:element>
</xsl:template>

Running this transformation in Saxon returns us back to the original element:


<?err-Warning foo="1234567" bar="abcd/efgh/jklm" content="content that records "some message""?>

No comments:

Post a Comment