将等号名称空间前缀与XSLT合并

时间:2014-03-06 14:28:27

标签: xml xslt namespaces xslt-2.0

假设您有一大块XML,其中定义了多个名称空间前缀,其中一些实际上是具有不同前缀的相同名称空间。使用XSLT有一种不太复杂的方法来合并这些前缀,这样你最终只能为每个命名空间添加一个前缀吗?例如选择最短的一个?


示例

<soapenv:Envelope
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:f="http://api.example.com/Service"
    xmlns:foo="http://api.example.com/Service">

   <soapenv:Body>

      <foo:serviceResponse>
         <f:profile id="1">Alice</f:profile>
         <f:profile id="2">Bob</f:profile>
      </foo:serviceResponse>

   </soapenv:Body>
</soapenv:Envelope>

应该变成例如:

<soap:Envelope
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:f="http://api.example.com/Service">

   <soap:Body>

      <f:serviceResponse>
         <f:profile id="1">Alice</f:profile>
         <f:profile id="2">Bob</f:profile>
      </f:serviceResponse>

   </soap:Body>
</soap:Envelope>

4 个答案:

答案 0 :(得分:0)

您可以尝试

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:key name="namespaces" match="dec" use="@ns"/>

<xsl:variable name="prefs">
  <xsl:for-each-group select="/*/namespace::*" group-by="string()">
    <xsl:for-each select="current-group()">
      <xsl:sort select="string-length(local-name())"/>
      <xsl:if test="position() eq 1">
        <dec ns="{string()}"><xsl:value-of select="local-name()"/></dec>
      </xsl:if>
    </xsl:for-each>
  </xsl:for-each-group>
</xsl:variable>

<xsl:template match="@*">
  <xsl:attribute name="{if (key('namespaces', namespace-uri(), $prefs)) then concat(key('namespaces', namespace-uri(), $prefs), ':') else ''}{local-name()}" namespace="{namespace-uri()}" select="."/>
</xsl:template>

<xsl:template match="*">
  <xsl:element name="{if (key('namespaces', namespace-uri(), $prefs)) then concat(key('namespaces', namespace-uri(), $prefs), ':') else ''}{local-name()}" namespace="{namespace-uri()}">
    <xsl:apply-templates select="@* , node()"/>
  </xsl:element>
</xsl:template>

</xsl:stylesheet>

但是它只检查根元素上的名称空间声明(当它们在文档中的任何元素上都是全部)时,更重要的是,XML模式或SOAP消息中的属性值可以是QName类型(例如xs:integer )并且结果文档可能不再有效(例如,您声明了两个前缀xxs,算法会删除xs,但属性值不固定为{ {1}})。因此,如果您想使用该XSLT来修复可能使用限定名称作为元素或属性值的SOAP / XML模式(并且不仅仅是XSLT修复/简化的元素或属性名称),请注意。

答案 1 :(得分:0)

你没有太多的控制权 - 这主要取决于你的处理器的心血来潮。例如,将身份转换模板应用于您的输入将导致:

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:f="http://api.example.com/Service" xmlns:foo="http://api.example.com/Service">
  <soapenv:Body>
    <foo:serviceResponse>
      <foo:profile id="1">Alice</foo:profile>
      <foo:profile id="2">Bob</foo:profile>
    </foo:serviceResponse>
  </soapenv:Body>
</soapenv:Envelope>

使用libxslt时,但Saxon将返回:

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:f="http://api.example.com/Service" xmlns:foo="http://api.example.com/Service">
   <soapenv:Body>
      <foo:serviceResponse>
         <f:profile id="1">Alice</f:profile>
         <f:profile id="2">Bob</f:profile>
      </foo:serviceResponse>
   </soapenv:Body>
</soapenv:Envelope>

您可以尝试使用以下内容完全剥离前缀:

<xsl:template match="*">
    <xsl:element name="{local-name()}" namespace="{namespace-uri()}">
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates/>
    </xsl:element>
</xsl:template>

在这里,撒克逊将回归:

<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
   <Body>
      <serviceResponse xmlns="http://api.example.com/Service">
         <profile id="1">Alice</profile>
         <profile id="2">Bob</profile>
      </serviceResponse>
   </Body>
</Envelope>

而libxslt会生成一些随机(但统一)的前缀,例如

