一个好的 XSLT 解决方案会将您的人类可读规则映射到简单的模板规则。用你的话来说,这里是规则:
-
specA 中的
<SmallWidget> 与specB 中的<Atom> 含义相同,因此只需重命名元素即可。
-
specA 中的
<Widgets> 与 specB 中的 <Molecule> 含义相同,因此只需重命名元素即可。
- 将
<Atom> 和<Molecule> 包装在一个名为<Widgets> 的元素中,这意味着与specA 的<Widgets> 不同。
- 其他所有内容都按原样复制,但在新命名空间中。
让我们试一试:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:in="http://widgetspecA.com/ns"
xmlns="http://widgetspecB.com/ns"
exclude-result-prefixes="in">
<!-- 1. Rename <SmallWidget> -->
<xsl:template mode="rename" match="in:SmallWidget">Atom</xsl:template>
<!-- 2. Rename <Widgets> -->
<xsl:template mode="rename" match="in:Widgets">Molecule</xsl:template>
<!-- 3. Wrap <Atom> & <Molecule> with <Widgets> -->
<xsl:template match="in:SmallWidget">
<!-- ASSUMPTION: in:Widgets immediately follows in:SmallWidget -->
<Widgets>
<xsl:apply-templates mode="convert" select="."/>
<xsl:apply-templates mode="convert" select="following-sibling::in:Widgets"/>
</Widgets>
</xsl:template>
<!-- Skip by this in regular processing;
it gets explicitly converted inside <Widgets> (see above) -->
<xsl:template match="in:Widgets"/>
<!-- Also, don't copy whitespace appearing
immediately before in:Widgets -->
<xsl:template match="text()
[following-sibling::node()[1][self::in:Widgets]]"/>
<!-- 4: Everything copied as is, but in the new namespace -->
<!-- Copy non-element nodes as is -->
<xsl:template match="@* | text() | comment() | processing-instruction()">
<xsl:copy/>
</xsl:template>
<!-- By default, just convert elements to new namespace
(exceptions under #3 above) -->
<xsl:template match="*">
<xsl:apply-templates mode="convert" select="."/>
</xsl:template>
<xsl:template mode="convert" match="*">
<!-- Optionally rename the element -->
<xsl:variable name="name">
<xsl:apply-templates mode="rename" select="."/>
</xsl:variable>
<xsl:element name="{$name}">
<xsl:apply-templates select="@* | node()"/>
</xsl:element>
</xsl:template>
<!-- By default, just use the same local
name as in the input document -->
<xsl:template mode="rename" match="*">
<xsl:value-of select="local-name()"/>
</xsl:template>
</xsl:stylesheet>
请注意,使用local-name() 函数而不是name() 函数很重要。如果您使用name(),如果您的输入文档开始使用未在样式表中明确声明的名称空间前缀,您的样式表将中断(除非您将namespace 属性添加到<xsl:element> 以强制执行名称空间,即使前缀出现)。但是,如果我们使用local-name(),我们是安全的;它永远不会包含前缀,因此结果元素将采用我们样式表的默认命名空间。
针对您的示例输入文档运行上述样式表会完全符合您的要求:
<Root xmlns="http://widgetspecB.com/ns">...any...<WidgetBox>...any...
<Widgets><Atom>
...any...
</Atom><Molecule>
...any...
</Molecule></Widgets>...any...
</WidgetBox>...any...</Root>
如果您有任何问题,请告诉我。 XSLT 不是很强大!
附:如果我想像您的示例那样在复制空白时非常精确,我可以使用逐步的“链式”处理,其中我一次仅将模板应用于一个节点,并且每个模板规则负责继续处理到它的下一个兄弟姐妹。但在这种情况下,这似乎有点矫枉过正。
更新:
您发布的新解决方案非常合理。不过可以简化一些。我采用了您的新解决方案,并在下面进行了一些建议的更改,同时 cmets 指出了我所做的更改以及我进行这些更改的原因。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:old="http://widgetspecA.com/ns"
xmlns="http://widgetspecB.com/ns"
exclude-result-prefixes="old">
<!-- "xml" is the default; no real need for this
<xsl:output method="xml"/>
-->
<!-- This works fine if you only want to copy elements, attributes,
and text. Just be aware that comments and PIs will get
effectively stripped out, because the default template rule
for those is to do nothing.
-->
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:copy-of select="@*"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="old:SmallWidget" mode="single">
<Atom>
<xsl:apply-templates/>
</Atom>
</xsl:template>
<xsl:template match="old:Widgets" mode="single">
<Molecule>
<xsl:apply-templates/>
</Molecule>
</xsl:template>
<!-- You actually only need one rule for <old:SmallWidget>.
Why? Because the behavior of this rule will always
be exactly the same as the behavior of the other rule
you supplied below.
-->
<xsl:template match="old:SmallWidget"> <!--[following-sibling::old:Widgets]">-->
<Widgets>
<!-- "." means exactly the same thing as "self::node()" -->
<xsl:apply-templates select="." mode="single"/>
<!-- If the node-set is empty, then this will be a no-op anyway,
so it's safe to have it here even for the case when
<old:Widgets> is not present in the source tree. -->
<!-- This XPath expression ensures
that you only process the next
sibling element - and then only
if it's name is <old:Widgets>.
Your schema might not allow it,
but this is a clearer communication
of your intention, and it will also
work correctly if another
old:SmallWidget/old:Widget pair
appeared later in the document.
-->
<xsl:apply-templates select="following-sibling::*[1][self::old:Widgets]"
mode="single"/>
</Widgets>
</xsl:template>
<!-- updated this predicate for the
same reason as above. Answers the
question: Is the element right before
this one a SmallWidget? (as opposed to:
Are there any SmallWidget elements
before this one?) -->
<xsl:template match="old:Widgets[preceding-sibling::*[1][self::old:SmallWidget]]"/>
<!-- Removed, because this rule effectively has the same behavior as the other one above
<xsl:template match="old:SmallWidget[not(following-sibling::old:Widgets)]">
<Widgets>
<xsl:apply-templates select="self::node()" mode="single"/>
</Widgets>
</xsl:template>
-->
<!-- no need for the predicate. The format of this pattern (just a name)
causes this template rule's priority to be 0. Your other rule
for <old:Widgets> above has priority of .5, which means that it
will override this one automatically. You don't need to repeat
the constraint. Alternatively, you could keep this predicate
and remove the other one. Either way it will work. (It's probably
a good idea to place these rules next to each other though,
so you can read it like an if/else statement) -->
<xsl:template match="old:Widgets"> <!--[not(preceding-sibling::*[1][self::old:SmallWidget])]">-->
<Widgets>
<xsl:apply-templates select="." mode="single"/>
</Widgets>
</xsl:template>
</xsl:stylesheet>