跨命名空间的XSL转换

时间:2009-05-06 18:46:48

标签: 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>

换句话说:

specA中的

<SmallWidget>与specB中的<Atom>意思相同,所以只需重命名该元素。

specA中的

<Widgets>与specB中的<Molecule>意思相同,所以只需重命名该元素。

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

1 个答案:

答案 0 :(得分:1)

一个好的XSLT解决方案会将您的人类可读规则映射到简单的模板规则。以下是规则:

    specA中的
  1. <SmallWidget>与specB中的<Atom>意义相同,因此只需重命名该元素。
  2. specA中的
  3. <Widgets>与specB中的<Molecule>意义相同,因此只需重命名该元素。
  4. <Atom><Molecule>换成名为<Widgets>的元素,这意味着与specA的<Widgets>不同。
  5. 其他所有内容都按原样复制,但在新命名空间中。
  6. 我们试一试:

    <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。如果我想在你的例子中真正精确复制空白,我可以使用逐步的“链”处理,我一次只将模板应用于一个节点,每个模板规则负责继续处理以下的兄弟姐妹。但对于这种情况来说,这似乎有些过分。

    更新: 您发布的新解决方案非常合理。虽然它可以简化一些。我已经采用了你的新解决方案,并在下面做了一些推荐的更改,以及表明我改变了什么以及为什么我做了这些更改的评论。

    <?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>
    
相关问题