How to put error message to out file in XSLT - xslt

I am looking for a way to generate additional file with error message in case of processing failure.
For example replacement for function concat:
<xsl:function name="my:concat">
<xsl:param name="value1" as="node()*"/>
<xsl:param name="value2" as="node()*"/>
<xsl:try>
<xsl:value-of select="concat($value1,$value2)"/>
<xsl:catch>
<xsl:value-of select="concat($value1[1],$value2[1])"/>
<xsl:variable name="LOCALNAME_ERROR" as="xs:string" select="'error.xml'"/>
<xsl:result-document href="{$LOCALNAME_ERROR}" method="xml" encoding="UTF-8" indent="yes" omit-xml-declaration="no">
<ERROR>
<DOCID>
<xsl:value-of select="'DOCID'"/>
</DOCID>
<ERRORCODE>
<xsl:value-of select="$err:code"/>
</ERRORCODE>
<ERRORDESCRIPTION>
<xsl:value-of select="$err:description"/>
</ERRORDESCRIPTION>
<FIELDNAME>
<xsl:value-of select="if (count($value1) > 1) then name($value1[1]) else name($value2[1])"/>
</FIELDNAME>
<FIELDPATH>
<xsl:value-of select="if (count($value1) > 1) then path($value1[1]) else path($value2[1])"/>
</FIELDPATH>
<FIELDVALUE>
<xsl:value-of select="if (count($value1) > 1) then $value1 else $value2"/>
</FIELDVALUE>
</ERROR>
</xsl:result-document>
</xsl:catch>
</xsl:try>
</xsl:function>
So in case of two occurrences it will produce a nice error message with info where error happened.
Test file:
<?xml version='1.0' encoding='ISO-8859-1'?>
<DUMMY_001>
<field1>value_1</field1>
<field2>value_abc</field2>
<field2>value_xyz</field2>
<field3>value_3</field3>
<field4>value_4</field4>
</DUMMY_001>
Outcome:
<ERROR>
<DOCID/>
<ERRORCODE>err:XPTY0004</ERRORCODE>
<ERRORDESCRIPTION>A sequence of more than one item is not allowed as the first argument of concat()</ERRORDESCRIPTION>
<FIELDNAME>field2</FIELDNAME>
<FIELDPATH>/Q{}DUMMY_001[1]/Q{}field2[1]</FIELDPATH>
<FIELDVALUE>value_abc value_xyz</FIELDVALUE>
</ERROR>
In saxon 9.9.1.7 the error is: XTDE1480 Cannot execute xsl:result-document while evaluating xsl:function, but in saxon 9.6.0.7 it works.
Could you advise if there is a reliable way to create such error messages in the latest saxon versions?

Related

Remove duplicate values in child nodes of XML file using XSLT

