Creating new XML nodes if sub node exists - xslt

I've been trying to achieve the following output using XSLT but have really been struggling. Thank you in advance for any assistance.
From
<par>
<run>Line one<break/>
Line two<break/>
</run>
<run>Another para of text<break/>
</run>
<run>3rd para but no break</run>
</par>
To
<document>
<para>Line one</para>
<para>Line two</para>
<para>Another para of text</para>
<para>3rd para but no break</para>
</document>
Thank you,
Dono

Here's a simple solution that is push-oriented and doesn't need <xsl:for-each>, <xsl:if>, or the self:: axis.
When this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/*">
<document>
<xsl:apply-templates />
</document>
</xsl:template>
<xsl:template match="run/text()">
<para>
<xsl:value-of select="normalize-space()" />
</para>
</xsl:template>
</xsl:stylesheet>
...is applied to the provided XML:
<par>
<run>Line one<break/>
Line two<break/>
</run>
<run>Another para of text<break/>
</run>
<run>3rd para but no break</run>
</par>
...the wanted result is produced:
<document>
<para>Line one</para>
<para>Line two</para>
<para>Another para of text</para>
<para>3rd para but no break</para>
</document>

Assuming that your <run> elements are only ever going to contain text and <break/> elements, and that you want to normalise whitespace and exclude <para> elements that would only contain whitespace (suggested by your desired output), the following should work:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="par">
<document>
<xsl:apply-templates select="*"/>
</document>
</xsl:template>
<xsl:template match="run">
<xsl:for-each select="text()">
<xsl:if test="normalize-space(self::text()) != ''">
<para>
<xsl:value-of select="normalize-space(self::text())"/>
</para>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Related

Creating text from character code in XSLT

I am transforming documents from one XML standard to another. One of the requirements is that a numbered list uses alphabetical instead of roman numbering. I can create the unicode from the list position but it seems impossible to insert those into the XML output.
Maybe I am simply not seeing the obvious solution here. In JavaScript I would be using the fromCharCode( ) function but I have not yet found a similar option in XSLT.
I am using XSL 2.0 but could also use 3.0 if needed to make things easier.
I created a simple XML plus XSL to show the problem I need to solve. The actual files are much too big to be sharing here.
Sample XML input document:
<doc>
<p>Some text</p>
<p>Some more text</p>
</doc>
Required output:
<doc>
<list>
<list-item>
<label>A</label>
<p>Some text</p>
</list-item>
<list-item>
<label>B</label>
<p>Some more text</p>
</list-item>
</list>
</doc>
I have tried this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:template match="/doc">
<xsl:copy>
<list>
<xsl:apply-templates select="p"/>
</list>
</xsl:copy>
</xsl:template>
<xsl:template match="p">
<xsl:variable name="number" as="xs:integer" select="position()"/>
<list-item>
<label>
<xsl:value-of select="concat('&#',$number + 65,';')" disable-output-escaping="yes"/>
</label>
<xsl:copy-of select="."/>
</list-item>
</xsl:template>
</xsl:stylesheet>
I have used disable-output-escaping on other generated items before (e.g. to put angular brackets into the XML output), but it seems that the standard character codes are not being handled in the same way. My output from the above XSL shows up like this:
doc>
<list>
<list-item>
<label>B</label>
<p>Some text</p>
</list-item>
<list-item>
<label>C</label>
<p>Some more text</p>
</list-item>
</list>
</doc>
Is there any way to do this elegantly (i.e. without me having to create a huge xsl:choose to call out every possible integer label value and then explicitly creating the alphabetical labels?
You can use codepoints-to-string() and have it convert the number into the letter.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/doc">
<xsl:copy>
<list>
<xsl:apply-templates select="p"/>
</list>
</xsl:copy>
</xsl:template>
<xsl:template match="p">
<list-item>
<label>
<xsl:value-of select="codepoints-to-string(position() + 64)"/>
</label>
<xsl:copy-of select="."/>
</list-item>
</xsl:template>
</xsl:stylesheet>
Or you could use xsl:number with value="position()" and format="A"
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/doc">
<xsl:copy>
<list>
<xsl:apply-templates select="p"/>
</list>
</xsl:copy>
</xsl:template>
<xsl:template match="p">
<list-item>
<label>
<xsl:number format="A" value="position()"/>
</label>
<xsl:copy-of select="."/>
</list-item>
</xsl:template>
</xsl:stylesheet>
A quick solution for values up to 26 (='Z') would be
<xsl:template match="p">
<xsl:variable name="alphabet" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:variable name="number" as="xs:integer" select="position()"/>
<list-item>
<label><xsl:value-of select="substring($alphabet,$number,1)"/></label>
<xsl:copy-of select="."/>
</list-item>
</xsl:template>
The output is as desired.
This could be extended to 26*26 by using a div and an xsl:if.

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>

Transforming XML Attributes into XML Elements

I have the following XML input:
<?xml version="1.0" encoding="utf-8"?>
<DOCUMENT>
<EXTERNALFILES>
<PRIMARYFILE FileName="C:\MSSB\POC\Exports\13f78581-1501-4dd0-8cf3-a5a300ba4083\121110-26-2001031100092.TIF" />
</EXTERNALFILES>
<OCRTEXTFILES />
<DOCUMENTINDEX Name="A2iA_CheckAmount">3304.49</DOCUMENTINDEX>
<DOCUMENTINDEX Name="A2iA_CheckCAR">3304.49</DOCUMENTINDEX>
<DOCUMENTINDEX Name="A2iA_CheckCodeline_OnUs1">28557833</DOCUMENTINDEX>
<DOCUMENTINDEX Name="A2iA_CheckCodeline_Transit">031100092</DOCUMENTINDEX>
<DOCUMENTINDEX Name="A2iA_CheckDate">10-26-2001</DOCUMENTINDEX>
<DOCUMENTINDEX Name="A2iA_CheckLAR">3304.49</DOCUMENTINDEX>
<DOCUMENTINDEX Name="A2iA_CheckNumber">1211</DOCUMENTINDEX>
<DOCUMENTINDEX Name="A2iA_CheckPayeeName">BCBSD</DOCUMENTINDEX>
<DOCUMENTINDEX Name="CheckPayerName">SOFTPRO NORTH AMERICA, INC.</DOCUMENTINDEX>
</DOCUMENT>
I need to transform this into the following output:
<?xml version="1.0" encoding="utf-8"?>
<DOCUMENT>
<A2iA_CheckAmount>3304.49</A2iA_CheckAmount>
<A2iA_CheckCAR>3304.49</A2iA_CheckCAR>
<A2iA_CheckLAR>3304.49</A2iA_CheckLAR>
<A2iA_CheckAccountNumber>28557833</A2iA_CheckAccountNumber>
<A2iA_CheckRoutingNumber>031100092</A2iA_CheckRoutingNumber>
<A2iA_CheckNumber>1211</A2iA_CheckNumber>
<A2iA_CheckAmount>3304.49</A2iA_CheckAmount>
<A2iA_CheckPayeeName>BCBSD</A2iA_CheckPayeeName>
<CheckPayerName>SOFTPRO NORTH AMERICA, INC</CheckPayerName>
</DOCUMENT>
I am using the following XSLT code:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:value-of select="concat('<DOCUMENT>')"/>
<xsl:template match="/*">
<xsl:for-each select="DOCUMENT/DOCUMENTINDEX/#*">
<xsl:value-of select="concat('<', name(), '>', ., '</', name(), '>')"/>
</xsl:for-each>
<xsl:value-of select="concat('</DOCUMENT>')"/>
</xsl:template>
I tested this with an online XSLT Tester and it threw a non-descriptive error. I am new to XSLT Transformation. Any help will be greatly appreciated. Thank You.
One possibility to achieve this is the following simple one(EDITED):
<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="/DOCUMENT">
<xsl:element name="Document">
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="DOCUMENTINDEX">
<xsl:element name="{#Name}">
<xsl:value-of select="text()" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
It gives the output (EDITED)
<Document>
<A2iA_CheckAmount>3304.49</A2iA_CheckAmount>
<A2iA_CheckCAR>3304.49</A2iA_CheckCAR>
<A2iA_CheckCodeline_OnUs1>28557833</A2iA_CheckCodeline_OnUs1>
<A2iA_CheckCodeline_Transit>031100092</A2iA_CheckCodeline_Transit>
<A2iA_CheckDate>10-26-2001</A2iA_CheckDate>
<A2iA_CheckLAR>3304.49</A2iA_CheckLAR>
<A2iA_CheckNumber>1211</A2iA_CheckNumber>
<A2iA_CheckPayeeName>BCBSD</A2iA_CheckPayeeName>
<CheckPayerName>SOFTPRO NORTH AMERICA, INC.</CheckPayerName>
</Document>

Grouping of same tags under an element in xslt

I am working XSLT where the source looks like this.
Source:
<Data>
<AB>all</AB>
<AB>all2</AB>
<CD>hhhhhh</CD>
<DE>hhhshhh</DE>
</Data>
Need to write XSLT to get output as
<Info>
<XXX>
<TTT value="all"/>
<TTT value="all2"/>
</XXX>
<!-- ....-->
<!-- ..to het all the elements.. -->
</Info>
I have to write xslt to match tag.
<xsl:template match="AB">
</xsl:template>
I can do it by matching Data tag.
<?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="Data">
<info>
<XXX>
<xsl:for-each select="AB">
<TTT>
<xsl:attribute name="value">
<xsl:value-of select="."/>
</xsl:attribute>
</TTT>
</xsl:for-each>
</XXX>
</info>
</xsl:template>
</xsl:stylesheet>
Can any one help me out how to do it by matching AB tag
<xsl:template match="AB">
</xsl:template>
Thank you.
I think you are asking how do you use xsl:apply-templates. If so, your XSLT would look like this
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Data">
<info>
<XXX>
<xsl:apply-templates select="AB"/>
</XXX>
</info>
</xsl:template>
<xsl:template match="AB">
<TTT value="{.}"/>
</xsl:template>
</xsl:stylesheet>
Do also note the use of Attribute Value Templates in the AB template to simplify the XSLT.
If you also require the other non-AB elements to be output unchanged, you would make use of the identity transform in your XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Data">
<info>
<XXX>
<xsl:apply-templates select="AB"/>
</XXX>
<xsl:apply-templates select="node()[not(self::AB)]" />
</info>
</xsl:template>
<xsl:template match="AB">
<TTT value="{.}"/>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This would also match all non-AB elements, outputing following the XXX element. In your case, it would output the following:
<info>
<XXX>
<TTT value="all" />
<TTT value="all2" />
</XXX>
<CD>hhhhhh</CD>
<DE>hhhshhh</DE>
</info>
Of course, there is no reason you couldn't have other templates matching elements like CD or DE to transform those too.

Removing duplicates in xsl

I have the following XML and I want to process it so that I do not get duplicates in the result set. I have simplified the problem to make it easier to understand. Also how can I use the results of one template as part of the input for another?
This:
<LINES xmlns:set="http://exslt.org/sets">
<STDINSTRSEQ>0</STDINSTRSEQ>
<STDINSTRSEQ>1</STDINSTRSEQ>
<STDINSTRSEQ>2</STDINSTRSEQ>
</LINES>
is the desired result. <STDINSTRSEQ> is the name of the field that should be treated as the key. I have provided the sample data here.
<PURCHASEORDER>
<POHEADER>
<REQUISITIONNO>1103025T12 000 000</REQUISITIONNO>
<REQLDATE>2004-10-26</REQLDATE>
<MTLREQDT></MTLREQDT>
<CREATEDT>2005-03-16</CREATEDT>
<VNDRLONGNM>DORI FOODS, INC.</VNDRLONGNM>
</POHEADER>
<LINES>
<LINE>
<POLINENBR>12</POLINENBR>
<STDINSTRDESC>NOTE: THIS PURCHASE ORDER SERVES AS CONFIRMATION</STDINSTRDESC>
<STDINSTRSEQ>0</STDINSTRSEQ>
</LINE>
<LINE>
<POLINENBR>11</POLINENBR>
<STDINSTRDESC>NOTE: THIS PURCHASE ORDER SERVES AS CONFIRMATION</STDINSTRDESC>
<STDINSTRSEQ>0</STDINSTRSEQ>
</LINE>
<LINE>
<POLINENBR>11</POLINENBR>
<STDINSTRDESC>THAT WE ACCEPT THE TERMS AND CONDITIONS OUTLINED IN</STDINSTRDESC>
<STDINSTRSEQ>1</STDINSTRSEQ>
</LINE>
<LINE>
<POLINENBR>12</POLINENBR>
<STDINSTRDESC>THAT WE ACCEPT THE TERMS AND CONDITIONS OUTLINED IN</STDINSTRDESC>
<STDINSTRSEQ>1</STDINSTRSEQ>
</LINE>
<LINE>
<POLINENBR>23</POLINENBR>
<STDINSTRDESC>YOUR CONTRACT DATED 02/16/2007 FOR THE FOLLOWING</STDINSTRDESC>
<STDINSTRSEQ>2</STDINSTRSEQ>
</LINE>
<LINE>
<POLINENBR>22</POLINENBR>
<STDINSTRDESC>YOUR CONTRACT DATED 02/16/2007 FOR THE FOLLOWING</STDINSTRDESC>
<STDINSTRSEQ>2</STDINSTRSEQ>
</LINE>
</LINES>
</PURCHASEORDER>
If I understand correctly what you are asking for, then given your sample XML this would work
<?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" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="/PURCHASEORDER/LINES" />
</xsl:template>
<xsl:template match="LINES">
<LINES xmlns:set="http://exslt.org/sets">
<xsl:apply-templates select="LINE/STDINSTRSEQ[not(. = preceding::STDINSTRSEQ)]"/>
</LINES>
</xsl:template>
<xsl:template match="STDINSTRSEQ">
<xsl:copy> <xsl:apply-templates/> </xsl:copy>
</xsl:template>
</xsl:stylesheet>
An optimized version that uses <xsl:key> to filter and provide unique results:
<?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" encoding="UTF-8" indent="yes"/>
<xsl:key name="STDIN" match="STDINSTRSEQ" use="./text()" />
<xsl:template match="/">
<xsl:apply-templates select="/PURCHASEORDER/LINES" />
</xsl:template>
<xsl:template match="LINES">
<LINES xmlns:set="http://exslt.org/sets">
<xsl:apply-templates select="LINE/STDINSTRSEQ[generate-id(.) = generate-id(key('STDIN', .))]"/>
</LINES>
</xsl:template>
<xsl:template match="STDINSTRSEQ">
<xsl:copy> <xsl:apply-templates/> </xsl:copy>
</xsl:template>
</xsl:stylesheet>
I'm not sure about the apply-templates. I don't understand how they work. So for a XML that was like that:
<xml>
<line>209</line>
<line>209</line>
<line>209</line>
<line>100</line>
<line>101</line>
<line>101</line>
<line>100</line>
<line>102</line>
<line>209</line>
<line>101</line>
<line>101</line>
<line>101</line>
<line>101</line>
<line>209</line>
<line>100</line>
</xml>
to remove the repetitions, leaving only unique values:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="no" />
<xsl:template match="/">
<Output>
<xsl:for-each select="//line">
<xsl:if test="not(. = preceding::line)">
<line>
<xsl:value-of select="string(.)" disable-output-escaping="no" />
</line>
</xsl:if>
</xsl:for-each>
</Output>
</xsl:template>
</xsl:stylesheet>
And the result was:
<Output>
<line>100</line>
<line>101</line>
<line>102</line>
<line>209</line>
</Output>
And it was much quicker than the solution provided by Mads Hansen.
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:key name="kSTDINSTRSEQ" match="STDINSTRSEQ" use="." />
<xsl:template match="PURCHASEORDER">
<xsl:apply-templates select="LINES" />
</xsl:template>
<xsl:template match="LINES">
<LINES xmlns:set="http://exslt.org/sets">
<xsl:copy-of select="
LINE/STDINSTRSEQ[
generate-id() = generate-id(key('kSTDINSTRSEQ', .)[1])
]
" />
</LINES>
</xsl:template>
</xsl:stylesheet>
produces
<LINES xmlns:set="http://exslt.org/sets">
<STDINSTRSEQ>0</STDINSTRSEQ>
<STDINSTRSEQ>1</STDINSTRSEQ>
<STDINSTRSEQ>2</STDINSTRSEQ>
</LINES>
To access this from another XSL template, write it to a temporary file and use the document() function. Alternatively, just do the grouping in the other XSLT file.