0

Firstly I am aware of this question: XSLT: Loop selecting two elements at a time

However I have not found it to work due to the element structure or I just fail with using mod, one of the two.

<input>
  <node>
    <id>1</id>
    <value>3</value>
  </node>
  <node>
    <id>1</id>
    <value>3</value>
  </node>
  <node>
    <id>1</id>
    <value>3</value>
  </node>
  <node>
    <id>1</id>
    <value>3</value>
  </node>
  <node>
    <id>2</id>
    <value>4</value>
  </node>
  <node>
    <id>2</id>
    <value>4</value>
  </node>
  <node>
    <id>2</id>
    <value>4</value>
  </node>
  <node>
    <id>2</id>
    <value>4</value>
  </node>
</input>

I have the following layout of XML which has the following structure: - Nodes of the same ID will ALWAYS be grouped together - There will always be four nodes to one ID

I wish to be able to select the four nodes of one ID at a time and loop through each group of four, so that I can manipulate the data into one output line.

What would be the best way to approach this?

Community
  • 1
  • 1
Mike
  • 618
  • 2
  • 8
  • 27

3 Answers3

1

This XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:key name="keyByID" match="node" use="id"/>

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

<xsl:template match="input">
    <xsl:for-each select="node[generate-id()=generate-id(key('keyByID',id)[1])]">
        <block>
            <id>
                <xsl:value-of select="id"/>
            </id>
            <value>
                <xsl:value-of select="value"/>
            </value>
        </block>
    </xsl:for-each>
</xsl:template>


</xsl:stylesheet>

applied to your Input XML:

<?xml version="1.0" encoding="UTF-8"?>
<input>
<node>
    <id>1</id>
    <value>3</value>
</node>
<node>
    <id>1</id>
    <value>3</value>
</node>
<node>
    <id>1</id>
    <value>3</value>
</node>
<node>
    <id>1</id>
    <value>3</value>
</node>
<node>
    <id>2</id>
    <value>4</value>
</node>
<node>
    <id>2</id>
    <value>4</value>
</node>
<node>
    <id>2</id>
    <value>4</value>
</node>
<node>
    <id>2</id>
    <value>4</value>
</node>
</input>

gives this grouped Output XML:

<?xml version="1.0" encoding="UTF-8"?>
<output>
<block>
    <id>1</id>
    <value>3</value>
</block>
<block>
    <id>2</id>
    <value>4</value>
</block>
</output>

The output is grouped by <id>. Is that what you are looking for? I am not sure. This Muenchian Grouping just simpliefies your structure.

Nimantha
  • 6,405
  • 6
  • 28
  • 69
Peter
  • 1,786
  • 4
  • 21
  • 40
1

As the node elements are guaranteed always to be in groups of 4, the wanted output can be produced by a very simple transformation that doesn't use any grouping method (such as Muenchian or sibling comparison) and is probably more efficient:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="node()|@*" name="identity">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="node">
  <sample>
   <xsl:call-template name="identity"/>
  </sample>
 </xsl:template>
 <xsl:template match="node[not(position() mod 4 = 1)]"/>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<input>
  <node>
    <id>1</id>
    <value>3</value>
  </node>
  <node>
    <id>1</id>
    <value>3</value>
  </node>
  <node>
    <id>1</id>
    <value>3</value>
  </node>
  <node>
    <id>1</id>
    <value>3</value>
  </node>
  <node>
    <id>2</id>
    <value>4</value>
  </node>
  <node>
    <id>2</id>
    <value>4</value>
  </node>
  <node>
    <id>2</id>
    <value>4</value>
  </node>
  <node>
    <id>2</id>
    <value>4</value>
  </node>
</input>

the wanted, correct result is produced:

<input>
   <sample>
      <node>
         <id>1</id>
         <value>3</value>
      </node>
   </sample>
   <sample>
      <node>
         <id>2</id>
         <value>4</value>
      </node>
   </sample>
</input>

Explanation:

  1. The identity rule copies "as-is" every node for which it is selected for execution.

  2. Another template overrides the identity template for every node element that is the 4k+1st node child of its parent. This template generates a wrapper element (sample) and then calls the identity template by name to copy itself to the output.

  3. Yet another template overrides the identity template for every node element. This template matches every node element but it will be selected for execution (preferred over the previous template), only for nodes not matched by the previous template -- that is for any node element that is not a 4k+1st node child of its parent. This is so, because this template is less specific than the previous template.

  4. The template discussed in 3. above, has no body and this effectively "deletes" the matched node element from the output.

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
0

If you can guarantee that nodes of the same id will always be adjacent like that then a

<xsl:template match="node[not(preceding-sibling::node[1]/id = id])">

Would match the first node for each id (technically any node whose id is different from that of the node before it, if there is one), and within that template you could use following-sibling:: to find the others, or simply define a key at the top level and then use that to extract all nodes with the same id.

Ian Roberts
  • 120,891
  • 16
  • 170
  • 183