В stackoverflow есть похожие вопросы, но ответы сортируют или группируют совпадающие элементы, выводя их из последовательности по сравнению с порядком ввода.
У меня есть следующие данные
<doc>
<paragraph indent = "0" stylename = "heading_l1_toc" SDgroup = "heading">heading-l1</paragraph>
<paragraph indent = "0" stylename = "heading_l2_toc" SDgroup = "heading">heading-l2</paragraph>
<paragraph indent = "0" stylename = "list" SDgroup = "list">AAA. Para 1.</paragraph>
<paragraph indent = "1" stylename = "list" SDgroup = "list">BBB. Para 2</paragraph>
<paragraph indent = "2" stylename = "continued" SDgroup = "list">BBB. Continued para1</paragraph>
<paragraph indent = "2" stylename = "continued" SDgroup = "list">BBB. Continued para2</paragraph>
<paragraph indent = "2" stylename = "list" SDgroup = "list">CCC. Para 3a</paragraph>
<paragraph indent = "2" stylename = "list" SDgroup = "list">DDD. Para 3b/4</paragraph>
<paragraph indent = "1" stylename = "list" SDgroup = "list">EEE. Para 5</paragraph>
<paragraph indent = "2" stylename = "continued" SDgroup = "list">EEE. Continued para</paragraph>
<paragraph indent = "0" stylename = "list" SDgroup = "list">FFF. Para 6</paragraph>
</doc>
Мне нужно вложить абзац[@stylename='list'} и следующий абзац[@stylename='continued'] на основе list/@indent = x и continue/@indent = x + 1.
Вывод должен выглядеть так:
<doc>
<paragraph indent = "0" stylename = "heading_l1_toc" SDgroup = "heading">heading-l1</paragraph>
<paragraph indent = "0" stylename = "heading_l2_toc" SDgroup = "heading">heading-l2</paragraph>
<List indent = "0" maxItems = "2">
<paragraph stylename = "list_unordered" SDgroup = "list">AAA. Para 1.</paragraph>
<List indent = "1" maxItems = "1">
<paragraph indent = "1" stylename = "list_unordered" SDgroup = "list">BBB. Para 2</paragraph>
<paragraph indent = "2" stylename = "continued" SDgroup = "list">BBB. Continued para1</paragraph>
<paragraph indent = "2" stylename = "continued" SDgroup = "list">BBB. Continued para2</paragraph>
<List indent = "2" maxItems = "2">
<paragraph indent = "2" stylename = "list_unordered" SDgroup = "list">CCC. Para 3a</paragraph>
<paragraph indent = "2" stylename = "list_unordered" SDgroup = "list">DDD. Para 34</paragraph>
</List>
<paragraph indent = "1" stylename = "list_unordered" SDgroup = "list">EEE. Para 5</paragraph>
<paragraph indent = "2" stylename = "continued" SDgroup = "list">EEE. Continued para</paragraph>
</List>
<paragraph indent = "0" stylename = "list_unordered" SDgroup = "list">FFF. Para 6</paragraph>
</List>
</doc>
Я перепробовал много, много версий для каждой группы + группы по/смежным и обычным для каждого, но в итоге получил неупорядоченный вывод и/или правильно вложенный вывод, но дублирующие данные (т. е. элемент правильно вложен и затем в какой-то момент тот же элемент выводится снова Я полагаю, что это происходит с многократно вложенными циклами for-each.
Я на 99,9% уверен, что это выполнимо, но не могу найти решение, которое дает то, что мне нужно. Еще раз прошу помощи, которая, конечно же, будет очень, очень признательна.
К вашему сведению: у меня нет контроля над входными данными или средствами их изменения, если только они не находятся в XSLT.





Я пытался понять, что делает ваш пример, но я не совсем понял. На случай, если это чем-то поможет, я применил подход рекурсивной функции.
Одна вещь, которую я не вижу в вашем примере, - это серия уровней увеличения/уменьшения/увеличения отступа, поэтому мне не с чем проверять, но метод, который я отправляю, должен справиться с этим.
Как упомянул @martin-honnen в своем вопросе, существует двусмысленность в отношении того, как обращаться с типом list w.r.t. отступ.
Кроме того, я понятия не имею, какое правило может быть для атрибута maxItems.
Для чего это стоит:
<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
version = "3.0"
xmlns:xs = "http://www.w3.org/2001/XMLSchema"
xmlns:l = "local:functions"
exclude-result-prefixes = "#all">
<xsl:output indent = "yes" omit-xml-declaration = "yes" />
<xsl:mode on-no-match = "shallow-copy"/>
<xsl:template match = "/doc" >
<xsl:copy>
<xsl:sequence select = "l:processParagraphs(head(*)/@indent, *)" />
</xsl:copy>
</xsl:template>
<xsl:function name = "l:processParagraphs" as = "element()*">
<xsl:param name = "indentLevel" as = "xs:string" />
<xsl:param name = "paras" as = "element(paragraph)*" />
<xsl:choose>
<xsl:when test = "head($paras)/@stylename eq 'list'
and
xs:integer(head($paras)/@indent) ge xs:integer($indentLevel)">
<List indent = "{head($paras)/@indent}" maxItems = "???">
<xsl:variable name = "itemsUnderList" as = "element(paragraph)*"
select = "l:nextInList(head($paras)/@indent, $paras)" />
<xsl:sequence select = "l:processInner(head($paras)/@indent, $itemsUnderList)" />
<xsl:sequence select = "l:processParagraphs($indentLevel, $paras except $itemsUnderList)" />
</List>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select = "l:processInner($indentLevel, $paras)" />
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:function name = "l:processInner" as = "element()*">
<xsl:param name = "indentLevel" as = "xs:string" />
<xsl:param name = "paras" as = "element(paragraph)*" />
<xsl:apply-templates select = "head($paras)" /> <!-- to replace list stylename -->
<xsl:if test = "tail($paras)" >
<xsl:sequence select = "l:processParagraphs(head($paras)/@indent, tail($paras))" />
</xsl:if>
</xsl:function>
<xsl:template match = "paragraph/@stylename[. eq 'list']">
<xsl:attribute name = "stylename" select = "'list_unordered'" />
</xsl:template>
<xsl:function name = "l:nextInList" as = "element(paragraph)*" >
<xsl:param name = "indentLevel" as = "xs:string" />
<xsl:param name = "paras" as = "element(paragraph)*" />
<xsl:if test = "xs:integer(head($paras)/@indent) ge xs:integer($indentLevel)" >
<xsl:sequence select = "(head($paras),
l:nextInList($indentLevel, tail($paras)))" />
</xsl:if>
</xsl:function>
</xsl:stylesheet>
производит (в https://martin-honnen.github.io/xslt3fiddle/)
<doc>
<paragraph indent = "0" stylename = "heading_l1_toc" SDgroup = "heading">heading-l1</paragraph>
<paragraph indent = "0" stylename = "heading_l2_toc" SDgroup = "heading">heading-l2</paragraph>
<List indent = "0" maxItems = "???">
<paragraph indent = "0" stylename = "list_unordered" SDgroup = "list">AAA. Para 1.</paragraph>
<List indent = "1" maxItems = "???">
<paragraph indent = "1" stylename = "list_unordered" SDgroup = "list">BBB. Para 2</paragraph>
<paragraph indent = "2" stylename = "continued" SDgroup = "list">BBB. Continued para1</paragraph>
<paragraph indent = "2" stylename = "continued" SDgroup = "list">BBB. Continued para2</paragraph>
<List indent = "2" maxItems = "???">
<paragraph indent = "2" stylename = "list_unordered" SDgroup = "list">CCC. Para 3a</paragraph>
<List indent = "2" maxItems = "???">
<paragraph indent = "2" stylename = "list_unordered" SDgroup = "list">DDD. Para 3b/4</paragraph>
</List>
<paragraph indent = "1" stylename = "list_unordered" SDgroup = "list">EEE. Para 5</paragraph>
<paragraph indent = "2" stylename = "continued" SDgroup = "list">EEE. Continued para</paragraph>
</List>
<List indent = "0" maxItems = "???">
<paragraph indent = "0" stylename = "list_unordered" SDgroup = "list">FFF. Para 6</paragraph>
</List>
</List>
</List>
</doc>
Спасибо, альтруизм. Извините за мое расплывчатое объяснение. maxItems должен подсчитывать количество <paragraph stylename = "list"> в этом <List> отступах RE: исходный файл представляет собой плоский XML, т. е. ВСЕ элементы являются одноуровневыми в <doc>. Наконец, вывод не совсем правильный. Вложенный <List> создается только тогда, когда значение отступа в пределах <paragraph stylename = "list"> > предыдущего отступа Para, содержащего: <paragraph indent = "2" stylename = "list" SDgroup = "list">DDD. Параграф 3b/4</paragraph> должен находиться в том же <List>, что и: <paragraph indent = "2" stylename = "list" SDgroup = "list">CCC. Параграф 3а</параграф>
это работает ОЧЕНЬ, ОЧЕНЬ хорошо. Спасибо. Спасибо. Вы не представляете, сколько сна я потерял, пытаясь достичь этой функциональности. Я до сих пор не совсем понимаю, как это работает, но я использую свой отладчик, чтобы пройти и посмотреть на внутренности, но результат - это то, что я искал. Еще раз спасибо.
После вашего комментария стало немного легче понять ваш вывод; однако я предлагаю, чтобы предоставленный вами вывод не соответствовал вашему описанию, потому что maxItems для второго элемента List (сразу после AAA должно быть 2, а не 1, поскольку есть два элемента list_unordered, которые являются непосредственными дочерними элементами этого List элемента.
Я переработал свой пример реализации:
<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
version = "3.0"
xmlns:xs = "http://www.w3.org/2001/XMLSchema"
xmlns:l = "local:functions"
exclude-result-prefixes = "#all">
<xsl:output indent = "yes" omit-xml-declaration = "yes" />
<xsl:mode on-no-match = "shallow-copy"/>
<xsl:template match = "/doc" >
<xsl:copy>
<xsl:sequence select = "l:processParagraphs('-1', *)" />
</xsl:copy>
</xsl:template>
<xsl:function name = "l:processParagraphs" as = "element()*">
<xsl:param name = "indentLevel" as = "xs:string" />
<xsl:param name = "paras" as = "element(paragraph)*" />
<xsl:choose>
<xsl:when test = "head($paras)/@stylename eq 'list'
and
xs:integer(head($paras)/@indent) gt xs:integer($indentLevel)">
<xsl:variable name = "itemsUnderList" as = "element(paragraph)*"
select = "l:nextInList(head($paras)/@indent, $paras)" />
<xsl:variable name = "processedInner" as = "element()*"
select = "l:processInner(head($paras)/@indent, $itemsUnderList)" />
<List indent = "{head($paras)/@indent}" maxItems = "{count($processedInner[@stylename eq 'list_unordered'])}">
<xsl:sequence select = "$processedInner" />
</List>
<xsl:sequence select = "l:processParagraphs($indentLevel, $paras except $itemsUnderList)" />
</xsl:when>
<xsl:otherwise>
<xsl:sequence select = "l:processInner($indentLevel, $paras)" />
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:function name = "l:processInner" as = "element()*">
<xsl:param name = "indentLevel" as = "xs:string" />
<xsl:param name = "paras" as = "element(paragraph)*" />
<xsl:apply-templates select = "head($paras)" /> <!-- to replace list stylename -->
<xsl:if test = "tail($paras)" >
<xsl:sequence select = "l:processParagraphs($indentLevel, tail($paras))" />
</xsl:if>
</xsl:function>
<xsl:template match = "paragraph/@stylename[. eq 'list']">
<xsl:attribute name = "stylename" select = "'list_unordered'" />
</xsl:template>
<xsl:function name = "l:nextInList" as = "element(paragraph)*" >
<xsl:param name = "indentLevel" as = "xs:string" />
<xsl:param name = "paras" as = "element(paragraph)*" />
<xsl:if test = "xs:integer(head($paras)/@indent) ge xs:integer($indentLevel)" >
<xsl:sequence select = "(head($paras),
l:nextInList($indentLevel, tail($paras)))" />
</xsl:if>
</xsl:function>
</xsl:stylesheet>
который производит:
<doc>
<paragraph indent = "0" stylename = "heading_l1_toc" SDgroup = "heading">heading-l1</paragraph>
<paragraph indent = "0" stylename = "heading_l2_toc" SDgroup = "heading">heading-l2</paragraph>
<List indent = "0" maxItems = "2">
<paragraph indent = "0" stylename = "list_unordered" SDgroup = "list">AAA. Para 1.</paragraph>
<List indent = "1" maxItems = "2">
<paragraph indent = "1" stylename = "list_unordered" SDgroup = "list">BBB. Para 2</paragraph>
<paragraph indent = "2" stylename = "continued" SDgroup = "list">BBB. Continued para1</paragraph>
<paragraph indent = "2" stylename = "continued" SDgroup = "list">BBB. Continued para2</paragraph>
<List indent = "2" maxItems = "2">
<paragraph indent = "2" stylename = "list_unordered" SDgroup = "list">CCC. Para 3a</paragraph>
<paragraph indent = "2" stylename = "list_unordered" SDgroup = "list">DDD. Para 3b/4</paragraph>
</List>
<paragraph indent = "1" stylename = "list_unordered" SDgroup = "list">EEE. Para 5</paragraph>
<paragraph indent = "2" stylename = "continued" SDgroup = "list">EEE. Continued para</paragraph>
</List>
<paragraph indent = "0" stylename = "list_unordered" SDgroup = "list">FFF. Para 6</paragraph>
</List>
</doc>
это работает ОЧЕНЬ, ОЧЕНЬ хорошо. Спасибо. Спасибо. Вы не представляете, сколько сна я потерял, пытаясь достичь этой функциональности. Я до сих пор не совсем понимаю, как это работает, но я использую свой отладчик, чтобы пройти и посмотреть на внутренности, но результат - это то, что я искал. Еще раз спасибо.
Основной алгоритм заключается в том, что при спуске элементов мы записываем каждый из них с помощью l:processParagraphs(), которая, если нам не нужен <List>, просто вызывает l:processInner(). если мы находим список, и этот список имеет уровень отступа выше, чем предыдущие уровни списка, то мы добавляем элемент <List>, обновляем indentLevel и затем рекурсивно обрабатываем в l:processParagraphs() те элементы, которые будут частью этого конкретного < List> (определяется l:nextInList()). (Функция l:nextInList() может быть упрощена с помощью выражения оси :following-sibling.)
Непонятно, по каким критериям начинать/формировать список, по крайней мере, мне.
<paragraph indent = "1" stylename = "list" SDgroup = "list">BBB. Para 2</paragraph>, кажется, запускаетlist, почему<paragraph indent = "1" stylename = "list" SDgroup = "list">EEE. Para 5</paragraph>не запускает другой?