How can I copy an XML structure into another one except some attributes or nodes
with the XML structure below:
<root foo1="bar1" foo2="bar2" foo3="bar3">
<foo1 foo1="bar1" />
<开发者_高级运维;data>
<foo1>bar1</foo1>
<foo2>bar2</foo2>
<foo3>bar3</foo3>
</data>
</root>
I would like to copy this XML structure into another one with some exception on attributes and/or node() names and get the following result using XSLT 1.0:
<root foo1="bar1" foo2="bar2">
<data>
<foo1>bar1</foo1>
<foo3>bar3</foo3>
</data>
</root>
My rules are:
1) Copy every root attributes except foo3
2) Copy every child nodes()
unless the ones named foo1 and foo2
My actual XSL stylesheet. I managed to get the root attributes restriction working :
<xsl:template match="root">
<root>
<xsl:for-each select="./@*">
<xsl:variable name="name" select="name()" />
<xsl:if test="name() != 'foo3'">
<xsl:attribute name="{$name}"><xsl:value-of select="." /></xsl:attribute>
</xsl:if>
</xsl:for-each>
</root>
</xsl:template>
Also, one harder question:
What if I want to matches my attributes and nodes dynamically. I would like to specify server-side what
attributes and nodes()
I would like to remove. It's probably like generating a string that is then used in the <xsl:if>
. I don't know if that's even possible.
Thank you.
I would like to copy this XML structure into another one with some exception [...]
My rules are:
1) Copy every root attributes except foo3
2) Copy every child nodes() unless the ones named foo1 and foo2
Update from comments:
Hi, this almost work. Except that data/foo1 should be copied
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root/@foo3|root/foo1|foo2"/>
</xsl:stylesheet>
Output:
<root foo1="bar1" foo2="bar2">
<data>
<foo1>bar1</foo1>
<foo3>bar3</foo3>
</data>
</root>
Note: Overwriting identity rule with empty templates
With the node names in parameter, this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="pStrip" select="'root/@foo3|root/foo1|foo2'"/>
<xsl:template match="node()|@*" name="identity">
<xsl:param name="pStripPaths" select="concat($pStrip,'|')"/>
<xsl:param name="pNodePath">
<xsl:call-template name="path"/>
<xsl:text>|</xsl:text>
</xsl:param>
<xsl:variable name="vStripPath"
select="substring-before($pStripPaths,'|')"/>
<xsl:choose>
<xsl:when test="not($pStripPaths)">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:when>
<xsl:when test="contains($pNodePath,concat('/',$vStripPath,'|'))"/>
<xsl:otherwise>
<xsl:call-template name="identity">
<xsl:with-param name="pStripPaths"
select="substring-after($pStripPaths,'|')"/>
<xsl:with-param name="pNodePath" select="$pNodePath"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="node()" name="path" mode="path">
<xsl:apply-templates select="parent::*" mode="path"/>
<xsl:value-of select="concat('/',
substring('@',
1 div (count(.|../@*)
= count(../@*))),
name())"/>
</xsl:template>
</xsl:stylesheet>
Output:
<root foo1="bar1" foo2="bar2">
<data>
<foo1>bar1</foo1>
<foo3>bar3</foo3>
</data>
</root>
Note: In XML, the element name refers to the schema, mostly defining the hierarchie position, but yours is not the case.
Edit: Just for fun, an XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:local="http://localhost">
<xsl:param name="pStrip" select="'root/@foo3|root/foo1|foo2'"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[local:match($pStrip,.)]|@*[local:match($pStrip,.)]"/>
<xsl:function name="local:match" as="xs:boolean">
<xsl:param name="pStripPaths" as="xs:string"/>
<xsl:param name="pNode" as="item()"/>
<xsl:variable name="vNodePath"
select="string-join(($pNode
/ancestor::node()
/name(),
if ($pNode instance of attribute())
then concat('@',name($pNode))
else name($pNode)),
'/')"/>
<xsl:sequence select="some $path in tokenize($pStripPaths,'\|')
satisfies ends-with($vNodePath,
concat('/',$path))"/>
</xsl:function>
</xsl:stylesheet>
Edit 2: All the stylesheet following the same string pattern.
You can use XPath to simplify your selection:
<xsl:for-each select="./@*[not(name()='foo3')]">
You then don't have to test for the names. You can do a similar thing for the elements:
*[not(name()='foo2')]
This transformation accepts a parameter with the XPath expressions to all elements/attributes that should be deleted:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pDeletes">
<element>/root/data/foo2</element>
<element>/root/foo1</element>
<attribute>/root/@foo3</attribute>
</xsl:param>
<xsl:variable name="vDeletes"
select="msxsl:node-set($pDeletes)"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:variable name="vPath">
<xsl:apply-templates select="." mode="buildPath"/>
</xsl:variable>
<xsl:if test="not(string($vPath) = $vDeletes/element)">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
<xsl:template match="@*">
<xsl:variable name="vPath">
<xsl:apply-templates select="." mode="buildPath"/>
</xsl:variable>
<xsl:if test="not(string($vPath) = $vDeletes/attribute)">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
<xsl:template match="*" mode="buildPath">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="concat('/',name())"/>
<xsl:variable name="vprecSiblings"
select="count(preceding-sibling::*[name()=name(current())])"/>
<xsl:if test="$vprecSiblings">
<xsl:value-of select="concat('[', $vprecSiblings, ']')"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="@*" mode="buildPath">
<xsl:variable name="vParentPath">
<xsl:apply-templates select=".." mode="buildPath"/>
</xsl:variable>
<xsl:value-of select="concat($vParentPath, '/@', name())"/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<root foo1="bar1" foo2="bar2" foo3="bar3">
<foo1 foo1="bar1" />
<data>
<foo1>bar1</foo1>
<foo2>bar2</foo2>
<foo3>bar3</foo3>
</data>
</root>
the wanted, correct result is produced:
<root foo1="bar1" foo2="bar2">
<data>
<foo1>bar1</foo1>
<foo3>bar3</foo3>
</data>
</root>
Do note:
Each XPath expression that identifies a node to be deleted, is the string value of an
element
orattribute
child of a global, externally provided parameter. The location steps of any such expression contain the name of the element and in the predicate part its ordinal number among all identically named siblings. If this is the first element the predicate should not be soecified. For attributes, the last location step contains exactly"@"AttributeName
, whereAttributeName
is the name of the attribute.The
xxx:node-set()
extension function is here only for demo purpuses. In practice, the$pDeletes
parameter will be provided externally and there is no need to apply thexxx:node-set()
function on it.All the XPath expressions could be provided in one string (using a better delimiter than the
|
XPath union operator :) ), but this may be less efficient than having each expression in a separate element. In the latter case elements or attributes can be searched separately and also if efficiency matters the expressions can be indexed, say by element or attribute name, thus providing very fast, sublinear search time.
精彩评论