I have the below xml data and I want remove the duplicate values from this xml.
<Report_Data>
<Report_Entry>
<Classifications_group>
<ClassificationGroupName Descriptor="EEO-1 Job Categories">
</ClassificationGroupName>
<ClassificationDescription>Professionals</ClassificationDescription>
</Classifications_group>
<Classifications_group>
<ClassificationGroupName Descriptor="Hartford Job Category">
</ClassificationGroupName>
<ClassificationDescription>Other</ClassificationDescription>
</Classifications_group>
<Classifications_group>
<ClassificationGroupName Descriptor="Hartford Job Category">
</ClassificationGroupName>
<ClassificationDescription>Other</ClassificationDescription>
</Classifications_group>
</Report_Entry>
<Report_Entry>
<Classifications_group>
<ClassificationGroupName Descriptor="EEO-1 Job Categories">
</ClassificationGroupName>
<ClassificationDescription>Administrative Support Workers</ClassificationDescription>
</Classifications_group>
<Classifications_group>
<ClassificationGroupName Descriptor="Hartford Job Category">
</ClassificationGroupName>
<ClassificationDescription>Other</ClassificationDescription>
</Classifications_group>
</Report_Entry>
</Report_Data>
I have used the following XSLT to remove duplicate values.
<?xml version="1.0" encoding="UTF-8"?>
<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:variable name="CRLF" select="'
'"/>
<xsl:output indent="no" method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Report_Data">
<xsl:value-of select="'ClassificationGroupName,ClassificationDescription'"/>
<xsl:value-of select="$CRLF"/>
<xsl:for-each select="Report_Entry">
<xsl:for-each-group select="Classifications_group" group-by="concat(ClassificationGroupName/#Descriptor, '|', ClassificationDescription)">
<xsl:value-of select="ClassificationGroupName/#Descriptor"/>
<xsl:value-of select="','"/>
<xsl:value-of select="ClassificationDescription"/>
<xsl:value-of select="$CRLF"/>
</xsl:for-each-group>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output:
ClassificationGroupName,ClassificationDescription
EEO-1 Job Categories,Professionals
Hartford Job Category,Other
EEO-1 Job Categories,Administrative Support Workers
Hartford Job Category,Other
Excepted output:
ClassificationGroupName,ClassificationDescription
EEO-1 Job Categories,Professionals
Hartford Job Category,Other
EEO-1 Job Categories,Administrative Support Workers
With the code that I have written, it removes duplicates only within the Report_Entry. I want to remove if there any other duplicate values with both ClassificationGroupName and ClassificationDescription of Classifications_group in any other Report_Entry as well.
What are the changes do I need to do to get the expected output?
I think you want to group all entries e.g. in XSLT 3 with a composite key
<xsl:template match="Report_Data">
<xsl:for-each-group select="Report_Entry/Classifications_group" composite="yes" group-by="ClassificationGroupName/#Descriptor, ClassificationDescription">
<xsl:value-of select="current-grouping-key()" separator=", "/>
<xsl:text>
</xsl:text>
</xsl:for-each-group>
</xsl:template>
or, with your XSLT 2 approach, using
<xsl:template match="Report_Data">
<xsl:value-of select="'ClassificationGroupName,ClassificationDescription'"/>
<xsl:value-of select="$CRLF"/>
<xsl:for-each-group select="Report_Entry/Classifications_group" group-by="concat(ClassificationGroupName/#Descriptor, '|', ClassificationDescription)">
<xsl:value-of select="ClassificationGroupName/#Descriptor"/>
<xsl:value-of select="','"/>
<xsl:value-of select="ClassificationDescription"/>
<xsl:value-of select="$CRLF"/>
</xsl:for-each-group>
</xsl:template>

XSLT sort across nodes and text

I'm trying to find, sort, and output a string of copyright years. I've got a working bit of code, but I just found that some of my years are not in the same tags as others.
Initially I thought all my years were in the following tag: <copyright-year>2020</copyright-year>, see below for a working bit of code to find, sort, and output those.
I just found that some of my copyright years look like this: <copyright-statement>© 2017 Company. All rights reserved.</copyright-statement>.
I can find the years in these statements using //copyright-statement/substring(.,3,4). However, when I tried to search for both types like this: <xsl:for-each-group select="//copyright-year|copyright-statement/substring(., 3, 4)" group-by="text()">, it gives the following warning:
Required item type of document-order sorter is node(); supplied expression ((./copyright-statement)/(fn:substring(...))) has item type xs:string. The expression can succeed only if the supplied value is an empty sequence.
And obviously doesn't work. Any idea how to merge these two sets of years to get: <output>2020, 2019, 2017</output>?
Sample XML
<?xml version="1.0" encoding="UTF-8"?>
<book>
<book-meta>
<copyright-year>2020</copyright-year>
</book-meta>
<body>
<book-part>
<book-part-meta>
<copyright-year>2019</copyright-year>
</book-part-meta>
</book-part>
</body>
<back>
<copyright-statement>© 2017 Company. All rights reserved.</copyright-statement>
</back>
</book>
Sample XSLT
<?xml version="1.0" encoding="UTF-8"?>
<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:template match="book">
<xsl:variable name="years">
<xsl:for-each-group select="//copyright-year" group-by="text()">
<xsl:sort select="." order="descending"/>
<xsl:value-of select="."/><xsl:if test="position() != last()"><xsl:text>, </xsl:text></xsl:if>
</xsl:for-each-group>
</xsl:variable>
<output><xsl:value-of select="$years"/></output>
</xsl:template>
</xsl:stylesheet>
Which version of which XSLT processor do you use? XSLT 3 has a sort function
<xsl:value-of select="reverse(sort(distinct-values((//copyright-year/xs:integer(.), //copyright-statement/xs:integer(substring(.,3,4))))))" separator=", "/>
https://xsltfiddle.liberty-development.net/bwdwsd
It might be easier to read that with the new => arrow operator:
<xsl:value-of
select="(//copyright-year/xs:integer(.), //copyright-statement/xs:integer(substring(.,3,4)))
=> distinct-values()
=> sort()
=> reverse()"
separator=", "/>
https://xsltfiddle.liberty-development.net/bwdwsd/2
But in general the step you need is to simply ensure you work with atomic values e.g. xs:integers seems the right value for years. I think in XSLT 2 I would wrap perform-sort into a function:
<xsl:function name="mf:sort" as="item()*">
<xsl:param name="input" as="item()*"/>
<xsl:perform-sort select="$input">
<xsl:sort order="descending"/>
</xsl:perform-sort>
</xsl:function>
<xsl:template match="book">
<xsl:value-of select="mf:sort(distinct-values((//copyright-year/xs:integer(.), //copyright-statement/xs:integer(substring(.,3,4)))))" separator=", "/>
</xsl:template>
https://xsltfiddle.liberty-development.net/bwdwsd/1
Here's one way to get the specify output;
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/book">
<xsl:variable name="years" as="xs:string*">
<xsl:perform-sort>
<xsl:sort select="." data-type="number" order="descending"/>
<xsl:apply-templates select="//(copyright-year | copyright-statement)"/>
</xsl:perform-sort>
</xsl:variable>
<output>
<xsl:value-of select="$years" separator=","/>
</output>
</xsl:template>
<xsl:template match="copyright-statement">
<xsl:value-of select="substring-before(substring-after(., '© '), ' ')"/>
</xsl:template>
</xsl:stylesheet>
Demo: https://xsltfiddle.liberty-development.net/bwdwsd/3

