How match non string in xslt - xslt-2.0

I want to match non string in xslt 2.0.
<td><list>
<list-item>
<p>aaaa</p>
<list>
<list-item>
<p>bbb</p>
</list-item>
</list>
</list-item>
</list>
</td>
I have done a template like this
<xsl:template match="td">
<para>
<xsl:apply-templates/>
</tps:p>
</xsl:template>
When input xml like below:
<td><list>
<list-item>
<p>aaaa</p>
<list>
<list-item>
<p>bbb</p>
</list-item>
</list>
</list-item>
</list>
kmkmkmk
</td>
Output(correct output):
<para>kmkmkmk</para>
When input xml like below (new line between td and list):
<td><list>
<list-item>
<p>aaaa</p>
<list>
<list-item>
<p>bbb</p>
</list-item>
</list>
</list-item>
</list>
</td>
Output(Incorrect output):
<para> </para>
But When input xml like below( no new line between td and list ):
<td><list>
<list-item>
<p>aaaa</p>
<list>
<list-item>
<p>bbb</p>
</list-item>
</list>
</list-item>
</list></td>
Output(correct output):
nothing display
I want to do when td has no any text nothing display and display only display td has text.
Why when there is a new line between <td> and <list> not working well and where is no new line working wells. How can I fix this?

Your samples are not minimal and complete but to transform a td element node which has non-whitespace text child contents you can use
<xsl:template match="td[text()[normalize-space()]]">
<para>
<xsl:apply-templates/>
</para>
</xsl:template>
Whether you additionally need <xsl:template match="td[not(text()[normalize-space()])]"/> or <xsl:template match="td[not(text()[normalize-space()])]"><xsl:apply-templates/></xsl:template> depends on how you set up the rest of the transformation.

Related

XSLT 2.0: Template match only if child element's value matches regex

Given this XML
<root>
<card>
<k>заада</k>
<blockquote>заада I</blockquote>
<blockquote>ир.</blockquote>
<blockquote>(или жүрөк заада)</blockquote>
</card>
...
</root>
I am trying to get only those Cards where any child blockquote element's value matches a regex. The XSL below isn't working, and I know why it doesn't work, I just can't figure out how to make it work.
<xsl:template match="/">
<xsl:apply-templates select="//card[matches(blockquote, '^\w+-? I:?$')]"/>
</xsl:template>
<xsl:template match="card">
<xsl:copy-of select="." />
</xsl:template>

Convert a large XML File (over 3G) to comma delimited file

