Search code examples
xmlxsltcount

Trying to count() number of certain XML elements


I have an XML file with thousands of prisons like this:

<bases xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<marker>
        <name>United States Penitentiary, Canaan</name>
        <adr>Canaan</adr>
        <state>Pennsylvania</state>
        <geo>41.562778, -75.421389</geo>
        <tag>Federal</tag>
        <wiki>United_States_Penitentiary,_Canaan</wiki>
</marker>
<marker>
        <name>Mohawk Correctional Facility</name>
        <adr>Rome</adr>
        <state>New York</state>
        <geo>43.184511, -75.483992</geo>
        <tag>State</tag>
        <wiki>Mohawk_Correctional_Facility</wiki>
</marker>
...
</bases>

I want to produce counts of the number of prisons whose <tag> is "Federal". I need to produce totals for eight values. I'm using an XSLT v1.0 processor (xmlstarlet) and I'm new to count().

I have so many examples of XSL that haven't worked, I'm not sure which to include here. (I've been at this for hours.) Here's one that doesn't work:

<xsl:template match="/bases/marker[tag='Federal']">
        TOTAL = <xsl:value-of select="count(.)"/>
</xsl:template>

Help, please.

Here's the solution from Sebastien that solves the count() problem:

<xsl:template match="/">
    TOTAL = <xsl:value-of select="count(//bases/marker[tag='Federal']) "/>
</xsl:template>

But I have multiple values of <tag> to process, and this doesn't work - States is equal to zero when there are hundreds:

<xsl:template match="/">
        Federal = <xsl:value-of select="count(//bases/marker[tag='Federal']) "/>
        State = <xsl:value-of select="count(//bases/marker[tag='Statel']) "/>
</xsl:template>

Solution

  • Here's an example of Muenchian grouping in XSLT 1.0 to go along with the link in the comments.

    Input (added another marker to show proper counting of State)

    <bases xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <marker>
            <name>United States Penitentiary, Canaan</name>
            <adr>Canaan</adr>
            <state>Pennsylvania</state>
            <geo>41.562778, -75.421389</geo>
            <tag>Federal</tag>
            <wiki>United_States_Penitentiary,_Canaan</wiki>
        </marker>
        <marker>
            <name>Mohawk Correctional Facility</name>
            <adr>Rome</adr>
            <state>New York</state>
            <geo>43.184511, -75.483992</geo>
            <tag>State</tag>
            <wiki>Mohawk_Correctional_Facility</wiki>
        </marker>
        <marker>
            <name>Casa Bonita</name>
            <adr>Lakewood</adr>
            <state>CO</state>
            <geo>39.741927, -105.070828</geo>
            <tag>State</tag>
            <wiki>Casa_Bonita</wiki>
        </marker>
    </bases>
    

    XSLT 1.0

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="text"/>
        <xsl:strip-space elements="*"/>
        
        <xsl:key name="marker_by_tag" match="marker" use="tag"/>
        
        <xsl:template match="/*">
            <xsl:for-each select="marker[count(.|key('marker_by_tag',tag)[1])=1]">
                <xsl:value-of select="concat(tag,' = ',count(key('marker_by_tag',tag)),'&#xA;')"/>
            </xsl:for-each>
        </xsl:template>
        
    </xsl:stylesheet>
    

    Output

    Federal = 1
    State = 2