XQuery compare two documents and replace nodes with same name

556 views Asked by At

I have a document structured like this:

<INVT_DATA xmlns="http://www.mrbook.com/InventoryData">
    <AUTHOR>...</AUTHOR>
    <TITLE>...</TITLE>
    <PUBLISHER>...</PUBLISHER>
</INVT_DATA>

Subchildren of INVT_DATA are always elements and not text nodes. I would like to use XQuery to compare a new document with the same structure. If a subelement is present in the new and the original, it should be replaced. If a subelement is present in the new, but is not in the old, it should be appended to INVT_DATA.

This XQuery, I think, would work, but it always seems to just append nodes instead:

declare namespace invtdata="http://www.mrbook.com/InventoryData";

copy $oldInvtData := $oldXml

modify 
( 
  for $mpf in $newXml/invtdata:INVT_DATA/*
  let $oldMpf := $oldInvtData/invtdata:INVT_DATA/*[name()=name($mpf)] 
  return if(exists($oldMpf)) then 
  replace node $oldMpf with $mpf 
  else insert node $mpf into $oldInvtData 
)
return $oldInvtData 

I searched for other similar problems and found this, but that is quite a bit of overkill for what I want to do. Any suggestions? If it helps, I am using XmlQuery from the Oracle XML DB, version 11.2.0.3.

2

There are 2 answers

0
Brad On BEST ANSWER

This turns out to be the correct code.... just one minor tweak.

declare namespace invtdata="http://www.mrbook.com/InventoryData";

declare variable $oldXml := doc('.../Brad1.xml');
declare variable $newXml := doc('.../Brad2.xml');

copy $oldInvtData := $oldXml

modify 
( 
  for $mpf in $newXml/invtdata:INVT_DATA/*
  let $oldMpf := $oldInvtData/invtdata:INVT_DATA/*
                 [local-name() = local-name($mpf)
                  and namespace-uri() = namespace-uri($mpf)] 
  return if(exists($oldMpf)) then 
  replace node $oldMpf with $mpf 
  else insert node $mpf into $oldInvtData/invtdata:INVT_DATA 
)
return $oldInvtData 

Thanks for the help!

2
C. M. Sperberg-McQueen On

Matching on name() is error-prone, because name() returns the name as it appears in the XML -- if your two inputs bind different prefixes to the namespace name, checking on equality of name() won't succeed. If you change your predicate to

[local-name() = local-name($mpf) 
 and
 namespace-uri() = namespace-uri($mpf)]

your code should work better.


Addendum:

That is, given an XML document Brad1.xml with the following contents:

<INVT_DATA xmlns="http://www.mrbook.com/InventoryData">
  <AUTHOR>old author</AUTHOR>
  <TITLE>old title</TITLE>
  <PUBLISHER>old publisher</PUBLISHER>
</INVT_DATA>

and an XML document Brad2.xml with the following contents:

<id:INVT_DATA xmlns:id="http://www.mrbook.com/InventoryData">
  <id:AUTHOR>new author</id:AUTHOR>
  <id:PUBLISHER>new publisher</id:PUBLISHER>
</id:INVT_DATA>

the code:

declare namespace invtdata="http://www.mrbook.com/InventoryData";

declare variable $oldXml := doc('.../Brad1.xml');
declare variable $newXml := doc('.../Brad2.xml');

copy $oldInvtData := $oldXml

modify 
( 
  for $mpf in $newXml/invtdata:INVT_DATA/*
  let $oldMpf := $oldInvtData/invtdata:INVT_DATA/*
                 [local-name() = local-name($mpf)
                  and namespace-uri() = namespace-uri($mpf)] 
  return if(exists($oldMpf)) then 
  replace node $oldMpf with $mpf 
  else insert node $mpf into $oldInvtData 
)
return $oldInvtData 

evaluates to:

<INVT_DATA xmlns="http://www.mrbook.com/InventoryData"
           xmlns:id="http://www.mrbook.com/InventoryData">
    <id:AUTHOR>new author</id:AUTHOR>
    <TITLE>old title</TITLE>
    <id:PUBLISHER>new publisher</id:PUBLISHER>
</INVT_DATA>

If you aren't seeing any nodes replaced at all, one likely cause is an error in an XPath expression (are you sure they are all correct? no typos?); another is failure to bind $oldXml or $newXml as you intend.