tags:

views:

59

answers:

4

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+)\([^\)]+\)/
+1  A: 

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

Nikolaus Gradwohl
+1  A: 

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"&gt;
  <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>
0xA3
0xA3 great code, but I do need it be happen only if the characters before the parenthesis match a number, this sample could match a string too.
Benjamin Ortuzar
You can check that using the XPath `number()` function. See my update.
0xA3
+1  A: 

This stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <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.

Alejandro
@Alejandro: The transformation produces incorrect result on this XML document: `<root> <list> <item label="21(1)">some text</item> <item label="(2)">some text</item> </list> <list> <item label="x(a)">some text</item> <item label="b">some text</item> </list></root>`
Dimitre Novatchev
@Alejandro: Another potential problem is that you don't explicitly strip whitespace-only nodes. If they aren't stripped automatically by the XSLT processor, then the `<xsl:attribute>` will be in error.
Dimitre Novatchev
@Dimitre: You are rigth, thanks! What can I say? There are not general solutions... ;) Corrected to test number before parentesis, enforce first `item` pattern and explicitly strip white space.
Alejandro
@Alejandro: Now it's much better. +1.
Dimitre Novatchev
+1  A: 

This XSLT 1.0 transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <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>
Dimitre Novatchev
@Dimitre: +1 Good solution. Beside that I like `number()=number()` test, I think that `boolean(number())` will make your answer more compact than mine.
Alejandro