XSLT 1.0 multi-passing - xslt

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.

Related

Output only Duplicates in the final Text Output

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

sorting of adjacent segments using a element in the xml message with XSLT 1.0

I am new to the XSLT. I need help in achieving the below output. I will explain my requirement using the below example:
Input:
<library>
<Name>aaaaa</Name>
<Street>wwww</Street>
<Country>qqqq</Country>
<stock>
<book>
<Details>
<Ranking>3</Ranking>
<Title>abc3</Title>
<Author>hhhhh3</Author>
</Details>
</book>
<book>
<Details>
<Ranking>2</Ranking>
<Title>abc2</Title>
<Author>hhhhh2</Author>
</Details>
</book>
<book>
<Details>
<Ranking>1</Ranking>
<Title>abc1</Title>
<Author>hhhhh1</Author>
</Details>
</book>
<book>
<Details>
<Ranking>4</Ranking>
<Title>abc4</Title>
<Author>hhhhh4</Author>
</Details>
</book>
</stock>
Output:
<library>
<Name>aaaaa</Name>
<Street>wwww</Street>
<Country>qqqq</Country>
<stock>
<book>
<Details>
<Ranking>1</Ranking>
<Title>abc1</Title>
<Author>hhhhh1</Author>
</Details>
</book>
<book>
<Details>
<Ranking>2</Ranking>
<Title>abc2</Title>
<Author>hhhhh2</Author>
</Details>
</book>
<book>
<Details>
<Ranking>3</Ranking>
<Title>abc3</Title>
<Author>hhhhh3</Author>
</Details>
</book>
<book>
<Details>
<Ranking>4</Ranking>
<Title>abc4</Title>
<Author>hhhhh4</Author>
</Details>
</book>
</stock>
The "book" segments need to be sorted in ascending order based on the "Ranking" using XSLT 1.0.
I tried sort function, but works if all the "Details" are in one "book". I tried lot of other things but no successes. Can someone please help me ?
It is always a good practice to share the XSLT that has been tried to achieve the desired results. This helps the solution providers to just fix the issue that you are facing instead of starting from scratch.
The following template will help you in sorting the <book> based on the <Ranking>.
<xsl:template match="stock">
<xsl:copy>
<xsl:apply-templates select="book">
<xsl:sort select="Details/Ranking" data-type="number" order="ascending" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
In addition to the above template use the identity transform template to copy the input XML as is.
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:output method="html" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="stock">
<xsl:copy>
<xsl:for-each select="book">
<xsl:sort order="ascending" select="Details/Ranking"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
Try it
My friend had helped me in resolving the issue. Below code is the answer to my questions. Thanks to Imran and Aniket for responding.
<?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>
<!-- suppress all but the last node; see next template -->
<xsl:template match="book[*]" />
<!-- sort by package id -->
<xsl:template match="book[last()]">
<xsl:for-each select="../book">
<xsl:sort order="ascending" select="Details/Ranking"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Insert list of elements inside XML tags

I am trying to obtain a list of all the elements with values that aren't in the (Line 1, Line2), and then insert them into the tags similar to the test.
Right now I can retrieve all the elements, but I'm having trouble restricting this to just my desired values. And then I'm unsure how to match and do a for each on elements outside my match criteria. Any advice would be greatly appreciated!
Given the Following XML:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<Line1>Element1</Line1>
<Line2>Element2</Line2>
</Header>
<ElementControl>
<Update>
<Element>test</Element>
</Update>
</ElementControl>
<Member>
<Identifier>123456789</Identifier>
<Contact>
<Person>
<Gender>MALE</Gender>
<Title>Mr</Title>
<Name>JOHN DOE</Name>
</Person>
<HomePhone/>
<eMailAddress/>
<ContactAddresses>
<Address>
<AddressType>POS</AddressType>
<Line1>100 Fake Street</Line1>
<Line2/>
<Line3/>
<Line4/>
<Suburb>Jupiter</Suburb>
<State>OTH</State>
<PostCode>9999</PostCode>
<Country>AUS</Country>
</Address>
</ContactAddresses>
</Contact>
</Member>
</Request>
Current XSL for getting elements
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:for-each select="node()[text() != '']">
<xsl:value-of select="local-name()"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:apply-templates select="node()"/>
</xsl:template>
</xsl:stylesheet>
My WIP xml for inserting the result xml tags is below. I'm unsure how to insert the results of the above xsl into this,
<xsl:template match="Element">
<xsl:copy-of select="."/>
<Element>Value1</Element>
</xsl:template>
And ultimate desired output:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<Line1>Element1</Line1>
<Line2>Element2</Line2>
</Header>
<ElementControl>
<Update>
<Element>Identifier</Element>
<Element>Gender</Element>
<Element>Title</Element>
<Element>Name</Element>
<Element>AddressType</Element>
<Element>Line1</Element>
<Element>Suburb</Element>
<Element>State</Element>
<Element>PostCode</Element>
<Element>Country</Element>
</Update>
</ElementControl>
<Member>
<Identifier>123456789</Identifier>
<Contact>
<Person>
<Gender>MALE</Gender>
<Title>Mr</Title>
<Name>JOHN DOE</Name>
</Person>
<HomePhone/>
<eMailAddress/>
<ContactAddresses>
<Address>
<AddressType>POS</AddressType>
<Line1>100 Fake Street</Line1>
<Line2/>
<Line3/>
<Line4/>
<Suburb>Jupiter</Suburb>
<State>OTH</State>
<PostCode>9999</PostCode>
<Country>AUS</Country>
</Address>
</ContactAddresses>
</Contact>
</Member>
</Request>
I would change the current template to use mode attribute, so it is only used in specific cases, rather than matching all elements. You should also change it to output elements, not text, like so:
<xsl:template match="node()" mode="copy">
<xsl:for-each select=".//node()[text() != '']">
<Element>
<xsl:value-of select="local-name()"/>
</Element>
</xsl:for-each>
</xsl:template>
Then you can call it like this....
<xsl:template match="ElementControl/Update">
<xsl:apply-templates select="../../Member" mode="copy" />
</xsl:template>
Try this XSLT. Note the use of the identity template to copy all other existing elements unchanged
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()" mode="copy">
<xsl:for-each select=".//node()[text() != '']">
<Element>
<xsl:value-of select="local-name()"/>
</Element>
</xsl:for-each>
</xsl:template>
<xsl:template match="ElementControl/Update">
<xsl:apply-templates select="../../Member" mode="copy" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Using apply-template instead of call-template

