#xslt
#xslt
Вопрос:
У меня есть XML-файл:
<document>
<line id="0">
<field id="2">X111</field>
<field id="3">1</field>
<field id="4">222222222222</field>
</line>
<line id="1">
<field id="2">X111</field>
<field id="3">1</field>
<field id="4">111111111111</field>
</line>
<line id="2">
<field id="2">X222</field>
<field id="3">1</field>
<field id="4">111111111111</field>
</line>
<line id="3">
<field id="2">X222</field>
<field id="3">1></field>
<field id="4">111111111111</field>
</line>
<line id="4">
<field id="2">X333</field>
<field id="3">1</field>
<field id="4">111111111111</field>
</line>
</document>
Из этого xml-файла я должен сгруппировать field2 (после этого field4 ), вопрос будет заключаться не в том, как сгруппировать поле 2 и получить три документа, а в том, как сгруппировать field4, если они совпадают?
Вывод:
<document>
<Result>
<Header>
<Field2>X111</Field2>
</Header>
<Line>
<Position>1</Position>
<Field4>222222222222</Field4>
<Sum>1<Sum>
<Position>2</Position>
<Field4>111111111111</Field4>
<Sum>1<Sum>
</Line>
</Result>
<Result>
<Header>
<Field2>X222</Field2>
</Header>
<Line>
<Position>1</Position>
<Field4>111111111111</Field4>
<Sum>2<Sum>
</Line>
</Result>
<Result>
<Header>
<Field2>X333</Field2>
</Header>
<Line>
<Position>1</Position>
<Field4>111111111111</Field4>
<Sum>1</Sum>
</Line>
</Result>
</document>
Я застрял в группировке строк, я не знал, как группировать одинаковые и разные поля, идентификатор которых = 4.
Моя программа выглядит:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:key name="kLine" match="line" use="string(field[@id=2])"/>
<xsl:key name="bLine" match="line" use="field[@id=4]"/>
<xsl:template match="document">
<document>
<xsl:apply-templates select="line[count( . | key('kLine', string(field[@id='2']))[1]) = 1]"/>
</document>
</xsl:template>
<xsl:template match="line">
<xsl:variable name="field2" select="field[@id='2']"/>
<result>
<Header>
<xsl:value-of select="field[@id='2']"/>
</Header>
<Line>
<xsl:for-each select="//line[field[@id='2']=$field2]">
<Position>
<xsl:value-of select="position()"/>
</Position>
<Field4><xsl:value-of select="field[@id='4']"/></Field4>
<Sum><xsl:value-of select="sum(key('bLine', field[@id='4'])/field[@id='3'])"/></Sum>
</xsl:for-each>
</Line>
</result>
</xsl:template>
</xsl:stylesheet>
Ответ №1:
Вы спрашиваете, как группировать на нескольких уровнях. Следующая таблица стилей:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="byField2" match="line" use="field[@id='2']" />
<xsl:key name="byField2AndField4" match="line"
use="concat(field[@id='2'], '|', field[@id='4'])" />
<xsl:template match="/">
<document><xsl:apply-templates /></document>
</xsl:template>
<xsl:template
match="line[generate-id() =
generate-id(key('byField2', field[@id='2'])[1])]">
<Result>
<Header>
<Field2><xsl:value-of select="field[@id='2']" /></Field2>
</Header>
<Line>
<xsl:apply-templates
select="key('byField2', field[@id='2'])
[generate-id() =
generate-id(key('byField2AndField4',
concat(field[@id='2'], '|', field[@id='4']))[1])]"
mode="field4" />
</Line>
</Result>
</xsl:template>
<xsl:template match="line" mode="field4">
<Position><xsl:value-of select="position()" /></Position>
<Field4><xsl:value-of select="field[@id='4']" /></Field4>
<Sum>
<xsl:value-of
select="sum(key('byField2AndField4',
concat(field[@id='2'], '|', field[@id='4']))/
field[@id='3'])" />
</Sum>
</xsl:template>
<xsl:template match="line" />
</xsl:stylesheet>
На этом входе:
<document>
<line id="0">
<field id="2">X111</field>
<field id="3">1</field>
<field id="4">222222222222</field>
</line>
<line id="1">
<field id="2">X111</field>
<field id="3">1</field>
<field id="4">111111111111</field>
</line>
<line id="2">
<field id="2">X222</field>
<field id="3">1</field>
<field id="4">111111111111</field>
</line>
<line id="3">
<field id="2">X222</field>
<field id="3">1></field>
<field id="4">111111111111</field>
</line>
<line id="4">
<field id="2">X333</field>
<field id="3">1</field>
<field id="4">111111111111</field>
</line>
</document>
Выдает желаемый результат:
<document>
<Result>
<Header>
<Field2>X111</Field2>
</Header>
<Line>
<Position>1</Position>
<Field4>222222222222</Field4>
<Sum>1</Sum>
<Position>2</Position>
<Field4>111111111111</Field4>
<Sum>1</Sum>
</Line>
</Result>
<Result>
<Header>
<Field2>X222</Field2>
</Header>
<Line>
<Position>1</Position>
<Field4>111111111111</Field4>
<Sum>2</Sum>
</Line>
</Result>
<Result>
<Header>
<Field2>X333</Field2>
</Header>
<Line>
<Position>1</Position>
<Field4>111111111111</Field4>
<Sum>1</Sum>
</Line>
</Result>
</document>
Мы используем два ключа. Первые группируются только по полю 2. Второй группирует путем объединения полей 2 и 4.
Комментарии:
1. @lwburk: Во втором выборе ключа отсутствует
[1]
предикат.2. @Alejandro — Забавно, что ты упомянул об этом. Я заметил это несколько минут назад и был удивлен, что это сработало в моих предыдущих тестах. Перечитывая спецификацию, я вижу, что
generate-id
возвращает идентификатор для первого узла в наборе в порядке документов, вот почему это все еще работает так, как ожидалось. Есть ли какое-то преимущество во включении дополнительного[1]
предиката?3. @lwburk: Другая причина, помимо удобства чтения, заключается в том, что неявное приведение набора узлов к singleton не является частью XPath 2.0, поэтому вызывает ошибку. Это хорошо, потому что тихое неявное приведение может скрыть некоторые логические ошибки.
4. @Alejandro — Обновлено на основе аргумента удобочитаемости, который достаточно убедителен. Однако меня смущает другая ваша причина. Описываемое мной поведение описано в рекомендации XSLT 1.0: «Функция generate-id возвращает строку, которая однозначно идентифицирует узел в наборе узлов аргумента, который является первым в порядке документа».
5. @lwburk: От w3.org/TR/xpath20/#id-incompat-in-false-mode : «Когда набор узлов, содержащий более одного узла, предоставляется в качестве аргумента функции или оператора, который ожидает один узел или значение, правило XPath 1.0 заключалось в том, что все узлы после первого отбрасывались. В XPath 2.0 ошибка типа возникает, если имеется более одного узла. Поведение XPath 1.0 всегда можно восстановить, используя предикат
[1]
для явного выбора первого узла в наборе узлов.» Но опять же, это всего лишь стиль и условности…