xsl-list
[Top] [All Lists]

Re: [xsl] Simple XML Diff

2008-07-11 01:13:18
Hi,

With XSLT 2.0 you can define a function to give you a unique value based on the location of a node inside the document and the value should be the same if the structure is identical. Then you can use a key to match the node in the master based on that value while you walk the nodes in your document.
The stylesheet below gives you a result like:

<?xml version="1.0" encoding="UTF-8"?><test>
<nodeA attribute1="XXX" attribute1-diff="111" attribute2="YYY" attribute2-diff="222">
        <nodeA1 diff="FFF">ZZZ</nodeA1>
    </nodeA>
    <nodeA attribute1="XXX" attribute2="YYY" attribute2-diff="ZZZ">
        <nodeA1>ZZZ</nodeA1>
    </nodeA>
    <nodeB>AAA</nodeB>
    <nodeC>BBB</nodeC>
    <nodeC diff="BBB">BBC</nodeC>
</test>

when it is applied on a document with the content

<?xml version="1.0" encoding="UTF-8"?>
<test>
    <nodeA attribute1="XXX" attribute2="YYY">
        <nodeA1>ZZZ</nodeA1>
    </nodeA>
    <nodeA attribute1="XXX" attribute2="YYY">
        <nodeA1>ZZZ</nodeA1>
    </nodeA>
    <nodeB>AAA</nodeB>
    <nodeC>BBB</nodeC>
    <nodeC>BBC</nodeC>
</test>

having a master.xml file in the same folder:

<?xml version="1.0" encoding="UTF-8"?>
<test>
    <nodeA attribute1="111" attribute2="222">
        <nodeA1>FFF</nodeA1>
    </nodeA>
    <nodeA attribute1="XXX" attribute2="ZZZ">
        <nodeA1>ZZZ</nodeA1>
    </nodeA>
    <nodeB>AAA</nodeB>
    <nodeC>BBB</nodeC>
    <nodeC>BBB</nodeC>
</test>


Here it is the stylesheet:


<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; version="2.0"
    xmlns:x="http://www.oxygenxml.com/xslt/functions";>

    <xsl:function name="x:getPath">
        <xsl:param name="node"/>
        <xsl:value-of
            select="
for $i in $node/ancestor-or-self::* return concat(local-name($i), count($i/preceding-sibling::*))
            "/>
    </xsl:function>

    <xsl:key name="path" match="*" use="x:getPath(.)"/>

    <xsl:template match="*">
        <xsl:variable name="id" select="x:getPath(.)"/>
        <xsl:variable name="this" select="."/>
<xsl:variable name="master" select="document('master.xml')/key('path', $id)"/>
        <xsl:copy>
            <xsl:if test="not($master/text()=$this/text())">
                <xsl:attribute name="diff" select="$master/text()"/>
            </xsl:if>
            <xsl:for-each select="@*">
                <xsl:apply-templates select=".">
<xsl:with-param name="master" select="$master/@*[name()=current()/name()]"/>
                </xsl:apply-templates>
            </xsl:for-each>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="@*">
        <xsl:param name="master"/>
        <xsl:copy/>
<xsl:if test="not(.=$master)"><xsl:attribute name="{name()}-diff" select="$master"/></xsl:if>
    </xsl:template>

</xsl:stylesheet>

Note that you can generate the diff attributes eventually in a specific namespace to avoid possible conflicts with existing attributes.

Best Regards,
George
--
George Cristian Bina
<oXygen/> XML Editor, Schema Editor and XSLT Editor/Debugger
http://www.oxygenxml.com

Mark Anderson wrote:
Hi All

I'm trying to write an XML template that will compare two XML files that have 
*identical* structures. I thought it would be easy or in the archives, but all 
I can find is references to full DIFF functions that look for new elements, etc.

My style sheet and an XML file (let's call it "new.xml") will get called from a 
32-bit windows app and it needs to transform the XML to HTML.

My stylesheet will contain a reference to a "master" xml doc (let's call it 
"master.xml").

What I need to do is compare every element an attribute in new.xml with the 
equivalent in master.xml and then highlight differences (the easy bit). What 
I'm stuck with is something that (I'm guessing) is dead easy, but I can't 
figure it out: how to iterate through all elements and attributes in new.xml 
and find the equivalent in master.xml.

Example

"New.xml"
<nodeA attribute1="XXX" attribute2="YYY">
        <nodeA1>ZZZ</nodeA1>
</nodeA>
<nodeB>AAA</nodeB>
<nodeC>BBB</nodeC>

"Master.xml"
<nodeA attribute1="111" attribute2="222">
        <nodeA1>FFF</nodeA1>
</nodeA>
<nodeB>AAA</nodeB>
<nodeC>BBB</nodeC>

So, this XSLTemplate I need to create needs to compare

New.xml/nodeA/@attribute1 to Master.xml/nodeA/@attribute1
New.xml/nodeA/nodeA1 to Master.xml/nodeA/nodeA1

I'm guessing there's a single apply-templates element I can use that will do 
this. The contents of this will be writing either HMTL listing all the elements 
that do no match in the two files. I don't anticipate a problem with this.

So the output would say something like:
New.xml/nodeA/@attribute1 = XXX   -   Master.xml/nodeA/@attribute1 = 111
New.xml/nodeA/nodeA1 = ZZZ        -   Master.xml/nodeA/nodeA1 = FFF

Ideally, I'd like to ignore certain elements (like creation date and time 
fields) as they will always be different between New.xml and Master.xml

I have to use XSLT1.0 for this

Could someone point me in the right direction?

Thanks

Mark

--~------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--


--~------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--

<Prev in Thread] Current Thread [Next in Thread>