我有一个XML文档,其结构如下:
<?xml version="1.0" encoding="UTF-8"?>
<text>
...
<cb n="1" />
...
<cb n="2" />
...
<cb n="" />
...
</text>
XML文档中每个以列为单位的部分都以<cb n="1" />
标记开头,以<cb n="" />
标记结尾,包含一个或多个<cb n="2" />
,<cb n="3" />
等。他们之间的标签。 <cb>
代码都是<text>
的直接子代。我想生成HTML,其中每个<cb n="1" />...<cb n="" />
块都转换为<div>...</div>
,每个<cb n="x" />...<cb n="x+1" />
块都转换为<div class="column">...</div>
。例如,上面的XML输出将是
<html>
<body>
...
<div>
<div class="column">
...
</div>
<div class="column">
...
</div>
</div>
...
</body>
</html>
我的XSLT样式表是:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="text">
<html>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<!-- this is the part that fails -->
<xsl:template match="cb[@n='1']">
<div>
<div class="column">
</xsl:template>
<xsl:template match="cb[@n='']">
</div>
</div>
</xsl:template>
<xsl:template match="cb">
</div>
<div class="column">
</xsl:template>
</xsl:stylesheet>
但这不起作用,因为样式表本身不是有效的XML。这种转换在XSLT 1.0中是否可行?
答案 0 :(得分:2)
首先要了解的是,在生成HTML或XML输出时,XSL会生成整个输出元素;隔离的开始或结束标记不能发送到输出中(部分原因是它们在输入中不被接受)。因此,输出文档中的每个节点都来自输入文档中特定节点的转换,因此作为转换作者的部分工作是选择将哪些输入节点转换为所需的输出节点。
特别是,包含输出列组的<div>
的源节点唯一合适的候选者是<text>
元素和<cb>
元素之一。如果您选择后者,那么您需要选择具有区别特征的一个,例如是第一个或最后一个,或具有特定的属性值。
此外,无论哪个节点的变换提供包含<div>
, 都要对列组内容负责,因为模板无法使用将内容添加到由不同模板生成的输出节点,甚至是同一模板的不同实例。如果您让<text>
元素的模板也进行转换,那么您必须做额外的工作以避免不必要的输出。
这是你可以把它放在一起的一种方式:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" />
<!-- identity transform for nodes not otherwise matched with a template -->
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match='/text'>
<html>
<body>
<!-- transform child nodes up to and including the first <cb>, if any -->
<xsl:apply-templates select="node()[not(preceding::cb)]" />
</body>
</html>
</xsl:template>
<!-- template for most <cb> elements: -->
<xsl:template match="cb">
<xsl:variable name="column" select="preceding-sibling::cb[1]/@n" />
<div class="column">
<!-- contents come from transforming nodes between the previous <cb>
and this one -->
<xsl:apply-templates
select="preceding-sibling::node()[preceding-sibling::cb[@n = $column]]" />
</div>
</xsl:template>
<!-- template for <cb> elements that are their parent's first child;
produces the column-group div, its contents, and the nodes following -->
<xsl:template match="cb[1]">
<div>
<xsl:apply-templates select="following-sibling::cb" />
</div>
<xsl:apply-templates
select="../cb[position() = last()]/following-sibling::node()" />
</xsl:template>
</xsl:stylesheet>
它没有使用(因此不依赖于)输入n
属性的特定值;它只依赖于它们的独特性。此外,因为它通过转换第一个<div>
来创建包含<cb>
的列组,所以它将省略完全没有任何<cb>
个元素。总的来说,请注意使用preceding-sibling
和following-sibling
轴来选择其他节点之间的节点。
答案 1 :(得分:0)
你的问题仍然不完全清楚。如果我猜对了,你想要输入如下:
<强> XML 强>
<text>
<cb n="1">a</cb>
<cb n="2">b</cb>
<cb n="2">c</cb>
<cb n=""></cb>
<cb n="4">d</cb>
<cb n="5">e</cb>
<cb n=""></cb>
<cb n="6">f</cb>
<cb n="7">g</cb>
<cb n="8">h</cb>
<cb n="9">i</cb>
<cb n="">j</cb>
</text>
并为以div
结尾的每组连续cb
元素创建一个<cb n=""/>
包装器。这在XSLT 2.0中很容易做到,但在XSLT 1.0中有点棘手:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:key name="cb-by-end" match="cb[not(@n='')]" use="generate-id(following-sibling::cb[@n=''][1])" />
<xsl:template match="/text">
<html>
<body>
<xsl:apply-templates select="cb[@n='']" mode="group"/>
</body>
</html>
</xsl:template>
<xsl:template match="cb" mode="group">
<div>
<xsl:apply-templates select="key('cb-by-end', generate-id())"/>
</div>
</xsl:template>
<xsl:template match="cb">
<div class="column">
<xsl:apply-templates/>
</div>
</xsl:template>
</xsl:stylesheet>
<强>结果强>
<html>
<body>
<div>
<div class="column">a</div>
<div class="column">b</div>
<div class="column">c</div>
</div>
<div>
<div class="column">d</div>
<div class="column">e</div>
</div>
<div>
<div class="column">f</div>
<div class="column">g</div>
<div class="column">h</div>
<div class="column">i</div>
</div>
</body>
</html>