我有一个使用XSLT 2.0 处理的XML文件,它可以包含一个附加到此DTD片段的标记
<!ELEMENT p (#PCDATA|foo|bar|table)* >
这就是输入文件看起来像这样(只是所有可能变体之一)
<p>foobar
<table attr1="a" attr2="b">...</table>
<foo fooattr="FOO">fdaghd</foo><bar>something</bar>sometext
<table attr1="B">...</table>
</p>
我需要将其转换为(必须保留名称空间,不使用副本或副本,因为这些会创建xmlns=""
属性)
<p>foobar</p>
<table attr1="a" attr2="b">...</table>
<p>
<foo fooattr="FOO">fdaghd</foo>
<bar>something</bar>
sometext
</p>
<table attr1="B">...</table>
那是&#34;分裂&#34;每当找到<p>
- 代码时都会<table>
- 标记,并在之后继续<p>
(如果还有剩下的孩子)。
请注意,这也是一个有效的输入示例
<p><table attr1="a" attr2="b">...</table></p>
应该转换为
<table attr1="a" attr2="b">...</table>
并且这也是一个有效的输入示例
<p>bbbb<foo>aaaa</foo></p>
根本不应该转换,输出应该是
<p>bbbb<foo>aaaa</foo></p>
到目前为止我的XSLT包含了这个
<xsl:template match="p[table]">
<xsl:call-template name="split-paragraph">
<xsl:with-param name="tables" select="table"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="split-paragraph">
<xsl:param name="tables"/>
<xsl:if test="$tables">
<xsl:for-each select="$tables[1]">
<xsl:if test="not(preceding-sibling::node//table)">
<p><xsl:apply-templates select="preceding-sibling::node()[not(table)]"/></p>
</xsl:if>
<xsl:apply-templates select="."/>
<xsl:if test="not(following-sibling::node()//table)">
<p><xsl:apply-templates select="following-sibling::node[. << following-sibling::node()[not(name()='table')][1]]"/></p>
</xsl:if>
<xsl:call-template name="split-paragraph">
<xsl:with-param name="tables"
select="$tables[position() > 1]"/>
</xsl:call-template>
</xsl:for-each>
</xsl:if>
</xsl:template>
<xsl:template match="table">
<xsl:element name="table">
<xsl:apply-templates select="attribute()"/>
<xsl:apply-templates select="node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="element()"><xsl:copy/></xsl:template>
适用于
<p>foo
<table attr1="gazonk"><a>bar</a></table>
<bar>xyzzy</bar>
<table attr2="2"><b>fie</b></table>
shfjkdashndk
</p>
产生
<p>foo</p>
<table attr1="gazonk">
<a>bar</a>
</table>
<p>
</p>
<p>foo
<table attr1="gazonk">
<a>bar</a>
</table>
<bar>xyzzy</bar>
</p>
<table attr2="2">
<b>fie</b>
</table>
<p>shfjkdashndk</p>
这不是所需的输出。我想要这个
<p>foo</p>
<table attr1="gazonk">
<a>bar</a>
</table>
<p><bar>xyzzy</bar></p>
<table attr2="2">
<b>fie</b>
</table>
<p>shfjkdashndk</p>
答案 0 :(得分:0)
尝试以下样式表:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="p[table]/child::text()">
<p><xsl:value-of select="normalize-space(.)"/></p>
</xsl:template>
<xsl:template match="p[table]/*[not(self::table)]">
<p>
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</p>
</xsl:template>
<xsl:template match="p[table]">
<xsl:apply-templates select="node()|@*"/>
</xsl:template>
</xsl:stylesheet>
答案 1 :(得分:0)
如果您使用的是XSLT 1.0,则可以将其视为分组问题。您正在将非表元素分组在一起,并且对要在节点之前分组的表元素的数量进行分组。使用Muenchian grouping,您可以像这样定义一个键
<xsl:key name="group" match="p/node()[not(self::table)]" use="concat(generate-id(..), '-', count(preceding-sibling::table))" />
(generate-id()
可以处理XML中的多个 p 元素
然后你可以通过一个模板来匹配 p 元素开始,你会跳过元素,但是他们选择它的子元素,使用“模式”来指示特殊处理(和避免使用两个匹配同一节点的模板)
<xsl:template match="p">
<xsl:apply-templates mode="group" />
</xsl:template>
您需要一个模板来匹配每个组中出现的第一个非表格元素,这样您就可以在其周围添加 p 标记以及该组中的其他项目,就像这样< / p>
<xsl:template match="node()[generate-id() = generate-id(key('group', concat(generate-id(..), '-', count(preceding-sibling::table)))[1])]" mode="group">
<p>
<xsl:apply-templates select="key('group', concat(generate-id(..), '-', count(preceding-sibling::table)))"/>
</p>
</xsl:template>
(注意,XSLT将使用Identity Template将其他节点输出到输出)
匹配表格更直接:
<xsl:template match="table" mode="group">
<xsl:apply-templates select="." />
</xsl:template>
最后,您需要一个模板来匹配前两个模板未拾取的其他节点,以忽略它们(因为之前的“组”模板已经输出它们。这就是“模式”的使用变为表观的)
<xsl:template match="node()" mode="group" />
试试这个XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="group" match="p/node()[not(self::table)]" use="concat(generate-id(..), '-', count(preceding-sibling::table))" />
<xsl:template match="p">
<xsl:apply-templates mode="group" />
</xsl:template>
<xsl:template match="node()[generate-id() = generate-id(key('group', concat(generate-id(..), '-', count(preceding-sibling::table)))[1])]" mode="group">
<p>
<xsl:apply-templates select="key('group', concat(generate-id(..), '-', count(preceding-sibling::table)))"/>
</p>
</xsl:template>
<xsl:template match="table" mode="group">
<xsl:apply-templates select="." />
</xsl:template>
<xsl:template match="node()" mode="group" />
<xsl:template match="@*|node()" >
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
编辑:为了应对命名空间,在XSLT 1.0中,您必须使用p
替换xpath表达式中的*[local-name() = 'p']
的所有实例,对于表也是如此。您还必须使用 xsl:element 来创建具有相同命名空间的新 p 元素。
试试这个XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="group" match="*[local-name() = 'p']/node()[not(self::*[local-name() = 'table'])]" use="concat(generate-id(..), '-', count(preceding-sibling::*[local-name() = 'table']))" />
<xsl:template match="*[local-name() = 'p']">
<xsl:apply-templates mode="group" />
</xsl:template>
<xsl:template match="node()[generate-id() = generate-id(key('group', concat(generate-id(..), '-', count(preceding-sibling::*[local-name() = 'table'])))[1])]" mode="group">
<xsl:element name="p" namespace="{namespace-uri(..)}">
<xsl:apply-templates select="key('group', concat(generate-id(..), '-', count(preceding-sibling::*[local-name() = 'table'])))"/>
</xsl:element>
</xsl:template>
<xsl:template match="*[local-name() = 'table']" mode="group">
<xsl:apply-templates select="." />
</xsl:template>
<xsl:template match="node()" mode="group" />
<xsl:template match="@*|node()" >
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
EDIT 2.0
以下是XSLT 2.0解决方案,您可以使用 xsl:for-each-group 对元素进行分组。在这种情况下,我将相邻的元素组合在一起,这取决于它们是否是一个表。
也不是在检查命名空间时使用通配符(这将会处理名称空间是否存在)
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="*:p">
<xsl:for-each-group select="node()" group-adjacent="boolean(self::*:table)">
<xsl:choose>
<xsl:when test="self::*:table">
<xsl:apply-templates select="current-group()" />
</xsl:when>
<xsl:otherwise>
<xsl:element name="p" namespace="{namespace-uri(..)}">
<xsl:apply-templates select="current-group()" />
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="@*|node()" >
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>