XSLT: Using variables in a key function (cont.) - xslt

I have a following XML file:
<titles>
<book title="XML Today" author="David Perry"/>
<book title="XML and Microsoft" author="David Perry"/>
<book title="XML Productivity" author="Jim Kim"/>
<book title="XSLT 1.0" author="Albert Jones"/>
<book title="XSLT 2.0" author="Albert Jones"/>
<book title="XSLT Manual" author="Jane Doe"/>
</titles>
and the transformation to eliminate the elements with #author starting with 'David' or 'Jane':
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="author1-search" match="book[starts-with(#author, 'David')]" use="#title"/>
<xsl:template match="book [key('author1-search', #title)]" />
<xsl:key name="author2-search" match="book[starts-with(#author, 'Jane')]" use="#title"/>
<xsl:template match="book [key('author2-search', #title)]" />
<xsl:template match="#* | node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The expected result will be the following XML file:
<titles>
<book title="XML Productivity" author="Jim Kim"/>
<book title="XSLT 1.0" author="Albert Jones"/>
<book title="XSLT 2.0" author="Albert Jones"/>
</titles>
In his reply to the XSLT: Using variables in a key function question Dimitre Novatchev has shown the method to use iteration to display books written by the selected authors using keys and the Exslt function node-set() with the inline xsl parameter
<xsl:param name="pAuthors">
<x>David Perry</x>
<x>Jane Doe</x>
</xsl:param>
Is it possible to apply this method to rewrite the transformation above so that it would use the pAuthors parameter and contain just one generic search key (instead of author1-search, author2-search, and so on)? XSLT 2.0 and the document() function are not supported.
Thanks in advance, Leo

