Reputation: 143
Given the following XML example file :
<root>
<list number="123">
<element>
<name value="Name1"/>
<line value="Line 1 :"/>
</element>
<element>
<name value="Name1"/>
<line value="Line 1 :"/>
</element>
</list>
</root>
I want to create an XSL file allowing me to replace each occurrence of Line 1 :
with Line 1 : 123
, the 123
coming from the list
node's attribute, number
.
I have the following XSL file :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="element">
<xsl:copy-of select="."/>
<xsl:if test="@value='Line 1 :'">
<xsl:attribute name="type">
<xsl:value-of select="root/list[@number]"/>
</xsl:attribute>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
but the transformation fails and returns something like "Attributes and namespaces nodes cannot be added to the parent element after a text, comment, pi, or sub-element node has already been added".
Any idea about what is wrong with this XSL ?
Upvotes: 1
Views: 960
Reputation: 22617
Actually, the error message you get:
Attributes and namespaces nodes cannot be added to the parent element after a text, comment, pi, or sub-element node has already been added
explains the problem very well already. Your template matching element
looks like this
<xsl:template match="element">
<xsl:copy-of select="."/>
<xsl:if test="@value='Line 1 :'">
<xsl:attribute name="type">
The first instruction inside it:
<xsl:copy-of select="."/>
copies the element
element with all its content. This includes child elements, text nodes, attributes and so on.
It is one of the rules of XSLT that once certain kinds of nodes are added to an element node, you cannot add more attribute nodes. But this is exactly what you are trying to do with
<xsl:attribute name="type">
which constructs an attribute node.
You do not actually need to xsl:copy-of
the whole content of the element
element, because you have an identity template in place that will copy everything anyway by default.
Only intervene in the exact spot that you would like to change. In this case, it is the value
attribute of the line
element - so write a template that matches exactly that.
I think what you need is the following. If not, please show the XML output you expect instead of describing it. I have assumed that there could be multiple list
elements with varying number
attributes.
XSLT Stylesheet
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="element/line/@value">
<xsl:attribute name="value">
<xsl:value-of select="concat(., ' ', ../../../@number)"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
XML Output
<?xml version="1.0" encoding="utf-8"?>
<root>
<list number="123">
<element>
<name value="Name1"/>
<line value="Line 1 : 123"/>
</element>
<element>
<name value="Name1"/>
<line value="Line 1 : 123"/>
</element>
</list>
</root>
EDIT
A more concise requirement would be : given any XML file, how can I concatenate any attribute value inside that is equal to "Line 1 :" with the 123 from the number above ? I assume that would need more than one template...
No, this would only be a small modification. Change the second template to:
<xsl:template match="list//@*[. = 'Line 1 :']">
<xsl:attribute name="{name()}">
<xsl:value-of select="concat(., ' ', ancestor::list[1]/@number)"/>
</xsl:attribute>
</xsl:template>
Upvotes: 4