I am trying to output only one line per unique value in my final text output after running an XML through an XSL stylesheet. In my research, I came upon the distinct-values function, but I'm unable to execute it the way that I want.
Here is my XML:
<Library>
<Book>
<Code>1</Code>
<Title>MANAGEMENT</Title>
</Book>
<Book>
<Code>1</Code>
<Title>MANAGEMENT</Title>
</Book>
<Book>
<Code>1</Code>
<Title>MANAGEMENT</Title>
</Book>
<Book>
<Code>1</Code>
<Title>MANAGEMENT</Title>
</Book>
<Book>
<Code>1</Code>
<Title>MANAGEMENT</Title>
</Book>
<Book>
<Code>10</Code>
<Title>MECHANICAL</Title>
</Book>
<Book>
<Code>106</Code>
<Title>TRANSPORTATION</Title>
</Book>
</Library>
And here is my current XSL (incorrect):
<xsl:template match="Book">
<xsl:value-of select="this:fixedOutput(Code)" />
<xsl:value-of select="this:fixedOutput(Title)" />
<xsl:value-of select="$linefeed" />
</xsl:template>
My output right now is:
1|MANAGEMENT| 1|MANAGEMENT| 1|MANAGEMENT| 1|MANAGEMENT| 1|MANAGEMENT|
10|MECHANICAL| 106|TRANSPORTATION|
But I want it to be this:
1|MANAGEMENT| 10|MECHANICAL| 106|TRANSPORTATION|
I'm not sure how to use the syntax of distinct values to get to where I need.
An XSLT 1.0 solution that uses key and the generate-id() function (Muenchian grouping) to get distinct values :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="bookCode" match="/Library/Book/Code" use="." />
<xsl:template match="/">
<xsl:for-each select="/Library/Book/Code[generate-id()
= generate-id(key('bookCode',.)[1])]">
<xsl:value-of select="this:fixedOutput(.)" />
<xsl:value-of select="this:fixedOutput(../Title)" />
<xsl:value-of select="$linefeed" />
</xsl:for-each>
</xsl:template>
An XSLT 2.0 solution which uses xsl:for-each-group as #michael.hor257k said :
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="Library">
<xsl:for-each-group select="Book" group-by="concat(Code,Title)">
<xsl:apply-templates select="." />
</xsl:for-each-group>
</xsl:template>
<xsl:template match="Book">
<xsl:value-of select="this:fixedOutput(Code)" />
<xsl:value-of select="this:fixedOutput(Title)" />
<xsl:value-of select="$linefeed" />
</xsl:template>
Note: As this:fixedOutput in your code doen't refer any namespace it has been used as it is.
Refer this: http://xsltransform.net/3MP2uBE
Related
I have the following input xml:
<bookstores>
<store>
<name>Store 1</name>
<books>
<book>
<title>Book 1</title>
<author>Author 1</author>
<year>2000</year>
<price/>
</book>
<book>
<title>Book 2</title>
<author></author>
<year>2001</year>
<price/>
</book>
</books>
</store>
<store>
<name>Store 3</name>
<books>
<book>
<title>Book 1</title>
<year>2012</year>
<price/>
</book>
</books>
</store>
</bookstores>
I need to get all stores that have books with identified authors, so the result should be:
<bookstores>
<store>
<name>Store 1</name>
<books>
<book>
<title>Book 1</title>
<author>Author 1</author>
<year>2000</year>
<price/>
</book>
</books>
</store>
</bookstores>
I tried to use exslt:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common">
<xsl:output method="xml" indent="yes" encoding="utf-8" />
<xsl:strip-space elements="*" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="firstPass">
<xsl:call-template name="processing" />
</xsl:variable>
<xsl:apply-templates select="exslt:node-set($firstPass)" />
</xsl:template>
<xsl:template name="processing" match="bookstores/store/books/book[author[string()='']]" />
<xsl:template match="bookstores/store/books/book[not(author)]" />
<xsl:template match="bookstores/store[not(books/book)]" />
</xsl:stylesheet>
Filter books with empty Author
Filter books without tag Author
Filter sotres without books with authors
but unfortunately I didn't get how to use it in a right way. How to use exslt with several match templates?
I think you can do it in one pass
<xsl:template match="store[not(books/book[author[normalize-space()]])]"/>
<xsl:template match="book[not(author[normalize-space()])]"/>
that way the complete code is
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="store[not(books/book[author[normalize-space()]])]"/>
<xsl:template match="book[not(author[normalize-space()])]"/>
</xsl:stylesheet>
and gives the wanted output at https://xsltfiddle.liberty-development.net/3NzcBtk.
Your approach can be simplified to
<xsl:template match="store/books/book[string(author)='']" />
<xsl:template match="store[not(books/book/author)]" />
The first one removes books without an author or with an empty author in one expression. The second one removes stores which either have books without an author or have no books at all, because without a book there can't be an author.
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).
I have one XML request which I need to modify (to XML) and then send it further. I have no prior knowledge of XSLT.
Say I have
<Combined>
<Profile>
<Fullname>John Doe</Fullname>
<OtherData>
<Birthdate>1996</Birthdate>
<FavoriteBooks>
<Book>
<id>1</id>
<description>Libre1</description>
</Book>
<Book>
<id>2</id>
<description>Libre2</description>
</Book>
<Book>
<id>3</id>
<description></description>
</Book>
<Book>
<id>4</id>
<description>Libre4</description>
</Book>
</FavoriteBooks>
</OtherData>
</Profile>
<LoadedData>
<NewBirthdate>1998</NewBirthdate>
<BooksUpdate>
<Book id="1">
<BookText>Book1</BookText>
</Book>
<Book id="2">
<BookText>Book2</BookText>
</Book>
<Book id="3">
<BookText>Book3</BookText>
</Book>
<Book id="4">
<BookText>Book4</BookText>
</Book>
<Book id="5">
<BookText>Book5</BookText>
</Book>
</BooksUpdate>
</LoadedData>
And want to get
<Profile>
<Fullname>John Doe</Fullname>
<OtherData>
<Birthdate>1998</Birthdate>
<FavoriteBooks>
<Book>
<id>1</id>
<description>Libre1Book1</description>
</Book>
<Book>
<id>2</id>
<description>Libre2Book2</description>
</Book>
<Book>
<id>3</id>
<description>empty</description>
</Book>
<Book>
<id>4</id>
<description>Libre4Book4</description>
</Book>
<Book>
<id>5</id>
<description>new Book5</description>
</Book>
</FavoriteBooks>
</OtherData>
I did a pretty pathetic attempt, which obviously does not work.
<?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:template match="/">
<Profile>
<xsl:apply-templates select="Combined/Profile/Fullname" />
</Profile>
<Otherdata>
<Birthdate>
<xsl:apply-templates select="Combined/LoadedData/NewBirthdate"/>
</Birthdate>
<FavoriteBooks>
<xsl:for-each select="/Combined/Profile/OtherData/FavoriteBooks/Book">
<Book>
<id>
<xsl:value-of select="id"/>
</id>
<description>
<xsl:value-of select="description"/>
<xsl:apply-templates select="/Combined/LoadedData/BooksUpdate/Book[#id='']" />
</description>
</Book>
</xsl:for-each>
</FavoriteBooks>
</Otherdata>
</xsl:template>
</xsl:stylesheet>
How can I get closer to what I want to get? Could you also advise me some book to jump start, because w3schools tutorials are useless :(
How's this?
According to the logic you described, the description would not be "empty" but rather "Book3" (empty string merged with "Book3").
<!-- root and static content -->
<xsl:template match="/">
<xsl:apply-templates select='Combined/Profile' />
</xsl:template>
<!-- identity/copy, with some tweaks -->
<xsl:template match='node()|#*'>
<!-- copy node -->
<xsl:copy>
<!-- add in its attributes -->
<xsl:apply-templates select='#*' />
<!-- now either apply same treatment to child nodes, or something special -->
<xsl:choose>
<!-- use updated birthdate -->
<xsl:when test='name() = "Birthdate"'>
<xsl:value-of select='/Combined/LoadedData/NewBirthdate' />
</xsl:when>
<!-- merge book descriptions -->
<xsl:when test='name() = "description"'>
<xsl:value-of select='concat(., /Combined/LoadedData/BooksUpdate/Book[#id = current()/../id]/BookText)' />
</xsl:when>
<!-- or just keep recursing -->
<xsl:otherwise>
<xsl:apply-templates select='node()' />
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
<!-- if we've done all books, add in any in the loaded data but not the original data -->
<xsl:if test='name() = "Book" and not(count(following-sibling::Book))'>
<xsl:variable name='orig_book_ids'>
<xsl:for-each select='../Book'>
<xsl:value-of select='concat("-",id,"-")' />
</xsl:for-each>
</xsl:variable>
<xsl:apply-templates select='/Combined/LoadedData/BooksUpdate/Book[not(contains($orig_book_ids, concat("-",#id,"-")))]' mode='new_books' />
</xsl:if>
</xsl:template>
<!-- new books -->
<xsl:template match='Book' mode='new_books'>
<Book>
<id><xsl:value-of select='#id' /></id>
<description>new <xsl:value-of select='BookText' /></description>
</Book>
</xsl:template>
You can run it at this XMLPlayground session (see output source).
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>