英文:
xslt 2/3 nested group with for-each-group maintaining input order
问题
以下是您要的翻译:
有类似的问题在stackoverflow上,但答案对匹配项进行排序或分组,使它们与输入顺序不一致。
我有以下数据
```xml
<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>
我需要基于列表/@indent = x和continued/@indent = x + 1嵌套段落[@stylename='list']和随后的段落[@stylename='continued']。
输出应如下所示:
<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-group + group-by/adjacent和常规for-each,但最终的输出结果是不按顺序输出和/或正确嵌套输出,但数据重复(即一个项目被正确嵌套,然后在某个时候再次输出相同的项目)。我相信这是可以做到的,但找不到可以产生我所需的解决方案。再次请求帮助,当然将非常感激。
FYI:我无法控制输入数据或更改它的方法 - 除非在XSLT内部。
英文:
There are similar questions on stackoverflow but the answers sort or group the matching items putting them out of sequence compared to the order of the input.
I have the following data
<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>
I need to nest the paragraph[@stylename='list'} and following paragraph[@stylename='continued'] based on the list/@indent = x and the continued/@indent = x + 1.
The output should look like:
<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>
I have tried many, many versions of for-each-group + group-by/adjacent and regular for-each, but end up with out of sequence output and/or correctly nested output but duplicate data (i.e. an item is correctly nested and then, at some point, the same item is output again. I believe this occurs with multiply nested for-each loops.
I'm 99.9% sure this is do'able but can't find a solution that produces what I need. Once again, I'm asking for help, which will of course, be greatly, greatly appreciated.
FYI: I have no control over the input data or means to change it - unless its within XSLT
答案1
得分: 1
我试图弄清楚你的示例在做什么,但我还没有完全理解。如果这有所帮助,我采用了递归函数的方法。
在你的示例中,我没有看到递增/递减/递增缩进级别的系列,所以我没有什么可对照的,但我提交的方法应该可以处理这种情况。
正如@martin-honnen在他的问题中提到的,关于如何处理list
类型在缩进方面存在歧义。
另外,我不知道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)" /> <!-- 替换列表样式名称 -->
<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
<details>
<summary>英文:</summary>
I was trying to figure out what your example was doing but I haven't quite nailed it. In case this helps somewhat, I've taken a recursive function approach.
One thing that I'm not seeing in your example are series of increasing/decreasing/increasing indentation levels, so I don't have anything to check against, but the method that I'm submitting should handle that.
As @martin-honnen mentioned in his question, there is an ambiguity about how to handle the ```list``` type w.r.t. indentation.
Also, I have no idea what the rule might be for the ```maxItems``` attribute.
For what it's worth:
<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>
produces (in 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>
</details>
# 答案2
**得分**: 1
根据您的评论,现在更容易理解您的输出;但是,我建议您提供的输出不符合您的描述,因为第二个List元素(紧跟在AAA后面的)的maxItems应为2,而不是1,因为该List元素的直接子元素中有两个list_unordered元素。
我已重新制定了我的示例实现:
```xml
<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>
英文:
Following your comment, it's a little easier to understand your output; however, I suggest that your provided output doesn't follow your description because the maxItems
for the second List
element (just after AAA
should be 2
and not 1
since there are two list_unordered
elements that are immediate children of that List
element.
I've reworked my example implementation:
<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>
which produces:
<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>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论