Hi,

I have an xml like this

<agency count="133">GOV.NIH.XYZ.465.56</agency>
<agency count="129">GOV.NIH.ABC.245.21</agency>
<agency count="216">GOV.NIH.NCI.456.23</agency>
<agency count="48">GOV.USDA.4412.345.08</agency>
<agency count="46">GOV.USDA.1411.345.00</agency>
<agency count="51">GOV.USDA.5512.231.05</agency>
<agency count="42">GOV.USDA.1411.16.10</agency>
<agency count="39">GOV.USDA.5512.16.12</agency>
<agency count="32">GOV.USDA.1411.17.03</agency>
<agency count="60">GOV.FDA.4412.33.56</agency>
<agency count="59">GOV.FDA.4410.45.87</agency>
<agency count="55">GOV.FDA.1411.56.213</agency>
<agency count="60">GOV.FDA.1412.86.45</agency>
<agency count="55">GOV.FDA.1431.32.45</agency>

I need to sort them based on the coutn for each agency .

The results would like something like this

Top 3 agencies

GOV.NIH (478)

GOV.NIH.NCI.456(216)
GOV.NIH.XYZ.465(133)
GOV.NIH.ABC.245(129)


GOV.FDA(289)

GOV.FDA.1412.86(60)
GOV.FDA.4412.33(60)
GOV.FDA.4410.45(59)


GOV.USDA(238)

GOV.USDA.5512.231(51)
GOV.USDA.4412.345(48)
GOV.USDA.1411.345(46)

I don't have the luxury of using XSLT2.0 grouping.

Can someone please tell me how can I achieve this in XSLT1.0?

Thank you.
Saraswathy.

Dani AI

Generated

Good practical answer from : the multi‑pass pattern (sort → group → render) is an easy-to-understand way to get working XSLT 1.0 output. For a production-ready stylesheet, two improvements are worth noting: compute and sort groups by their numeric totals, and prefer the Muenchian grouping pattern for performance and portability.

A concise Muenchian sketch (group by the first two dot-separated tokens, sort groups by their summed counts, then emit the top three agencies per group):

<xsl:key name="kGroup" match="agency"
         use="concat(substring-before(.,'.'),'.',
                     substring-before(substring-after(. ,'.'),'.'))"/>

<xsl:for-each select="agency[generate-id() = generate-id(
             key('kGroup',
                 concat(substring-before(.,'.'),'.',
                        substring-before(substring-after(. ,'.'),'.')))[1])]">
  <xsl:sort select="sum(key('kGroup',
               concat(substring-before(.,'.'),'.',
                      substring-before(substring-after(. ,'.'),'.')))/@count)"
            data-type="number" order="descending"/>
  <xsl:variable name="groupKey" select="concat(substring-before(.,'.'),'.',
                                               substring-before(substring-after(. ,'.'),'.'))"/>
  <xsl:value-of select="concat($groupKey,' (', sum(key('kGroup',$groupKey)/@count),')')"/>

  <xsl:for-each select="key('kGroup',$groupKey)">
    <xsl:sort select="number(@count)" data-type="number" order="descending"/>
    <xsl:if test="position() &lt;= 3">
      <xsl:value-of select="concat(.,' (',@count,')')"/>
    </xsl:if>
  </xsl:for-each>
</xsl:for-each>

Notes and pitfalls: result-tree-fragments created in a variable must be converted to node-sets (EXSLT exsl:node-set()) on many XSLT 1.0 processors before applying templates. sum() and numeric xsl:sort expect valid numeric strings—sanitize attributes (remove thousands separators) before summing if necessary. If agency naming is inconsistent (different token counts, extra whitespace), normalize with normalize-space() and guard substring logic so group keys stay correct.

Overall: the multi‑pass example from shows the approach; for larger inputs or production use, switch to Muenchian grouping, numeric sorts on @count, and explicit summing of group totals so groups are ordered by total count as in the desired output.

Recommended Answers

All 3 Replies

So, this really is challenging in XSLT 1.0. You basically have to break it down into phases of sorting, grouping, then processing. In 2.0, you can do all of this in basically one pass.

The solution posted below, is broken into three steps. The first step is to take the input and sort them into the correct order. These are all the templates that run in no mode at all. We store that result tree in a variable, and use it as the input to the next part. Next we take the sorted result and divide them into "Groups" and wrap each group with a "Group" element. This was the difficult part that can be easily done in XSLT 2.0. These are the templates in the "Group" mode. We store that result in a variable tree, and use it as the input to the next part. This part take the sorted groups and generates an html output grabbing the first 3 agents of each sorted group. I'm sure there's a better way to do this, even in 1.0, but this illustrates the idea and steps you'll have to take to get there.

Input Document.

