0

I have the following list of items.

<items>
    <item type="Type1">Item1</item>
    <item type="Type2">Item2<item>
    <item type="Type2">Item3<item>
    <item type="Type3">Item4<item>
    <item type="Type3">Item5<item>
    <item type="Type1">Item6<item>
    <item type="Type3">Item7<item>
    <item type="Type1">Item8<item>
    <item type="Type2">Item9<item>
    <item type="Type1">Item10<item>
<items>

I'm having trouble figuring out the XSLT required so that the above are displayed in groups of Type1(x1), Type2(x2), Type3(x4), where the counts are the number in parenthesis or less. In other words, the goal is to create a repeating pattern: the next item of Type1 if any of those remain, then the next two items of Type2 or fewer if less than two remain, then the next four items of Type3 or fewer if less than four remain.

So the desired output would look something like the below:

<div class="Items">
    <div class="Type1">Item1</div>
    <div class="Type2">Item2</div>
    <div class="Type2">Item3</div>
    <div class="Type3">Item4</div>
    <div class="Type3">Item5</div>
    <div class="Type3">Item7</div>
    <div class="Type1">Item6</div>
    <div class="Type2">Item9</div>
    <div class="Type1">Item8</div>
    <div class="Type1">Item10</div>
</div>

From the above output, you can see that the ordering has changed. i.e. there is <=1 Type 1, followed by <=2 Type2, followed by <=4 Type3, and this pattern repeats itself. I suppose the items will need to be grouped into the pattern described and repeat itself until the full list if items are exhausted. I hope I make sense.

Can anyone provide the required XSLT or some pointers please?

Thanks, John.

John Fu
  • 1,812
  • 2
  • 15
  • 20
  • 1
    Can you add a little more context, I can't see how the ouput is ordered at all? They aren't grouped or ordered by class, and it seems that apart from switching Items 6 and 7, and 8 and 9, the output is the same. –  May 16 '13 at 02:55
  • As currently formulated, this isn't a real question -- it is difficult to make sense of the required (if any) rules that the transformation should implement. Please, edit and explain. – Dimitre Novatchev May 16 '13 at 03:03
  • Hi guys, I've edited the post. Thanks for looking into this. – John Fu May 16 '13 at 04:54

1 Answers1

0

Please give this a whirl:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>
  <xsl:key name="kType2Group" match="item[@type = 'Type2']"
           use="floor(count(preceding-sibling::item[@type = 'Type2']) div 2) + 1"/>
  <xsl:key name="kType3Group" match="item[@type = 'Type3']"
           use="floor(count(preceding-sibling::item[@type = 'Type3']) div 4) + 1"/>

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

  <xsl:template match="/*">
    <xsl:copy>
      <xsl:apply-templates select="item[@type = 'Type1']" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="item[@type = 'Type1']">
    <xsl:call-template name="Copy" />
    <xsl:apply-templates select="key('kType2Group', position())" />
    <xsl:apply-templates select="key('kType3Group', position())" />
  </xsl:template>
</xsl:stylesheet>

When run on your input XML, the result is:

<items>
  <item type="Type1">Item1</item>
  <item type="Type2">Item2</item>
  <item type="Type2">Item3</item>
  <item type="Type3">Item4</item>
  <item type="Type3">Item5</item>
  <item type="Type3">Item7</item>
  <item type="Type1">Item6</item>
  <item type="Type2">Item9</item>
  <item type="Type1">Item8</item>
  <item type="Type1">Item10</item>
</items>

The keys in this case are used to separate the Type2 and Type3 items into groups, so that they can be retrieved by their group number (1, 2, 3, etc.) the logic for determining the group numbers is to count the number of preceding items of the same type, divide that number by the number of items in each group, round down, and add one. So for example, this calculation carried out for the first four Type2s would be:

floor(0 div 2) + 1 = floor(0) + 1 = 0 + 1 = 1
floor(1 div 2) + 1 = floor(0.5) + 1 = 0 + 1 = 1
floor(2 div 2) + 1 = floor(1) + 1 = 1 + 1 = 2
floor(3 div 2) + 1 = floor(1.5) + 1 = 1 + 1 = 2

and so on.

The general logic of the XSLT is:

  • Match the root element, copy it, and apply templates to all Type1 items.
  • The template for Type1 items:
    • Copies the item itself.
    • Uses the key to apply templates to the Type2 items in its group (position() will be 1 for the first Type1 item, 2 for the second, and so on.
    • Uses the key to apply templates to the Type3 items in its group
  • The first template, the identity template, does the job of copying anything that doesn't have any other template to match it.
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • Thanks @JLRishe. That worked when I put it through http://xslttest.appspot.com/. As you can probably tell, I'm not very good with XSLT. It'd be great if you can give a me a bit of a walkthrough your solution? Especially around the use of keys. – John Fu May 16 '13 at 06:17
  • Added some elaboration above. – JLRishe May 16 '13 at 06:47
  • Awesome! This is some powerful stuff. I will try to apply this to the scenario I have at hand. Thanks again. – John Fu May 16 '13 at 06:52
  • Hi @JLRishe, I find that this solution only works when there are more Type1 items than the groups. eg. If I only had 1 Type1 item, it would only show a group of Type 2 and Type 3, but not the remaining Type2 and Type 3 groups, given that there are more of course. Similarly, in the case where there are no Type1 items, and only Type2 and Type3 items, this XSLT would display nothing. Any ideas how we can get around this? Many thanks. – John Fu May 20 '13 at 07:08
  • Good point. I will come up with an improved approach in the next 24 hours. Sorry for the oversight. – JLRishe May 20 '13 at 18:40