Это пример проблемы группировки, и в XSLT 1.0 общепринятым способом группировки является использование мюнхианской группировки. К сожалению, ваш сценарий требует поиска символов нижнего регистра поверх этого, а это немного запутанно в XSLT 1.0.
Тем не менее, я подготовил решение, и оно выглядит следующим образом:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" doctype-system="about:legacy-compat"
encoding="UTF-8" indent="yes" />
<xsl:key name="kEntryInitial" match="entry/@term"
use="translate(substring(., 1, 1),
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz')"/>
<xsl:template match="/">
<html>
<head></head>
<body>
<!-- Jump into the data.xml DOM so that keys work -->
<xsl:apply-templates select="document('data.xml')/glossary" />
</body>
</html>
</xsl:template>
<xsl:template match="/glossary">
<!-- Select terms with distinct initials (case invariant) -->
<xsl:variable name="termsByDistinctInitial"
select="entry/@term[generate-id() =
generate-id(key('kEntryInitial',
translate(substring(., 1, 1),
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz'))[1])]" />
<!-- Header -->
<xsl:apply-templates select="$termsByDistinctInitial" mode="header">
<xsl:sort select="." data-type="text" order="ascending" />
</xsl:apply-templates>
<!-- Glossary -->
<dl>
<xsl:apply-templates select="$termsByDistinctInitial" mode="main">
<xsl:sort select="." data-type="text" order="ascending" />
</xsl:apply-templates>
</dl>
</xsl:template>
<xsl:template match="@term" mode="header">
<xsl:variable name="initial">
<xsl:call-template name="ToLower">
<xsl:with-param name="value" select="substring(., 1, 1)" />
</xsl:call-template>
</xsl:variable>
<a href="#{$initial}">
<xsl:value-of select="$initial" />
</a>
<xsl:if test="position() != last()">
<xsl:text> |</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="@term" mode="main">
<xsl:variable name="initial">
<xsl:call-template name="ToLower">
<xsl:with-param name="value" select="substring(., 1, 1)" />
</xsl:call-template>
</xsl:variable>
<a name="{$initial}">
<h1>
<xsl:value-of select="$initial" />
</h1>
</a>
<xsl:apply-templates select="key('kEntryInitial', $initial)/.." />
</xsl:template>
<xsl:template match="entry">
<dt>
<xsl:apply-templates select="@term"/>
</dt>
<dd>
<xsl:apply-templates select="@definition"/>
</dd>
</xsl:template>
<xsl:template name="ToLower">
<xsl:param name="value" />
<xsl:value-of select="translate(substring($value, 1, 1),
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz')"/>
</xsl:template>
</xsl:stylesheet>
При запуске на входном XML это дает следующее:
<!DOCTYPE html SYSTEM "about:legacy-compat">
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body><a href="#a">a</a> |<a href="#b">b</a> |<a href="#c">c</a> |<a href="#o">o</a>
<dl><a name="a"><h1>a</h1></a><dt>apple</dt>
<dd>A red fruit with seeds</dd>
<dt>avocado</dt>
<dd>A mellow fruit enjoyed in guacamole</dd><a name="b"><h1>b</h1></a><dt>banana</dt>
<dd>A tropical yellow fruit</dd><a name="c"><h1>c</h1></a><dt>cantaloupe</dt>
<dd>A kind of melon</dd>
<dt>Cherry</dt>
<dd>A red fruit that grows in clusters </dd>
<dt>cranberry</dt>
<dd>A sour berry enjoyed at Thanksgiving</dd><a name="o"><h1>o</h1></a><dt>orange</dt>
<dd>An orange citrus fruit</dd>
</dl>
</body>
</html>
Одна вещь, которую я бы предложил рассмотреть, — это использование простого XSLT для «подготовки» вашего глоссария с инициалами:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="entry">
<xsl:copy>
<xsl:attribute name="initial">
<xsl:value-of select="translate(substring(@term, 1, 1),
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz')"/>
</xsl:attribute>
<xsl:apply-templates select="@* | node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Это производит:
<glossary>
<entry initial="c" term="cantaloupe" definition="A kind of melon" />
<entry initial="b" term="banana" definition="A tropical yellow fruit" />
<entry initial="a" term="apple" definition="A red fruit with seeds" />
<entry initial="o" term="orange" definition="An orange citrus fruit" />
<entry initial="c" term="Cherry" definition="A red fruit that grows in clusters " />
<entry initial="c" term="cranberry" definition="A sour berry enjoyed at Thanksgiving" />
<entry initial="a" term="avocado" definition="A mellow fruit enjoyed in guacamole" />
</glossary>
затем, если вы используете эту подготовленную версию в качестве глоссария, основной XSLT может избавиться от всех этих уродливых translate()
функций и станет намного чище:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" doctype-system="about:legacy-compat"
encoding="UTF-8" indent="yes" />
<xsl:key name="kEntryInitial" match="entry/@initial" use="."/>
<xsl:template match="/">
<html>
<head></head>
<body>
<!-- Jump into the data.xml DOM so that keys work -->
<xsl:apply-templates select="document('data2.xml')/glossary" />
</body>
</html>
</xsl:template>
<xsl:template match="/glossary">
<!-- Select terms with distinct initials (case invariant) -->
<xsl:variable name="termsByDistinctInitial"
select="entry/@initial[generate-id() =
generate-id(key('kEntryInitial', .)[1])]" />
<!-- Header -->
<xsl:apply-templates select="$termsByDistinctInitial" mode="header">
<xsl:sort select="." data-type="text" order="ascending" />
</xsl:apply-templates>
<!-- Glossary -->
<dl>
<xsl:apply-templates select="$termsByDistinctInitial" mode="main">
<xsl:sort select="." data-type="text" order="ascending" />
</xsl:apply-templates>
</dl>
</xsl:template>
<xsl:template match="@initial" mode="header">
<a href="#{.}">
<xsl:value-of select="." />
</a>
<xsl:if test="position() != last()">
<xsl:text> |</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="@initial" mode="main">
<a name="{.}">
<h1>
<xsl:value-of select="." />
</h1>
</a>
<xsl:apply-templates select="key('kEntryInitial', .)/.." />
</xsl:template>
<xsl:template match="entry">
<dt>
<xsl:apply-templates select="@term"/>
</dt>
<dd>
<xsl:apply-templates select="@definition"/>
</dd>
</xsl:template>
</xsl:stylesheet>
Конечно, конечный результат такой же, как и в первом примере. Если ваш XSLT-процессор поддерживает функцию node-set()
, также можно выполнить оба этих шага обработки в одном XSLT.
person
JLRishe
schedule
29.01.2013
xsl:sort
различает прописные и строчные буквы, но это на самом деле не проблема. Нижний регистр важен, потому что (1) мюнхенский метод группировки будет рассматривать их как отдельные символы, если они не имеют последовательного регистра, и (2) вам нужно иметь одинаковый регистр в ваших заголовках. - person JLRishe   schedule 29.01.2013preceding-sibling
заняла более 40 минут, а мюнхенский подход завершился за 2 секунды. С точки зрения информатики, мюнхен имеет временную сложность O(N), в то время как временная сложность группировки сравнения братьев и сестер квадратична — O(N^2). - person JLRishe   schedule 29.01.2013<xsl:for-each>
не обязательно должен быть реализован XSLT-процессором как последовательный цикл, процессор может вычислять значения различных итераций в любом порядке или даже параллельно в нескольких потоках, пока конечный вывод производится в правильном порядке (например, он может обрабатывать элементы в порядке документа, а затем применятьsort
в конце при выводе фрагментов). - person Ian Roberts   schedule 29.01.2013for-each
с его собственными предыдущими элементами того же уровня в порядке документа, но нет способа получить доступ к предыдущему элементу в порядке, установленном<xsl:sort>
я> - person Ian Roberts   schedule 29.01.2013<xsl:for-each select="item[not(. = preceding-sibling::item)]"><xsl:sort select="." />...</xsl:for-each>
Не могу понять, как это сделать с частичными значениями, так что это может даже не быть вариант в этом случае. - person JLRishe   schedule 29.01.2013