【问题标题】:XSL Transform Across Namespaces跨命名空间的 XSL 转换
【发布时间】:2009-05-06 18:46:47
【问题描述】:

我的 XML 看起来像这样:

<Root xmlns="http://widgetspecA.com/ns">
  ...any...
  <WidgetBox>
    <A/>
    <B/>
    <SmallWidget> <!-- minOccurs='0' -->
      ...any...
    </SmallWidget>
    <Widgets> <!-- minOccurs='0' -->
      ...any...
    </Widgets>
    ...any...
  </WidgetBox>
  ...any...
</Root>

我想把它变成这样:

<Root xmlns="http://widgetspecB/ns">
  ...any...
  <WidgetBox>
    <A/>
    <B/>
    <Widgets>
      <Atom>
        ...any...
      </Atom>
      <Molecule>
        ...any...
      </Molecule>
    </Widgets>
    ...any...
  </WidgetBox>
  ...any...
</Root>

换句话说:

specA 中的&lt;SmallWidget&gt; 与specB 中的&lt;Atom&gt; 含义相同,因此只需重命名元素即可。

specA 中的&lt;Widgets&gt; 与specB 中的&lt;Molecule&gt; 含义相同,因此只需重命名元素即可。

&lt;Atom&gt;&lt;Molecule&gt; 包装在一个名为&lt;Widgets&gt; 的元素中,这意味着不同于specA 的&lt;Widgets&gt;

其他所有内容都按原样复制,但在新命名空间中。

XSLT 会为此做什么?

解决方案?:最后我选择了这个:

<?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">

  <xsl:output method="xml"/>

  <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>

  <xsl:template match="old:SmallWidget[following-sibling::old:Widgets]">
      <Widgets>
       <xsl:apply-templates select="self::node()" mode="single"/>
       <xsl:apply-templates select="following-sibling::old:Widgets" mode="single"/>
      </Widgets>
  </xsl:template>

  <xsl:template match="old:Widgets[preceding-sibling::old:SmallWidget]"/>

  <xsl:template match="old:SmallWidget[not(following-sibling::old:Widgets)]">
      <Widgets>
       <xsl:apply-templates select="self::node()" mode="single"/>
      </Widgets>
  </xsl:template>

  <xsl:template match="old:Widgets[not(preceding-sibling::old:SmallWidget)]">
      <Widgets>
       <xsl:apply-templates select="self::node()" mode="single"/>
      </Widgets>
  </xsl:template>

</xsl:stylesheet>

【问题讨论】:

  • 我已经修改了我的解决方案以包括对任意元素的支持。来自除“widgetspecA.com/ns”之外的其他命名空间的所有节点都照原样复制,它们的命名空间被单独保留。
  • 我已经删除了我的答案,因为这似乎是一个移动的目标。下次请发布完整且明确的问题描述。
  • 韦恩,你的最终解决方案做得很好。不过还是可以简化的。请参阅我编辑的答案。

标签: xml xslt namespaces


【解决方案1】:

一个好的 XSLT 解决方案会将您的人类可读规则映射到简单的模板规则。用你的话来说,这里是规则:

  1. specA 中的&lt;SmallWidget&gt; 与specB 中的&lt;Atom&gt; 含义相同,因此只需重命名元素即可。
  2. specA 中的 &lt;Widgets&gt; 与 specB 中的 &lt;Molecule&gt; 含义相同,因此只需重命名元素即可。
  3. &lt;Atom&gt;&lt;Molecule&gt; 包装在一个名为&lt;Widgets&gt; 的元素中,这意味着与specA 的&lt;Widgets&gt; 不同。
  4. 其他所有内容都按原样复制,但在新命名空间中。

让我们试一试:

<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 属性添加到&lt;xsl:element&gt; 以强制执行名称空间,即使前缀出现)。但是,如果我们使用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>

【讨论】:

  • 如果 SmallWidget 和 Widgets 都是源模式中的可选元素怎么办?
  • 我看到您的最终解决方案已经处理了这种情况。我已将您的代码附加到我编辑的答案中,并进行了更改以帮助您简化它(并可能使其更健壮)。
  • 是的,这要简单得多。感谢您帮助我消除了我的 XSL 技能上的一些锈迹......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多