2

Let's say that I have the following XML file:

<Persons>
    <Person>
        <Name>A</Name>
        <LastName>AAA</LastName>
    </Person>
    <Person>
        <Name>B</Name>
        <LastName>BBB</LastName>
    </Person>
    <Person>
        <Name>C</Name>
        <LastName>AAA</LastName>
    </Person>
    <Person>
        <Name>D</Name>
        <LastName>AAA</LastName>
    </Person>
    <Person>
        <Name>E</Name>
        <LastName>BBB</LastName>
    </Person>
</Persons>

I need to produce a table with all of the families (last names), and the number of people that has each last name. As for the example, the table should look like that:

Family - People

AAA - 3

BBB - 2

How can I make a loop that will run on every last name?

Mathias Müller
  • 22,203
  • 13
  • 58
  • 75
Nir H.
  • 23
  • 3
  • Have you tried something? Please post the code here. – Lingamurthy CS Aug 17 '15 at 12:07
  • I suppose you need something dynamic, meaning you don't know how many different last names there are? – Kilazur Aug 17 '15 at 12:18
  • 1
    What version of XSLT are you using? In XSLT 2.0 this is likely significantly easier to do (as is has `xsl:for-each-group`). Also note that "loops" are not a concept in XSLT. Instead, it applies templates on selections, the looping is done internally (though you can force a loop-like approach using `xsl:for-each`, but it is often not necessary). – Abel Aug 17 '15 at 12:55
  • @Kilazur , Sorry for the late response. Yes, I need a dynamic code. How can I use for-each-group where the last names are the groups? can I have an example? Thank you! – Nir H. Aug 18 '15 at 08:44

2 Answers2

1

There are many ways of doing this, here's one approach. It uses Muenchian grouping (thanks, Michael) and it is (relatively) straightforward. If you need guidance understanding the code, you should probably start with some tutorial on matching templates, on keys, or on grouping with for-each (not used in this example).

The below code adds a count attribute per Person for how many have the same surname:

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

    <xsl:output indent="yes" />

    <xsl:key match="LastName" use="text()" name="lastname" />

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

    <xsl:template match="Person[generate-id() = key('lastname', LastName)[1]]">
        <xsl:copy>            
            <xsl:attribute name="count">
                <xsl:value-of select="count(key('lastname', current()/LastName))" />
            </xsl:attribute>
            <xsl:copy-of select="*" />
        </xsl:copy>
    </xsl:template>

    <!-- remove nodes we are not interested in, incl. whitespace only text nodes -->
    <xsl:template match="node()" >
        <xsl:apply-templates />
    </xsl:template>

</xsl:stylesheet>

With your input it generates:

<?xml version="1.0" encoding="UTF-8"?>
<Persons>
   <Person count="3">
      <Name>A</Name>
      <LastName>AAA</LastName>
   </Person>
   <Person count="2">
      <Name>B</Name>
      <LastName>BBB</LastName>
   </Person>
</Persons>

PS: In XSLT 2.0 you can achieve the same slightly easier with xsl:for-each-group.

Community
  • 1
  • 1
Abel
  • 56,041
  • 24
  • 146
  • 247
  • Thanks for the response! The output need to be a HTML file (and the code need to go through a much bigger xml file), so instead of playing the XML file, Do I need to create a map (in which the key will be the last names and the value is the number of persons)? If I do, how can I add the keys in the code, because I can't really know / write all the last names. It doesn't matter if I use XSLT 1.0 or 2.0. Thank you, I really appreciate all of your help! – Nir H. Aug 17 '15 at 14:49
  • @NirH., if you can, then you should switch to XSLT 2.0 if you haven't already done it. Then simply use `xsl:for-each-group` (the context help in your oXygen or Stylus Studio will help you), no need to mess with keys. It also doesn't matter whether you create XML, HTML or Text, that's all up to you and irrespective of the chose approach. – Abel Aug 17 '15 at 15:38
  • "*Not entirely sure it is "Muenchian grouping"*" No, it is not. Which is a pity, since you have already set up the necessary key, and all you need to do is replace the "very inefficient"(quoted from Jeni Tennison's article) expression `"Person[not(LastName = preceding-sibling::Person/LastName)]` with one of the Muenchian alternatives. – michael.hor257k Aug 17 '15 at 16:31
0

The easiest way I've found to do this in the past is a technique called Muenchian Grouping. Details are here (http://www.jenitennison.com/xslt/grouping/muenchian.html). The technique involves first generating your unique keys (surname in your case), and then selecting the elements who meet that key.

Chris Disley
  • 1,286
  • 17
  • 30