From guesses looking in the code, I believe, the OP is how to use XSLT keys in helping eliminate books written by any author from a given set of authors.
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pAuthors">
<x>David Perry</x>
<x>Jane Doe</x>
</xsl:param>
<xsl:variable name="vParams" select= "
ext:node-set($pAuthors)/*"/>
<xsl:key name="kBookByAuthor" match="book"
use="#author"/>
<xsl:variable name="vBooksToExclude" select=
"key('kBookByAuthor', $vParams)"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="book">
<xsl:if test="count(.|$vBooksToExclude) != count($vBooksToExclude)">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<titles>
<book title="XML Today" author="David Perry"/>
<book title="XML and Microsoft" author="David Perry"/>
<book title="XML Productivity" author="Jim Kim"/>
<book title="XSLT 1.0" author="Albert Jones"/>
<book title="XSLT 2.0" author="Albert Jones"/>
<book title="XSLT Manual" author="Jane Doe"/>
</titles>
produces the wanted, correct result, in which any book that has an author from the provided set of authors (David Perry and Jane Doe), is skipped:
<titles>
<book title="XML Productivity" author="Jim Kim"/>
<book title="XSLT 1.0" author="Albert Jones"/>
<book title="XSLT 2.0" author="Albert Jones"/>
</titles>
Explanation:
We use the following XPath expression to determine whether a node $n doesn't belong to a node-set $s:
count($n | $s) != count($s)
Final update:
As the OP wants, here we exclude from copying to the output all books, that have an author, whose first name is provided as one of many values in an external/global parameter. We still use a single key.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pAuthors">
<x>David</x>
<x>Jane</x>
</xsl:param>
<xsl:variable name="vParams" select= "
ext:node-set($pAuthors)/*"/>
<xsl:key name="kBookByAuthor" match="book"
use="substring-before(#author, ' ')"/>
<xsl:variable name="vBooksToExclude" select=
"key('kBookByAuthor', $vParams)"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="book">
<xsl:if test="count(.|$vBooksToExclude) != count($vBooksToExclude)">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document (added another David author):
<titles>
<book title="XML Today" author="David Perry"/>
<book title="XML and Microsoft" author="David Perry"/>
<book title="Just example" author="David Masters"/>
<book title="XML Productivity" author="Jim Kim"/>
<book title="XSLT 1.0" author="Albert Jones"/>
<book title="XSLT 2.0" author="Albert Jones"/>
<book title="XSLT Manual" author="Jane Doe"/>
</titles>
produces the wanted, correct result:
<titles>
<book title="XML Productivity" author="Jim Kim" />
<book title="XSLT 1.0" author="Albert Jones" />
<book title="XSLT 2.0" author="Albert Jones" />
</titles>

Related

How to number node-tags

In this (example)xml i need to number the book-tags like <book1>, <book2> etc.
The resulting XML will be imported and therefore needs that notation. The result will never have more then 6 book-nodes. The xslt i've writen names all <book>
This is the original xml
<?xml version="1.0"?>
<catalog>
<book>
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
</book>
<book>
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
</book>
<book>
<author>Corets, Eva</author>
<title>Maeve Ascendant</title>
</book>
<book>
<author>Randall, Cynthia</author>
<title>Lover Birds</title>
</book>
<book>
<author>Corets, Eva</author>
<title>Oberon's Legacy</title>
</book>
<book>
<author>Corets, Eva</author>
<title>The Sundered Grail</title>
</book>
<book>
<author>Thurman, Paula</author>
<title>Splish Splash</title>
</book>
</catalog>
This is the xsl i already have:
<xsl:stylesheet version="2.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="catalog">
<catalog>
<xsl:if test="//book[author='Corets, Eva']">
<book>
<sample>Here comes info about Eva Corets</sample>
</book>
</xsl:if>
<xsl:if test="//book[author='Ralls, Kim']">
<book>
<sample>Here comes info about Kim Ralls</sample>
</book>
</xsl:if>
<xsl:if test="//book[author='Thurman, Paula']">
<book>
<sample>Here comes info about Paula Thurman</sample>
</book>
</xsl:if>
</catalog>
</xsl:template>
</xsl:stylesheet>
Which outputs:
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<book>
<sample>Here comes info about Eva Corets</sample>
</book>
<book>
<sample>Here comes info about Kim Ralls</sample>
</book>
<book>
<sample>Here comes info about Paula Thurman</sample>
</book>
</catalog>
This what i need:
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<book1>
<sample>Here comes info about Eva Corets</sample>
</book1>
<book2>
<sample>Here comes info about Kim Ralls</sample>
</book2>
<book3>
<sample>Here comes info about Paula Thurman</sample>
</book3>
</catalog>
this should do it.
<xsl:stylesheet version="2.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="catalog">
<catalog>
<xsl:for-each select="book[author='Corets, Eva']">
<xsl:variable name="book_el" select="concat('book', position())"></xsl:variable>
<xsl:element name="{$book_el}">
<xsl:copy-of select="node()"/>
</xsl:element>
</xsl:for-each>
</catalog>
</xsl:template>
</xsl:stylesheet>
If you can only use XSLT 1.0, then you can make use of a technique call Muenchian Grouping to get the distinct authors from the books, although you would need extra work to get them in a specific order.
Try this XSLT
<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:key name="books" match="book" use="author" />
<xsl:param name="authors">|Corets, Eva|Ralls, Kim|Thurman, Paula|</xsl:param>
<xsl:template match="catalog">
<catalog>
<xsl:for-each select="book[contains($authors, concat('|', author, '|'))][generate-id() = generate-id(key('books', author)[1])]">
<xsl:sort select="string-length(substring-before($authors, concat('|', author, '|')))" />
<xsl:element name="book{position()}">
<sample>
<xsl:text>Here comes info about </xsl:text>
<xsl:value-of select="author" />
</sample>
<info>
<xsl:for-each select="key('books', author)">
<xsl:if test="position() > 1">, </xsl:if>
<xsl:value-of select="title" />
</xsl:for-each>
</info>
</xsl:element>
</xsl:for-each>
</catalog>
</xsl:template>
</xsl:stylesheet>
You could replace the xsl:sort with <xsl:sort select="author" /> if you just wanted them alphabetically listed.

How to Strip out specific Node using XSLT

I have the following xml and looking to produce an out put which contains only GENRE_1 and GENRE_3 and any other book ids. This means GENRE_4, 5 and 6 will be stripped out. I have tried using the sample xslt but not getting it right. Will appreciate any help.
<bookstore xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Payload xmlns:ns0="http://www.themindelectric.com/">
<books xmlns:ns0="http://www.themindelectric.com/collections/">
<book xmlns:ns0="http://www.themindelectric.com">
<id>GENRE_1</id>
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2001</year>
<price>30.00</price>
</book>
<book xmlns:ns0="http://www.themindelectric.com">
<id>GENRE_3</id>
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2003</year>
<price>29.99</price>
</book>
<book xmlns:ns0="http://www.themindelectric.com">
<id>TEST_3</id>
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2003</year>
<price>29.99</price>
</book>
<book xmlns:ns0="http://www.themindelectric.com">
<id>ANOTHER_1</id>
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2003</year>
<price>29.99</price>
</book>
<book xmlns:ns0="http://www.themindelectric.com">
<id>GENRE_5</id>
<title lang="en">XQuery Kick Start</title>
<author>James McGovern</author>
<author>Per Bothner</author>
<author>Kurt Cagle</author>
<author>James Linn</author>
<author>Vaidyanathan Nagarajan</author>
<year>2003</year>
<price>49.99</price>
</book>
<book xmlns:ns0="http://www.themindelectric.com">
<id>GENRE_1</id>
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2005</year>
<price>39.95</price>
</book>
<book xmlns:ns0="http://www.themindelectric.com">
<id>GENRE_1</id>
<title lang="en">XQuery Kick Start</title>
<author>James McGovern</author>
<author>Per Bothner</author>
<author>Kurt Cagle</author>
<author>James Linn</author>
<author>Vaidyanathan Nagarajan</author>
<year>2007</year>
<price>49.99</price>
</book>
<book xmlns:ns0="http://www.themindelectric.com">
<id>GENRE_6</id>
<title lang="en">Learning Java</title>
<author>Testing</author>
<year>2005</year>
<price>39.95</price>
</book>
<book xmlns:ns0="http://www.themindelectric.com">
<id>GENRE_4</id>
<title lang="en">XQuery Kick Start</title>
<author>James McGovern</author>
<author>Per Bothner</author>
<author>Kurt Cagle</author>
<author>James Linn</author>
<author>Vaidyanathan Nagarajan</author>
<year>2007</year>
<price>49.99</price>
</book>
</books>
</Payload>
EXPECTED OUTPUT
<bookstore xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Payload xmlns:ns0="http://www.themindelectric.com/">
<books xmlns:ns0="http://www.themindelectric.com/collections/">
<book xmlns:ns0="http://www.themindelectric.com">
<id>GENRE_1</id>
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2001</year>
<price>30.00</price>
</book>
<book xmlns:ns0="http://www.themindelectric.com">
<id>GENRE_3</id>
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2003</year>
<price>29.99</price>
</book>
<book xmlns:ns0="http://www.themindelectric.com">
<id>TEST_3</id>
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2003</year>
<price>29.99</price>
</book>
<book xmlns:ns0="http://www.themindelectric.com">
<id>ANOTHER_1</id>
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2003</year>
<price>29.99</price>
</book>
<book xmlns:ns0="http://www.themindelectric.com">
<id>GENRE_1</id>
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2005</year>
<price>39.95</price>
</book>
<book xmlns:ns0="http://www.themindelectric.com">
<id>GENRE_1</id>
<title lang="en">XQuery Kick Start</title>
<author>James McGovern</author>
<author>Per Bothner</author>
<author>Kurt Cagle</author>
<author>James Linn</author>
<author>Vaidyanathan Nagarajan</author>
<year>2007</year>
<price>49.99</price>
</book>
</books>
</Payload>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://www.themindelectric.com">
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:template>
<xsl:template match="/bookstore/Payload/books/book[starts-with(id,'GENRE')]">
<xsl:call-template name="genre"/>
</xsl:template>
<xsl:template name="genre">
<xsl:choose>
<xsl:when
test="count(/bookstore/Payload/books/book[id='GENRE_1']) != 0 or
count(/bookstore/Payload/books/book[id='GENRE_3']) != 0">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:when>
<xsl:otherwise />
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
If I understand correctly your new(!) requirement, you want to exclude any book that has an id that starts with GENRE, except GENRE_1 and GENRE_3:
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="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="book[starts-with(id, 'GENRE') and not(id='GENRE_1' or id='GENRE_3')]"/>
</xsl:stylesheet>
A simple solution is just to have an empty template matching all books not having the genre id that you want to keep:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://www.themindelectric.com">
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:template>
<xsl:template match="book[not(id='GENRE_1' or id='GENRE_3'
or id='TEST_3' or id='ANOTHER_1')]"/>
</xsl:stylesheet>
This copies everything except books that have not the ids you want to keep.
Update: For the clarified requirement in the updated question: to keep all books that either have the id GENRE_1 or GENRE_3 or have an id not matching GENRE:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://www.themindelectric.com">
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:template>
<xsl:template match="book[starts-with(id, 'GENRE_') and
not(contains(id,1) or contains(id,3))]"/>
</xsl:stylesheet>
As mentioned in the comments by michael.hor257k, using contains() in the match pattern won't work in case you have to handle more than 9 genres. In this case just matching the ids you want to keep in the not() - not(id='GENRE_1' or id='GENRE_3') - like in the correct answer given by michael.hor257k is the right approach.

Groups two xml files like a sql group-by [2]

This is an evolution of my ask here :
Groups two xml files like a sql group-by
The Example given and Dimitre Solution was counting distinct isbn value.
Now modify library xml to have
mylibrary.xml :
<library>
<book id="1" isbn="1"/>
<book id="2" isbn="1"/>
<book id="3" isbn="2"/>
<book id="4" isbn="4"/>
<book id="5" isbn="5"/>
<book id="6" isbn="4"/>
<book id="7" isbn="4"/>
</library>
and this one that can be used :
bookreference.xml :
<reference>
<book isbn="1">
<category>SF</category>
</book>
<book isbn="2">
<category>SF</category>
</book>
<book isbn="3">
<category>SF</category>
</book>
<book isbn="4">
<category>Comedy</category>
</book>
<book isbn="5">
<category>Comedy</category>
</book>
</reference>
i want to get the numbers of book i got in mylibrary 'even if some have same isbn', groupby category, using xslt 1-0.
output wanted:
SF : 3 book(s)
Comedy : 4 book(s)
my xslt propose here : Groups two xml files like a sql group-by works fine but of course use 'for-each' loop and extension functions.
Surely there is a better solution.
Again a very good question! (+1)
This transformation, using two keys for achieving full efficiency:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kBookByCat" match="book"
use="category"/>
<xsl:key name="kBookByIsbn" match="book"
use="#isbn"/>
<xsl:variable name="vDoc" select="/"/>
<xsl:variable name="vRef" select=
"document('file:///c:/temp/delete/reference.xml')"/>
<xsl:variable name="vMyIsbns" select="/*/*/#isbn"/>
<xsl:variable name="vResult">
<xsl:apply-templates select="$vRef/*"/>
</xsl:variable>
<xsl:template match="/">
<xsl:copy-of select="$vResult"/>
</xsl:template>
<xsl:template match=
"book[generate-id()
=
generate-id(key('kBookByCat', category)[1])
]
">
<xsl:variable name="vBooksinCat" select=
"key('kBookByCat', category)"/>
<xsl:value-of select="category"/> : <xsl:text/>
<xsl:for-each select="$vDoc">
<xsl:value-of select="count(key('kBookByIsbn',$vBooksinCat/#isbn))"/>
</xsl:for-each>
<xsl:text> book(s)
</xsl:text>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when applied on the provided XML document contained in the file mylibrary.xml:
<library>
<book id="1" isbn="1"/>
<book id="2" isbn="1"/>
<book id="3" isbn="2"/>
<book id="4" isbn="4"/>
<book id="5" isbn="5"/>
<book id="6" isbn="4"/>
<book id="7" isbn="4"/>
</library>
and having this provided XML document in C:\temp\delete\reference.xml:
<reference>
<book isbn="1">
<category>SF</category>
</book>
<book isbn="2">
<category>SF</category>
</book>
<book isbn="3">
<category>SF</category>
</book>
<book isbn="4">
<category>Comedy</category>
</book>
<book isbn="5">
<category>Comedy</category>
</book>
</reference>
produces the wanted, correct output:
SF : 3 book(s)
Comedy : 4 book(s)
modified Dimitri version to work for this
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kBookByCat" match="book" use="category"/>
<xsl:variable name="vRef" select="document('file:///c:/temp/delete/reference.xml')"/>
<xsl:variable name="meh" select="*"/>
<xsl:template match="/">
<xsl:apply-templates select="$vRef/reference/book[generate-id()=generate-id(key('kBookByCat', category)[1])]" />
</xsl:template>
<xsl:template match="book">
<xsl:variable name="cat" select="category"/>
<xsl:value-of select="category"/> : <xsl:text/>
<xsl:variable name="isbns" select="$vRef/reference/book[category=$cat]/#isbn"/>
<xsl:value-of select="count($meh/book[#isbn=$isbns])"/>
<xsl:text> book(s)
</xsl:text>
</xsl:template>
in add to the great dimitri response, i propose the following to not print book category that have 0 book set in mylibrary :
<xsl:variable name="catname" select="category"/>
<xsl:for-each select="$vDoc">
<xsl:variable name="cnt" select="count(key('kBookByIsbn',$vBooksinCat/#isbn))"/>
<xsl:if test="$cnt > 0">
<xsl:value-of select="$catname"/> :
<xsl:text/>
<xsl:value-of select="$cnt"/>
<xsl:text> book(s)
</xsl:text>
</xsl:if>
</xsl:for-each>

