Xml 将相等的命名空间前缀与XSLT合并

Xml 将相等的命名空间前缀与XSLT合并,xml,xslt,namespaces,xslt-2.0,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/" xm

假设您有一块定义了多个名称空间前缀的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>

爱丽丝
上下快速移动

您可以尝试以下方法

<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
),这样生成的文档可能不再有效(例如,声明了两个前缀
x
xs
,该算法消除了
xs
,但属性值不是固定的,比如
x:integer
)。因此,如果要使用XSLT修复SOAP/XML模式中可能使用限定名称作为元素或属性值的内容,请注意(不仅仅是XSLT修复/简化的元素或属性名称)。

您对此没有太多控制权-这主要取决于处理者的突发奇想。例如,将标识转换模板应用于输入将导致:

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

爱丽丝
上下快速移动
您可以尝试使用以下方法完全去除前缀:

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

爱丽丝
上下快速移动

这两个问题都没有错。

这是一个有趣的问题。这接近于您所需要的。但没有花费太多时间,所以可能有办法大幅缩短时间

  • 较短的前缀用于输出XML
  • 元素上存在多余的命名空间声明。这是因为在运行时决定应丢弃哪些前缀,并且您只能将NCName的静态列表指定为
    排除结果前缀的值
后者可以在下面的转换后修复,通过简单的标识转换排除不再使用的前缀

样式表

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

输出

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

爱丽丝
上下快速移动
输出(应用类似于的内容后)


爱丽丝
上下快速移动

这就是我最终使用的答案。不确定它与其他答案相比如何,或者它是否具有相同的功能集,但它适用于我的情况,我不记得我是如何获得它的。如果我在此处调整了其中一个答案,或者我在其他地方找到了它。在任何情况下,它都适用于我。请评论是否有任何遗漏,或者r在其他人身上做得更好,并投票给他们:)




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

只要名称空间是正确的,你为什么还要关心前缀呢?因为我是一个完美主义者,喜欢干净的东西p、 我可以添加它,而不必再为它烦恼;)“如果有一个简单的解决方案”嗯,现在你知道了,哈哈。对于XSLT2.0和XSLT2.0处理器,序列化不应该依赖于处理器的突发奇想。