Connecting Tech Pros Worldwide Help | Site Map

XSLT compare attributes of XML nodes

Newbie
 
Join Date: Sep 2008
Posts: 22
#1: Nov 17 '08
Hi all,
I am trying to merge 2 XML files that first of all i need to compare nodes of both files according to 2 attributes in the nodes. If those 3 attributes are equal, i need to replace the existing node with the new one. For example first xml file will be like;

Expand|Select|Wrap|Line Numbers
  1. <Root>
  2.           <Node Id ="1" FormId="form1" Value="value1"/>
  3.           <Node Id ="2" FormId="form1" Value="value2"/>
  4.           <Node Id ="3" FormId="form1" Value="value3"/>
  5. </Root>
and second xml file will be like;

Expand|Select|Wrap|Line Numbers
  1. <Root>
  2.           <Node Id ="1" FormId="form1" Value="NewValue1"/>
  3.           <Node Id ="2" FormId="form1" Value="NewValue2"/>
  4.           <Node Id ="4" FormId="form1" Value="NewValue4"/>
  5. </Root>
and when i merged these 2 files result should be;

Expand|Select|Wrap|Line Numbers
  1. <Root>
  2.           <Node Id ="1" FormId="form1" Value="NewValue1"/>
  3.           <Node Id ="2" FormId="form1" Value="NewValue2"/>
  4.           <Node Id ="3" FormId="form1" Value="value3"/>
  5.           <Node Id ="4" FormId="form1" Value="NewValue4"/>
  6. </Root>
In those files, i compare the nodes of the second file with the nodes of the first file according to "Id" and "FormId" attributes. If those attributes are equal, that node in the first file will be replaced with the node in the second file. If they are not equal, current node in the second file will be written in the first file. What i have done so far is merging two xml files by comparing the distinct nodes according to all attributes in the nodes. But i couldn't manage only comparing nodes according to "Id" and "FormId" attributes. I am using that XSLT code to compare the equalty of the nodes and it works fine.

Expand|Select|Wrap|Line Numbers
  1. <xsl:for-each select="$node1/@*">       
  2.             <xsl:if test="not($node2/@* [local-name()=local-name(current()) and namespace-uri()=namespace-uri(current()) and .=current()])">.</xsl:if>
  3. </xsl:for-each>
When i changed that code only to compare according to those 2 attributes it doesn't work.

Expand|Select|Wrap|Line Numbers
  1. <xsl:for-each select="$node1/@*">
  2.             <xsl:if test="not($node2/@* [@Id = current()/@Id and @FormId = current()/@FormId])">.</xsl:if>
  3. </xsl:for-each>
Any help would be appreciated.
Dormilich's Avatar
Moderator
 
Join Date: Aug 2008
Location: Leipzig, Germany
Posts: 3,629
#2: Nov 17 '08

re: XSLT compare attributes of XML nodes


you put in one attribute node too many. use $node2 instead of $node2/@*. 1)

a more elagant way would be without the if clause
Expand|Select|Wrap|Line Numbers
  1. <xsl:for-each select="$node1[@Id != $node2/@Id][@FormId != $node2/@FormId]">
  2. // code goes here for every unique $node1
  3. </xsl:for-each>
regards

1)
Expand|Select|Wrap|Line Numbers
  1. // your first condition
  2. $node2/@* [@Id = current()/@Id]
  3. // resolves to
  4. $node2/@*/@Id = $node1/@*/@Id
  5. // which returns true (both are empty/invalid) ...
Newbie
 
Join Date: Sep 2008
Posts: 22
#3: Nov 17 '08

re: XSLT compare attributes of XML nodes


Dormilich, thanks for your reply,
I tried what you recommended like the following;

Expand|Select|Wrap|Line Numbers
  1. <xsl:for-each select="$node1[@Id != $node2/@Id][@FormId != $node2/@FormId]">
  2.             <xsl:if test="not($node2/@*/@Id = $node1/@*/@Id and $node2/@*/@FormId = $node1/@*/@FormId)">.</xsl:if>
  3. </xsl:for-each>
But it still gives the same output. What am i doing wrong?
Dormilich's Avatar
Moderator
 
Join Date: Aug 2008
Location: Leipzig, Germany
Posts: 3,629
#4: Nov 17 '08

re: XSLT compare attributes of XML nodes


you misunderstood. my code doesn't require <xsl:if>. the for-each loop should select all unique elements of $node1 (so that you can copy them).

regards

Expand|Select|Wrap|Line Numbers
  1. <xsl:if test="not($node2/@*/@Id = $node1/@*/@Id and $node2/@*/@FormId = $node1/@*/@FormId)">.</xsl:if>
will always fail since there are no elements who fulfill this condition.

