3

I'm trying to wrap my head around this and I think the easiest way to explain it is to just show you, below. I've seen this but it doesn't always apply due to the fact I have standalone items also at the end which would match.

The seemingly tricky part is Whatever3 to Whatever6, and then Whatever7 and Whatever8, then finally the new position of Whatever9 - they need to be grouped and the original sequence maintained. (Ignore my naming, there is no way to use xsl:sort)

I've considered xsl:for-each with xsl:if inside, but issue is you can't guarantee how many "groups" vs "non-group" items there are.

Thanks!

XML

<root>
   <item>
      <position>1</position>
      <label>Whatever1</label>
   </item>
   <item>
      <position>2</position>
      <label>Whatever2</label>
   </item>
   <item>
      <position>3</position>
      <label>Whatever3</label>
      <marker id="unique1">start_group</marker>
   </item>
   <item>
      <position>4</position>
      <label>Whatever4</label>
   </item>
   <item>
      <position>5</position>
      <label>Whatever5</label>
   </item>
   <item>
      <position>6</position>
      <label>Whatever6</label>
      <marker>last_in_group</marker>
   </item>
   <item>
      <position>7</position>
      <label>Whatever7</label>
      <marker id="unique2">start_group</marker>
   </item>
   <item>
      <position>8</position>
      <label>Whatever8</label>
      <marker>last_in_group</marker>
   </item>  
   <item>
      <position>9</position>
      <label>Whatever9</label>
   </item>
</root>

Result

<structure>
   <item>
      <position>1</position>
      <label>Whatever1</label>
    </item>
   <item>
      <position>2</position>
      <label>Whatever2</label>
    </item>
    <group position="3" id="unique1">
        <item>
          <position>1</position>
          <label>Whatever3</label>
        </item>
        <item>
          <position>2</position>
          <label>Whatever4</label>
        </item>
        <item>
          <position>3</position>
          <label>Whatever5</label>
        </item>
        <item>
          <position>4</position>
          <label>Whatever6</label>
        </item>
    </group>
    <group position="4" id="uniqueid2">
        <item>
          <position>1</position>
          <label>Whatever7</label>
        </item>
        <item>
          <position>2</position>
          <label>Whatever8</label>
        </item>
    </group>
    <item>
      <position>**5**</position>
      <label>Whatever9</label>
    </item>
</structure>

======================

Here is what I have so far, the only problem I have (besides it being messy) is Whatever4 and Whatever5 are showing up outside the Group.

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

  <xsl:template match="root">
  <structure>
    <xsl:apply-templates select="item[not(marker)] | item[marker='start_group']"/>
  </structure>
 </xsl:template>

  <xsl:template match="item[marker='start_group']">
   <group>
    <xsl:variable name="currentPosition" select="number(position/text())"/>
    <xsl:variable name="lastGroup" select="count(following-sibling::*[local-name() = 'item' and marker='last_in_group'][1]/preceding-sibling::*) + 1"/>

    <xsl:attribute name="position">
        <xsl:value-of select="$currentPosition"/>
    </xsl:attribute>
    <xsl:attribute name="id">
        <xsl:value-of select="marker/@id"/>
    </xsl:attribute>

   <item>
    <position><xsl:value-of select="number(position/text()) - $currentPosition + 1"/></position>
    <label><xsl:value-of select="label/text()"/></label>
   </item>

   <!-- position() gets reset in for-loop, so need to adjust with outer position -->
   <xsl:for-each select="following-sibling::item[(position() + $currentPosition) &lt;= $lastGroup]">
         <item>
                <position><xsl:value-of select="number(position/text()) - $currentPosition + 1"/></position>
                <label><xsl:value-of select="label/text()"/></label>
         </item>
    </xsl:for-each>
   </group>
 </xsl:template>

  <xsl:template match="item[not(marker)]">
   <item>
    <position><xsl:value-of select="position/text()"/></position>
    <label><xsl:value-of select="label/text()"/></label>
   </item>
 </xsl:template>