Getting internal error while evaluating template - xslt

I have an issue while converting xml into csv. I'm getting a strange issue and need help.
This is my XML and XSLT, I'm getting the below error while transforming
"java.lang.RuntimeException: Internal error evaluating template at line 47 in module " that's at the line with content " xsl:template match="UsrOrder"" - bolded text
Tried but unable to figure out the issue.
XML
Below is the XML input I'm giving to XSLT and this has to be converted to CSV
<?xml version="1.0" encoding="UTF-8"?>
<document>
<businessobjects>
<UsrOrder>
<PlanOn>228.01</PlanOn>
<PROG/>
<FUND/>
<ORGN/>
<ACCT/>
<Buyer/>
<Delivery_Notes/>
<Supplier/>
<Line>1</Line>
<Item_Name>NewAir AC-12000CF Airco filter</Item_Name>
<Unit_of_Measure>STK</Unit_of_Measure>
<Order_Date/>
<Retrofit/>
<Description/>
<Product_Code/>
<Related_Invoice_Number/>
<Order_Attachments/>
<Quantity>1.0000</Quantity>
<Unit_Price>9.98</Unit_Price>
</UsrOrder>
</businessobjects>
</document>
```
XSLT
====
<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:csv="csv:csv">
<xsl:output method="text" encoding="utf-8" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />
<xsl:variable name="delimiter" select="','" />
<csv:columns>
<column>Supplier</column>
<column>FUND</column>
<column>ORGN</column>
<column>ACCT</column>
<column>PROG</column>
<column>PlanOn</column>
<column>Delivery_Notes</column>
<column>Buyer</column>
<column>Line</column>
<column>ItemName</column>
<column>Quantity</column>
<column>Unit_Price</column>
<column>Unit_of_Measure</column>
<column>Description</column>
<column>Product_Code</column>
<column>Category</column>
<column>Retrofit</column>
<column>Related_Invoice_Number</column>
<column>OrderDate</column>
<column>OrderAttachments</column>
</csv:columns>
<xsl:template match="/document/businessobjects">
<!-- Output the CSV header -->
<xsl:for-each select="document('')/*/csv:columns/*">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:value-of select="$delimiter"/>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
<!-- Output rows for each matched property -->
<xsl:apply-templates select="UsrOrder" />
</xsl:template>
**<xsl:template match="UsrOrder">**
<xsl:variable name="OrderOrderLines" select="." />
<!-- Loop through the columns in order -->
<xsl:for-each select="document('')/*/csv:columns/*">
<!-- Extract the column name and value -->
<xsl:variable name="column" select="." />
<xsl:variable name="value" select="$OrderOrderLines/*[name() = $column]" />
<xsl:value-of select="$value"/>
<!-- Quote the value if required -->
<!-- <xsl:choose>
<xsl:when test="contains($value, '"')">
<xsl:variable name="x" select="replace($value, '"', '""')"/>
<xsl:value-of select="concat('"', $x, '"')"/>
</xsl:when>
<xsl:when test="contains($value, $delimiter)">
<xsl:value-of select="concat('"', $value, '"')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value"/>
</xsl:otherwise>
</xsl:choose>-->
<!-- Add the delimiter unless we are the last expression -->
<xsl:if test="position() != last()">
<xsl:value-of select="$delimiter"/>
</xsl:if>
</xsl:for-each>
<!-- Add a newline at the end of the record -->
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
"Internal error" means it's a problem in the XSLT processor, not in your code. You can consider things like checking known bugs and upgrading to the latest maintenance release; when you've done that, report it to the vendor.
Another technique is to try a different XSLT processor and see if it comes up with anything. When I run this on the current Saxon version (9.9.1.5), I get:
Static error at xsl:template on line 31 column 53 of test.xsl:
XTSE0120: No character data is allowed between top-level elements
Errors were reported during stylesheet compilation
So it could be that if you fix the problem in your stylesheet, the failure will go away. (The characters in question are not actually on line 31, they are the asterisks on line 46.)

XSLT 1.0 to retrieve or parse a specific value

I have an issue with the XSLT below. I need help to fix my transformation. I am using XSLT 1.0. Input can be 120KVA or 120MVA or 120.0KVA or 120.0KV. Output i want to parse into 3 parts. i.e.
<tns:ratedApparentPower>
<tns:unitSymbolUnit>VA</tns:unitSymbolUnit>
<tns:multiplier>K</tns:multiplier>
<tns:floatValue>120.0</tns:floatValue>
</tns:ratedApparentPower>
My current Transformation is:
<tns:ratedApparentPower>
<tns:unitSymbolUnit>VA</tns:unitSymbolUnit>
<tns:multiplier>
<xsl:value-of select="substring(translate(//ns0:ATTRIBUTE_GROUPS_ITEM[ns0:NAME='DISTXFR']/ns0:ATTRIBUTES/ns0:ATTRIBUTES_ITEM[ns0:NAME='KVA']/ns0:VALUE,'1234567890', ''),1,1)" />
</tns:multiplier>
<tns:floatValue>
<xsl:value-of select="substring-before(//ns0:ATTRIBUTE_GROUPS_ITEM[ns0:NAME='DISTXFR']/ns0:ATTRIBUTES/ns0:ATTRIBUTES_ITEM[ns0:NAME='KVA']/ns0:VALUE,substring(translate(//ns0:ATTRIBUTE_GROUPS_ITEM[ns0:NAME='DISTXFR']/ns0:ATTRIBUTES/ns0:ATTRIBUTES_ITEM[ns0:NAME='KVA']/ns0:VALUE,'1234567890', ''),1,1))" />
</tns:floatValue>
</tns:ratedApparentPower>
</xsl:if>
Generated O/P:
<tns:ratedApparentPower>
<tns:unitSymbolUnit>VA</tns:unitSymbolUnit>
<tns:multiplier>.</tns:multiplier>
<tns:floatValue>120</tns:floatValue>
</tns:ratedApparentPower>
My doubts are:
How to get <tns:unitSymbolUnit>VA</tns:unitSymbolUnit>? Currently, I am hardcoding it. But it can be V or VA or any other value
How to get multiplier? With my current logic I get as <tns:multiplier>.</tns:multiplier> when I have 120.0 it works fine.
With my current logic I get <tns:floatValue>120</tns:floatValue> instead of 120.0.
Is there any way I can shorten the path (//ns0:ATTRIBUTE_GROUPS_ITEM[ns0:NAME='DISTXFR']/ns0:ATTRIBUTES/ns0:ATTRIBUTES_ITEM[ns0:NAME='KVA']/ns0:VALUE) by assigning it to some variable instead of using whole path every time?
Michael I edited your template belwo to match my requiremnts and usinga callTemplate but receive empty response
<xsl:template name="convertFloatValues">
<xsl:param name="floatValue1"/>
<xsl:variable name="unit" select="translate($floatValue1, '0123456789.', '')"/>
<unitSymbolUnit>
<xsl:value-of select="substring($unit, 2)"/>
</unitSymbolUnit>
<multiplier>
<xsl:value-of select="substring($unit, 1 , 1)"/>
</multiplier>
<floatValue>
<xsl:value-of select="substring-before(., $unit)"/>
</floatValue>
</xsl:template>
Call Template example . Not sure what I am missing. Can you please help me.
<tns:ratedApparentPower>
<xsl:call-template name="convertFloatValues">
<xsl:with-param name="floatValue1" select="/ns0:OutputParameters/ns0:XXJEAM_ASSET_SEARCH_PKG-24GETAS/ns0:ATTRIBUTE_GROUPS/ns0:ATTRIBUTE_GROUPS_ITEM[ns0:NAME='DISTXFR']/ns0:ATTRIBUTES/ns0:ATTRIBUTES_ITEM[ns0:NAME='KVA']/ns0:VALUE"/>
</xsl:call-template>
</tns:ratedApparentPower>
Input:
<OutputParameters xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="*****">
<XXJEAM_ASSET_SEARCH_PKG-24GETAS>
<ATTRIBUTE_GROUPS_ITEM>
<NAME>DISTXFR</NAME>
<ATTRIBUTES>
<ATTRIBUTES_ITEM>
<COLUMN_NAME>C_ATTRIBUTE3</COLUMN_NAME>
<NAME>Volt</NAME>
<VALUE>500</VALUE>
</ATTRIBUTES_ITEM>
<ATTRIBUTES_ITEM>
<COLUMN_NAME>C_ATTRIBUTE4</COLUMN_NAME>
<NAME>KVA</NAME>
<VALUE>500.0KVA</VALUE>
</ATTRIBUTES_ITEM>
</ATTRIBUTES>
</ATTRIBUTE_GROUPS_ITEM>
</XXJEAM_ASSET_SEARCH_PKG-24GETAS>
</OutputParameters>
Consider the following example:
XML
<input>
<item>20KVA</item>
<item>120MVA</item>
<item>120.0KVA</item>
<item>120.0KV</item>
</input>
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:template match="/input">
<output>
<xsl:for-each select="item">
<xsl:variable name="unit" select="translate(., '0123456789.', '')" />
<ratedApparentPower>
<unitSymbolUnit>
<xsl:value-of select="substring($unit, 2)" />
</unitSymbolUnit>
<multiplier>
<xsl:value-of select="substring($unit, 1 , 1)" />
</multiplier>
<floatValue>
<xsl:value-of select="substring-before(., $unit)" />
</floatValue>
</ratedApparentPower>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>
<ratedApparentPower>
<unitSymbolUnit>VA</unitSymbolUnit>
<multiplier>K</multiplier>
<floatValue>20</floatValue>
</ratedApparentPower>
<ratedApparentPower>
<unitSymbolUnit>VA</unitSymbolUnit>
<multiplier>M</multiplier>
<floatValue>120</floatValue>
</ratedApparentPower>
<ratedApparentPower>
<unitSymbolUnit>VA</unitSymbolUnit>
<multiplier>K</multiplier>
<floatValue>120.0</floatValue>
</ratedApparentPower>
<ratedApparentPower>
<unitSymbolUnit>V</unitSymbolUnit>
<multiplier>K</multiplier>
<floatValue>120.0</floatValue>
</ratedApparentPower>
</output>

unable to read .properties file in xslt using unparsed-text()

When i try to read the err.properties file into vText
<xsl:variable name="errorMessages">
<xsl:variable name="vText"
select="unparsed-text('./err.properties','UTF-8')" />
<ixbrlErrors>
<xsl:analyze-string select="$vText"
regex="(ix\d\d)\s?=\s?(.+)\n?">
<xsl:matching-substring>
<xsl:element name="{regex-group(1)}">
<xsl:value-of select="normalize-space(regex-group(2))"></xsl:value-of>
</xsl:element>
</xsl:matching-substring>
</xsl:analyze-string>
</ixbrlErrors>
</xsl:variable>
saxon gives me the following error..
Error on line 42 of validationXslt.xsl:
XTDE1200: Failed to read input file
file:/D:/ws/proj/target/classes/IX/err.properties
(java.nio.charset.MalformedInputException): Input length = 1
in variable errorMessages
at xsl:call-template name="AllStartsFromHere" (file:/D:/ws/proj/target/classes/IX/validationXslt.xsl#57)
Transformation failed: Run-time errors were reported
line 42 is regex="(ix\d\d)\s?=\s?(.+)\n?">
I tried saving err.properties with utf-8 encoding but there is no effect.
I assume your Input in .properties file like:
this is 254125
this is 9856524
ix25 = 20111
iax25 = 20222
ix25 = 20333
ibx25 = 20444
here with the XSLT 1.0 i tried to achieve the output as i understand:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dp="http://www.datapower.com/extensions" xmlns="http://www.datapower.com/extensions"
xmlns:dpconfig="http://www.datapower.com/param/config" extension-element-prefixes="dp"
exclude-result-prefixes="dp dpconfig">
<xsl:output omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:variable name="errorMessages" select="unparsed-text('err.properties','UTF-8')"/>
<xsl:analyze-string select="$errorMessages" regex="(ix\d\d)\s*=\s*(.*)">
<xsl:matching-substring>
<xsl:value-of select="concat('<',regex-group(1),'>', regex-group(2), '</',regex-group(1),'>', '
')" disable-output-escaping="yes"/>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:template>
</xsl:stylesheet>
Below output i got:
<ix25>20111</ix25>
<ix25>20333</ix25>