I have a need to compress missing data out of an XML doc using only xslt 1.0, re-sequencing it such that an item's index is greater by one than any index after which an index is missing. (Difficult to even explain in coherent terms, I'll admit).
I've tried certain algorithms, but of course none will work predicated on an output that's changing and without iteratively running the script continuously until the output no longer changes. An oversimplified example:
Input:
<Set Id="gump">
<!-- Missing 2,3, must renumber 4->2 and 5->3 -->
<parameter fieldId="primary.1.label" value="Was 1"/>
<parameter fieldId="primary.4.label" value="Was 4"/>
<parameter fieldId="primary.4.tag" value="Was 4"/>
<parameter fieldId="primary.5.label" value="Was 5"/>
<parameter fieldId="primary.5.somefld" value="Was 5"/>
</Set>
The index embedded in the fieldId attribute must be output sequentially, 1, 2, 3; not 1, 4, 5, e.g. - "primary.4.label" must be "primary.2.label" in the output, etc.
In the input sample there are no items with an index of 2 or 3. This is the gap that must be filled by subsequent items with a greater index than the ones that are missing. So all index 4s become index 2s, all index 5s become index 3s.
The subfields ("label", "tag", "somefld") may be different between indexed sets. Some may be present in one set and absent in another.
Output:
<Set Id="gump">
<!-- Desired output -->
<parameter fieldId="primary.1.label" value="Was 1"/>
<parameter fieldId="primary.2.label" value="Was 4"/>
<parameter fieldId="primary.2.tag" value="Was 4"/>
<parameter fieldId="primary.3.label" value="Was 5"/>
<parameter fieldId="primary.3.somefld" value="Was 5"/>
</Set>
I'm rather a neophyte to xslt, so I've struggled with keys, etc.
--- edited ---
Apparently, this is a grouping problem - which in XSLT 1.0 is best solved using the Muenchian method.
Assuming that the number to replace is always between the first and the second period in the given string, you can apply the following stylesheet:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="param-by-id" match="parameter" use="substring-before(substring-after(#fieldId, '.'), '.')" />
<xsl:template match="Set">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:for-each select="parameter[count(. | key('param-by-id', substring-before(substring-after(#fieldId, '.'), '.'))[1]) = 1]">
<xsl:variable name="i" select="position()" />
<xsl:for-each select="key('param-by-id', substring-before(substring-after(#fieldId, '.'), '.'))">
<parameter>
<xsl:copy-of select="#*"/>
<xsl:attribute name="fieldId">
<xsl:value-of select="substring-before(#fieldId, '.')"/>
<xsl:text>.</xsl:text>
<xsl:value-of select="$i"/>
<xsl:text>.</xsl:text>
<xsl:value-of select="substring-after(substring-after(#fieldId, '.'), '.')"/>
</xsl:attribute>
</parameter>
</xsl:for-each>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
to your input and receive:
<?xml version="1.0" encoding="UTF-8"?>
<Set Id="gump">
<parameter fieldId="primary.1.label" value="Was 1"/>
<parameter fieldId="primary.2.label" value="Was 4"/>
<parameter fieldId="primary.2.tag" value="Was 4"/>
<parameter fieldId="primary.3.label" value="Was 5"/>
<parameter fieldId="primary.3.somefld" value="Was 5"/>
</Set>
Related
input:
<DS>
<TL>
<msg>
<output_getquerydata>
<queries>
<query name="q1">
<parameters>
<parameter name="id">906OREA</parameter>
</parameters>
<results>
<record>
<column name="actionState">sdss</column>
</record>
</results>
</query>
<query name="q2">
<parameters>
<parameter name="resCode">CTL</parameter>
<parameter name="prodCode">89CMID</parameter>
<parameter name="pos">1,2,4,3</parameter>
</parameters>
<results>
<record id="1">
<column name="position">1</column>
<column name="ExternalProductId"/>
</record>
<record id="9">
<column name="position"/>
<column name="ExternalProductId">316442</column>
</record>
</results>
</query>
<query name="q2">
<parameters>
<parameter name="resCode">CTL</parameter>
<parameter name="prodCode">91VPRM</parameter>
<parameter name="pos">1,2,4,3</parameter>
</parameters>
<results>
<record id="1">
<column name="position"/>
<column name="ExternalProductId">316495</column>
</record>
</results>
</query>
</queries>
</output_getquerydata>
</msg>
<TL>
<ArticleNr>89CMID</ArticleNr>
</TL>
<TL>
<ArticleNr>89CMID</ArticleNr>
</TL>
<TL>
<ArticleNr>89CMID</ArticleNr>
</TL>
<TL>
<ArticleNr>91VPRM</ArticleNr>
</TL>
</TL>
</DS>
XSL:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="Article" match="tLoading" use="./ArticleNr"/>
<xsl:key name="prod" match="query[#name='q2']/results/record" use="./column[#name='ExternalProductId'][node()]"/>
<xsl:template match="DS">
<msglist>
<xsl:for-each select="./TL[./msg/output_getquerydata/queries/query/results/record/column[#name='actionState'] !='finished'] ">
<xsl:variable name="distinctArticle" select="//TL[string(ArticleNr)][count(. | key('Article',ArticleNr)[1]) = 1]"/>
<msg>
<xsl:for-each select="$distinctArticle">
<load-part>
<!--I need here the value from column[#name='ExtPR'], that has parameter[#name='prodCode']=the current TL articleNr node.
-->
<productId>
<xsl:value-of select="key('prod',column[#name='ExternalProductId'])"/>
</productId>
<!--something-->
</load-part>
</xsl:for-each>
</msg>
</xsl:for-each>
</msglist>
</xsl:template>
</xsl:stylesheet>
Desired OUTPUT:
<msglist>
<msg>
<load-part>
<productId>316442</productId>
</load-part>
<load-part>
<productId>316442</productId>
</load-part>
<load-part>
<productId>316442</productId>
</load-part>
<load-part>
<productId>316495</productId>
</load-part>
</msg>
</msglist>
I need in the productID node, the value from column[#name='ExternalProductId'], that has parameter[#name='prodCode']=the current <TL><ArticleNr> node.
I know that this 'for each' code that I've put, returns only two values, because i'm searching for the distinct values, so I was thinking that I will try with a key, but i'm not sure what I am missing.
Thank you
edited for the correct output values
Firstly, your Article key looks wrong (in the context of your question) as there are no tLoading elements in your XML. So it should be this...
<xsl:key name="Article" match="TL" use="ArticleNr"/>
But to answer your direct question, you need to define your prod key like so
<xsl:key name="prod"
match="query[#name='q2']/results/record"
use="../../parameters/parameter[#name='prodCode']"/>
Then, to look it up, do this...
<xsl:value-of select="key('prod', ArticleNr)/column[#name='ExternalProductId']"/>
Or maybe this, as they "q2" query has two ExternalProductIds...
<xsl:value-of select="key('prod', ArticleNr)/column[#name='ExternalProductId'][. != '']"/>
Try this XSLT (which retains your distinct check, and so only outputs two rows)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:key name="Article" match="TL" use="ArticleNr"/>
<xsl:key name="prod" match="query[#name='q2']/results/record" use="../../parameters/parameter[#name='prodCode']"/>
<xsl:template match="DS">
<msglist>
<xsl:for-each select="./TL[./msg/output_getquerydata/queries/query/results/record/column[#name='actionState'] !='finished'] ">
<xsl:variable name="distinctArticle" select="//TL[string(ArticleNr)][count(. | key('Article',ArticleNr)[1]) = 1]"/>
<msg>
<xsl:for-each select="$distinctArticle">
<load-part>
<productId>
<xsl:value-of select="ArticleNr" />
<xsl:text> - </xsl:text>
<xsl:value-of select="key('prod', ArticleNr)/column[#name='ExternalProductId'][. != '']"/>
</productId>
</load-part>
</xsl:for-each>
</msg>
</xsl:for-each>
</msglist>
</xsl:template>
</xsl:stylesheet>
Consider the following schema:
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.zoo.org/animals" targetNamespace="http://www.zoo.org/animals" elementFormDefault="unqualified" attributeFormDefault="unqualified" version="1.0.0">
<xsd:complexType name="Animals_Type">
<xsd:sequence>
<xsd:element ref="Cat" minOccurs="0" maxOccurs="unbounded" />
<xsd:element ref="Dog" minOccurs="0" maxOccurs="unbounded" />
<xsd:element ref="Mouse" minOccurs="0" maxOccurs="unbounded" />
<xsd:element ref="Lion" minOccurs="0" maxOccurs="unbounded" />
<xsd:element ref="Tiger" minOccurs="0" maxOccurs="unbounded" />
<xsd:element ref="Bear" minOccurs="0" maxOccurs="unbounded" />
<xsd:element ref="Penguin" minOccurs="0" maxOccurs="unbounded" />
<xsd:element ref="Monkey" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:element name="Animals" type="Animals_Type"/>
<xsd:element name="Cat" type="xsd:string"/>
<xsd:element name="Dog" type="xsd:string"/>
<xsd:element name="Mouse" type="xsd:string"/>
<xsd:element name="Lion" type="xsd:string"/>
<xsd:element name="Tiger" type="xsd:string"/>
<xsd:element name="Bear" type="xsd:string"/>
<xsd:element name="Penguin" type="xsd:string"/>
<xsd:element name="Monkey" type="xsd:string"/>
</xsd:schema>
With the following input xml:
<Animals xmlns="http://www.zoo.org/animals">
<Cat>Pixel</Cat>
<Dog>Ada</Dog>
<Mouse>Minnie</Mouse>
<Lion>Donnie</Lion>
<Tiger>Phil</Tiger>
<Bear>Susie</Bear>
<Penguin>Bob</Penguin>
<Monkey>Lennie</Monkey>
</Animals>
Where the desired output xml is to add a Bear named Billy:
<Animals xmlns="http://www.zoo.org/animals">
<Cat>Pixel</Cat>
<Dog>Ada</Dog>
<Mouse>Minnie</Mouse>
<Lion>Donnie</Lion>
<Tiger>Phil</Tiger>
<Bear>Susie</Bear>
<Bear>Billy</Bear>
<Penguin>Bob</Penguin>
<Monkey>Lennie</Monkey>
</Animals>
The following xslt will add Billy the Bear to the xml, however it will add Billy at the end after all other elements are copied and thus will create a schema invalid xml:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://www.zoo.org/animals">
<!-- element template that copies over elements -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="Animals">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
<xsl:element name="Bear" namespace="{namespace-uri()}">Billy</xsl:element>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Creates schema invalid xml due to sequence out of order:
<?xml version="1.0" encoding="UTF-8"?>
<Animals xmlns="http://www.zoo.org/animals">
<Cat>Pixel</Cat>
<Dog>Ada</Dog>
<Mouse>Minnie</Mouse>
<Lion>Donnie</Lion>
<Tiger>Phil</Tiger>
<Bear>Susie</Bear>
<Penguin>Bob</Penguin>
<Monkey>Lennie</Monkey>
<Bear>Billy</Bear>
</Animals>
A better xslt that will add Billy the Bear in the correct location is:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://www.zoo.org/animals">
<xsl:strip-space elements="*" />
<!-- element template that copies over elements -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="Animals">
<xsl:copy>
<xsl:apply-templates select="#*|node()[not(self::Penguin)][not(self::Monkey)]" />
<xsl:element name="Bear" namespace="{namespace-uri()}">Billy</xsl:element>
<xsl:apply-templates select="#*|node()[not(self::Cat)][not(self::Dog)][not(self::Mouse)][not(self::Lion)][not(self::Tiger)][not(self::Bear)]" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Creates correct xml:
<?xml version="1.0" encoding="UTF-8"?>
<Animals xmlns="http://www.zoo.org/animals">
<Cat>Pixel</Cat>
<Dog>Ada</Dog>
<Mouse>Minnie</Mouse>
<Lion>Donnie</Lion>
<Tiger>Phil</Tiger>
<Bear>Susie</Bear>
<Bear>Billy</Bear>
<Penguin>Bob</Penguin>
<Monkey>Lennie</Monkey>
</Animals>
The concern I have with this xslt is that it is directly coupled to the current schema. For instance if the schema is later updated with a simple element addition to the sequence, this xslt will break.
What is the most flexible way to add an element to the middle of a sequence? Is there a transform that can be written such that new elements being added to the sequence in the future will not break the transform?
I believe you want something like this:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:zoo="http://www.zoo.org/animals">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pAddElementName" select="'Bear'"/>
<xsl:param name="pAddValue" select="'Billy'"/>
<xsl:variable name="vSchema" select="document('animals.xsd')"/>
<xsl:variable name="vElementNameSpecified" select=
"$vSchema/*/xs:complexType[#name='Animals_Type']
/xs:sequence/xs:element[#ref=$pAddElementName]"/>
<xsl:variable name="vPreceding" select=
"$vSchema/*/xs:complexType[#name='Animals_Type']
/xs:sequence
/xs:element[following-sibling::xs:element
[#ref=$pAddElementName]]/#ref/string()"/>
<xsl:variable name="vFollowing" select=
"$vSchema/*/xs:complexType[#name='Animals_Type']
/xs:sequence
/xs:element[preceding-sibling::xs:element
[#ref=$pAddElementName]]/#ref/string()"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*[$vElementNameSpecified]/zoo:*[name()=$vPreceding][last()]">
<xsl:call-template name="identity"/>
<xsl:element name="{$pAddElementName}" namespace="http://www.zoo.org/animals">
<xsl:sequence select="$pAddValue"/>
</xsl:element>
</xsl:template>
<xsl:template match="/*[$vElementNameSpecified][not(zoo:*[name()=$vPreceding])]
/zoo:*[name()=$vFollowing][1]">
<xsl:element name="{$pAddElementName}" namespace="http://www.zoo.org/animals">
<xsl:sequence select="$pAddValue"/>
</xsl:element>
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="/*[$vElementNameSpecified]
[not(zoo:*[name()=$vPreceding])
and not(zoo:*[name()=$vFollowing])]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:element name="{$pAddElementName}" namespace="http://www.zoo.org/animals">
<xsl:sequence select="$pAddValue"/>
</xsl:element>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Explanation:
The XML Schema is accessed using the document() function.
All different names of elements that can precede the element to be added, are dynamically identified.
All different names of elements that can follow the element to be added, are dynamically identified.
There are three cases: a) preceding elements exist; b) preceding elements don't exist, but following elements exist; c) neither preceding elements nor following elements exist. The transformation contains a template for each of these cases, that adds the wanted new element in its correct place.
I have a generic template I've designed with 2 params title and category.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.1"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:param name="category" />
<xsl:param name="title" />
<xsl:template name="test-group">
<fo:block>
<xsl:value=of select="$title" />
</fo:block>
<fo:block>
<xsl:value-of select="$category" />
</fo:block>
</xsl:template>
</xsl:stylesheet>
In the parent template I have the following:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:include href="templates/generic_template.xsl" />
...
<xsl:call-template name="test-group">
<xsl:with-param name="category" select="'animals'" />
<xsl:with-param name="title" select="'DOGS'" />
</xsl:call-template>
...
</xsl:stylesheet>
However, when the transform completes title and category are blank. I'm using FOP 2.0 so I'm not sure if this is a known shortcoming.
When defining a xsl:template that takes parameters, the parameter names used within the xsl:template should be declared using xls:param elements nested within.
<xsl:template name="test-group">
<xsl:param name="category" />
<xsl:param name="title" />
...
</xsl:template>
The parameters being attached to the xsl:template and not the xsl:stylesheet.
This is similar to when calling the template with xsl:call-template, except you are specifying the values instead using xsl:with-param.
I have a database application for students in classes at schools. I send down a lot of data in a single package to the user interface. To assemble complex XML, I often have to make multiple data fetches as XML, and then combine them.
I'm trying to find a way to use XSLT to perform something akin to a SQL JOIN. For example, given the following two XML documents:
<Xml>
<Classes>
<Class Name="BIOLOGY101" ClassId="11"/>
<Class Name="PHYSICS101" ClassId="13"/>
<Class Name="CALCULUS101" ClassId="17"/>
<Class Name="BIOLOGY101" ClassId="19"/>
</Classes>
</Xml>
<Xml>
<Students>
<Student Name="Bob Johnson" ClassId="11"/>
<Student Name="Bob Johnson" ClassId="17"/>
<Student Name="Bob Johnson" ClassId="19"/>
<Student Name="Joe Jackson" ClassId="11"/>
<Student Name="Joe Jackson" ClassId="13"/>
<Student Name="Joe Jackson" ClassId="17"/>
<Student Name="Rick Robertson" ClassId="13"/>
<Student Name="Rick Robertson" ClassId="17"/>
<Student Name="Rick Robertson" ClassId="19"/>
</Students>
</Xml>
I'd like to run them through a single XSLT, to produce the this:
<Xml>
<Classes>
<Class Name="BIOLOGY101" ClassId="11">
<Student Name="Bob Johnson"/>
<Student Name="Joe Jackson"/>
</Class>
<Class Name="PHYSICS101" ClassId="13">
<Student Name="Joe Jackson"/>
<Student Name="Rick Robertson"/>
</Class>
<Class Name="CALCULUS101" ClassId="17">
<Student Name="Rick Robertson" "/>
<Student Name="Joe Jackson"/>
<Student Name="Bob Johnson"/>
</Class>
<Class Name="BIOLOGY101" ClassId="19">
<Student Name="Bob Johnson"/>
<Student Name="Rick Robertson" />
</Class>
</Classes>
</Xml>
Notice that I omitted the ClassId attribute from the <Student> nodes.
I can assemble the two XML documents into a single document to pass into the XSLT, if that makes it easier to process.
Since this data comes from a database, I will be JOIN-ing different XML documents. I might JOIN Classes to Schools, or Grades to Students, or Activities to Schools. But they will all follow the same pattern: a numeric attribute from the child nodes will correspond to a numeric attribute in the parent node.
Accomplishing this in XSLT is not quite as straightforward as it would be in SQL, but assuming you assembled the two input files into a single document ahead of time (which I would recommend if it's not problematic for you):
<Xml>
<Classes>
<Class Name="BIOLOGY101" ClassId="11"/>
<Class Name="PHYSICS101" ClassId="13"/>
<Class Name="CALCULUS101" ClassId="17"/>
<Class Name="BIOLOGY101" ClassId="19"/>
</Classes>
<Students>
<Student Name="Bob Johnson" ClassId="11"/>
<Student Name="Bob Johnson" ClassId="17"/>
<Student Name="Bob Johnson" ClassId="19"/>
<Student Name="Joe Jackson" ClassId="11"/>
<Student Name="Joe Jackson" ClassId="13"/>
<Student Name="Joe Jackson" ClassId="17"/>
<Student Name="Rick Robertson" ClassId="13"/>
<Student Name="Rick Robertson" ClassId="17"/>
<Student Name="Rick Robertson" ClassId="19"/>
</Students>
</Xml>
This XSLT can be used to join the data together:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<!-- Key to allow retrieving students by their class -->
<xsl:key name="kStudentByClass" match="Student" use="#ClassId"/>
<!-- Identity Template -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Class">
<xsl:copy>
<!-- Copy all attributes for this Class element -->
<xsl:apply-templates select="#*" />
<!-- Copy in all students for the current class -->
<xsl:apply-templates select="key('kStudentByClass', #ClassId)" />
</xsl:copy>
</xsl:template>
<!-- Omit the Students element and Students' ClassId
attribute from the output -->
<xsl:template match="Students | Student/#ClassId" />
</xsl:stylesheet>
When this is run on the input XML above, it produces:
<Xml>
<Classes>
<Class Name="BIOLOGY101" ClassId="11">
<Student Name="Bob Johnson" />
<Student Name="Joe Jackson" />
</Class>
<Class Name="PHYSICS101" ClassId="13">
<Student Name="Joe Jackson" />
<Student Name="Rick Robertson" />
</Class>
<Class Name="CALCULUS101" ClassId="17">
<Student Name="Bob Johnson" />
<Student Name="Joe Jackson" />
<Student Name="Rick Robertson" />
</Class>
<Class Name="BIOLOGY101" ClassId="19">
<Student Name="Bob Johnson" />
<Student Name="Rick Robertson" />
</Class>
</Classes>
</Xml>
And if you can change your XML a little to indicate the outer and inner group, and which attribute to match on, like this:
<Xml>
<Classes outer="true" matchAttribute="ClassId">
....
</Classes>
<Students inner="true">
....
</Students>
</Xml>
Then you could use this more generic XSLT which, while less efficient, should work for any input similar to the above:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:variable name="innerItems"
select="/*/*[#inner = 'true']/*" />
<xsl:variable name="matchAttribute"
select="/*/*[#outer = 'true']/#matchAttribute" />
<!-- Identity Template -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*/*[#outer = 'true']/*">
<xsl:copy>
<!-- Copy all attributes for this element -->
<xsl:apply-templates select="#*" />
<xsl:variable name="matchValue"
select="#*[local-name() = $matchAttribute]"/>
<!-- Copy in all matching items -->
<xsl:apply-templates
select="$innerItems[#*[local-name() = $matchAttribute] =
$matchValue]" />
</xsl:copy>
</xsl:template>
<xsl:template match="/*/*[#inner = 'true']/*">
<xsl:copy>
<xsl:apply-templates
select="#*[local-name() != $matchAttribute] | node()" />
</xsl:copy>
</xsl:template>
<!-- Omit the inner group element, and the meta attributes-->
<xsl:template match="/*/*[#inner = 'true'] | #outer | #matchAttribute" />
</xsl:stylesheet>
JLRishe's answer is the correct one and works perfectly, but just in case someone else comes a-looking, if you need to use XSLT to combine the two files, you could do something like this
a.xml
<Xml>
<Classes>
<Class Name="BIOLOGY101" ClassId="11"/>
<Class Name="PHYSICS101" ClassId="13"/>
<Class Name="CALCULUS101" ClassId="17"/>
<Class Name="BIOLOGY101" ClassId="19"/>
</Classes>
</Xml>
b.xml
<Xml>
<Students>
<Student Name="Bob Johnson" ClassId="11"/>
<Student Name="Bob Johnson" ClassId="17"/>
<Student Name="Bob Johnson" ClassId="19"/>
<Student Name="Joe Jackson" ClassId="11"/>
<Student Name="Joe Jackson" ClassId="13"/>
<Student Name="Joe Jackson" ClassId="17"/>
<Student Name="Rick Robertson" ClassId="13"/>
<Student Name="Rick Robertson" ClassId="17"/>
<Student Name="Rick Robertson" ClassId="19"/>
</Students>
</Xml>
Stylesheet
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Class">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<!-- Apply <Student> elements from b.xml -->
<xsl:apply-templates
select="document('b.xml')/Xml/Students/Student
[#ClassId = current()/#ClassId]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Student">
<xsl:copy>
<xsl:apply-templates select="#Name"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output
<?xml version="1.0"?>
<Xml>
<Classes>
<Class Name="BIOLOGY101" ClassId="11">
<Student Name="Bob Johnson"/>
<Student Name="Joe Jackson"/>
</Class>
<Class Name="PHYSICS101" ClassId="13">
<Student Name="Joe Jackson"/>
<Student Name="Rick Robertson"/>
</Class>
<Class Name="CALCULUS101" ClassId="17">
<Student Name="Bob Johnson"/>
<Student Name="Joe Jackson"/>
<Student Name="Rick Robertson"/>
</Class>
<Class Name="BIOLOGY101" ClassId="19">
<Student Name="Bob Johnson"/>
<Student Name="Rick Robertson"/>
</Class>
</Classes>
</Xml>
JLRishe's answer was great. I was lost until I got that. (I posted the question.)
Since I know I'm dealing with data from SQL Server, I will always have parent and child nodes that correspond to two tables, and the attributes that connect them are fields from the two tables: an independent table with a primary-key field and a dependent table with a foreign key field. So I generalized the solution for my very specific needs as follows:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- Parameters to make solution general -->
<xsl:param name="ParentNode">Class</xsl:param>
<xsl:param name="ParentIdAttribute">ClassId</xsl:param>
<xsl:param name="ChildNode">Student</xsl:param>
<xsl:param name="ChildReferenceAttribute">ClassId</xsl:param>
<!-- Key to allow retrieving child nodes by the Parent node -->
<xsl:key name="kChildByParent" match="*[name() = $ChildNode]" use="#*[name()=$ChildReferenceAttribute]"/>
<!-- Identity Template -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- Parent Node Matching Template -->
<xsl:template match="*[name() = $ParentNode]">
<xsl:copy>
<!-- Copy all attributes and child nodes for the Parent element -->
<xsl:apply-templates select="#* | node()" />
<!-- Copy in all children that reference the current parent -->
<xsl:apply-templates select="key('kChildByParent', #*[name()=$ParentIdAttribute])" />
</xsl:copy>
</xsl:template>
<!-- Omit the child nodes and reference attribute of the child nodes, -->
<xsl:template match="ChildNodes | *[name() = $ChildNode]/#*[name()=$ChildReferenceAttribute]" />
</xsl:stylesheet>
I am having two xml files. I want to copy one xml file data into another file under last occurrence of element. Below are the xml I am having :
---------- xml-1---------------
<?xml version="1.0"?>
<parent>
....
<form id="1" name="world"/>
<source id="1" name="abc1"/>
<source id="2" name="abc2"/>
<source id="3" name="abc3"/>
<file id="1" name="xyz"/>
....
</parent>
----------- xml-2--------------
<?xml version="1.0"?>
<root>
<source id="4" data="anything"/>
<source id="5" data="anything"/>
<source id="6" data="anything"/>
<source id="7" data="anything"/>
</root>
------------------The desired output I want-----------------
<?xml version="1.0"?>
<parent>
....
<source id="1" name="abc1"/>
<source id="2" name="abc2"/>
<source id="3" name="abc3"/>
<source id="4" data="anything"/>
<source id="5" data="anything"/>
<source id="6" data="anything"/>
<source id="7" data="anything"/>
<file id="1" name="xyz"
....
</parent>
============== xslt I am using =============
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="source">
<xsl:choose>
<xsl:when test="'id=3'">
<xsl:call-template name="identity"/>
<xsl:copy-of select="document('file:///D:/Softwares/JEE eclipse/JEEworkspace/Migration/TestMigrationWithoutDeletingProject/xml2.xml')/*/*"/>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I am trying to find it out on the basis of id attribute but it is copying xml-2 data after every source element of xml-1 data. Please help me on this.
I think there are two small problems in your code:
The call to <xsl:call-template name="identity"/> should be outside (or rather before) your choose section. By the way: since you not have an otherwise section it would be slightly shorter to use xsl:if here.
The test for the correct insertion point should be test="#id = 3" since the term "'id=3'" is simply a string which always yields true().