2
votes

I have some code that compares two XML documents for attribute differences (updates only, not new attribute nodes) and generates a set of XPath pointers to the attributes and the new values for the attribute.

The Setup

For example, given an old XML and new xml:

Old XML

<EntityA>
  <EntityB id="foo1" value="bar1" ignoredbutsave="bazz1"/>
</EntityA>

New XML

<EntityA>
  <EntityB id="foo2" value="bar2"/>
</EntityA>

My code would return

/EntityA/EntityB/@id, foo2
/EntityA/EntityB/@value, bar2

I would like to generate an XSLT that merges the old XML into the new XML, to create the following XML:

<EntityA>
  <EntityB id="foo2" value="bar2" ignoredbutsave="bazz1"/>
</EntityA>

All answers I've found on SO assume some prior knowledge about the attribute name. In this case, I'm given only the XPath reference the attribute, not the name itself. I know I could parse the XPath string to derive the attribute name, but would prefer to keep that complexity out of the code.

What I've tried

I can't use an attribute value template because I need to copy the ignoredbutsave attribute from the old XML. I've tried to use an xsl:param to select the attribute name from the XPath and use it within an xsl:attribute, like this:

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

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

  <xsl:template match="/EntityA/EntityB/@id">
    <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
       <xsl:param name="newValue" select="name(/EntityA/EntityB/@id)"/>
         <xsl:attribute name="$newValue">newAttributeId</xsl:attribute>
     </xsl:copy>
  </xsl:template>

  <xsl:template match="/EntityA/EntityB/@value">
    <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
       <xsl:param name="myattrname" select="name(/EntityA/EntityB/@value)"/>
         <xsl:attribute name="$myattrname">newAttributeValue</xsl:attribute>
     </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

However, this causes the error The value '$myattrname' of the attribute 'name' is not a valid QName.

So, the question is given an XPath for an attribute and a new value for that attribute, how do I generate an an XSLT that updates that value without explicitly referencing the attribute name?

1
Have you tried to put your $myattrname in curly brackets?Marcus Rickert
@MarcusRickert - yes <xsl:attribute name="{$myattrname}"> does work.lreeder

1 Answers

1
votes

This XSLT transformation:

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

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

  <xsl:template match="/EntityA/EntityB/@id">
    <xsl:attribute name="{name()}">foo2</xsl:attribute>
  </xsl:template>

  <xsl:template match="/EntityA/EntityB/@value">
    <xsl:attribute name="{name()}">bar2</xsl:attribute>
  </xsl:template>
</xsl:stylesheet>

Applied to your old XML:

<EntityA>
  <EntityB id="foo1" value="bar1" ignoredbutsave="bazz1"/>
</EntityA>

Yields your old XML with the required attribute value substitutions made:

<EntityA>
  <EntityB id="foo2" value="bar2" ignoredbutsave="bazz1"/>
</EntityA>