我有一个XML文件,其中元素B位于元素A内,我想将它们向上移动。从:
<?xml version="1.0" encoding="utf-8"?>
<root>
<A>
<C>Text</C>
Text again
More text
<D>Other text</D>
<B>Text again</B>
<C>No</C>
<D>May be</D>
<B>What?</B>
</A>
<A>
Something
<B>Nothing</B>
<D>Again</D>
<B>Content</B>
End
</A>
</root>
我希望有:
<?xml version="1.0" encoding="utf-8"?>
<root>
<A>
<C>Text</C>
Text again
More text
<D>Other text</D>
</A>
<B>Text again</B>
<A>
<C>No</C>
<D>May be</D>
</A>
<B>What?</B>
<A>
Something
</A>
<B>Nothing</B>
<A>
<D>Again</D>
</A>
<B>Content</B>
<A>
End
</A>
</root>
我拥有的最接近的 XSLT 程序是这样的:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="A">
<xsl:for-each select="*">
<xsl:choose>
<xsl:when test="name()='B'">
<xsl:apply-templates select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:element name="A">
<xsl:apply-templates select="."/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
它有两个问题:它忽略文本节点(这可能只是将 |text() 添加到 select="*") 的问题,但更重要的是,它为每个节点创建一个元素,而我希望它们在一个 .例如,上面的样式表使:
<A><C>No</C></A>
<A><D>May be</D></A>
我想要的地方:
<A><C>No</C>
<D>May be</D></A>
在我的 XML 文件中,始终是 的直接子级,并且没有 或嵌套。
主要用例是生成 HTML,其中 UL 和 OL 不能位于 P 内。
这个问题与 xslt 平展 DocBook para 元素中的子元素相关但不完全相同(也可能与使用 XSLT 平展 xml 层次结构有关)。)
正如我在对您的问题的评论中所说,这不是关于在层次结构中向上移动元素。它是关于对节点进行分组,并为由划分B
元素确定的每个组创建新的父A
元素。
在 XSLT 1.0 中,这可以使用所谓的同级递归来实现:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<xsl:copy>
<xsl:apply-templates select="A"/>
</xsl:copy>
</xsl:template>
<xsl:template match="A">
<xsl:copy>
<xsl:apply-templates select="node()[1][not(self::B)]" mode="sibling"/>
</xsl:copy>
<xsl:apply-templates select="B[1]" mode="sibling"/>
</xsl:template>
<xsl:template match="node()" mode="sibling">
<xsl:copy-of select="." />
<xsl:apply-templates select="following-sibling::node()[1][not(self::B)]" mode="sibling"/>
</xsl:template>
<xsl:template match="B" mode="sibling">
<xsl:copy-of select="." />
<xsl:if test="following-sibling::node()[normalize-space()]">
<A>
<xsl:apply-templates select="following-sibling::node()[1][not(self::B)]" mode="sibling"/>
</A>
<xsl:apply-templates select="following-sibling::B[1]" mode="sibling"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
XSLT-1.0 解决方案(非常丑陋)如下。输出是如愿以偿的,但仅适用于此简单 MCVE。一般解决方案将比评论中提到的@michael.hor257k复杂得多。如果没有更多的数据,就不太可能在 XSLT-1.0 中创建更好的解决方案。XSLT-2.0 及更高版本的解决方案可以简化此操作。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/root">
<xsl:copy>
<xsl:for-each select="A">
<xsl:if test="normalize-space(text()[1])">
<A>
<xsl:copy-of select="text()[1]" />
</A>
</xsl:if>
<xsl:if test="preceding::*">
<xsl:copy-of select="B[1]" />
</xsl:if>
<A>
<xsl:copy-of select="C[1] | C[1]/following-sibling::text()[1] | D[1]" />
</A>
<xsl:if test="not(preceding::*)">
<xsl:copy-of select="B[1]" />
</xsl:if>
<A>
<xsl:copy-of select="C[2] | C[2]/following-sibling::text()[1]" />
<xsl:if test="D[2]">
<xsl:copy-of select="D[2]" />
</xsl:if>
</A>
<xsl:copy-of select="B[2]" />
<xsl:if test="normalize-space(text()[last()])">
<A>
<xsl:copy-of select="text()[last()]" />
</A>
</xsl:if>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
关于以下情况
<A><C>No</C></A>
<A><D>May be</D></A>
在上面的代码中对其进行了适当的处理。所以它的输出是
<A>
<C>No</C>
<D>May be</D>
</A>
在 XSLT 2 或 3 中使用 group-adjacent=". instance of element(B)"
或 group-adjacent="boolean(self::B)"
很容易,下面是一个 XSLT 3 示例(XSLT 3 在 Java 和 .NET (https://sourceforge.net/projects/saxon/files/Saxon-HE/) 上由 Saxon 9.8 或 9.9 以及自 2017 年发布以来的 Altova 支持 XSLT 3):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="A">
<xsl:for-each-group select="node()" group-adjacent=". instance of element(B)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:apply-templates select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy select="..">
<xsl:apply-templates select="current-group()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/gWmuiKv
在 XSLT 2 中,您需要将<xsl:mode on-no-match="shallow-copy"/>
拼写为标识转换模板,并使用xsl:element
而不是xsl:copy
:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="A">
<xsl:for-each-group select="node()" group-adjacent=". instance of element(B)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:apply-templates select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{name(..)}" namespace="{namespace-uri(..)}">
<xsl:apply-templates select="current-group()"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
</xsl:transform>
http://xsltransform.hikmatu.com/pPqsHT2