Grouping when using 2 different XML files as sources?

what i want to do is a typical grouping that can usualy be done using xsl:key, but it become more complicated as data to groups are in 2 differents files. How process ?
Here is an example of what i want to do, can i request your help ? must be xslt-1.0 compliant.
bookreference.xml :
<t>
<book isbn="1">
<category>SF</category>
</book>
<book isbn="2">
<category>SF</category>
</book>
<book isbn="3">
<category>SF</category>
</book>
<book isbn="4">
<category>Comedy</category>
</book>
<book isbn="5">
<category>Comedy</category>
</book>
</t>
mylibrary.xml :
<t>
<book isbn="1">
<price>10</price>
</book>
<book isbn="2">
<price>10</price>
</book>
<book isbn="3">
<price>20</price>
</book>
<book isbn="4">
<price>5</price>
</book>
</t>
output wanted:
SF : 3 book(s) - Total : 40$
Comedy : 2 book(s) - Total : 5$
As already suggested in my comment, you can first merge two documents into a result tree fragment, then use exsl:node-set to get a node-set on which you can then apply Muenchian grouping:
<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:param name="price-url" select="'test2011113002.xml'"/>
<xsl:variable name="doc2" select="document($price-url)"/>
<xsl:output method="text"/>
<xsl:variable name="rtf">
<xsl:apply-templates select="//book" mode="merge"/>
</xsl:variable>
<xsl:template match="book" mode="merge">
<xsl:copy>
<xsl:variable name="isbn" select="#isbn"/>
<xsl:copy-of select="#* | node()"/>
<xsl:for-each select="$doc2">
<xsl:copy-of select="key('k1', $isbn)/price"/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:key name="k1" match="book" use="#isbn"/>
<xsl:key name="k2" match="book" use="category"/>
<xsl:template match="/">
<xsl:apply-templates select="exsl:node-set($rtf)/book[generate-id() = generate-id(key('k2', category)[1])]"/>
</xsl:template>
<xsl:template match="book">
<xsl:variable name="current-group" select="key('k2', category)"/>
<xsl:value-of select="concat($current-group/category, ': ', count($current-group), ' - Total : ', sum($current-group/price), '
')"/>
</xsl:template>
</xsl:stylesheet>
Good question, +1.
There is no need for complicated (more than necessary) grouping or for any extension functions:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kBookByCat" match="book"
use="category"/>
<xsl:key name="kPriceByIsbn" match="price"
use="../#isbn"/>
<xsl:variable name="vMyLib" select=
"document('file:///c:/temp/delete/mylibrary.xml')"/>
<xsl:template match=
"book[generate-id()
=
generate-id(key('kBookByCat', category)[1])
]
">
<xsl:variable name="vBooksinCat" select=
"key('kBookByCat', category)"/>
<xsl:value-of select="category"/> : <xsl:text/>
<xsl:value-of select="count($vBooksinCat)"/>
<xsl:text> book(s) - Total : $</xsl:text>
<xsl:for-each select="$vMyLib">
<xsl:value-of select="sum(key('kPriceByIsbn', $vBooksinCat/#isbn))"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When this transformation is applied on the first of the two provided data fragments (corrected to well-formed XML document):
<t>
<book isbn="1">
<category>SF</category>
</book>
<book isbn="2">
<category>SF</category>
</book>
<book isbn="3">
<category>SF</category>
</book>
<book isbn="4">
<category>Comedy</category>
</book>
<book isbn="5">
<category>Comedy</category>
</book>
</t>
and having the second data fragment (corrected to well-formed XML document) saved as C:\Temp\Delete\mylibrary.xml,
The wanted, correct result is produced:
SF : 3 book(s) - Total : $40
Comedy : 2 book(s) - Total : $5

