tags:

views:

145

answers:

5

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?

A: 

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.

David
A: 

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.

harpo
+2  A: 

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>
Rich
+1 for showing how lazy I am ;)
Kyle Walsh
It's a good day when I can potentially answer one of these. Even better if I get some rep ;)
Rich
Looks like Robert expanded upon your expansion of what I was thinking. Does this make me an abstract base class?
Kyle Walsh
+4  A: 

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]"/>
Robert Rossney
A: 

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"&gt;

  <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>
Tomalak