XSLT: группировка и сортировка.... как?
У меня есть XML-файл, который выглядит следующим образом...
<states>
<state>
<name>North Carolina</name>
<city>Charlotte</city>
</state>
<state>
<name>Alaska</name>
<city>Fairbanks</city>
</state>
<state>
<name>Virginia</name>
<city>Leesburg</city>
</state>
<state>
<name>Alaska</name>
<city>Coldfoot</city>
</state>
<state>
<name>North Carolina</name>
<city>Harrisburg</city>
</state>
<state>
<name>Virginia</name>
<city>Ashburn</city>
</state>
</states>
Мне нужно подготовить отчет, в котором перечислены все штаты, в алфавитном порядке с каждым городом, следующим.... например,..
Alaska - Fairbanks, Coldfoot
North Carolina - Charlotte, Harrisburg
Virginia - Leesburg, Ashburn
(города не должны быть в альфа-порядке, только штаты)
Я попытался решить эту проблему, выполнив для каждого состояния / состояния, отсортировав их по имени и обработав. Как это....
<xsl:for-each select="states/state">
<xsl:sort select="name" data-type="text" order="ascending"/>
<xsl:value-of select="name"/>-<xsl:value-of select="city"/>
</xsl:for-each>
Это дало мне....
Alaska - Fairbanks
Alaska - Coldfoot
North Carolina - Charlotte
North Carolina - Harrisburg
Virginia - Leesburg
Virginia - Ashburn
Сортировка сработала, теперь хочу сгруппировать. Единственное, что я мог подумать, это сравнить с предыдущим состоянием, так как оно отсортировано, оно должно распознавать, не изменилось ли значение состояния. Как это...
<xsl:for-each select="states/state">
<xsl:sort select="name" data-type="text" order="ascending"/>
<xsl:variable name="name"><xsl:value-of select="name">
<xsl:variable name="previous-name"><xsl:value-of select="(preceding-sibling::state)/name">
<xsl:if test="$name != $previous-name">
<br/><xsl:value-of select="name"/>-
</xsl:if>
<xsl:value-of select="city"/>
</xsl:for-each>
К сожалению, похоже, что функция предыдущего брата не очень хорошо работает с сортировкой, поэтому в первый раз (на первой Аляске) она увидела первую Северную Каролину в качестве предшествующего брата. Это вызывает некоторые странные результаты, которые мне совсем не понравились.
Итак, я использую XSLT1.0... Любые мысли / предложения?
Спасибо
3 ответа
Эта таблица стилей:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kStateByName" match="state" use="name"/>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates
select="/*/state[count(.|key('kStateByName',name)[1])=1]">
<xsl:sort select="name"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="state">
<xsl:value-of select="concat(name,' - ')"/>
<xsl:apply-templates select="key('kStateByName',name)/city"/>
</xsl:template>
<xsl:template match="city">
<xsl:value-of select="concat(.,substring(', ',
1 div (position()!=last())),
substring('
',
1 div (position()=last())))"/>
</xsl:template>
</xsl:stylesheet>
Выход:
Alaska - Fairbanks, Coldfoot
North Carolina - Charlotte, Harrisburg
Virginia - Leesburg, Ashburn
Примечание: группировка по названию штата. Выражение подстроки разделителя работает только в стиле pull (применение шаблонов к городу)
Решение XSLT 2.0:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="states">
<xsl:for-each-group select="state" group-by="name">
<xsl:sort select="name"/>
<xsl:value-of select="concat(name,
' - ',
string-join(current-group()/city,', '),
'
')"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
Просто для удовольствия, это выражение XPath 2.0:
string-join(for $state in distinct-values(/*/*/name)
return concat($state,
' - ',
string-join(/*/*[name=$state]/city,
', ')),
'
')
Для группировки в XSLT 1.0 вам, вероятно, придется использовать метод Мюнхена. Это может быть трудно понять, но как только вы добьетесь успеха, вам будет хорошо идти.
Это вернет отдельный список состояний:
<xsl:for-each select="states/state">
<xsl:sort select="name" />
<xsl:if test="not(name = preceding-sibling::state/name)" >
<xsl:value-of select="name" />
</xsl:if>
</xsl:for-each>
Я использовал ваш пример XML, создал небольшую таблицу стилей с вышеприведенным, провел через Xalan-j, и он возвращает:
Аляска Северная Каролина Вирджиния
Таким образом, оттуда вы сможете применить шаблон или другой цикл for-each, чтобы получить список городов для каждого отдельного штата.
Крис