<agencies>
	<agency count="133">GOV.NIH.XYZ.465.56</agency>
	<agency count="42">GOV.USDA.1411.16.10</agency>
	<agency count="55">GOV.FDA.1411.56.213</agency>
	<agency count="60">GOV.FDA.1412.86.45</agency>
	<agency count="129">GOV.NIH.ABC.245.21</agency>
	<agency count="48">GOV.USDA.4412.345.08</agency>
	<agency count="46">GOV.USDA.1411.345.00</agency>
	<agency count="39">GOV.USDA.5512.16.12</agency>
	<agency count="216">GOV.NIH.NCI.456.23</agency>
	<agency count="32">GOV.USDA.1411.17.03</agency>
	<agency count="60">GOV.FDA.4412.33.56</agency>
	<agency count="59">GOV.FDA.4410.45.87</agency>
	<agency count="51">GOV.USDA.5512.231.05</agency>
	<agency count="55">GOV.FDA.1431.32.45</agency>
</agencies>

Transformation

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:output indent="yes" method="html"/>

	<xsl:template match="/">
		<xsl:variable name="sortedList">
			<xsl:apply-templates select="agencies"/>
		</xsl:variable>
		<xsl:variable name="groupedList">
			<xsl:apply-templates select="$sortedList" mode="Groups"/>
		</xsl:variable>
		<xsl:apply-templates select="$groupedList/agencies" mode="getTop3"/>
	</xsl:template>

	<xsl:template match="agencies">
		<xsl:copy>
			<xsl:apply-templates select="agency">
				<xsl:sort select="."/>
			</xsl:apply-templates>
		</xsl:copy>
	</xsl:template>

	<xsl:template match="agency">
		<xsl:copy>
			<xsl:copy-of select="@*"/>
			<xsl:value-of select="."/>
		</xsl:copy>
	</xsl:template>

	<xsl:template match="/" mode="Groups">
		<agencies>
			<xsl:apply-templates select="agencies" mode="Groups">
				<xsl:with-param name="pos" select="'1'"/>
			</xsl:apply-templates>
		</agencies>
	</xsl:template>

	<xsl:template match="agencies" mode="Groups">
		<xsl:param name="pos"/>
		<xsl:variable name="groupKey" select="substring-before(agency[position() = $pos],concat('.',substring-after(substring-after(agency[position() = $pos],'.'),'.')))"/>
		<xsl:variable name="matchedSetNo" select="count(agency[starts-with(.,$groupKey)])"/>
		<Group>
			<xsl:for-each select="agency[starts-with(.,$groupKey)]">
				<xsl:copy-of select="."/>
			</xsl:for-each>
		</Group>
		<xsl:if test="($pos + $matchedSetNo) &lt;= (count(agency))">
			<xsl:apply-templates select="../agencies" mode="Groups">
				<xsl:with-param name="pos" select="$pos + $matchedSetNo"/>
			</xsl:apply-templates>
		</xsl:if>
	</xsl:template>

	<xsl:template match="agencies" mode="getTop3">
		<html>
			<body>
				<h1>Top 3 Agencies</h1>
				<br/>
				<xsl:apply-templates select="Group" mode="getTop3"/>
			</body>
		</html>
	</xsl:template>

	<xsl:template match="Group" mode="getTop3">
		<h2>
			<xsl:value-of select="concat(substring-before(agency[1],'.'),'.',substring-before(substring-after(agency[1],'.'),'.'))"/>
		</h2>
		<br/>
		<xsl:apply-templates select="agency[1]|agency[2]|agency[3]" mode="getTop3"/>
	</xsl:template>

	<xsl:template match="agency" mode="getTop3">
		<xsl:value-of select="concat(.,'(',@count,')')"/>
		<br/>
	</xsl:template>
</xsl:stylesheet>

Output HTML Code

<html>
	<body>
		<h1>Top 3 Agencies</h1>
		<br/>
		<h2>GOV.FDA</h2>
		<br/>GOV.FDA.1411.56.213(55)
		<br/>GOV.FDA.1412.86.45(60)
		<br/>GOV.FDA.1431.32.45(55)
		<br/>
		<h2>GOV.NIH</h2>
		<br/>GOV.NIH.ABC.245.21(129)
		<br/>GOV.NIH.NCI.456.23(216)
		<br/>GOV.NIH.XYZ.465.56(133)
		<br/>
		<h2>GOV.USDA</h2>
		<br/>GOV.USDA.1411.16.10(42)
		<br/>GOV.USDA.1411.17.03(32)
		<br/>GOV.USDA.1411.345.00(46)
		<br/>
	</body>
</html>

Good luck!

You are awesome.

Thank you very much for such a quick and timely solution to this complex problem I've been facing for days.

Hahahaha. You're welcome. My solution is ugly as sin and definitely quick (and dirty). I take no responsibility for it, and the problems it may cause you, at all.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.