ThunderFrame
ThunderFrame

Reputation: 9461

Select node with attribute relative to current node's attribute

I have some XML that looks like this:

<root>
  <node id="2_1" name="FOO" />
  <node id="2_2" class="child" />
  <node id="2_3" class="child" />
  <node id="2_4" class="child" />
  <node id="3_1" name="BAR" />
  <node id="3_2" class="child" />
  <node id="3_3" class="child" />
  <node id="3_4" class="child" />
</root>

That is, a child's parent node id always shares the same prefix, but ends in _1, and all child node suffixes start from 2 and increment.

I'm trying to produce output similar to:

<groups>
  <group id="2_1">
    <child id="2_2" />
    <child id="2_3" />
    <child id="2_4" />
  </group>
  <group id="3_1">
    <child id="3_2" />
    <child id="3_3" />
    <child id="3_4" />
  </group>
</groups>

I have this XSLT to get the groups, but I'm unsure how to select the children:

<xsl:template match="/">

<groups>
  <xsl:for-each select="/root/node[@name]">
    <group>
      <xsl:attribute name="id">
        <xsl:value-of select="@id" />
      </xsl:attribute>
    </group>
  </xsl:for-each>
</groups>

</xsl:template>

Am I going about this the right way? I think I need a template that accepts a parameter, but I'm unsure how to assign that parameter the current group's id value.

Upvotes: 0

Views: 641

Answers (1)

Tim C
Tim C

Reputation: 70638

Actually, you don't need to go the full way with grouping here, despite my comment. If indeed the rule the group id always ends in _1 and the child elements end in higher number, you can simplify it a bit.

You would still create a key, but one that matches only

<xsl:key name="nodes" match="node" use="substring-before(@id, '_')" />

Then, if you start by selecting the _1 elements, you can get the child elements by using the key

<xsl:apply-templates 
     select="key('nodes', substring-before(@id, '_'))[substring-after(@id, '_') != '1']" />

Try this XSLT

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

    <xsl:key name="nodes" match="node" use="substring-before(@id, '_')" />

    <xsl:template match="root">
        <groups>
            <xsl:apply-templates select="node[substring-after(@id, '_') = '1']" />
        </groups>
    </xsl:template>

    <xsl:template match="node[substring-after(@id, '_') = '1']">
        <group id="{@id}">
            <xsl:apply-templates select="key('nodes', substring-before(@id, '_'))[substring-after(@id, '_') != '1']" />
        </group>
    </xsl:template>

    <xsl:template match="node">
        <child id="{@id}">
        </child>
    </xsl:template>
</xsl:stylesheet>

Alternatively, if the "group" elements always have a name attribute, and the child items don't, you could slightly simplify it to this...

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

    <xsl:key name="nodes" match="node" use="substring-before(@id, '_')" />

    <xsl:template match="root">
        <groups>
            <xsl:apply-templates select="node[@name]" />
        </groups>
    </xsl:template>

    <xsl:template match="node[@name]">
        <group id="{@id}">
            <xsl:apply-templates select="key('nodes', substring-before(@id, '_'))[not(@name)]" />
        </group>
    </xsl:template>

    <xsl:template match="node">
        <child id="{@id}">
        </child>
    </xsl:template>
</xsl:stylesheet>

Upvotes: 1

Related Questions