Concatenating XML
I have three files of xml
<step>
<Products>
<Product UserTypeID="Country">
<Name>Cyprus</Name>
<Product UserTypeID="Resort">
<Name>Argaka</Name>
<Product UserTypeID="Property">
<Name>Villa Tester</Name>
</Product>
</Product>
<Product UserTypeID="Resort">
<Name>Coral Bay</Name>
<Product UserTypeID="Property">
<Name>1</Name>
</Product>
<Product UserTypeID="Property">
<Name>2</Name>
</Product>
</Product>
</Product>
<Product UserTypeID="Country">
<Name>Greece</Name>
<Product UserTypeID="Region">
<Name>Corfu</Name>
<Product UserTypeID="Resort">
<Name>Aghios Stefanos</Name>
<Product UserTypeID="Property">
<Name>Villa Joanna</Name>
</Product>
<Product UserTypeID="Property">
<Name>Villa Eleonas</Name>
</Product>
</Product>
<Product UserTypeID="Resort">
<Name>Kassiopi</Name>
<Product UserTypeID="Property">
<Name>Villa 2</Name>
</Product>
</Product>
</Product>
</Product>
</Products>
<step>
<Products>
<Product UserTypeID="Country">
<Name>Cyprus</Name>
<Product UserTypeID="Resort">
<Name>Argaka</Name>
<Product UserTypeID="Property">
<Name>Villa Jaime</Name>
</Product>
</Product>
</Product>
<Product UserTypeID="Country">
<Name>Greece</Name>
<Product UserTypeID="Region">
<Name>Corfu</Name>
<Product UserTypeID="Resort">
<Name>Acharavi</Name>
<Product UserTypeID="Property">
<Name>Villa 1</Name>
</Product>
<Product UserTypeID="Property">
<Name>Villa 2</Name>
</Product>
</Product>
<Product UserTypeID="Resort">
<Name>Gouvia</Name>
<Product UserTypeID="Property">
<Name>Villa De Bono</Name>
</Product>
</Product>
<Product UserTypeID="Resort">
<Name>Kassiopi</Name>
<Product UserTypeID="Property">
<Name>Villa 1</Name>
</Product>
</Product>
</Product>
</Product>
</Products>
<step>
<Products>
<Product UserTypeID="Country">
<Name>Cyprus</Name>
<Product UserTypeID="Resort">
开发者_JAVA百科 <Name>Aghia Marina</Name>
<Product UserTypeID="Property">
<Name>Villa Aghia Marina</Name>
</Product>
</Product>
<Product UserTypeID="Resort">
<Name>Coral Bay</Name>
<Product UserTypeID="Property">
<Name>Ascos Coral Villas</Name>
</Product>
<Product UserTypeID="Property">
<Name>Coral Villa</Name>
</Product>
<Product UserTypeID="Property">
<Name>Lella Villas</Name>
</Product>
</Product>
</Product>
<Product UserTypeID="Country">
<Name>Greece</Name>
<Product UserTypeID="Region">
<Name>Corfu</Name>
<Product UserTypeID="Resort">
<Name>Acharavi</Name>
<Product UserTypeID="Property">
<Name>Villa Angelos</Name>
</Product>
<Product UserTypeID="Property">
<Name>Villa Eleonas</Name>
</Product>
</Product>
<Product UserTypeID="Resort">
<Name>Aghios Stefanos</Name>
<Product UserTypeID="Property">
<Name>Villa Joanna</Name>
</Product>
<Product UserTypeID="Property">
<Name>Villa Eleonas</Name>
</Product>
</Product>
<Product UserTypeID="Resort">
<Name>Kassiopi</Name>
<Product UserTypeID="Property">
<Name>Villa Imerolia</Name>
</Product>
<Product UserTypeID="Property">
<Name>Test Property</Name>
</Product>
</Product>
</Product>
</Product>
</Products>
Each file has the same products (by ./name) but with differing sub products (by ./name) and I need to concatenate them into one tree with one product per product/name, containing all sub products on the same rules so that I can output one structure.
I have an xslt method found, that will create a node set as below
<xsl:variable name="step-output">
<xsl:for-each select="/index/file">
<xsl:copy-of select="document(.)" />
</xsl:for-each>
</xsl:variable>
<xsl:variable name="step-products" select="exsl:node-set($step-output)//Products" />
But this, when I create other templates will create three products by product/name, i.e. cyprus will turn up three times.
Does anyone know how to do what I'm after?? My result needs to be as follows
<step>
<Products>
<Product UserTypeID="Country">
<Name>Cyprus</Name>
<Product UserTypeID="Resort">
<Name>Aghia Marina</Name>
<Product UserTypeID="Property">
<Name>Villa Aghia Marina</Name>
</Product>
</Product>
<Product UserTypeID="Resort">
<Name>Argaka</Name>
<Product UserTypeID="Property">
<Name>Villa Jaime</Name>
</Product>
<Product UserTypeID="Property">
<Name>Villa Tester</Name>
</Product>
</Product>
<Product UserTypeID="Resort">
<Name>Coral Bay</Name>
<Product UserTypeID="Property">
<Name>Ascos Coral Villas</Name>
</Product>
<Product UserTypeID="Property">
<Name>Coral Villa</Name>
</Product>
<Product UserTypeID="Property">
<Name>Lella Villas</Name>
</Product>
<Product UserTypeID="Property">
<Name>1</Name>
</Product>
<Product UserTypeID="Property">
<Name>2</Name>
</Product>
</Product>
</Product>
<Product UserTypeID="Country">
<Name>Greece</Name>
<Product UserTypeID="Region">
<Name>Corfu</Name>
<Product UserTypeID="Resort">
<Name>Acharavi</Name>
<Product UserTypeID="Property">
<Name>Villa Angelos</Name>
</Product>
<Product UserTypeID="Property">
<Name>Villa Eleonas</Name>
</Product>
<Product UserTypeID="Property">
<Name>Villa 1</Name>
</Product>
<Product UserTypeID="Property">
<Name>Villa 2</Name>
</Product>
</Product>
<Product UserTypeID="Resort">
<Name>Aghios Stefanos</Name>
<Product UserTypeID="Property">
<Name>Villa Joanna</Name>
</Product>
<Product UserTypeID="Property">
<Name>Villa Eleonas</Name>
</Product>
</Product>
<Product UserTypeID="Resort">
<Name>Gouvia</Name>
<Product UserTypeID="Property">
<Name>Villa De Bono</Name>
</Product>
</Product>
<Product UserTypeID="Resort">
<Name>Kassiopi</Name>
<Product UserTypeID="Property">
<Name>Villa Imerolia</Name>
</Product>
<Product UserTypeID="Property">
<Name>Test Property</Name>
</Product>
<Product UserTypeID="Property">
<Name>Villa 1</Name>
</Product>
<Product UserTypeID="Property">
<Name>Villa 2</Name>
</Product>
</Product>
</Product>
</Product>
</Products>
Here is an XSLT 2.0 stylesheet that should do the job:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="/">
<step>
<Products>
<xsl:for-each-group select="document(index/file)/step/Products/Product" group-by="Name">
<Product UserTypeID="{@UserTypeID}">
<Name><xsl:value-of select="current-grouping-key()"/></Name>
<xsl:for-each-group select="current-group()/Product" group-by="Name">
<xsl:sort select="current-grouping-key()"/>
<Product UserTypeID="{@UserTypeID}">
<Name><xsl:value-of select="current-grouping-key()"/></Name>
<xsl:for-each select="current-group()/Product">
<xsl:sort select="Name"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</Product>
</xsl:for-each-group>
</Product>
</xsl:for-each-group>
</Products>
</step>
</xsl:template>
</xsl:stylesheet>
You need to run it against an index XML document with the structure
<index>
<file>test2010020803.xml</file>
<file>test2010020804.xml</file>
<file>test2010020805.xml</file>
</index>
that lists the other files you want to process.
XSLT 2.0 stylesheets can be executed with Saxon 9 which comes in a .NET and a Java version so it runs everywhere where either at least Java 1.5 or .NET 2.0 is available or can be installed. Other options are AltovaXML tools (Windows only) and Gestalt.
If you are tied to XSLT 1.0 then you can do it as follows, as long as you have exsl:node-set or similar support:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl"
version="1.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="k1" match="step/Products/Product" use="Name"/>
<xsl:key name="k2" match="step/Products/Product/Product" use="concat(../Name, '|', Name)"/>
<xsl:template match="/">
<xsl:variable name="rtf">
<xsl:copy-of select="document(index/file)/*"/>
</xsl:variable>
<step>
<Products>
<xsl:for-each select="exsl:node-set($rtf)/step/Products/Product[generate-id() = generate-id(key('k1', Name)[1])]">
<Product UserTypeID="{@UserTypeID}">
<xsl:copy-of select="Name"/>
<xsl:for-each select="key('k1', Name)/Product[generate-id() = generate-id(key('k2', concat(../Name, '|', Name))[1])]">
<xsl:sort select="Name"/>
<Product UserTypeID="{@UserTypeID}">
<xsl:copy-of select="Name"/>
<xsl:for-each select="key('k2', concat(../Name, '|', Name))/Product">
<xsl:sort select="Name"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</Product>
</xsl:for-each>
</Product>
</xsl:for-each>
</Products>
</step>
</xsl:template>
</xsl:stylesheet>
The keys would look like this:
<xsl:key name="k1" match="step/Products/Product" use="Name"/>
<xsl:key name="k2" match="step/Products/Product/Product" use="concat(../Name, '|', Name)"/>
<xsl:key name="k3" match="step/Products/Product/Product/Product"
use="concat(../../Name, '|', ../Name, '|', Name)"/>
<xsl:key name="k4"
match="step/Products/Product/Product/Product/Product"
use="concat(../../../Name, '|', ../../Name, '|', ../Name, '|', Name)"/>
<xsl:key name="k5"
match="step/Products/Product/Product/Product/Product/Product"
use="concat(../../../../Name, '|', ../../../Name, '|', ../../Name, '|', ../Name, '|', Name)"/>
<xsl:key name="k6"
match="step/Products/Product/Product/Product/Product/Product/Product"
use="concat(../../../../../Name, '|', ../../../../Name, '|', ../../../Name, '|', ../../Name, '|', ../Name, '|', Name)"/>
That is all typed directly here in the forum editor so could have bugs.
Editing the text to create your files will work, but may be hard to maintain.
The easiest way would be to parse the XML of all 3 files into object form. Programmatically add the objects under a single parent node then regenerate a new XML file.
Does your environment make this an acceptable solution?
精彩评论