Is it possible to replace the call-template statement in the following stylesheet with a apply-statement? So that the structure of the templates are nearly the same. With structure I mean that I have a xpath to select a element form the source xml e.g. /shiporder/address/city and I have a target xpath for my output xml e.g. /root/Address/Country then I step reverse through the source path. All /shiporder/address/city goes under Country all /shiporder/address goes under Address and the root shiporder become the tag root.
Source XML:
<shiporder>
<shipto>orderperson1</shipto>
<shipfrom>orderperson2</shipfrom>
<address>
<city>London</city>
</address>
<address>
<city>Berlin</city>
</address>
</shiporder>
Stylesheet:
<xsl:template match="/">
<xsl:apply-templates select="shiporder"/>
</xsl:template>
<xsl:template match="/shiporder">
<root>
<xsl:apply-templates select="address/city"/>
<xsl:call-template name="Identity" />
</root>
</xsl:template>
<xsl:template name="Identity">
<Identity>
<xsl:call-template name="Name" />
</Identity>
</xsl:template>
<xsl:template name="Name">
<Name>
<xsl:apply-templates select="/shiporder/shipto"/>
</Name>
</xsl:template>
<xsl:template match="/shiporder/shipto">
<Last>
<xsl:apply-templates select="text()"/>
</Last>
</xsl:template>
<xsl:template match="/shiporder/address/city">
<Country>
<xsl:apply-templates select="text()"/>
</Country>
</xsl:template>
you can use the following:
<?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" encoding="UTF-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/shiporder">
<root>
<xsl:apply-templates select="address/city"/>
<xsl:apply-templates select="shipto"/>
</root>
</xsl:template>
<xsl:template match="shipto">
<Identity>
<Name>
<Last><xsl:value-of select="."/></Last>
</Name>
</Identity>
</xsl:template>
<xsl:template match="/shiporder/address/city">
<Country>
<xsl:value-of select="."/>
</Country>
</xsl:template>
</xsl:stylesheet>
Generally speaking, <xsl:call-template name="..."/> can be turned into a <xsl:apply-templates select="current()" mode="..."/> and <xsl:template match="node()" mode="..."/> (as long as this mode is not used anywhere else).
But there, the upvoted answer is way more suited.

Convert a Node in an XML from European to US Decimal Formating

I Need to convert a Decimal Node which is in European format to US formating. Please help me.
The XML Looks like below
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Order>
<Header>
<OrderNumber>PO000001</OrderNumber>
<OrderDate>20150108</OrderDate>
<BuyerCode>00132</BuyerCode>
<SupplierCode>V00048</SupplierCode>
<ShipTo>
<shipCode>00132</shipCode>
<address1>Askent Sok. No:3/A Kat:1 </address1>
<address2>Kosifler İş Merkezi</address2>
<shipCity>İÇERENKÖY/ATAŞEHİR/İSTANBUL</shipCity>
<shipPostCode>TR-34752</shipPostCode>
<shipCountry>
<shipCountryCode />
<shipCountryName />
</shipCountry>
</ShipTo>
<OrderText>
<TextHeader />
</OrderText>
<Lines>
<LineNumber>10000</LineNumber>
<ItemNumber>AICM00320-WU9E1</ItemNumber>
<Quantity>1,1</Quantity>
<Uom>PC</Uom>
<DeliveryDate>20150122</DeliveryDate>
<LineText>
<TextLines />
</LineText>
</Lines>
<Lines>
<LineNumber>20000</LineNumber>
<ItemNumber>AICM00400-WU9E1</ItemNumber>
<Quantity>5.000</Quantity>
<Uom>PC</Uom>
<DeliveryDate>20150122</DeliveryDate>
<LineText>
<TextLines />
</LineText>
</Lines>
<Lines>
<LineNumber>30000</LineNumber>
<ItemNumber>AICM00400-WU9E1</ItemNumber>
<Quantity>10</Quantity>
<Uom>PC</Uom>
<DeliveryDate>20150122</DeliveryDate>
<LineText>
<TextLines />
</LineText>
</Lines>
</Header>
</Order>
I need to format only the Quantity Node, which is repeated through out the XML.
I have used an xslt to convert all the decimal node from the US to European format. It look like below
<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:decimal-format name="eu" decimal-separator=',' grouping-separator='.' />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[number()=number()]">
<xsl:copy>
<xsl:value-of select="format-number(., '#.##0,##########', 'eu')" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I need a similar XSLT which convert all the Quantity Node (only quantity node). Please help me guys. Thanks
Replace
<xsl:template match="*[number()=number()]">
<xsl:copy>
<xsl:value-of select="format-number(., '#.##0,##########', 'eu')" />
</xsl:copy>
</xsl:template>
with
<xsl:template match="Quantity">
<xsl:copy>
<xsl:value-of select="format-number(translate(., ',.', '.'), '#,##0.##########')" />
</xsl:copy>
</xsl:template>