Explanation:
Expand|Select|Wrap|Line Numbers
  1. $node2/@*/@Id -> empty
  2. $node1/@*/@Id -> empty
  3. empty = empty -> true
  4. not(true) -> false -> if statement skipped
Newbie
 
Join Date: Sep 2008
Posts: 22
#5: Nov 17 '08

re: XSLT compare attributes of XML nodes


Sorry for misunderstanding. My XSLT code has two parts. First part compares the nodes and second part copies the nodes according to the results of comparation. When comparing if required attributes are equal, i need to get a "=" and after that update the node. If attributes are not equal, i need to get a "!" and then copy the node from second file to the first file. Below is the comparation part of the code.

Expand|Select|Wrap|Line Numbers
  1. <!-- 
  2.  Comparing single nodes: 
  3.      if $node1 and $node2 are equivalent then the template creates a 
  4.      text node "=" otherwise a text node "!" -->
  5.      <xsl:template name="m:compare-nodes">
  6.       <xsl:param name="node1" />
  7.       <xsl:param name="node2" />
  8.        <xsl:variable name="type1">
  9.         <xsl:apply-templates mode="m:detect-type" select="$node1" />
  10.       </xsl:variable>
  11.        <xsl:variable name="type2">
  12.         <xsl:apply-templates mode="m:detect-type" select="$node2" />
  13.       </xsl:variable>
  14.        <xsl:choose>
  15.          <!--  Are $node1 and $node2 element nodes with the same name? -->
  16.          <xsl:when test="$type1='element' and $type2='element' and local-name($node1)=local-name($node2) and namespace-uri($node1)=namespace-uri($node2) and name($node1)!=$dontmerge and name($node2)!=$dontmerge">
  17.            <!--  Comparing the attributes -->
  18.            <xsl:variable name="diff-att">
  19.              <!--  same number ... -->
  20.             <xsl:if test="count($node1/@*)!=count($node2/@*)">.</xsl:if>
  21.              <!--  ... and same name/content -->
  22.              <xsl:for-each select="$node1/@*">
  23.              <!--<xsl:for-each select="$node1/Caption">-->
  24.                <xsl:value-of select="@Id" />
  25.                <xsl:value-of select="@FormId" />
  26.                <xsl:value-of select="@LangId" />
  27.                <!--<xsl:if test="not($node2/Caption/Id/FormId/LangId [local-name()=local-name(current()) and namespace-uri()=namespace-uri(current()) and .=current()])">.</xsl:if>-->
  28.                <xsl:if test="not($node2/@* [local-name()=local-name(current()) and namespace-uri()=namespace-uri(current()) and .=current()])">.</xsl:if>
  29.             </xsl:for-each>
  30.           </xsl:variable>
  31.            <xsl:choose>
  32.             <xsl:when test="string-length($diff-att)!=0">!</xsl:when>
  33.             <xsl:otherwise>=</xsl:otherwise>
  34.           </xsl:choose>
  35.         </xsl:when>
  36.          <!--  Other nodes: test for the same type and content -->
  37.         <xsl:when test="$type1!='element' and $type1=$type2 and name($node1)=name($node2) and ($node1=$node2 or ($normalize='yes' and normalize-space($node1)= normalize-space($node2)))">=</xsl:when>
  38.          <!--  Otherwise: different node types or different name/content -->
  39.         <xsl:otherwise>!</xsl:otherwise>
  40.       </xsl:choose>
  41.     </xsl:template>
Sorry i don't know much about XSLT so i have difficulty in solving the problem.

Regards,
Newbie
 
Join Date: Sep 2008
Posts: 22
#6: Nov 17 '08

re: XSLT compare attributes of XML nodes


Anybody knows how to fix that??
Dormilich's Avatar
Moderator
 
Join Date: Aug 2008
Location: Leipzig, Germany
Posts: 3,629
#7: Nov 17 '08

re: XSLT compare attributes of XML nodes


if you just (well...) want to merge two files, why making it by the means of a comparison string? Isn't that complicated (well, it looks so...)?

As I (hopefully) understand you right you want to merge two xml files omitting duplicate nodes (regarding the attributes) from the first xml. Do you need "!" and "=" in the output too?

I'd do a straightforward merging.

the idea is to copy all nodes from xml no.2 and add all nodes from xml no.1 that are not present in xml no.2.

like (sketch)
Expand|Select|Wrap|Line Numbers
  1. // select mergable nodes
  2. <xsl:variable name="node1" select="//*[self::name() != $dontmerge]"/>
  3. //... there may be more conditions depending on the xml
  4.  
  5. <xsl:template match="/">
  6.   <xsl:apply-templates/>
  7. </xsl:template>
  8.  
  9. // add xml no.2
  10. <xsl:template match="$node2">
  11.   <xsl:copy-of select="."/> // or whatever value you need
  12. </xsl:template>
  13.  
  14. // add xml no.1 using conditions
  15. <xsl:template match="$node1[@Id != $node2/@Id][@FormId != $node2/@FormId]">
  16.   <xsl:copy-of select="."/> // or whatever value you need
  17. </xsl:template>
