PDA

View Full Version : XSL Transformation - Loop Subtags under Multiple Tags


pradeep_rajr
10-30-2009, 12:17 PM
I have an XML file
<Library>
<Books>
<Book>AAA</Book>
<Book>BBB</Book>
<Book>CCC</Book>
<Book>DDD</Book>
</Books>
<Authors>
<Author>111</Author>
<Author>222</Author>
<Author>333</Author>
<Author>444</Author>
</Author>
</Library>

I need to write XSL transformation to convert the above XML to the following output.
<Library>
<Books>
<Book>AAA</Book>
<Author>111</Author>
</Books>
<Books>
<Book>BBB</Book>
<Author>222</Author>
</Books>
<Books>
<Book>CCC</Book>
<Author>333</Author>
</Books>
<Books>
<Book>DDD</Book>
<Author>444</Author>
</Books>
</Library>

oesxyl
10-30-2009, 07:26 PM
put you code between [ code] and [ /code] tags, without spaces, please. Thank you.

the xml source is bad formated from a structural point of view because in xml order of elements is not relevant and you can't identify, without assuming that both are ordered, which book belong to which author.

anyway you can use something like this:

<xsl:template match="/">
<Library>
<xsl:apply-template select="Library/Books/Book"/>
</Library>
</xsl:template>

<xsl:template match="Book">
<xsl:variable name="pos" select="position()"/>
<Books>
<xsl:copy-of select="."/>
<xsl:copy-of select="/Library/Authors/Author[position() = $pos]"/>
</Books>
</xsl:template>

I didn't test it but I guess will work.

As a note, this forum is to help people and is better to show what did you try before you ask for something. :)

best regards

Arbitrator
11-01-2009, 10:29 AM
I need to write XSL transformation to convert the above XML to the following output.The following code does this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?xml-stylesheet type="application/xml" href="CF180944.xslt" media="all"?>
<Library>
<Books>
<Book>AAA</Book>
<Book>BBB</Book>
<Book>CCC</Book>
<Book>DDD</Book>
</Books>
<Authors>
<Author>111</Author>
<Author>222</Author>
<Author>333</Author>
<Author>444</Author>
</Authors>
</Library>

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xslt:stylesheet version="1.0" xmlns:xslt="http://www.w3.org/1999/XSL/Transform">
<xslt:output method="xml" version="1.0" encoding="UTF-8" omit-xml-declaration="no" standalone="yes" indent="yes" media-type="application/xml"></xslt:output>
<xslt:template match="/">
<Library>
<xslt:for-each select="descendant::Book">
<Books>
<xslt:variable name="position" select="position()"></xslt:variable>
<xslt:copy-of select="self::node()"></xslt:copy-of>
<xslt:copy-of select="/descendant::Author[position() = $position]"></xslt:copy-of>
</Books>
</xslt:for-each>
</Library>
</xslt:template>
</xslt:stylesheet>

anyway you can use something like this:

[…]

I didn't test it but I guess will work.I tested that code and it didn't work. You had an apparent typo where you misspelled the name of the apply-templates element, but I still can't figure out what you did wrong despite spending some time messing with your code; I guess I don't understand the template and apply-templates elements well enough for that.

oesxyl
11-01-2009, 09:31 PM
The following code does this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?xml-stylesheet type="application/xml" href="CF180944.xslt" media="all"?>
<Library>
<Books>
<Book>AAA</Book>
<Book>BBB</Book>
<Book>CCC</Book>
<Book>DDD</Book>
</Books>
<Authors>
<Author>111</Author>
<Author>222</Author>
<Author>333</Author>
<Author>444</Author>
</Authors>
</Library>

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xslt:stylesheet version="1.0" xmlns:xslt="http://www.w3.org/1999/XSL/Transform">
<xslt:output method="xml" version="1.0" encoding="UTF-8" omit-xml-declaration="no" standalone="yes" indent="yes" media-type="application/xml"></xslt:output>
<xslt:template match="/">
<Library>
<xslt:for-each select="descendant::Book">
<Books>
<xslt:variable name="position" select="position()"></xslt:variable>
<xslt:copy-of select="self::node()"></xslt:copy-of>
<xslt:copy-of select="/descendant::Author[position() = $position]"></xslt:copy-of>
</Books>
</xslt:for-each>
</Library>
</xslt:template>
</xslt:stylesheet>

I tested that code and it didn't work. You had an apparent typo where you misspelled the name of the apply-templates element, but I still can't figure out what you did wrong despite spending some time messing with your code; I guess I don't understand the template and apply-templates elements well enough for that.
yes, you are right, I miss a "s" from apply-templates, thank you for correction.

this work, it's tested, in fact the only thing corrected the missing "s" and I add what I omit because was obvious in my opinion.

<?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"
version="1.0"
encoding="utf-8"
indent="yes"/>

<xsl:template match="/">
<Library>
<xsl:apply-templates select="Library/Books/Book"/>
</Library>
</xsl:template>

<xsl:template match="Book">
<Books>
<xsl:variable name="position" select="position()"/>
<xsl:copy-of select="."/>
<xsl:copy-of select="/Library/Authors/Author[position() = $position]"/>
</Books>
</xsl:template>
</xsl:stylesheet>

mine and yours are the same thing, :)
The only difference is apply-templates vs. for-each.
xslt is a declarative language, not procedural, main idea is that is not used to "do" things but to "match and replace".
Think about apply-templates as a place where you will "replace" the subtree wich is the result of the "match" of the xpath expression you write in the select attribute.
for-each is slower then apply-templates and is very hard to find a example where is it better to use it instead of apply-templates.

best regards

