Xml 将相等的命名空间前缀与XSLT合并
假设您有一块定义了多个名称空间前缀的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
示例
<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处理器,序列化不应该依赖于处理器的突发奇想。