1

I have the following source XML document:

<UserDefinedFields>
  <UserDefinedField>
    <Name>ABC</Name>
    <Value>123</Value>
  </UserDefinedField>
  <UserDefinedField>
    <Name>XYZ</Name>
    <Value>645q3245</Value>
  </UserDefinedField>
</UserDefinedFields>

I want to overwrite matching nodes from an input XML if there is a matching <Name> value.. So in other words, the end result of merging this in:

<UserDefinedField>
  <Name>XYZ</Name>
  <Value>NEWVALUE!</Value>
</UserDefinedField>

... would be:

<UserDefinedFields>
  <UserDefinedField>
    <Name>ABC</Name>
    <Value>123</Value>
  </UserDefinedField>
  <UserDefinedField>
    <Name>XYZ</Name>
    <Value>NEWVALUE!</Value>
  </UserDefinedField>
</UserDefinedFields>

What is an appropriate XSLT transformation to achieve this?

XSLT 2.0 or 1.0 answers are fine... 2.0 preferred though.

3 Answers 3

1

You can do this with grouping:

<xsl:for-each-group 
  select="$doc1//UserDefinedField, $doc2//UserDefinedField" 
  group-by="Name">
  <xsl:copy-of select="current-group()[last()]"/>
</xsl:for-each-group>
Sign up to request clarification or add additional context in comments.

6 Comments

Dr. Kay, Is my understanding correct that the proposed solution will output even such $doc2//UserDefinedField elements, that don't match any $doc1//UserDefinedField element ? If so, this isn't what the OP wants -- he wants to output only such $doc2//UserDefinedField elements that match ("override") elements in $doc1
I'm not sure where your reading of the OP's requirements comes from, but yes, my solution assumes that if there is an entry in doc2 that matches no entry in doc1, the entry in doc2 will be added to the output. If that's not what's wanted you can adjust the solution with something like if(current-group()[1]/(/) is $doc1) then ...
Dr. Kay, Re: " flag I'm not sure where your reading of the OP's requirements comes from " . It is not just my reading -- it is exactly as specified in the question: "I want to overwrite matching nodes from an input XML if there is a matching value.. " Note the if...
@DimitreNovatchev There's nothing in the requirement that says what should happen to values in the second file that don't match anything in the first file. Three plausible strategies are (a) add them to the output, (b) ignore them, (c) report an error. You chose (b), I chose (a).
Dr. Kay, Generally, when nothing is specified, I choose to do nothing. :)
|
0

Assuming you are instructing your processor to process the "initial" XML document, and supplying the path to the "overriding" document as a parameter, you could do:

XSLT 2.0

<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:param name="path-to-update">update.xml</xsl:param>
<xsl:key name="fld" match="UserDefinedField" use="Name" />

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

<xsl:template match="Value">
    <xsl:variable name="update" select="key('fld', ../Name, document($path-to-update))" />
    <xsl:copy>
        <xsl:value-of select="if ($update) then $update/Value else ."/>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Comments

0

Just use this template to override the identity rule:

  <xsl:template match="UserDefinedField[key('kFieldByName', Name, $vDoc2)]/Value/text()">
    <xsl:value-of select="key('kFieldByName', ../../Name, $vDoc2)[1]"/>
  </xsl:template>

Here I assume that the second document has a document element (top element) and can contain many UserDefinedField elements at different depth. For convenience only, the second document is inlined in the transformation -- in a real case the doc() function can be used. I also declare an <xsl:key> to efficiently find a new value using the Name child's value of a UserDefinedField in the second document.

Here is the complete transformation:

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

 <xsl:key name="kFieldByName" match="Value" use="../Name"/>

 <xsl:variable name="vDoc2">
  <patterns>
    <UserDefinedField>
      <Name>XYZ</Name>
        <Value>NEWVALUE!</Value>
      </UserDefinedField>
    </patterns>
 </xsl:variable>

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

  <xsl:template match="UserDefinedField[key('kFieldByName', Name, $vDoc2)]/Value/text()">
    <xsl:value-of select="key('kFieldByName', ../../Name, $vDoc2)[1]"/>
  </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<UserDefinedFields>
  <UserDefinedField>
    <Name>ABC</Name>
    <Value>123</Value>
  </UserDefinedField>
  <UserDefinedField>
    <Name>XYZ</Name>
    <Value>645q3245</Value>
  </UserDefinedField>
</UserDefinedFields>

the wanted, correct result is produced:

<UserDefinedFields>
  <UserDefinedField>
      <Name>ABC</Name>
      <Value>123</Value>
  </UserDefinedField>
  <UserDefinedField>
      <Name>XYZ</Name>
      <Value>NEWVALUE!</Value>
  </UserDefinedField>
</UserDefinedFields>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.