Search code examples
xsltxslt-2.0xslt-3.0

How to find updated date in processing instruction - XSLT


I am try to create attribute lastUpdatedOn in p element if find the processing instruction in p element and create the value updated date.
Input XML

<root>
    <p content-type="new">Ongoing technological and <?lastUpdatedOn 20/01/2021?>privacy developments present practicing <?lastUpdatedOn 27/01/2021?>attorneys with significant challenges in the field of information privacy and <?lastUpdatedOn 25/01/2020?>security.</p>
    <p content-type="new">Developments present <?lastUpdatedOn 26/01/2021?>practicing.</p>
    <p content-type="new">Practicing the Labs.</p>
</root>

Expected Output

<root>
    <p content-type="new" lastupdatedon="27/01/2021">Ongoing technological and <?lastUpdatedOn 20/01/2021?>privacy developments present practicing <?lastUpdatedOn 27/01/2021?>attorneys with significant challenges in the field of information privacy and <?lastUpdatedOn 25/01/2020?>security.</p>
    <p content-type="new" lastupdatedon="26/01/2021">Developments present <?lastUpdatedOn 26/01/2021?>practicing.</p>
    <p content-type="new">Practicing the Labs.</p>
</root>

XSLT Code

<xsl:template match="node()|@*">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="p">
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:if test="processing-instruction(lastUpdatedOn)">
            <xsl:attribute name="lastupdatedon">
                <xsl:value-of select="processing-instruction(lastUpdatedOn)"/>
            </xsl:attribute>
        </xsl:if>
        <xsl:apply-templates/>
    </xsl:copy>
</xsl:template>

Solution

  • The right pattern or test is processing-instruction('lastUpdatedOn'). I would stuff it into the match of the p e.g.

    <xsl:template match="p[processing-instruction('lastUpdatedOn')]">
      <xsl:copy>
         <xsl:apply-templates select="@*, processing-instruction('lastUpdatedOn'), node() except processing-instruction('lastUpdatedOn')"/>
      </xsl:copy>
    </xsl:template>
    
    <xsl:template match="processing-instruction('lastUpdatedOn')">
       <xsl:attribute select="{name()}" select="."/>
    </xsl:template>
    

    the rest can be handled by the identity transformation with e.g. <xsl:mode on-no-match="shallow-copy"/>.

    As you seem to have multiple processing instruction sorting them with

    <xsl:template match="p[processing-instruction('lastUpdatedOn')]">
      <xsl:copy>
         <xsl:apply-templates select="@*, sort(processing-instruction('lastUpdatedOn'), (), function($p) { xs:date(replace($p, '([0-9]{2})/([0-9]{2})/([0-9]{4})', '$3-$2-$1')) })[last()], node() except processing-instruction('lastUpdatedOn')"/>
      </xsl:copy>
    </xsl:template>
    
    <xsl:template match="processing-instruction('lastUpdatedOn')">
       <xsl:attribute name="lastupdatedon" select="."/>
    </xsl:template>
    

    can help to just display the last date. Higher-order fn:sort is available in Saxon PE and EE since 9.8 at least and in Saxon HE for 10 and later.

    The above would move the date to the attribute but delete the processing instructions from the p, if you need to retain them perhaps

      <xsl:template match="p[processing-instruction('lastUpdatedOn')]">
        <xsl:copy>
           <xsl:apply-templates select="@*, sort(processing-instruction('lastUpdatedOn')!xs:date(replace(., '([0-9]{2})/([0-9]{2})/([0-9]{4})', '$3-$2-$1')))[last()], node()"/>
        </xsl:copy>
      </xsl:template>
      
      <xsl:template match=".[. instance of xs:date]">
         <xsl:attribute name="lastupdatedon" select="."/>
      </xsl:template>
    

    is a better approach.