Can't get XSLT node copy to work when dealing

2019-06-09 16:02发布

问题:

I have two XML files:

data1.xml

<?xml version="1.0" encoding="UTF-8"?>
<tables>
    <table>
        <row>
            <cell colname="1">A</cell>
            <cell colname="2">
                <carType>Sedan</carType>
                <gasType>Gasoline</gasType>
            </cell>
            <cell colnane="4">B</cell>
            <cell colname="5">C</cell>
        </row>
        <row>
            <cell colname="1">A1</cell>
            <cell colname="2">
                <carType>Truck</carType>
                <gasType>Diesel</gasType>
            </cell>
            <cell colname="4">B1</cell>
            <cell colname="5">C1</cell>
        </row>
    </table>
 </tables>

data2.xml:
<?xml version="1.0" encoding="UTF-8"?>
<tables>
    <table>
        <row>
            <cell colname="1">A</cell>
            <cell colname="2">
                <carType>SedanXYZ</carType>
                <gasType>GasolineXYZ</gasType>
            </cell>
            <cell colname="4">B</cell>
            <cell colname="5">C</cell>
        </row>
        <row>
            <cell colname="1">A2</cell>
            <cell colname="2">
                <carType>Motorcycle</carType>
                <gasType>Gasoline</gasType>
            </cell>
            <cell colname="4">U</cell>
            <cell colname="5">Z</cell>
        </row>
        <row>
            <cell colname="1">A1</cell>
            <cell colname="2">
                <carType>TruckXYZ</carType>
                <gasType>DieselXYZ</gasType>
            </cell>
            <cell colname="4">B1</cell>
            <cell colname="5">C1</cell>
        </row>
    </table>
 </tables>

Basically I want to copy whatever contained in colname="2" from data2.xml to data1.xml and keep the rest of the data in data1.xml the same. The keys to find the equality are colname="4" and colname="5". My XSLT looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="2.0">   

    <xsl:output method="xml" version="1.0" encoding="iso-8859-1" indent="yes"/>

    <xsl:param name="doc2"/> 

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

    <xsl:template match="cell[@colname='2']">
        <xsl:variable name="key-value">
            <xsl:call-template name="key-value"/>
        </xsl:variable>

        <xsl:for-each select="document($doc2)//row">
           <xsl:if test="key('keyx', $key-value)">   
               <xsl:copy-of select="cell[@colname='2']"/>
           </xsl:if>
        </xsl:for-each>

    </xsl:template>

    <!-- Just copy any other elements, attributes, etc. -->
    <xsl:template match="@*|node()" >
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>


    <xsl:key name="keyx" match="row" 
    use="concat(cell[@colname='4'], cell[@colname='5'])"/>

    <!-- This template retrives the key value for an element -->
    <xsl:template name="key-value">
        <xsl:value-of select="concat(../cell[@colname='4'],../cell[@colname='5'])"/>
    </xsl:template>


</xsl:stylesheet>

The result I'm expecting:

<?xml version="1.0" encoding="iso-8859-1"?>
<tables>
   <table>
       <row>
            <cell colname="1">A</cell>
            <cell colname="2">
                <carType>SedanXYZ</carType>
                <gasType>GasolineXYZ</gasType>
            </cell>
            <cell colname="4">B</cell>
            <cell colname="5">C</cell>
       </row>
       <row>
            <cell colname="1">A1</cell>
            <cell colname="2">
                <carType>TruckXYZ</carType>
                <gasType>DieselXYZ</gasType>
            </cell>
            <cell colname="4">B1</cell>
            <cell colname="5">C1</cell>
       </row>
   </table>
</tables>

BUT I'm getting incorrect output like this:

