xml - 跨命名空间的 XSL 转换

标签 xml xslt namespaces

我有看起来像这样的 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>

换句话说:
<SmallWidget>在 specA 中的含义与 <Atom> 相同在 specB 中,因此只需重命名元素。
<Widgets>在 specA 中的含义与 <Molecule> 相同在 specB 中,因此只需重命名元素。

包裹 <Atom><Molecule>在名为 <Widgets> 的元素中,这意味着与 specA 的 <Widgets> 不同的东西.

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

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>

最佳答案

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

  • <SmallWidget>在 specA 中的含义与 <Atom> 相同在 specB 中,因此只需重命名元素。
  • <Widgets>在 specA 中的含义与 <Molecule> 相同在 specB 中,因此只需重命名元素。
  • 包裹 <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 是不是很强大!

    附言如果我想像您的示例中那样真正精确地复制空白,我可以使用逐步的“链”处理,其中我一次仅将模板应用于一个节点,并且每个模板规则负责继续处理它的以下 sibling 。但对于这种情况,这似乎有点过分了。

    更新:
    您发布的新解决方案非常合理。不过可以简化一些。我已经采用了您的新解决方案并在下面进行了一些推荐的更改,并附有说明我更改的内容以及我进行这些更改的原因的评论。
    <?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>
    

    关于xml - 跨命名空间的 XSL 转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/831101/

    相关文章:

    namespaces - 类库和命名空间有什么区别?

    Javascript:命名空间

    javascript - 从 E4X 复制的 XML 中删除命名空间属性

    xslt - 如何在 xslt 中设置标志/变量以再次输入(与转换为 Junit 格式有关)

    javascript - 将 XML 文档(通过 ajax 调用获得)渲染到新窗口

    xslt - 如何限制 XSLT 1.0 中的字符串字数?

    c++ - 在命名空间中定义 double 常量的最佳方法是什么?

    java - Android java http Xml 转换为 Json Caused by : java. lang.NoSuchMethodError: No direct method <init>(Ljava/io/Reader;)

    java - 在 Java 中使用 XML SOAP Web 服务

    android - 四个圆角不同颜色的按钮