Abhijit Sarkar
Abhijit Sarkar

Reputation: 24518

XSLT: select all nodes that start with

I am trying to do a XSLT transformation where the input XML is arbitrary. The only thing that's constant in it is that it'll have a node the name of which starts with 'first'. I need to get the value of that node and it's immediate sibling. However, the following template produces only the XML declaration.

If it matters, this code is in Ruby using Nokogiri XML parser. However, I think this is more of a XSLT/XPath question rather than a Ruby question and hence tagging accordingly.

Input XML:

<?xml version="1.0"?>
<employees>
  <employee>
    <first_name>Winnie</first_name>
    <last_name>the Pooh</last_name>
  </employee>
  <employee>
    <first_name>Jamie</first_name>
    <last_name>the Weeh</last_name>
  </employee>
</employees>

Desired output XML:

<?xml version="1.0"?>
<people>
  <person>
    <first>Winnie</first>
    <last>the Pooh</last>
  </person>
  <person>
    <first>Jamie</first>
    <last>the Weeh</last>
  </person>
</people>

XSLT:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" encoding="UTF-8" />
<xsl:template match="/">
  <xsl:for-each select="node()[starts-with(name(), 'first')]">
    <people>
      <person>
        <name>
      <first><xsl:value-of select="." /></first>
      <last><xsl:value-of select="following-sibling::*[1]" /></last>
    </name>
      </person>
    </people>
  </xsl:for-each>
</xsl:template>
  </xsl:stylesheet>

Upvotes: 3

Views: 21175

Answers (3)

Abhijit Sarkar
Abhijit Sarkar

Reputation: 24518

<?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" encoding="UTF-8" />
  <!-- match the node that has a name starting with 'first' -->
  <xsl:template match="node()[starts-with(name(), 'first')]">
    <xsl:element name="people">
      <xsl:element name="person">
        <xsl:element name="name">
          <xsl:element name="first">
            <xsl:value-of select="." />
          </xsl:element>
          <xsl:element name="last">
            <xsl:value-of select="following-sibling::*[1]" />
          </xsl:element>
        </xsl:element>
      </xsl:element>
    </xsl:element>
    <xsl:apply-templates />
  </xsl:template>
  <!-- stop the processor walking the rest of the tree and hitting text nodes -->
  <xsl:template match="text()|@*" />
</xsl:stylesheet>

Upvotes: 4

Michael Kay
Michael Kay

Reputation: 163302

I don't see how you can change employees/employee to persons/person if the input XML is as arbitrary as you suggest. However, you can achieve roughly the right effect with

<xsl:template match="*">
  <xsl:copy><xsl:apply-templates/></xsl:copy>
</xsl:template>

<xsl:template match="*[starts-with(name(), 'first')]">
  <first><xsl:apply-templates/></first>
</xsl:template>

<xsl:template match="*[preceding-sibling::*[1][starts-with(name(), 'first')]]">
  <last><xsl:apply-templates/></last>
</xsl:template>

Upvotes: 4

nwellnhof
nwellnhof

Reputation: 33618

Use .//node() instead of node().

Upvotes: 0

Related Questions