<?xml version="1.0" encoding="iso-8859-1"?>
<tables>
    <table>
        <row>
            <cell colname="1">A</cell>
            <cell colname="2">
                <carType>SedanXYZ</carType>
                <gasType>GasolineXYZ</gasType>
            </cell>
            <cell colname="2">
                <carType>Motorcycle</carType>
                <gasType>Gasoline</gasType>
            </cell>
            <cell colname="2">
               <carType>TruckXYZ</carType>
               <gasType>DieselXYZ</gasType>
            </cell>
            <cell colname="4">B</cell>
            <cell colname="5">C</cell>
        </row>
        <row>
            <cell colname="1">A1</cell>
            <cell colname="2">
               <carType>SedanXYZ</carType>
               <gasType>GasolineXYZ</gasType>
            </cell>
            <cell colname="2">
               <carType>Motorcycle</carType>
               <gasType>Gasoline</gasType>
            </cell>
            <cell colname="2">
               <carType>TruckXYZ</carType>
               <gasType>DieselXYZ</gasType>
            </cell>
            <cell colname="4">B1</cell>
            <cell colname="5">C1</cell>
       </row>
    </table>
</tables>

So several questions:

  1. What's wrong with my XSLT ?
  2. What's the technique to debug the key calls ?.

    Thanks you!.

    John

回答1:

I think you cannot use the key function here, because you need to create a key set on $doc2 and XML Spy does not allow me to do so. And the key on the source XML will not suffice.
So I wrote another solution for you, not using key at all:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="2.0">   

    <xsl:output method="xml" version="1.0" encoding="iso-8859-1" indent="yes"/>

    <xsl:param name="doc2"><xsl:copy-of select="document('C:\test\data2.xml')"/></xsl:param>

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

    <xsl:template match="cell[@colname='2']">
        <xsl:variable name="keyValue" select="concat(../cell[@colname='4'], ../cell[@colname='5'])"/>
        <xsl:for-each select="$doc2/tables/table/row[concat(cell[@colname='4'], cell[@colname='5']) = $keyValue]">
            <xsl:copy-of select="cell[@colname='2']"/>
        </xsl:for-each>
    </xsl:template>

    <!-- Just copy any other elements, attributes, etc. -->
    <xsl:template match="@*|node()" >
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

Oh, and by the way, you have a couple of colnane typo's in the source files.

EDIT forgot about the third argument in the key function :-(. So adding

<xsl:key name="keyx" match="row" use="concat(cell[@colname='4'], cell[@colname='5'])"/>

and changing the for-each to

    <xsl:for-each select="key('keyx', $keyValue, $doc2)">
        <xsl:copy-of select="cell[@colname='2']"/>
    </xsl:for-each>

will achieve your objectives.

EDIT 2

OK the above replaces colname="2" cells, but does not leave data1 ones when no data2 matches are found.

Use the following instead:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="2.0">   

    <xsl:output method="xml" version="1.0" encoding="iso-8859-1" indent="yes"/>

    <xsl:param name="doc2"/> <!-- supplied in parameter list in specific tool -->
    <xsl:key name="keyx" match="row" use="concat(cell[@colname='4'], cell[@colname='5'])"/>

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

    <xsl:template match="cell[@colname='2']">
        <xsl:variable name="keyValue" select="concat(../cell[@colname='4'], ../cell[@colname='5'])"/>
        <xsl:variable name="doc2Matches" select="key('keyx', $keyValue, document($doc2))"/>
        <xsl:choose>
            <xsl:when test="$doc2Matches">
                <xsl:copy-of select="$doc2Matches[1]/cell[@colname='2']"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:copy-of select="."/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <!-- Just copy any other elements, attributes, etc. -->
    <xsl:template match="@*|node()" >
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>


回答2:

What's wrong with your code: you're using key() without the third argument. The third argument tells the system which document you want to search. If you omit it, it searches the document containing the context node, whereas you want to search the "other" document. With this kind of task it's best to have two global variables containing the two document nodes, so you can switch readily between them.

Personally I tackle this kind of merging task using xsl:for-each-group. In the select attribute of for-each-group, select all the relevant elements from both documents. In the body of for-each-group, if current-group() contains a single element, output that one, otherwise decide which one to output based on a test such as root(.) is $doc-two.



标签: xslt xslt-2.0