Хорошее решение XSLT сопоставит ваши понятные человеку правила с простыми шаблонными правилами. Вот правила, по вашим словам:
<SmallWidget>
в specA означает то же самое, что и <Atom>
в specB, поэтому просто переименуйте элемент.
<Widgets>
в specA означает то же самое, что и <Molecule>
в specB, поэтому просто переименуйте элемент.
- Оберните
<Atom>
и <Molecule>
в элемент с именем <Widgets>
, что означает что-то отличное от <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()
, мы в безопасности; он никогда не будет включать префикс, поэтому элемент result примет пространство имен нашей таблицы стилей по умолчанию.
Запуск приведенной выше таблицы стилей для вашего образца входного документа дает именно то, что вы запрашивали:
<Root xmlns="http://widgetspecB.com/ns">...any...<WidgetBox>...any...
<Widgets><Atom>
...any...
</Atom><Molecule>
...any...
</Molecule></Widgets>...any...
</WidgetBox>...any...</Root>
Дайте мне знать, если у вас есть какие-либо вопросы. Разве XSLT не мощный!
P.S. Если бы я хотел быть очень точным в репликации пробелов, как в вашем примере, я мог бы использовать пошаговую «цепную» обработку, где я применяю шаблоны только к одному узлу за раз, и каждое правило шаблона отвечает за продолжение обработки на его следующий родной брат. Но это казалось излишним для этой ситуации.
UPDATE:
Новое решение, которое вы разместили, очень разумно. Некоторые могут быть упрощены, хотя. Я взял ваше новое решение и внес некоторые рекомендуемые изменения ниже, вместе с комментариями, указывающими, что я изменил и почему я сделал эти изменения.
<?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>