-1

I need to transform using XSLT an XML input:

<CONTAINER>container1</CONTAINER>
<STOP>stop1</STOP>
<PO>po1</PO>
<PO>po2</PO>
<PO>po3</PO>
<CONTAINER>container2</CONTAINER>
<STOP>stop2</STOP>
<PO>po4</PO>
<PO>po5</PO>
<PO>po6</PO>
<STOP>stop3</STOP>
<PO>po7</PO>
<PO>po8</PO>

into

<CONTAINER>container1</CONTAINER>
<STOP>
<new_tag>Collapsed STOP</new_tag>
<PO>po1</PO>
<PO>po2</PO>
<PO>po3</PO>
</STOP>
<CONTAINER>container2</CONTAINER>
<STOP>
<new_tag>Collapsed STOP</new_tag>
<PO>po4</PO>
<PO>po5</PO>
<PO>po6</PO>
<STOP>stop3</STOP>
<PO>po7</PO>
<PO>po8</PO>
</STOP>

So basically I need to collapse all POs tag in a single STOP instead of having many STOPs, each one having a group of POs ad children.

Can someone help me on this? I'm very new to XSLT so I cannot fing a way to perform (if possible) this transformation.

2 Answers2

2

Assumptions

  1. Your input should be a well-formed document. I have wrapped it in an element to make it so.
  2. You are using XSLT 2. You didn't state the version.
  3. Each CONTAINER in the document is always and immediately proceeded by a STOP.
  4. The first child of the root element is CONTAINER.

This XSLT 2 transform ...

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

<xsl:output indent="yes" encoding="utf-8" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />

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

<xsl:template match="t">
  <xsl:copy>
    <xsl:for-each-group select="*" group-starting-with="CONTAINER">
      <CONTAINER><xsl:value-of select="current-group()[self::CONTAINER]/text()" /></CONTAINER>
      <STOP>
        <new_tag>Collapsed STOP</new_tag>
        <xsl:apply-templates select="current-group()
          [not(self::CONTAINER)]      (: Excluded because we have already dealt with CONTAINER.  :)
          [position() ge 2     ]      (: Exclude the first STOP, because already dealt with.     :)
          " />
      </STOP>
    </xsl:for-each-group>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>

... will transform this input document ...

<t>
    <CONTAINER>container1</CONTAINER>
    <STOP>stop1</STOP>
    <PO>po1</PO>
    <PO>po2</PO>
    <PO>po3</PO>
    <CONTAINER>container2</CONTAINER>
    <STOP>stop2</STOP>
    <PO>po4</PO>
    <PO>po5</PO>
    <PO>po6</PO>
    <STOP>stop3</STOP>
    <PO>po7</PO>
    <PO>po8</PO>
</t>

... into this output ...

<t>
   <CONTAINER>container1</CONTAINER>
   <STOP>
      <new_tag>Collapsed STOP</new_tag>
      <PO>po1</PO>
      <PO>po2</PO>
      <PO>po3</PO>
   </STOP>
   <CONTAINER>container2</CONTAINER>
   <STOP>
      <new_tag>Collapsed STOP</new_tag>
      <PO>po4</PO>
      <PO>po5</PO>
      <PO>po6</PO>
      <STOP>stop3</STOP>
      <PO>po7</PO>
      <PO>po8</PO>
   </STOP>
</t>

Learning note

Refer to my answer here, to learn more about the xsl:for-each-group instruction.

Community
  • 1
  • 1
Sean B. Durkin
  • 12,659
  • 1
  • 36
  • 65
0

XSLT 1.0 solution:

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

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

    </xsl:template>

    <xsl:template match="/root/CONTAINER">
        <xsl:copy-of select="."/>
        <xsl:apply-templates />
    </xsl:template>

    <xsl:template match="/root/STOP">
        <STOP>
            <new_tag><xsl:value-of select="." /></new_tag>
            <xsl:variable name="stopnum">
                <xsl:number level="single" count="STOP" />
            </xsl:variable>
            <xsl:for-each select="following-sibling::PO[count(preceding-sibling::STOP) = $stopnum]">
                <xsl:copy-of select="." />                
            </xsl:for-each>
        </STOP>
    </xsl:template>

    <xsl:template match="text()" />

</xsl:transform>

The key part is in here:

            <xsl:variable name="stopnum">
                <xsl:number level="single" count="STOP" />
            </xsl:variable>
            <xsl:for-each select="following-sibling::PO[count(preceding-sibling::STOP) = $stopnum]">

We set a variable that tells us the position of the last STOP node that's been processed, and then select all PO nodes in our for-each that have that many preceding-siblings that are STOP nodes.

Note that I've wrapped your XML in a <root> and </root> node to make it valid XML.

Input:

<root>
<CONTAINER>container1</CONTAINER>
<STOP>stop1</STOP>
<PO>po1</PO>
<PO>po2</PO>
<PO>po3</PO>
<CONTAINER>container2</CONTAINER>
<STOP>stop2</STOP>
<PO>po4</PO>
<PO>po5</PO>
<PO>po6</PO>
<STOP>stop3</STOP>
<PO>po7</PO>
<PO>po8</PO>
</root>

Output:

<root>
   <CONTAINER>container1</CONTAINER>
   <STOP>
      <new_tag>stop1</new_tag>
      <PO>po1</PO>
      <PO>po2</PO>
      <PO>po3</PO>
   </STOP>
   <CONTAINER>container2</CONTAINER>
   <STOP>
      <new_tag>stop2</new_tag>
      <PO>po4</PO>
      <PO>po5</PO>
      <PO>po6</PO>
   </STOP>
   <STOP>
      <new_tag>stop3</new_tag>
      <PO>po7</PO>
      <PO>po8</PO>
   </STOP>
</root>
Dan Field
  • 20,885
  • 5
  • 55
  • 71