this idea should at least work for your example....

regards
Newbie
 
Join Date: Sep 2008
Posts: 22
#8: Nov 17 '08

re: XSLT compare attributes of XML nodes


Thanks again for your reply. In your code i can only insert the nodes which doesn't exist in the file1. But i also want to replace the nodes having the same "Id" and "FormId" attribute values. For ex. if i have a line in file1 like;

Expand|Select|Wrap|Line Numbers
  1. <Node Id="1" FormId="form1" Values="value1"> 
and a line in file2 like;

Expand|Select|Wrap|Line Numbers
  1. <Node Id="1" FormId="form1" Values="new Value"> 
which both nodes have same "Id" and "FormId" attribute values("1" and "form1") and corresponding output will be;

Expand|Select|Wrap|Line Numbers
  1. <Node Id="1" FormId="form1" Values="new Value"> 
that first node will be replaced with second node. So i have two options according to what i get from the comparation("!" or "="). If result is "!" i will insert the node from the file2 to file1, and if result is "=" i will replace the node(as in the example above). So is it possible to modify the code i give to get a result "!" or "=" according to the values of "Id" and "FormId" attributes.
Hope it is obvious to understand.

Regards
Moderator
 
Join Date: Mar 2006
Posts: 1,103
#9: Nov 17 '08

re: XSLT compare attributes of XML nodes


Sorry if I'm misinterpreting (my mind isn't working that well yet today).
Are you sorting based on id? Is it possible to have the same id, but different FormId?

Would have suggested a 1-liner:
<xsl:copy-of select="$node1[not ($node2[@Id = current()/@Id])] | $node2"/>

or if order matters, change to a for-each:
Expand|Select|Wrap|Line Numbers
  1. <xsl:for-each select="$node1[not ($node2[@Id = current()/@Id])] | $node2">
  2.   <xsl:sort select="@Id" order="ascending"/>
  3.   <xsl:copy-of select="."/>
  4. </xsl:for-each>
  5.  
Otherwise, I agree with Dormilich about the xpath being wrong. (attributes don't have children attributes)

---
I think we're going about the same problem 2 different ways.
If we're going to 'update' the old node with new values, why not just copy the new node?
Newbie
 
Join Date: Sep 2008
Posts: 22
#10: Nov 17 '08

re: XSLT compare attributes of XML nodes


Of course it may have the same Id and different FormId and i can already copy the node in the merge part. And in the xml files attributes don't have child attributes(i know it is impossible). Just each node has attributes ("Id", "FormId", "Values"). Merging part of the XSLT file can copy the nodes correctly. But when i comparing the nodes("m:compare-nodes" template i sent in one of the messages above) i always get a "!" that shows nodes are not equal(eventhough "Id" and "FormId" attribute values are equal). I only want to get the correct output ("!" or "=") from the compare template. By the way i don't need to sort the nodes. Here i need to modify the above "m:compare-nodes" template.

Regards
Moderator
 
Join Date: Mar 2006
Posts: 1,103
#11: Nov 17 '08

re: XSLT compare attributes of XML nodes


Problem here:
.=current()

. = '' (empty string)
current() = '' (empty string)

Of course you are going to always get false.

Further, you can reduce
local-name() = local-name(current()) and namespace-uri() = namespace-uri(current())
to name() = name(current())


The solution you provide seems overcomplicated; don't know if there's a specific reason why you're doing it this way.
Newbie
 
Join Date: Sep 2008
Posts: 22
#12: Nov 18 '08

re: XSLT compare attributes of XML nodes


Thank you all for your helps. But result is still same. Only if nodes are totally equal, they are not inserted into the file. Otherwise all nodes(even having same "Id" and "FormId" which i want those nodes to be evaluated as equal and then replace those nodes in the file1 with the nodes in the file2) are inserted.
Dormilich's Avatar
Moderator
 
Join Date: Aug 2008
Location: Leipzig, Germany
Posts: 3,629
#13: Nov 18 '08

re: XSLT compare attributes of XML nodes


there seems to be a bit of confusion about how to replace the nodes.

there are basicly two points of view:
1) ("forward view") get the nodes of xml 1 and replace the duplicate ones by the corresponding nodes of xml 2 + adding the unique nodes of xml 2
2) ("backward view") get the nodes of xml 2 and insert the unique nodes of xml 1

personally, I favour option 2, because (as I understand it) all nodes of xml 2 will show up in the merged xml.

regards
Reply