I need to convert a large XML file (over 3G) into a comma delimited file. I created an XSL file to convert it. Unfortunately, the file is too large to process using XSLT 1.0. I tried using XSLT 3.0 (Saxon), but I get the error "XTSE3430: Template rule is not streamable".
Script:
java -cp saxon9ee.jar net.sf.saxon.Transform -t -s:costing.xml -xsl:costing.xsl -o:costing.csv
Error Message:
Java version 1.8.0_191
Using license serial number
Stylesheet compilation time: 345.113654ms
Processing file:costing.xml
Streaming file:costing.xml
Using parser com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser
URIResolver.resolve href="" base="file:costing.xsl"
Using parser com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser
Building tree for file:costing.xsl using class net.sf.saxon.tree.tiny.TinyBuilder
Tree built in 5.206935ms
Tree size: 237 nodes, 104 characters, 25 attributes
Error on line 71 of costing.xsl:
XTSE3430: Template rule is not streamable
* Operand {($currNode/element())/element()} of {let $vv:v0 := ...} selects streamed
nodes in a context that allows arbitrary navigation (line 86)
Template rule is not streamable
* Operand {($currNode/element())/element()} of {let $vv:v0 := ...} selects streamed nodes in a context that allows arbitrary navigation (line 86)
XML Structure:
<?xml version="1.0" encoding="UTF-8"?>
<DATA_DS>
<COSTREPORT>
<DR>
<PSU>ABC</PSU>
<TRU>ABC</TRU>
<CA>0</CA>
<DA>0.00</DA>
<UOM>ABC</UOM>
<FN>0</FN>
<RID>0</RID>
<SD>2018-10-25</SD>
<DN>ABC</DN>
<ETD>2018-10-31</ETD>
<DID>0</DID>
<LN>ABC</LN>
<LID>0</LID>
<PN>ABC</PN>
<EN>Jane Doe</EN>
<EID>0</EID>
<ELN>ABC</ELN>
<ELV>ABC</ELV>
<RELA>1234</RELA>
<ETM>A0</ETM>
<ASG>A0</ASG>
<MN>ABC</MN>
<CRY>ABC</CRY
><IVN>ABC</IVN>
<AD>2018-10-31</AD>
<CID>0</CID>
<CCN>ABC</CCN
><BOC>ABC</BOC>
<SG1>0</SG1>
<SG2>0</SG2>
<SG3>0</SG3>
<SG4>0</SG4>
<SG5>0</SG5>
<SG9>0</SG9>
<SG10>0</SG10>
<TRUID>0</TRUID>
</DR>
<DR>
[...]
</DR>
[...]
</COSTREPORT>
</DATA_DS>
XSL file:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:mode streamable="yes" />
<xsl:output method="text" />
<xsl:variable name="delimiter" select="','" />
<!-- define an array containing the fields we are interested in -->
<xsl:variable name="fieldArray">
<field>PSU</field> <!-- string -->
<field>TRU</field> <!-- string -->
<field>CA</field> <!-- number -->
<field>DA</field> <!-- number -->
<field>UOM</field> <!-- string -->
<field>FN</field> <!-- number -->
<field>RID</field> <!-- number -->
<field>SD</field> <!-- date -->
<field>DN</field> <!-- string -->
<field>ETD</field> <!-- date -->
<field>DID</field> <!-- number -->
<field>LN</field> <!-- string -->
<field>LID</field> <!-- number -->
<field>PN</field> <!-- string -->
<field>EN</field> <!-- string -->
<field>EID</field> <!-- number -->
<field>ELN</field> <!-- string -->
<field>ELV</field> <!-- string -->
<field>RELA</field> <!-- number -->
<field>ETM</field> <!-- string -->
<field>ASG</field> <!-- string -->
<field>MN</field> <!-- string -->
<field>CRY</field> <!-- string -->
<field>IVN</field> <!-- string -->
<field>AD</field> <!-- date -->
<field>CID</field> <!-- number -->
<field>CCN</field> <!-- string -->
<field>BOC</field> <!-- string -->
<field>SG1</field> <!-- number -->
<field>SG2</field> <!-- number -->
<field>SG3</field> <!-- number -->
<field>SG4</field> <!-- number -->
<field>SG5</field> <!-- number -->
<field>SG9</field> <!-- number -->
<field>SG10</field> <!-- number -->
<field>TRUID</field> <!-- number -->
</xsl:variable>
<xsl:param name="fields" select="document('')/*/xsl:variable[#name='fieldArray']/*" />
<!-- HEADER -->
<xsl:template match="/">
<!-- output the header row -->
<xsl:for-each select="$fields">
<xsl:if test="position() != 1">
<xsl:value-of select="$delimiter"/>
</xsl:if>
<xsl:value-of select="." />
</xsl:for-each>
<!-- output newline -->
<xsl:text>
</xsl:text>
<xsl:apply-templates select="DATA_DS/COSTREPORT/DR"/>
</xsl:template>
<!-- BODY -->
<xsl:template match="DR">
<xsl:variable name="currNode" select="." />
<!-- output the data row -->
<!-- loop over the field names and find the value of each one in the xml -->
<xsl:for-each select="$fields">
<xsl:if test="position() != 1">
<xsl:value-of select="$delimiter"/>
</xsl:if>
<xsl:value-of select="$currNode/*/*[name() = current()]" />
</xsl:for-each>
<!-- output newline -->
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
The problem is the variable:
<xsl:variable name="currNode" select="." />
This binds a variable to a streamed input node, which isn't allowed because there's no way Saxon can ensure that your selections from this input node are done "in the right order"; you select children/descendants of this node by name, and the streamability analysis can't establish that these descendants are selected in the order they appear in the input.
The answer is actually simple: change the variable to
<xsl:variable name="currNode" select="copy-of(.)" />
This way, every time you hit a DR element, Saxon will read the subtree rooted at that element and hold it as a tree in memory. Because the variable is now a regular in-memory node, rather than a streamed node, there are no restrictions on how it is used.
Allow me a couple of other comments on your code.
Firstly, the document('') construct that was popular in XSLT 1.0 is now thoroughly obsolete. It's much better to put your lookup data in a global variable and access it directly, using
<xsl:param name="fields" select="$fieldArray/*"/>
The document('') call will actually fail if you try to compile the stylesheet and execute it somewhere other than the original source code location.
Secondly, the code to output the header row:
<xsl:for-each select="$fields">
<xsl:if test="position() != 1">
<xsl:value-of select="$delimiter"/>
</xsl:if>
<xsl:value-of select="." />
</xsl:for-each>
can be simplified to
<xsl:value-of select="$fields" separator="{$delimiter}"/>
Similarly, the code for the data rows:
<xsl:for-each select="$fields">
<xsl:if test="position() != 1">
<xsl:value-of select="$delimiter"/>
</xsl:if>
<xsl:value-of select="$currNode/*/*[name() = current()]" />
</xsl:for-each>
simplifies to
<xsl:value-of select="for $f in $fields return $currNode/*/*[name()=$f]"
separator="{$delimiter}"/>

