coder
coder

Reputation: 4283

How to group xml elements that is in sequence, using XSLT?

There are examples to group items using xsl:key, but those don't work for my scenario.

Each set with column1="H" should be named <transaction>, and all the items with column1="D" following the "H" should be inside the <transaction> as <item>, until it reaches the next "H". Then it repeats with the same rule.

Problem: The values are enclosed in double quotes, but the output shouldn't have double quotes.

<root>
    <row>
        <column1>"H"</column1>
        <column2>"2016-09-09"</column2>
    </row>
    <row>
        <column1>"D"</column1>
        <column2>"Conference Services Meeting Package"</column2>
    </row>
    <row>
        <column1>"D"</column1>
        <column2>"Audio Visual Meeting Package"</column2>
    </row>
    <row>
        <column1>"H"</column1>
        <column2>"2016-09-09"</column2>
    </row>
    <row>
        <column1>"D"</column1>
        <column2>"Meeting Package Lunch"</column2>
    </row>
    <row>
        <column1>"D"</column1>
        <column2>"Marinated Roasted Olives</column2>
    </row>
    <row>
        <column1>"D"</column1>
        <column2>"Mezza Plate Humus with Smoked Paprika Butter"</column2>
    </row>
    <row>
        <column1>"D"</column1>
        <column2>"Pastry Bread Block Loaf Bread"</column2>
    </row>
</root>

Output:

<xml>
    <transaction>
        <item>Conference Services Meeting Package</item>
        <item>Audio Visual Meeting Package</item>
    </transaction>
    <transaction>
        <item>Meeting Package Lunch</item>
        <item>Marinated Roasted Olives</item>
        <item>Mezza Plate Humus with Smoked Paprika Butter</item>
        <item>Pastry Bread Block Loaf Bread</item>
    </transaction>
</xml>

Upvotes: 1

Views: 229

Answers (2)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243459

This transformation:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:key name="kFollowing" match='row[column1=&apos;"D"&apos;]' 
          use='generate-id(preceding-sibling::row[column1=&apos;"H"&apos;][1])'/>

  <xsl:template match="/*">
    <xml>
      <xsl:apply-templates select='row[column1=&apos;"H"&apos;]'/>
    </xml>
  </xsl:template>

  <xsl:template match="row">
    <transaction>
      <xsl:apply-templates select="key('kFollowing', generate-id())/column2"/>
    </transaction>
  </xsl:template>

  <xsl:template match="column2">
    <item><xsl:value-of select=
     'concat(translate(substring(.,1,1),&apos;"&apos;,""), 
             substring(.,2, string-length(.) -2),
             translate(substring(.,string-length()),&apos;"&apos;,""))'/></item>
  </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<root>
    <row>
        <column1>"H"</column1>
        <column2>"2016-09-09"</column2>
    </row>
    <row>
        <column1>"D"</column1>
        <column2>"Conference Services Meeting Package"</column2>
    </row>
    <row>
        <column1>"D"</column1>
        <column2>"Audio Visual Meeting Package"</column2>
    </row>
    <row>
        <column1>"H"</column1>
        <column2>"2016-09-09"</column2>
    </row>
    <row>
        <column1>"D"</column1>
        <column2>"Meeting Package Lunch"</column2>
    </row>
    <row>
        <column1>"D"</column1>
        <column2>"Marinated Roasted Olives</column2>
    </row>
    <row>
        <column1>"D"</column1>
        <column2>"Mezza Plate Humus with Smoked Paprika Butter"</column2>
    </row>
    <row>
        <column1>"D"</column1>
        <column2>"Pastry Bread Block Loaf Bread"</column2>
    </row>
</root>

produces exactly the wanted, correct result:

<xml>
   <transaction>
      <item>Conference Services Meeting Package</item>
      <item>Audio Visual Meeting Package</item>
   </transaction>
   <transaction>
      <item>Meeting Package Lunch</item>
      <item>Marinated Roasted Olives</item>
      <item>Mezza Plate Humus with Smoked Paprika Butter</item>
      <item>Pastry Bread Block Loaf Bread</item>
   </transaction>
</xml>

Explanation:

  1. A key that defines all corresponding "D" <column2> elements as a function of the generate-id() of their corresponding (nearest preceding) "H" <column1> element.

  2. Translating the 1st and last characters from (only if they are a quote) " to the empty string. Thus "Marinated Roasted Olives is processed correctly, even though it doesn't end with a quote -- which most probably is accidental mistake.

  3. No <xsl:for-each> (even nested!) instruction.

Upvotes: 3

michael.hor257k
michael.hor257k

Reputation: 116959

Try it this way:

XSLT 1.0

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

<xsl:key name="tx" match="row[column1='&quot;D&quot;']" use="generate-id(preceding-sibling::row[column1='&quot;H&quot;'][1])" />

<xsl:template match="/root">
    <xml>
        <xsl:for-each select="row[column1='&quot;H&quot;']">
            <transaction>
                <xsl:for-each select="key('tx', generate-id())">
                    <item>
                        <xsl:value-of select="column2"/>
                    </item>
                </xsl:for-each>
            </transaction>
        </xsl:for-each>
    </xml>
</xsl:template>

</xsl:stylesheet>

This solves the problem of grouping adjacent items. The problem of removing the double quotes is rather trivial, and can be solved easily by using the substring() function. Post a separate question if you can't make it work.

Upvotes: 2

Related Questions