While transforming a xml to xml using xsl 1.0. xsl:attribute: Cannot add attributes to an element if children have been already added to the element

StackOverflow https://stackoverflow.com/questions/21984867

  •  15-10-2022
  •  | 
  •  

Question

I am using xmlstarlet which supports only xsl 1.0 to transform one xml to another.

I am trying to find the value of attribute 'atr1' and insert another attribute 'atr2' in the same node.

When the below sample.xml is used

<a>
 <b></b>
 <c>
  <d atr1="#{not MGR_1}" atr2="#{condition1}"></d>
  <d atr1="#{ MGR_2}" ></d>
  <d atr1="2"></d>
 </c>
</a>

With the following transform.xsl

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>

<xsl:template match="@*|node()">
<xsl:copy>
  <xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>

<xsl:template match="d[contains(@atr1, 'MGR_')]">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>   
<xsl:attribute name="atr2">
<xsl:choose>
  <xsl:when test="@atr2">
   <xsl:value-of select="concat('#{', substring(@atr2,3,string-length(@atr2)-3), ' and not ', substring(@atr1,3))" />            
  </xsl:when>
  <xsl:otherwise>
   <xsl:value-of select="concat('#{not ', substring(@atr1,3))" />
  </xsl:otherwise>
</xsl:choose> 
</xsl:attribute>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

I get the expected o/p as below.

<a>
  <b/>
  <c>
    <d atr1="#{not MGR_1}" atr2="#{condition1 and not not MGR_1}"/>
    <d atr1="#{ MGR_2}" atr2="#{not  MGR_2}"/>
    <d atr1="2"/>
  </c>
</a>

But when I try to use the below xml. Which has an other inner element x, then the problem arises.

<a>
 <b></b>
 <c>
  <d atr1="#{not MGR_1}" atr2="#{condition1}"></d>
  <d atr1="#{ MGR_2}" ></d>
  <d atr1="2"></d>
  <d atr1="#{ MGR_3}" >
    <x>blah blah blah</x>
  </d>
 </c>
</a>

On trying to transform the above XML. I get a error message as

$ xml tr transform.xsl sample.xml
runtime error: file transform.xsl line 14 element attribute
xsl:attribute: Cannot add attributes to an element if children have been already added to the element.

Where am I getting it wrong. What could be the correct XSL to get the desired o/p on the modified XML.

Was it helpful?

Solution

The error message you get actually speaks for itself. You cannot add attributes if child nodes are already assigned to a node.

Simply separate applying a template to all attributes @* from applying a template to all potential child nodes node(). Note that this captures not only child elements.

By the way, I could reproduce your error message only with Saxon 9.5.1 but not with Saxon 6.5.5 and Xalan 2.7.1.

Stylesheet

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>

<xsl:template match="@*|node()">
<xsl:copy>
  <xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>

<xsl:template match="d[contains(@atr1, 'MGR_')]">
<xsl:copy>
<xsl:apply-templates select="@*"/>   
<xsl:attribute name="atr2">
<xsl:choose>
  <xsl:when test="@atr2">
   <xsl:value-of select="concat('#{', substring(@atr2,3,string-length(@atr2)-3), ' and not ', substring(@atr1,3))" />            
  </xsl:when>
  <xsl:otherwise>
   <xsl:value-of select="concat('#{not ', substring(@atr1,3))" />
  </xsl:otherwise>
</xsl:choose> 
</xsl:attribute>
<xsl:apply-templates select="node()"/>  
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

OTHER TIPS

You can only add attributes to nodes when no other nodes are copied. Change this:

<xsl:template match="d[contains(@atr1, 'MGR_')]">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>   
        <xsl:attribute name="atr2">
            <xsl:choose>
                <xsl:when test="@atr2">
                    <xsl:value-of select="concat('#{', substring(@atr2,3,string-length(@atr2)-3), ' and not ', substring(@atr1,3))" />            
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="concat('#{not ', substring(@atr1,3))" />
                </xsl:otherwise>
            </xsl:choose> 
        </xsl:attribute>
    </xsl:copy>
</xsl:template>

To:

<xsl:template match="d[contains(@atr1, 'MGR_')]">
    <xsl:copy>
        <xsl:apply-templates select="@*"/>  <!-- changed this -->
        <xsl:attribute name="atr2">
            <xsl:choose>
                <xsl:when test="@atr2">
                    <xsl:value-of select="concat('#{', substring(@atr2,3,string-length(@atr2)-3), ' and not ', substring(@atr1,3))" />            
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="concat('#{not ', substring(@atr1,3))" />
                </xsl:otherwise>
            </xsl:choose> 
        </xsl:attribute>
        <xsl:apply-templates select="node()"/> <!-- changed this -->
    </xsl:copy>
</xsl:template>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top