Arbitrator
11-02-2009, 05:57 AM
this work, it's tested, in fact the only thing corrected the missing "s" and I add what I omit because was obvious in my opinion.Hmm... I tried your original code and couldn't get it to work then, but now it works. I guess I added another typo when I fixed the apply-templates typo.

mine and yours are the same thing, :)
The only difference is apply-templates vs. for-each.
xslt is a declarative language, not procedural, main idea is that is not used to "do" things but to "match and replace".
Think about apply-templates as a place where you will "replace" the subtree wich is the result of the "match" of the xpath expression you write in the select attribute.
for-each is slower then apply-templates and is very hard to find a example where is it better to use it instead of apply-templatesI understand it better now. I was confused, for one, because the style sheet kept failing when I used the XPath /descendant::Books to reference the Books element; apparently, I'm supposed to reference the element directly (e.g., child::Books).

Anyway, I still like for-each better. It seems more direct and clear; the template element-based code is working in reverse order (i.e., bottom to top) which I find somewhat confusing.

By the way, I'm still wondering if there's an expanded form of position(); I couldn't get self::node()/position() or similar to work.

oesxyl
11-02-2009, 09:15 AM
Hmm... I tried your original code and couldn't get it to work then, but now it works. I guess I added another typo when I fixed the apply-templates typo.
I notice from your post that we use different prefix for the namespace to transform, I use xsl and you xslt. I guess this was the problem.

I understand it better now. I was confused, for one, because the style sheet kept failing when I used the XPath /descendant::Books to reference the Books element; apparently, I'm supposed to reference the element directly (e.g., child::Books).

Anyway, I still like for-each better. It seems more direct and clear; the template element-based code is working in reverse order (i.e., bottom to top) which I find somewhat confusing.
is not bottom to top, is top to bottom, :)
First template is usualy for root node and any apply-templates from inside will be applied to the nodes( subtree) given by the xpath expression from it's select and will ignore the rest of the tree. That's why is faster then for-each who will test each node to find the match.
Each template will be work only if one template before it will give him a subtree where to do his job.( Kind of goto from previous templates, :))

By the way, I'm still wondering if there's an expanded form of position(); I couldn't get self::node()/position() or similar to work.
probably I don't understand what you ask but I try to answer to what I guess you ask, :)
position() will return a number not a node so it can be use to retrive a node only in a test condition like [position() = last()] or [position() = $givennode].
position in xml is not relevant and usualy you need it in case of the wrong design of xml source.
As in op xml, there are two separate lists of books and authors and without the assumptions that:
- number of books is the same as numbers of authors
- one book have only one author
- order in both list is relevant, book $n have author $n
the design is broken. If we need two separate lists we must have some id/ref relation between books and authors and to not do any assumption, if it is possible

best regards

Arbitrator
11-02-2009, 10:11 AM
I notice from your post that we use different prefix for the namespace to transform, I use xsl and you xslt. I guess this was the problem.No, because then I would have gotten well-formedness errors as a result of an undeclared prefix. I'm pretty sure it had to do the the XPaths; I originally tried to expand all of your XPaths to show axes and probably got something wrong there.

is not bottom to top, is top to bottom, :)
First template is usualy for root node and any apply-templates from inside will be applied to the nodes( subtree) given by the xpath expression from it's select and will ignore the rest of the tree. That's why is faster then for-each who will test each node to find the match.In order for apply-templates to mean anything, it needs to process a template first; that's why I say from bottom-to-top.

The explanation is helpful though; thanks. I would have expected more specific templates to be processed first then apply-templates second, but it sounds like the second template is completely ignored unless it is referenced by apply-templates (which calls it then causes it to be applied).

Each template will be work only if one template before it will give him a subtree where to do his job.( Kind of goto from previous templates, :))If I'm not mistaken, GOTO is considered bad design because it's confusing. :)

probably I don't understand what you ask but I try to answer to what I guess you ask, :)
position() will return a number not a node so it can be use to retrive a node only in a test condition like [position() = last()] or [position() = $givennode].Basically, there are lots of shortcuts in XPath; for example, . is the same as self::node() and Books is a shortcut for child::Books. I was wondering if position() was a shortcut for something.

position in xml is not relevant and usualy you need it in case of the wrong design of xml source.
As in op xml, there are two separate lists of books and authors and without the assumptions that:
- number of books is the same as numbers of authors
- one book have only one author
- order in both list is relevant, book $n have author $n
the design is broken. If we need two separate lists we must have some id/ref relation between books and authors and to not do any assumption, if it is possibleYes, I agree.

oesxyl
11-02-2009, 10:37 AM
No, because then I would have gotten well-formedness errors as a result of an undeclared prefix. I'm pretty sure it had to do the the XPaths; I originally tried to expand all of your XPaths to show axes and probably got something wrong there.
probably, I only try to guess, :)

In order for apply-templates to mean anything, it needs to process a template first; that's why I say from bottom-to-top.
templates order is important, you know that, and I see them top to bottom, :)
In fact everything in a stylesheet is just a template for something, :)

The explanation is helpful though; thanks. I would have expected more specific templates to be processed first then apply-templates second, but it sounds like the second template is completely ignored unless it is referenced by apply-templates (which calls it then causes it to be applied).

If I'm not mistaken, GOTO is considered bad design because it's confusing. :)
yes is it, I couldn't find another example, so I think is more like a bad design of my explanation, :)

Basically, there are lots of shortcuts in XPath; for example, . is the same as self::node() and Books is a shortcut for child::Books. I was wondering if position() was a shortcut for something.
if it is a given position something like:

/Library/Books/Book[3]

will work, I'm sure you know this because I remember you use it, but I don't know to be any shortcut for it in general. I'm not sure, xpath specs are pretty huge and not everything is implemented in xslt processors so I prefere to use what I know it work with libxslt.

best regards