spikeheap
spikeheap

Reputation: 3887

Pattern-matching element names in XSLT

I need to traverse an XML tree and ensure that some elements have an attribute attached to them, adding it if it's missing. The elements which need these attributes all have a common element name prefix and all elements with this prefix need the attribute.

Here's a contrived example, as I'm not able to share the actual XML:

<people>
  <person>
    <PRJ_NAME>Bob</PRJ_NAME>
    <PRJ_AGE>22</PRJ_AGE>
    <address>
      <PRJ_FIRST_LINE>1 Test Street</PRJ_FIRST_LINE>
      <PRJ_SECOND_LINE>London</PRJ_SECOND_LINE>
    </address>
  </person>
  <person>
    <PRJ_NAME>Tony</PRJ_NAME>
    <PRJ_AGE>43</PRJ_AGE>
    <address>
      <PRJ_FIRST_LINE>23 Test Lane</PRJ_FIRST_LINE>
      <PRJ_SECOND_LINE>Edinburgh</PRJ_SECOND_LINE>
    </address>
  </person>
</people>

The desired output is to add an m (modified) attribute to all elements prefixed with PRJ_. These elements are nested to different levels, and the tree is non-trivial, so I can't just hard-code the paths:

<people>
  <person>
    <PRJ_NAME m="2015-11-09">Bob</PRJ_NAME>
    <PRJ_AGE m="2015-11-09">22</PRJ_AGE>
    <address>
      <PRJ_FIRST_LINE m="2015-11-09">1 Test Street</PRJ_FIRST_LINE>
      <PRJ_SECOND_LINE m="2015-11-09">London</PRJ_SECOND_LINE>
    </address>
  </person>
  <person>
    <PRJ_NAME m="2015-11-09">Tony</PRJ_NAME>
    <PRJ_AGE m="2015-11-09">43</PRJ_AGE>
    <address>
      <PRJ_FIRST_LINE m="2015-11-09">23 Test Lane</PRJ_FIRST_LINE>
      <PRJ_SECOND_LINE m="2015-11-09">Edinburgh</PRJ_SECOND_LINE>
    </address>
  </person>
</people>

So far I've been working along the following lines based on other SO answers such as adding attribute to the node which works fine for a single element name, but I'm struggling to make it apply to patterns of element names:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="2.0">

    <xsl:variable name="dateNow" select="format-date(current-date(), '[Y0001]-[M01]-[D01]')"/>

    <xsl:template match="node()|@*">
      <xsl:copy>
          <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
    </xsl:template>
    <xsl:template match="PRJ_FIRST_NAME">
        <PRJ_FIRST_NAME m="{$dateNow}">
            <xsl:apply-templates select="@*|node()"/>
        </PRJ_FIRST_NAME>
    </xsl:template>
</xsl:stylesheet>

Upvotes: 2

Views: 3811

Answers (1)

Martin Honnen
Martin Honnen

Reputation: 167716

Change that template to

<xsl:template match="*[starts-with(local-name(), 'PRJ_') and not(@m)]">
    <xsl:element name="{name()}">
        <xsl:attribute name="m" select="$dateNow"/>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:element>
</xsl:template>

and your approach is fine, although I think to get the format you shown in the result sample you need to use <xsl:variable name="dateNow" select="format-date(current-date(), '[Y0001]-[M01]-[D01]')"/>.

Online at http://xsltransform.net/94rmq63.

Upvotes: 3

Related Questions