Groups two xml files like a sql group-by

With the following file as input of xsltprocessor :
mylibrary.xml :
<library>
<book isbn="1"/>
<book isbn="3"/>
<book isbn="5"/>
</library>
and this one that can be used :
bookreference.xml :
<reference>
<book isbn="1">
<category>SF</category>
</book>
<book isbn="2">
<category>SF</category>
</book>
<book isbn="3">
<category>SF</category>
</book>
<book isbn="4">
<category>Comedy</category>
</book>
<book isbn="5">
<category>Comedy</category>
</book>
</reference>
i want to get the numbers of book i got in mylibrary, groupby category, using xslt 1-0.
output wanted:
SF : 2 book(s)
Comedy : 1 book(s)
here is the xsl i write using Martin Honnen method explains in 'Grouping when using 2 different XML files as sources?' ,
i think that solve the problem but i do not validate yet and perhaps someone have a better solution.
<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 method="xml" indent="yes"/>
<xsl:key name="book-by-category" match="book" use="category"/>
<xsl:param name="bookref" select="'bookreference.xml'"/>
<xsl:variable name="doc" select="document($bookref)/reference"/>
<xsl:variable name="rtf">
<xsl:apply-templates select="//library" mode="merge"/>
</xsl:variable>
<xsl:template match="library" mode="merge">
<xsl:element name="mybookcat">
<xsl:for-each select="book">
<xsl:variable name="isbn" select="#isbn"/>
<xsl:element name="book">
<xsl:attribute name="isbn"><xsl:value-of select="$isbn"/></xsl:attribute>
<xsl:for-each select="$doc/book[#isbn=$isbn]">
<xsl:element name="category">
<xsl:value-of select="./category"/>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="exsl:node-set($rtf)/mybookcat"/>
</xsl:template>
<xsl:template match="mybookcat">
<xsl:for-each select="book[count(. | key('book-by-category',category)[1]) = 1]">
<xsl:sort select="category"/>
<xsl:value-of select="category" /><xsl:text> : </xsl:text>
<xsl:variable name="current-cat" select="key('book-by-category', category)"/>
<xsl:value-of select="count($current-cat)"/><xsl:text> book(s)
</xsl:text>
<xsl:for-each select="key('book-by-category', category)">
<xsl:sort select="#isbn" data-type="number" />
<xsl:value-of select="#isbn" /><xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
here is the xsl i write using Martin Honnen method explains in
'Grouping when using 2 different XML files as sources?' , i think that
solve the problem but i do not validate yet and perhaps someone have a
better solution
.
Here is a simpler XSLT 1.0 solution that doesn't use any extension functions or xsl:for-each:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kBookByCat" match="book"
use="category"/>
<xsl:variable name="vRef" select=
"document('file:///c:/temp/delete/reference.xml')"/>
<xsl:variable name="vMyIsbns" select="/*/*/#isbn"/>
<xsl:variable name="vResult">
<xsl:apply-templates select="$vRef/*"/>
</xsl:variable>
<xsl:template match="/">
<xsl:copy-of select="$vResult"/>
</xsl:template>
<xsl:template match=
"book[generate-id()
=
generate-id(key('kBookByCat', category)[1])
]
">
<xsl:variable name="vBooksinCat" select=
"key('kBookByCat', category)"/>
<xsl:value-of select="category"/> : <xsl:text/>
<xsl:value-of select="count($vBooksinCat[#isbn=$vMyIsbns])"/>
<xsl:text> book(s)
</xsl:text>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When applied on the provided XML document contained in MyLibrary.xml:
<library>
<book isbn="1"/>
<book isbn="3"/>
<book isbn="5"/>
</library>
and having this provided XML document contained in the file C:\temp\delete\reference.xml:
<reference>
<book isbn="1">
<category>SF</category>
</book>
<book isbn="2">
<category>SF</category>
</book>
<book isbn="3">
<category>SF</category>
</book>
<book isbn="4">
<category>Comedy</category>
</book>
<book isbn="5">
<category>Comedy</category>
</book>
</reference>
the wanted, correct result is produced:
SF : 2 book(s)
Comedy : 1 book(s)
II. An XSLT 2.0 solution:
This is slightly simpler, as we can define the key to be dependent on the second document.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kBookByCat" match="book[#isbn = $vMyIsbns]"
use="category"/>
<xsl:variable name="vRef" select=
"document('file:///c:/temp/delete/reference.xml')"/>
<xsl:variable name="vMyIsbns" select="/*/*/#isbn"/>
<xsl:variable name="vResult">
<xsl:apply-templates select="$vRef/*"/>
</xsl:variable>
<xsl:template match="/">
<xsl:copy-of select="$vResult"/>
</xsl:template>
<xsl:template match=
"book[generate-id()
=
generate-id(key('kBookByCat', category)[1])
]
">
<xsl:variable name="vBooksinCat" select=
"key('kBookByCat', category)"/>
<xsl:value-of select="category"/> : <xsl:text/>
<xsl:value-of select="count($vBooksinCat)"/>
<xsl:text> book(s)
</xsl:text>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>