Benjamin Ortuzar
Benjamin Ortuzar

Reputation: 7831

xslt pattern match transformation

How can I transform the following XML with XSLT from this:

<root>
    <list>
        <item label="21(1)">some text</item>
        <item label="(2)">some text</item>
    </list>
    <list>
        <item label="a">some text</item>
        <item label="b">some text</item>
    </list>
</root>

to this:

<root>
    <list label="21">
        <item label="(1)">some text</item>
        <item label="(2)">some text</item>
    </list>
    <list>
        <item label="a">some text</item>
        <item label="b">some text</item>
    </list>
</root>

So, if there is a number before a parenthesis on the label attribute of the first item, that number needs to be aded as the value of the label attribute for the parent list item.

The pattern to match the attribute would be something like:

/(\d+)\([^\)]+\)/

Upvotes: 3

Views: 1275

Answers (4)

user357812
user357812

Reputation:

This stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:strip-space elements="*"/>
 <xsl:template match="node()|@*" name="identity">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
 </xsl:template>
 <xsl:template match="item[1][boolean(number(substring-before(@label,'(')))]">
    <xsl:attribute name="label">
        <xsl:value-of select="substring-before(@label,'(')"/>
    </xsl:attribute>
    <xsl:call-template name="identity"/>
 </xsl:template>
 <xsl:template match="item[1]/@label[boolean(number(substring-before(.,'(')))]">
    <xsl:attribute name="label">
        <xsl:value-of select="concat('(',substring-after(.,'('))"/>
    </xsl:attribute>
 </xsl:template>
</xsl:stylesheet>

Output:

<root>
    <list label="21">
        <item label="(1)">some text</item>
        <item label="(2)">some text</item>
    </list>
    <list>
        <item label="a">some text</item>
        <item label="b">some text</item>
    </list>
</root>

Edit: Compact predicate.

Edit 2: Test number before parentesis. Explicity strip white space only nodes.

Upvotes: 1

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243529

This XSLT 1.0 transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

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

 <xsl:template match=
  "list[number(substring-before(item[1]/@label, '('))
       =
       number(substring-before(item[1]/@label, '('))
       ]">
  <list label="{substring-before(item[1]/@label, '(')}">
    <xsl:apply-templates select="node()|@*"/>
  </list>
 </xsl:template>

 <xsl:template match=
   "item[1]/@label[number(substring-before(., '('))
           =
           number(substring-before(., '('))
           ]">
   <xsl:attribute name="label">
    <xsl:value-of select="concat('(',substring-after(.,'('))"/>
   </xsl:attribute>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<root>
    <list>
        <item label="21(1)">some text</item>
        <item label="(2)">some text</item>
    </list>
    <list>
        <item label="a">some text</item>
        <item label="b">some text</item>
    </list>
</root>

produces the wanted, correct result:

<root>
    <list label="21">
        <item label="(1)">some text</item>
        <item label="(2)">some text</item>
    </list>
    <list>
        <item label="a">some text</item>
        <item label="b">some text</item>
    </list>
</root>

Upvotes: 0

Dirk Vollmar
Dirk Vollmar

Reputation: 176219

As mentioned by Nikolaus you can use the substring-before and substring-after XPath functions. A sample XSL transformation would look like this:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

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

  <xsl:template match="list">
    <list>
      <xsl:variable name="prefix" select="substring-before(./item/@label, '(')" />
      <xsl:if test="$prefix != '' and number($prefix)">
        <xsl:attribute name="label">
          <xsl:value-of select="substring-before(./item/@label, '(')"/>
        </xsl:attribute>
      </xsl:if>
      <xsl:apply-templates />
    </list>
  </xsl:template>

  <xsl:template match="item">
    <item>
      <xsl:attribute name="label">
        <xsl:variable name="prefix" select="substring-before(@label, '(')" />
        <xsl:choose>
          <xsl:when test="$prefix != '' and number($prefix)">
            <xsl:value-of select="concat('(', substring-after(@label, '('))"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="@label"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:attribute>
      <xsl:apply-templates />
    </item>
  </xsl:template>
</xsl:stylesheet>

Upvotes: 1

Nikolaus Gradwohl
Nikolaus Gradwohl

Reputation: 20124

you can use the xslt function substring-before to get the substring befor '('

Upvotes: 1

Related Questions