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
Related
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.
I'm trying use ends-with in xslt for return any values. for example: I've this xml:
<BOOKS>
<BOOK>
<TITLE>title1</TITLE>
<ISSN>12313213</ISSN>
</BOOK>
<BOOK>
<TITLE>title2</TITLE>
<ISSN>67895776</ISSN>
</BOOK>
<BOOK>
<TITLE>title3</TITLE>
<ISSN>54363645</ISSN>
</BOOK>
</BOOKS>
and this static xml(book.xml):
<BOOKS>
<BOOK>
<VALUE>test title12</VALUE>
<PRICE>1235,23</PRICE>
</BOOK>
<BOOK>
<VALUE>test title1</VALUE>
<PRICE>345,23</PRICE>
</BOOK>
</BOOKS>
I need to verify if there's a book title in title xml. My code:
<xsl:template match="/">
<xsl:variable name="book" select="document('file:///E:/book.xml')"/>
<BOOKS>
<xsl:for-each select="$book/books/book">
<xsl:variable name="value" select="VALUE"/>
<xsl:variable name="price" select="ESTRATO"/>
<xsl:for-each select="//BOOKS">
<xsl:for-each select="BOOK">
<xsl:if test="ends-with($value, #TITLE)">
<BOOK>
<TITLE><xsl:value-of select="#TITLE"/></TITLE>
<ISSN><xsl:value-of select="$value"/></ISSN>
<PRICE><xsl:value-of select="$price"/></PRICE>
</BOOK>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</BOOKS>
</xsl:template>
I wanna return when the book title ends-with in the tag VALUE in the xml books. Can anyone Help me? Thanks.
I've tried to use something like that How to use contains in xslt? but doesn't worked.
Your question is not entirely clear. I think you want to do something like this:
XSLT 2.0
<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:param name="book" select="document('file:///E:/book.xml')"/>
<xsl:template match="/BOOKS">
<BOOKS>
<xsl:for-each select="BOOK">
<BOOK>
<xsl:copy-of select="TITLE | ISSN"/>
<PRICE>
<xsl:value-of select="$book/BOOKS/BOOK[ends-with(VALUE, current()/TITLE)]/PRICE"/>
</PRICE>
</BOOK>
</xsl:for-each>
</BOOKS>
</xsl:template>
</xsl:stylesheet>
Note that XML is case-sensitive: book does not match/select BOOK.
I'm trying use contains in xslt for return any values.
for example:
I've this xml:
<BOOKS>
<BOOK>
<TITLE>title1</TITLE>
<ISSN>12313213</ISSN>
</BOOK>
<BOOK>
<TITLE>title2</TITLE>
<ISSN>67895776</ISSN>
</BOOK>
<BOOK>
<TITLE>title3</TITLE>
<ISSN>54363645</ISSN>
</BOOK>
</BOOKS>
and this static xml(issn.xml):
<ISSNS>
<ISSN>
<VALUE>12313213, 67895776</VALUE>
<PRICE>1235,23</PRICE>
</ISSN>
<ISSN>
<VALUE>5463432, 54363645</VALUE>
<PRICE>345,23</PRICE>
</ISSN>
</ISSNS>
I need to verify if have book issn in issn xml.
My code:
<xsl:template match="/">
<xsl:variable name="issn" select="document('file:///E:/issn.xml')"/>
<BOOKS>
<xsl:for-each select="$issn/ISSNS/ISSN">
<xsl:variable name="value" select="VALUE"/>
<xsl:variable name="price" select="ESTRATO"/>
<xsl:for-each select="//BOOKS">
<xsl:for-each select="BOOK">
<xsl:if test="contains($value, #ISSN)">
<BOOK>
<TITLE><xsl:value-of select="#TITLE"/></TITLE>
<ISSN><xsl:value-of select="$value"/></ISSN>
<PRICE><xsl:value-of select="$price"/></PRICE>
</BOOK>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</BOOKS>
</xsl:template>
I wanna return when the book issn contains in issn value. Can anyone Help me? Thanks
If I understand this correctly, you want to do:
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:param name="issns" select="document('file:///E:/issn.xml')"/>
<xsl:template match="/BOOKS">
<BOOKS>
<xsl:for-each select="BOOK">
<xsl:variable name="issn" select="$issns/ISSNS/ISSN[contains(VALUE, current()/ISSN)] "/>
<xsl:if test="$issn">
<BOOK>
<xsl:copy-of select="*"/>
<xsl:copy-of select="$issn/PRICE"/>
</BOOK>
</xsl:if>
</xsl:for-each>
</BOOKS>
</xsl:template>
</xsl:stylesheet>
Note that this assumes one ISSN value cannot contain another (which could possibly generate a false match).
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>
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>