xmlproperty and retrieving property value of xml element if specific attribute is present/set

My Xml looks like:
<root>
<foo location="bar"/>
<foo location="in" flag="123"/>
<foo location="pak"/>
<foo location="us" flag="256"/>
<foo location="blah"/>
</root>
For foo xml element flag is optional attribute.
And when I say:
<xmlproperty file="${base.dir}/build/my.xml" keeproot="false"/>
<echo message="foo(location) : ${foo(location)}"/>
prints all locations :
foo(location) : bar,in,pak,us,blah
Is there a way to get locations only if flag is set to some value?
Is there a way to get locations only if flag is set to some value?
Not with xmlproperty, no, as that will always conflate values that have the same tag name. But xmltask can do what you need as it supports the full power of XPath:
<taskdef name="xmltask" classname="com.oopsconsultancy.xmltask.ant.XmlTask">
<classpath path="xmltask.jar" />
</taskdef>
<xmltask source="${base.dir}/build/my.xml">
<copy path="/root/foo[#flag='123' or #flag='256']/#location"
property="foo.location"
append="true" propertySeparator="," />
</xmltask>
<echo>${foo.location}</echo><!-- prints in,us -->
If you absolutely cannot use third-party tasks then I'd probably approach the problem by using a simple XSLT to extract just the bits of the XML that you do want into another file:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:param name="targetFlag" />
<xsl:template name="ident" match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()" /></xsl:copy>
</xsl:template>
<xsl:template match="foo">
<xsl:if test="#flag = $targetFlag">
<xsl:call-template name="ident" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Call this with the xslt task
<xslt in="${base.dir}/build/my.xml" out="filtered.xml" style="extract.xsl">
<param name="targetFlag" expression="123" />
</xslt>
This will create filtered.xml containing just
<root>
<foo location="in" flag="123"/>
</root>
(modulo changes in whitespace) and you can load this using xmlproperty in the normal way.

multi value grouping with xslt 2