<?xml version="1.0" encoding="UTF-8"?>
<ns13:Envelope xmlns:ns13="http://schemas.xmlsoap.org/soap/envelope/">
  <ns13:Body>
    <ns14:serviceResponse xmlns:ns14="http://api.example.com/Service">
      <ns14:profile id="1">Alice</ns14:profile>
      <ns14:profile id="2">Bob</ns14:profile>
    </ns14:serviceResponse>
  </ns13:Body>
</ns13:Envelope>

并且它们都没有错。

答案 2 :(得分:0)

有趣的问题。这接近你所需要的。但是没有花很多时间在上面,所以可能有办法大幅缩短它。

  • 较短的前缀用于输出XML
  • 元素上有多余的名称空间声明。这是因为在运行时决定应丢弃哪些前缀,并且只能指定NCNames的静态列表作为exclude-result-prefixes的值。

后者可以在下面的转换后修复,使用简单的标识转换来排除不再使用的前缀。例如,请参阅Dimitre Novatchev的回答here

<强>样式表

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:f="http://api.example.com/Service"
    xmlns:foo="http://api.example.com/Service">

    <xsl:output method="xml" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="*">
      <xsl:variable name="node" select="."/>
      <xsl:variable name="prefix" select="substring-before(name(), concat(':', local-name()))"/>
      <xsl:variable name="scope">
          <xsl:for-each select="in-scope-prefixes(.)">
              <xsl:element name="{.}">
                  <xsl:value-of select="namespace-uri-for-prefix(.,$node)"/>
              </xsl:element>
          </xsl:for-each>
      </xsl:variable>
      <xsl:choose>
          <xsl:when test="$scope/*[name() != $prefix and namespace-uri-for-prefix($prefix,$node) = . and string-length(./name()) lt string-length($prefix)]">
              <xsl:variable name="match" select="$scope/*[name() != $prefix and namespace-uri-for-prefix($prefix,$node) = .]"/>
              <xsl:element name="{concat($match/name(),':',$node/local-name())}">
                  <xsl:apply-templates select="@*|node()"/>
              </xsl:element>
          </xsl:when>
          <xsl:otherwise>
                <xsl:copy>
                   <xsl:apply-templates select="@*|node()"/>
                </xsl:copy>
          </xsl:otherwise>
      </xsl:choose>

    </xsl:template>

    <xsl:template match="@*|processing-instruction()|text()|comment()">
        <xsl:copy>
         <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

<强>输出

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
      <f:serviceResponse xmlns:f="http://api.example.com/Service">
         <f:profile xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                    xmlns:foo="http://api.example.com/Service"
                    id="1">Alice</f:profile>
         <f:profile xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                    xmlns:foo="http://api.example.com/Service"
                    id="2">Bob</f:profile>
      </f:serviceResponse>
   </soap:Body>
</soap:Envelope>

输出(在应用与this类似的内容后)

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
      <f:serviceResponse xmlns:f="http://api.example.com/Service">
         <f:profile id="1">Alice</f:profile>
         <f:profile id="2">Bob</f:profile>
      </f:serviceResponse>
   </soap:Body>
</soap:Envelope>

答案 3 :(得分:0)

这是我最终使用的。不确定它与其他答案相比如何,或者它是否具有相同的功能集,但它适用于我的情况,我不记得我是如何得到它的。如果我在这里调整了其中一个答案,或者我在其他地方找到了答案。在任何一种情况下,它都适合我。请评论是否有任何其他内容缺失或更好的内容,并对其进行投票:)

<stylesheet version="2.0" xmlns="http://www.w3.org/1999/XSL/Transform">

    <!--
        Pulls namespace declarations up towards root node.
    -->

    <template match="@* | text() | processing-instruction() | comment()">
        <copy />
    </template>

    <template match="*">
        <copy copy-namespaces="no">
            <for-each-group group-by="local-name()" select="descendant-or-self::*/namespace::*">
                <copy-of select="." />
            </for-each-group>
            <apply-templates select="@* , node()"/>
        </copy>
    </template>

</stylesheet>

注意 :不要认为这实际上合并了前缀(不记得了,现在无法测试它),但是我用它了在返回SOAP消息之前的ESB进程结束时,名称空间被清理了很多,所以它至少做了一些事情:p