472,123 Members | 1,329 Online
Bytes | Software Development & Data Engineering Community
Post +

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 472,123 software developers and data experts.

XSLT compare attributes of XML nodes

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.
Nov 17 '08 #1
12 17952
Dormilich
8,658 Expert Mod 8TB
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) ...
Nov 17 '08 #2
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?
Nov 17 '08 #3
Dormilich
8,658 Expert Mod 8TB
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
Nov 17 '08 #4
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,
Nov 17 '08 #5
Anybody knows how to fix that??
Nov 17 '08 #6
Dormilich
8,658 Expert Mod 8TB
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
Nov 17 '08 #7
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
Nov 17 '08 #8
jkmyoung
2,057 Expert 2GB
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?
Nov 17 '08 #9
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
Nov 17 '08 #10
jkmyoung
2,057 Expert 2GB
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.
Nov 17 '08 #11
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.
Nov 18 '08 #12
Dormilich
8,658 Expert Mod 8TB
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
Nov 18 '08 #13

Post your reply

Sign in to post your reply or Sign up for a free account.

Similar topics

5 posts views Thread by inquirydog | last post: by
2 posts views Thread by Jon Martin Solaas | last post: by
3 posts views Thread by Ian Roddis | last post: by
reply views Thread by leo001 | last post: by

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.