CJ F
CJ F

Reputation: 835

XSLT processing when order is important

I'll admit I'm new to xslt and I'm going through various tutorials and documentation as I go, but I have a situation that I'm not sure how to look up the answer for.

Basically it is this. I have an xml document that looks like so...

<tag1>
  <tag2>
    foo
  </tag2>
  <tag3>
    bar
   </tag3>

  <tag2>
    goo
  </tag2>
  <tag3>
    mar
  </tag3>
</tag1>

So the catch here is that the order is important. Items in tag2 and tag3 go together in groups. That is, "foo" and "bar" need to be processed together, as do "goo" and "mar".

I don't know how to capture that in a construct. That is, I can match tag2 and tag3, but I don't know how to get them in order, together.

Any ideas?

Upvotes: 1

Views: 628

Answers (5)

Tomalak
Tomalak

Reputation: 338188

Here is an XSLT 1.0 transformation that groups two consecutive children of <tag1>, based on their position in the document.

<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:template match="tag1">
    <root>
      <xsl:apply-templates select="*[position() mod 2 = 1]" />
    </root>
  </xsl:template>

  <xsl:template match="tag1/*">
    <xsl:variable name="next" select="following-sibling::*[1]" />   
    <together>
      <xsl:value-of select="normalize-space(concat(., $next))" />
    </together>
  </xsl:template>
</xsl:stylesheet>

Here is how it works:

  • starting at <tag1>, it outputs a root <root> node, and then selects those children of <tag1> who are at the start of the group (position() mod 2 = 1) for further processing
  • for each of those nodes, the second template outputs the text contents of the selected node and it's following sibling

My test output is:

<root>
  <together>foo bar</together>
  <together>goo mar</together>
</root>

Upvotes: 0

Robert Rossney
Robert Rossney

Reputation: 96712

Here's a straightforward way to do grouping if your source data is already ordered the way you expect:

<xsl:apply-templates select="/tag1/*[position() mod 2 = 1]" mode="group"/>

This will apply templates to every odd-positioned child element of tag1 irrespective of name. You use a mode to limit the template that gets applied (because that template has to match *). Within the template, you can use the following-sibling search axis to get the next node. This gives you something like:

<xsl:template match="*" mode="group">
   <xsl:variable name="next" select="following-sibling::*[1]"/>
   <group>
      <xsl:copy-of select="."/>
      <xsl:copy-of select="$next"/>
   </group>
</xsl:template>

It's even simpler if you can rely on the element names:

<xsl:apply-templates select="/tag1/tag2"/>

<xsl:template match="tag2">
   <xsl:variable name="next" select="following-sibling::tag3[1]"/>
   <group>
      <xsl:copy-of select="."/>
      <xsl:copy-of select="$next"/>
   </group>
</xsl:template>

Note that if you're grouping every N elements, you can get a list of the current element and its following N-1 siblings with:

<xsl:variable name="list" select=". | following-sibling::*[position() &lt; $n]"/>

Upvotes: 4

Rich
Rich

Reputation: 671

Kyle seems to be on the right track:

specifically, if you simply wanted to get each pair of tag2 and tag3 values, and assuming those values always appear in order in the tag1 node, I would use XSL such as (very simplistic):

<xsl:template match="//tag1">
  <xsl:for-each select="tag2">
    <xsl:value-of select="self::node()" />
    <xsl:value-of select="following-sibling::tag3[1]" />
  </xsl:for-each>
</xsl:template>

Upvotes: 2

harpo
harpo

Reputation: 43168

I suspect this is not possible in a single, straightforward select. Alternatively, you can process the groups separately,

<xsl:apply-templates select="foo|bar"/>
<xsl:apply-templates select="goo|mar"/>

using a template that matches all of these.

Upvotes: 0

David
David

Reputation: 34563

If you just need to process all of the "tag2" & "tag3" elements in order sequentially then you can just specify "tag2 | tag3" as the XPath query when you are selecting them out.

If the are really a unit that needs to be processed then "tag2" & "tag3" should really be grouped together under a new parent element.

Upvotes: 0

Related Questions