使用XSLT 3.0修改XML根元素与命名空间的问题排查
问题描述
尝试使用XSLT 3.0(搭配SAXON-HE 12.4)将XML根元素命名空间从urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2切换为urn:oasis:names:specification:ubl:schema:xsd:Invoice-2,同时保留cac、cbc公共命名空间,转换中遇到命名空间残留及模板匹配失效问题。
输入XML
<CreditNote xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2"><cbc:CustomizationID>12345</cbc:CustomizationID><cbc:ProfileID>67890</cbc:ProfileID><cbc:ID>abcdef</cbc:ID><cac:BillingReference><cac:InvoiceDocumentReference><cbc:ID>ghijk</cbc:ID><cbc:IssueDate>2024-08-05</cbc:IssueDate></cac:InvoiceDocumentReference></cac:BillingReference></CreditNote>
期望输出XML
<?xml version="1.0" encoding="UTF-8"?><ubl:Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"><cbc:ID>abcdef</cbc:ID><cac:BillingReference><cac:InvoiceDocumentReference><cbc:ID>ghijk</cbc:ID></cac:InvoiceDocumentReference></cac:BillingReference></ubl:Invoice>
当前使用的XSLT
<?xml version="1.0" encoding="UTF-8"?><xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cn="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2" xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" exclude-result-prefixes="cn"><xsl:output method="xml" indent="yes"/><xsl:template match="/cn:CreditNote"><ubl:Invoice xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"><xsl:apply-templates select="cbc:ID"/><xsl:apply-templates select="cac:BillingReference"/></ubl:Invoice></xsl:template><xsl:template match="@* | node()"><xsl:copy copy-namespaces="no"><xsl:apply-templates select="@* | node()"/></xsl:copy></xsl:template><xsl:template match="cn:*"><xsl:element name="{local-name()}"><xsl:apply-templates select="@*|node()"/></xsl:element></xsl:template><xsl:template match="*/cac:BillingReference"><xsl:copy><xsl:copy-of select="namespace::*[not(. = ('urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2'))]"/><xsl:apply-templates select="cac:InvoiceDocumentReference"/></xsl:copy></xsl:template></xsl:stylesheet>
实际输出XML
<?xml version="1.0" encoding="UTF-8"?><ubl:Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"><cbc:ID>abcdef</cbc:ID><cac:BillingReference xmlns="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2"><cac:InvoiceDocumentReference><cbc:ID>ghijk</cbc:ID><cbc:IssueDate>2024-08-05</cbc:IssueDate></cac:InvoiceDocumentReference></cac:BillingReference></ubl:Invoice>
核心疑问
- 如何消除输出中残留的原命名空间
urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2? cac:BillingReference模板为何未生效,导致cbc:IssueDate未被剔除?
解决方案
问题1:原命名空间残留的原因与解决
cac:BillingReference带上原命名空间,是因为原输入中该元素的父节点属于原默认命名空间,使用<xsl:copy>复制时,即使设置copy-namespaces="no",元素的默认命名空间上下文仍会被继承,导致输出时被迫声明原默认命名空间。
解决方式:
- 针对
cac:*和cbc:*元素,单独编写复制模板,确保它们的命名空间正确绑定,同时避免继承父元素的默认命名空间。 - 根元素
ubl:Invoice无需重复声明命名空间,stylesheet根节点已绑定正确的默认命名空间和前缀。
问题2:模板未匹配的原因与解决
cac:BillingReference模板未生效,是因为它与通用模板match="@* | node()"的优先级相同(均为0.5),处理器会优先选择后定义的模板,但原代码中通用模板定义在前面,且模板匹配路径*/cac:BillingReference的上下文存在命名空间干扰。
解决方式:
- 给目标模板设置更高优先级(
priority="1"),确保处理器优先匹配。 - 直接针对
cac:BillingReference和cac:InvoiceDocumentReference编写模板,明确指定只保留需要的子元素;同时给需要剔除的元素编写空模板。
修正后的XSLT
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cn="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2" xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" exclude-result-prefixes="cn"> <xsl:output method="xml" indent="yes" encoding="UTF-8"/> <!-- 替换根元素为Invoice --> <xsl:template match="/cn:CreditNote"> <ubl:Invoice> <xsl:apply-templates select="cbc:ID"/> <xsl:apply-templates select="cac:BillingReference"/> </ubl:Invoice> </xsl:template> <!-- 正确复制cbc和cac元素,避免命名空间继承 --> <xsl:template match="cbc:* | cac:*"> <xsl:copy copy-namespaces="no"> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template> <!-- 直接剔除不需要的元素 --> <xsl:template match="cbc:CustomizationID | cbc:ProfileID | cbc:IssueDate"/> <!-- 高优先级匹配BillingReference,只保留目标子元素 --> <xsl:template match="cac:BillingReference" priority="1"> <cac:BillingReference> <xsl:apply-templates select="cac:InvoiceDocumentReference"/> </cac:BillingReference> </xsl:template> <!-- 高优先级匹配InvoiceDocumentReference,只保留ID --> <xsl:template match="cac:InvoiceDocumentReference" priority="1"> <cac:InvoiceDocumentReference> <xsl:apply-templates select="cbc:ID"/> </cac:InvoiceDocumentReference> </xsl:template> <!-- 处理属性和文本节点 --> <xsl:template match="@* | text()"> <xsl:copy/> </xsl:template> </xsl:stylesheet>
转换结果验证
使用修正后的XSLT转换后,输出将完全符合期望:
- 根元素替换为
ubl:Invoice,命名空间配置正确 cac:BillingReference无原命名空间残留cbc:IssueDate及其他不需要的元素被成功剔除
内容的提问来源于stack exchange,提问作者Robert Mark Bram
相关产品推荐
相关产品推荐