For starters my apology for the title, but I do not know a good way to describe my problem. A code sample will therefore make things more clear.
Assume I have following xml tree :
<root>
<node>
<value xml:lang="en">Some English Content</value>
<value xml:lang="fr">Some French Content</value>
<value xml:lang="de">Some German Content</value>
</node>
<node>
<value xml:lang="en">Some English Content</value>
<value xml:lang="de">Some German Content</value>
</node>
<node>
<value xml:lang="en">Some Other English Content</value>
<value xml:lang="fr">Some Other French Content</value>
<value xml:lang="de">Some Other German Content</value>
</node>
<node>
<value xml:lang="en">Some English Content</value>
<value xml:lang="fr">Some French Content</value>
<value xml:lang="de">Some German Content</value>
</node>
<node>
<value xml:lang="fr">Some French Content</value>
<value xml:lang="de">Some German Content</value>
</node>
</root>
So basically there are various nodesets, with a number of localized strings , and I want to group these sets based on the content. Node 1,2,4 and 5 are about the same topic, but not all strings might be available in all locales, so i can not really use a reference string (say English, as it's not available in node 5). Node 3 contains different content, so it should be part of a different group.
Sounds probably pretty complex, but this is the result I like to get (using xslt 2) :
<values>
<group>
<value xml:lang="en">Some English Content</value>
<value xml:lang="fr">Some French Content</value>
<value xml:lang="de">Some German Content</value>
</group>
<group>
<value xml:lang="en">Some Other English Content</value>
<value xml:lang="fr">Some Other French Content</value>
<value xml:lang="de">Some Other German Content</value>
</group>
</values>
Any idea on how to best deal with this ? Note that I can have up to 40 different languages in a node, and can have hundreds of nodes in a file, so resources can become an issue also.
I am not sure I understand your requirement fully but I wrote some code that first tries to fill up node elements with values for the missing languages and then to group on the filled elements:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf">
<xsl:param name="sep" as="xs:string" select="'|'"/>
<xsl:output indent="yes"/>
<xsl:variable name="main-doc" select="/"/>
<xsl:variable name="languages" as="xs:string*">
<xsl:perform-sort select="distinct-values(root/node/value/#xml:lang)">
<xsl:sort select="."/>
</xsl:perform-sort>
</xsl:variable>
<xsl:key name="k1" match="node/value" use="concat(#xml:lang, $sep, .)"/>
<xsl:template match="root">
<values>
<xsl:variable name="filled" as="element(node)*">
<xsl:apply-templates select="node" mode="fill"/>
</xsl:variable>
<xsl:for-each-group select="$filled" group-by="string-join(value, $sep)">
<group>
<xsl:copy-of select="value"/>
</group>
</xsl:for-each-group>
</values>
</xsl:template>
<xsl:template match="node" mode="fill">
<xsl:copy>
<xsl:variable name="this" as="element(node)" select="."/>
<xsl:for-each select="$languages">
<value xml:lang="{.}">
<xsl:value-of
select="if ($this/value[lang(current())])
then $this/value[lang(current())]
else (key('k1',
concat($this/value[1]/#xml:lang, $sep, $this/value[1]),
$main-doc)/../value[lang(current())])[1]"/>
</value>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
As you can see, I needed some order to fill up the elements so I sorted on the distinct #xml:lang, I am not sure you want that. With that approach the output for the input you have posted with Saxon 9.5 is
<values>
<group>
<value xml:lang="de">Some German Content</value>
<value xml:lang="en">Some English Content</value>
<value xml:lang="fr">Some French Content</value>
</group>
<group>
<value xml:lang="de">Some Other German Content</value>
<value xml:lang="en">Some Other English Content</value>
<value xml:lang="fr">Some Other French Content</value>
</group>
</values>
I am also not sure which strategy is the intended one to fill elements (see also the comment I posted). In the end I decided to key the node/value elements on the concatenation of #xml:lang and their content, then when filling up elements where a language is missing I simply match on the first value child a node has. So that basically means if some node has a first value xml:lang="foo" with contents bar, the match is simply on that language foo and contents bar and we copy over the value contents for the missing language.
If you don't want the sorting and you can live with the order of the initial sequence of attributes then you can omit the sorting, the stylesheet then is
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf">
<xsl:param name="sep" as="xs:string" select="'|'"/>
<xsl:output indent="yes"/>
<xsl:variable name="main-doc" select="/"/>
<xsl:variable name="languages" as="xs:string*" select="distinct-values(root/node/value/#xml:lang)"/>
<xsl:key name="k1" match="node/value" use="concat(#xml:lang, $sep, .)"/>
<xsl:template match="root">
<values>
<xsl:variable name="filled" as="element(node)*">
<xsl:apply-templates select="node" mode="fill"/>
</xsl:variable>
<xsl:for-each-group select="$filled" group-by="string-join(value, $sep)">
<group>
<xsl:copy-of select="value"/>
</group>
</xsl:for-each-group>
</values>
</xsl:template>
<xsl:template match="node" mode="fill">
<xsl:copy>
<xsl:variable name="this" as="element(node)" select="."/>
<xsl:for-each select="$languages">
<value xml:lang="{.}">
<xsl:value-of
select="if ($this/value[lang(current())])
then $this/value[lang(current())]
else (key('k1',
concat($this/value[1]/#xml:lang, $sep, $this/value[1]),
$main-doc)/../value[lang(current())])[1]"/>
</value>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
That way the output is as you asked for:
<values>
<group>
<value xml:lang="en">Some English Content</value>
<value xml:lang="fr">Some French Content</value>
<value xml:lang="de">Some German Content</value>
</group>
<group>
<value xml:lang="en">Some Other English Content</value>
<value xml:lang="fr">Some Other French Content</value>
<value xml:lang="de">Some Other German Content</value>
</group>
</values>

Unsure whether I need a group or a sort or something else

Hi I am an occasional user of XSLT so am probably missing something obvious, but hopefully someone can point it out!
The original XML has the structure;
<test>
<input>a</input>
<input>b</input>
<input>c</input>
<input>d</input>
<input>e</input>
</test>
The XSL file contains the following processing commands;
<xsl:template name="convertInputToNumeric">
<xsl:param name="inputs" />
<xsl:for-each select="input">
<NumericCode>
<xsl:call-template name="toNumericCode">
<xsl:with-param name="type">Input</xsl:with-param>
<xsl:with-param name="" select="." />
</xsl:call-template>
</NumericCode>
</xsl:for-each>
</xsl:template>
the call template 'toNumericCode' takes the current input and looks up in another xml file a numeric representation for the input eg the input 'a' returns the value '001'
<Conversion type="Input">
<Convert>
<FROM>a</FROM>
<TO>001</TO>
</Convert>
<Convert>
<FROM>b</FROM>
<TO>002</TO>
</Convert>
<Convert>
<FROM>c</FROM>
<TO>001</TO>
</Convert>
<Convert>
<FROM>d</FROM>
<TO>001</TO>
</Convert>
<Convert>
<FROM>e</FROM>
<TO>002</TO>
</Convert>
</Conversion>
so running the XSL I currently get
<test>
<NumericCode>001</NumericCode>
<NumericCode>002</NumericCode>
<NumericCode>001</NumericCode>
<NumericCode>001</NumericCode>
<NumericCode>002</NumericCode>
</test>
but actually what I want is that I only get the distinct nodes eg
<test>
<NumericCode>001</NumericCode>
<NumericCode>002</NumericCode>
</test>
I don't know how best to do this as I would want to group based on the numeric code value that is returned from the template 'toNumericCode' rather than the initial input value?
You may use distinct-values(). Have look, I have change your shared template with this one:
<xsl:template name="convertInputToNumeric">
<xsl:param name="inputs" />
<xsl:parm name="abc"><xsl:for-each select="input">
<NumericCode>
<xsl:call-template name="toNumericCode">
<xsl:with-param name="type">Input</xsl:with-param>
<xsl:with-param name="" select="." />
</xsl:call-template>
</NumericCode>
</xsl:for-each>
</xsl:parm>
<xsl:for-each select="distinct-values($abc/NumericCode)">
<NumericCode><xsl:value-of select="."/></NumericCode>
</xsl:for-each>
</xsl:template>
output:
<NumericCode>001</NumericCode><NumericCode>002</NumericCode>

Resources