I'm trying to use XSLT to remove tags/attributes belonging to a namespace. The difficulty is that tags from differnt namespaces can be embedded in each other.
Sample:
<?xml version="1.0" encoding="utf-8"?>
<Collection xmlns="http://s0" xmlns:ns1="http://s1">
<Identifier Name="CollectionX"
ns1:GlobalID="{E436833B-B0A6-4E0D-804B-60052B767AE3}"
ns1:LocalID="{0130C866-7A91-4544-A82B-E0C0F2E3BCB2}" />
<Properties>
<ns1:Collectible>1982</ns1:Collectible>
<Displayed>Reserved</Displayed>
<Picture>Reserved.jpeg</Picture>
</Properties>
<WeakLinks>
<Link Type="resource" Language="en-us"/>
</WeakLinks>
</Collection>
I want to filter all tags/properties that do not belong to ns1 as long as they do not have any ns1 children.
So the result should be:
<?xml version="1.0" encoding="utf-8"?>
<Collection xmlns="http://s0" xmlns:ns1="http://s1">
<Identifier
ns1:GlobalID="{E436833B-B0A6-4E0D-804B-60052B767AE3}"
ns1:LocalID="{0130C866-7A91-4544-A82B-E0C0F2E3BCB2}" />
开发者_运维百科<Properties>
<ns1:Collectible>1982</ns1:Collectible>
</Properties>
</Collection>
How can I accoplish this with XSLT? Any help?
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns1="http://s1">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"*[not(attribute::ns1:*)
and
not(descendant-or-self::ns1:*)
]
|
@*[not(namespace-uri()='http://s1')]
"/>
</xsl:stylesheet>
when applied on the provided XML document:
<Collection xmlns="http://s0" xmlns:ns1="http://s1">
<Identifier Name="CollectionX"
ns1:GlobalID="{E436833B-B0A6-4E0D-804B-60052B767AE3}"
ns1:LocalID="{0130C866-7A91-4544-A82B-E0C0F2E3BCB2}" />
<Properties>
<ns1:Collectible>1982</ns1:Collectible>
<Displayed>Reserved</Displayed>
<Picture>Reserved.jpeg</Picture>
</Properties>
<WeakLinks>
<Link Type="resource" Language="en-us"/>
</WeakLinks>
</Collection>
produces the wanted, correct result:
<Collection xmlns="http://s0" xmlns:ns1="http://s1">
<Identifier ns1:GlobalID="{E436833B-B0A6-4E0D-804B-60052B767AE3}"
ns1:LocalID="{0130C866-7A91-4544-A82B-E0C0F2E3BCB2}"/>
<Properties>
<ns1:Collectible>1982</ns1:Collectible>
</Properties>
</Collection>
Explanation:
The identity rule (template) copies every node "as-is".
There is just one template overriding the identity rule. This templates has no body -- meaning that it effectively filters (deletes) any matched node from being copied to the output. The nodes matched are exactly the ones that must be filtered out: 1) any element that doesn't have attributes belonging to the namespace to which the prefix
ns1:
is bound and also isn't itself belonging to that namespace and also it has no descendent element nodes belonging to that namespace. And 2) any attribute that doesn't belong to that namespace.
Remember: Overriding the identity rule is the most fundamental and most powerful XSLT design pattern. More about this design pattern can be found here.
You can select elements with the ns1
namespace by using ns1:*
.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns1="http://s1">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@ns1:* |
node()[attribute::ns1:* |
descendant-or-self::ns1:*] |
text() | comment() | processing-instruction()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Update
I updated the XPath for matching attributes to @ns1:*
to only capture attributes with the desired namespace. I also fixed the support for comments and processing instructions work in my testing. Given the following XML
<?xml version="1.0" encoding="utf-8"?>
<Collection xmlns="http://s0" xmlns:ns1="http://s1">
<Identifier Name="CollectionX"
ns1:GlobalID="{E436833B-B0A6-4E0D-804B-60052B767AE3}"
ns1:LocalID="{0130C866-7A91-4544-A82B-E0C0F2E3BCB2}" />
<!-- comment -->
<Properties>
<ns1:Collectible>1982</ns1:Collectible>
<Displayed>Reserved</Displayed>
<Picture>Reserved</Picture>
</Properties>
<WeakLinks>
<Link Type="resource" Language="en-us"/>
</WeakLinks>
</Collection>
The XSL above produces this output (tested with Saxon and MSXML).
<?xml version="1.0" encoding="UTF-8"?>
<Collection xmlns="http://s0" xmlns:ns1="http://s1">
<Identifier ns1:GlobalID="{E436833B-B0A6-4E0D-804B-60052B767AE3}"
ns1:LocalID="{0130C866-7A91-4544-A82B-E0C0F2E3BCB2}"/>
<!-- comment -->
<Properties>
<ns1:Collectible>1982</ns1:Collectible>
</Properties>
</Collection>
Update 2
I removed my previous reference to attributes with no-namespace. According to the XPath specification which is summarized very well here by @Dimitre.Novatchev, an attribute with no namespace prefix belongs to "no-namespace", not the default namespace or the namespace of the parent node. If you do want to match these add @*[parent::ns1:* and namespace-uri()=''] |
to the match expression in <apply-templates ...>
. This would apply a situation like <ns1:Collectible WhatIsMyNamespace="no-namespace">
where you want to match WhatIsMyNamespace="no-namespace"
.
精彩评论