</xsl:stylesheet>
Community
  • 1
  • 1
  • What have you tried? Apart from considering xsl:for-each and xsl:if of course. Please post the XSLT that you've come up with so far. – toniedzwiedz Jun 21 '12 at 20:23
  • Give me a little bit, I need to switch laptops to where my XSLT is and translate it from my true XML format to the mockup I did above. – Jonathon J Howey Jun 21 '12 at 20:32

1 Answers1

3

I. XSLT 1.0 Solution:

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:strip-space elements="*"/>

 <xsl:key name="kFollowing"
  match="item[not(marker[. = 'start_group'])
            and
              preceding-sibling::*[marker][1]/marker = 'start_group'
             ]"
  use="generate-id(preceding-sibling::*
                    [marker[. = 'start_group']]
                     [1])"/>

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

 <xsl:template match="item[marker[. = 'start_group']]">
   <group position="{1 +count(preceding-sibling::*[. = 'start_group'])}"
   id="{marker/@id}">
     <xsl:copy-of select=".|key('kFollowing', generate-id())"/>
   </group>
 </xsl:template>

 <xsl:template match=
  "item[not(marker[. = 'start_group'])
      and
       preceding-sibling::*[marker][1]/marker = 'start_group'
       ]"/>
</xsl:stylesheet>

when applied on the provided XML document:

<root>
    <item>
        <position>1</position>
        <label>Whatever1</label>
    </item>
    <item>
        <position>2</position>
        <label>Whatever2</label>
    </item>
    <item>
        <position>3</position>
        <label>Whatever3</label>
        <marker id="unique1">start_group</marker>
    </item>
    <item>
        <position>4</position>
        <label>Whatever4</label>
    </item>
    <item>
        <position>5</position>
        <label>Whatever5</label>
    </item>
    <item>
        <position>6</position>
        <label>Whatever6</label>
        <marker>last_in_group</marker>
    </item>
    <item>
        <position>7</position>
        <label>Whatever7</label>
        <marker id="unique2">start_group</marker>
    </item>
    <item>
        <position>8</position>
        <label>Whatever8</label>
        <marker>last_in_group</marker>
    </item>
    <item>
        <position>9</position>
        <label>Whatever9</label>
    </item>
</root>

produces the wanted, correct result:

<root>
   <item>
      <position>1</position>
      <label>Whatever1</label>
   </item>
   <item>
      <position>2</position>
      <label>Whatever2</label>
   </item>
   <group position="1" id="unique1">
      <item>
         <position>3</position>
         <label>Whatever3</label>
         <marker id="unique1">start_group</marker>
      </item>
      <item>
         <position>4</position>
         <label>Whatever4</label>
      </item>
      <item>
         <position>5</position>
         <label>Whatever5</label>
      </item>
      <item>
         <position>6</position>
         <label>Whatever6</label>
         <marker>last_in_group</marker>
      </item>
   </group>
   <group position="1" id="unique2">
      <item>
         <position>7</position>
         <label>Whatever7</label>
         <marker id="unique2">start_group</marker>
      </item>
      <item>
         <position>8</position>
         <label>Whatever8</label>
         <marker>last_in_group</marker>
      </item>
   </group>
   <item>
      <position>9</position>
      <label>Whatever9</label>
   </item>
</root>

II. XSLT 2.0 solution:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

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

 <xsl:template match="/*">
  <root>
   <xsl:for-each-group select="item" group-starting-with=
   "*[marker eq 'start_group'
    or
      not(marker)
    and
      preceding-sibling::*[marker][1]/marker eq 'last_in_group'
     ]
   ">
     <xsl:choose>
       <xsl:when test="current-group()[1]/marker">
               <group position=
               "{1 +count(current-group()[1]
                            /preceding-sibling::*
                                  [marker = 'start_group'])}"
               id="{marker/@id}">
                 <xsl:apply-templates select="current-group()"/>
               </group>
       </xsl:when>
       <xsl:otherwise>
        <xsl:apply-templates select="current-group()"/>
       </xsl:otherwise>
     </xsl:choose>
   </xsl:for-each-group>
  </root>
 </xsl:template>
</xsl:stylesheet>

When this XSLT 2.0 transformation is applied on the same XML document (above), the same correct result is produced.

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