XSL - using variables in nodepath - java

I have a xsl as below
<xsl:variable name="foo" select="concat('some','stuff')" />
<xsl:if test="$foo">
<xsl:element name="hello">
<xsl:attribute name="id">
<xsl:value-of select="-1"/>
</xsl:attribute>
<xsl:element name="region">
<xsl:value-of select="$foo/child"/> <!-- foo is variable, but always has a 'child' node -->
</xsl:element>
</xsl:element>
</xsl:if>
I get the below output:
<hello id="-1">
and an exception:
java.lang.ClassCastException: org.apache.xpath.objects.XString incompatible with
org.apache.xpath.objects.XNodeSet
what am I doing wrong?

<xsl:variable name="foo" select="concat('some','stuff')" />
will create a string with value 'somestuff'.
The string turns your line
<xsl:value-of select="$foo/child"/>
effectively into
<xsl:value-of select="'somestuff'/child"/>
which is not a valid XPath expression.
Thie string cannot be used in any node expressions, only in string operations.
Replace your
<xsl:variable name="foo" select="concat('some','stuff')" />
by something like
<xsl:variable name="foo" select="./somestuff" />
which returns a node.

Related

Converting unusual XML data to CSV through XSLT

<?xml version="1.0" encoding="UTF-8"?>
<FirstTag version="1.0" createTime="15:59:59" DATE="20161209">
<SecondTag Name="House01">
<a>
<Furniture FURN_ID="FUR00001" FURN_AMT="2" price="10000"/>
<Furniture FURN_ID="FUR00002" FURN_AMT="1" price="20000"/>
</a>
<b>
<Furniture FURN_ID="FUR00003" FURN_AMT="2" price="30000"/>
<Furniture FURN_ID="FUR00004" FURN_AMT="1" price="40000"/>
</b>
<c>
<Furniture FURN_ID="FUR00005" FURN_AMT="2" price="50000"/>
<Furniture FURN_ID="FUR00006" FURN_AMT="1" price="60000"/>
</c>
<d>
<Furniture FURN_ID="FUR00007" FURN_AMT="1" price="70000"/>
<Furniture FURN_ID="FUR00008" FURN_AMT="1" price="80000"/>
</d>
<e>
<Furniture FURN_ID="FUR00009" FURN_AMT="1" price="90000"/>
<Furniture FURN_ID="FUR00010" FURN_AMT="1" price="100000"/>
</e>
<f>
<Furniture FURN_ID="FUR00011" FURN_AMT="1" price="110000"/>
<Furniture FURN_ID="FUR00012" FURN_AMT="2" price="120000"/>
<Furniture FURN_ID="FUR00013" FURN_AMT="2" price="120000"/>
</f>
</SecondTag>
</FirstTag>
Above is the simple xml (with node value), that I produced from my Java program. The point is, I want to send this xml data to another application, where there's already a csv load function from the UI/batch processes. I've heard of XSLT but never use of it, tried some of the tutorial but got confused in the time to get all the values into a csv.
Here's what it should look like in csv (to start, after success need to do some calculation):
In this example in one house (HOUSE01) I would like to output all the furniture in different room (i.e. a is room 1, b is room 2, c is room 3, etc).
I've been trying to build the XSLT, below is the XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:variable name="delimiter" select="','" />
<!-- define an array containing the fields we are interested in -->
<xsl:variable name="fieldArray">
<field>Name</field>
<field>a</field>
<field>b</field>
<field>c</field>
<field>d</field>
<field>e</field>
<field>f</field>
</xsl:variable>
<xsl:param name="fields" select="document('')/*/xsl:variable[#name='fieldArray']/*" />
<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="/*/*"/>
</xsl:template>
<xsl:template match="a">
<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()]/#FURN_ID" />
<!-- <xsl:value-of select="$currNode/*[name() = current()]" /> -->
</xsl:for-each>
<!-- output newline -->
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
I'm using some reference from another page, and can build some simple XSLT to transform XML to CSV, however, I need some guidance in order to solve my main XML issue. In the future after I can get the node value inside the loop, I'd like to sum the total price of every furniture for each room.
Expected final csv result:
Name,a,b,c,d,e,f
House01,40000,100000,160000,150000,190000,350000
Thank you.
Getting the value of an attribute in XML
This XSLT will give the output you specified. See demo.
Updated: I missed the a value in the output.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:text>Name,a,b,c,d,e,f
</xsl:text>
<xsl:apply-templates select="FirstTag/SecondTag/a/Furniture"/>
</xsl:template>
<xsl:template match="Furniture">
<xsl:variable name="pos" select="position()"/>
<xsl:value-of select="../../#Name"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="#FURN_ID"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="../../b/Furniture[position()=$pos]/#FURN_ID"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="../../c/Furniture[position()=$pos]/#FURN_ID"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="../../d/Furniture[position()=$pos]/#FURN_ID"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="../../e/Furniture[position()=$pos]/#FURN_ID"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="../../f/Furniture[position()=$pos]/#FURN_ID"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
The second (final) .csv can be produced as follows:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="text" encoding="UTF-8" />
<xsl:template match="/FirstTag">
<!-- first pass -->
<xsl:variable name="values-rtf">
<xsl:for-each select="SecondTag/*">
<xsl:copy>
<xsl:for-each select="Furniture">
<value>
<xsl:value-of select="#FURN_AMT * #price"/>
</value>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="values" select="exsl:node-set($values-rtf)/*" />
<!-- header -->
<xsl:text>Name,</xsl:text>
<xsl:for-each select="$values">
<xsl:value-of select="name()"/>
<xsl:if test="position()!=last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
<!-- summary -->
<xsl:value-of select="SecondTag/#Name"/>
<xsl:text>,</xsl:text>
<xsl:for-each select="$values">
<xsl:value-of select="sum(value)"/>
<xsl:if test="position()!=last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This assumes you are limited to XSLT 1.0; in XSLT 2.0 this could be done in a single pass.
Note that I am assuming the input XML will contain a single "house" (SecondTag), with a variable number of "rooms" (a, b, c, etc.). Otherwise it's not clear what the header of the .csv should be.
I am not sure if you need to also have the interim .csv - and in any case, the logic required to create it is not clear (why is FUR00013 missing from the output?).

Convert text to xml using XSLT

I have a text file as given below:
value1 value2 value3 value4
I want to convert it as following xml using XSLT
<values>
<value>value1</value>
<value>value2</value>
<value>value3</value>
<value>value4</value>
</values>
Thanks in advance.
Assuming XSLT 2.0,
<xsl:template name="main">
<values>
<xsl:for-each select="tokenize(unparsed-text('input.txt'), '\s+')">
<value><xsl:value-of select="."/></value>
</xsl:for-each>
</values>
</xsl:template>
if you can edit your input such that it contains a root element, and a tab character as a separator, such as below:
<root>value1 value2 value3 value4</root>
then, you can apply the following stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<values>
<xsl:call-template name="tokenizeString">
<xsl:with-param name="list" select="."/>
<xsl:with-param name="delimiter" select="' '"/>
</xsl:call-template>
</values>
</xsl:template>
<xsl:template name="tokenizeString">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<value>
<!-- get everything in front of the first delimiter -->
<xsl:value-of select="substring-before($list,$delimiter)"/>
</value>
<xsl:call-template name="tokenizeString">
<!-- store anything left in another variable -->
<xsl:with-param name="list" select="substring-after($list,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$list = ''">
<xsl:text/>
</xsl:when>
<xsl:otherwise>
<value>
<xsl:value-of select="$list"/>
</value>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
and produce:
<values>
<value>value1</value>
<value>value2</value>
<value>value3</value>
<value>value4</value>
</values>

XSLT 1: XPath - select/extract a number from a string, with default value

I'm transforming a document using the XSL engine provided by out-of-the-box Java 6.
I need to grab a value from an attribute, and use it as an index attribute in a document I am transforming to.
For example, the element:
<myElem bogen="K [1]"/>
the attribute 'bogen' may have values:
"K [1]"
"K [2]"
up to 'n' values.
It may also simply have
"K" to indicate the same as "K [1]"
the corresponding transformation result (from "K [1]" would look like this:
<myTransformedElem index="1"/>
I could use the transformation:
<xsl:value-of select="translate(#bogen,translate(#bogen, '0123456789', ''), '')"/>
if it were just values expected as "K [1]".
But where there is the possibility of having no actual number included, such as "K", this stuffs me up.
The only way I can do it is if I abuse the 'number' function, expect a 'NaN' and translate that. Which seems exceedingly ugly.
For example:
<xsl:value-of select="translate(string(number(translate(#bogen,translate(#bogen, '0123456789', ''), ''))), 'aN', '1')"/>
gets me the correct result each time.
Is there a better, nicer way to do this in XSLT without resorting to custom methods (i.e., import static java methods) ?
For example; were I to need a default value of a multi-character string, I couldn't use the above solution.
thanks in advance.
sean
Here's one way to do this:
<xsl:template name="GetIndex">
<xsl:param name="value" />
<xsl:param name="default" select="1" />
<xsl:variable name="foundIndex"
select="substring-before(
substring-after($value, '['), ']')" />
<xsl:choose>
<xsl:when test="$foundIndex">
<xsl:value-of select="$foundIndex"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$default"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This call produces the value 1:
<xsl:call-template name="GetIndex">
<xsl:with-param name="value" select="'K[1]'" />
</xsl:call-template>
This call produces the value 37:
<xsl:call-template name="GetIndex">
<xsl:with-param name="value" select="'K[37]'" />
</xsl:call-template>
This call produces the value 1:
<xsl:call-template name="GetIndex">
<xsl:with-param name="value" select="'K'" />
</xsl:call-template>
This call produces the value 999:
<xsl:call-template name="GetIndex">
<xsl:with-param name="value" select="'K'" />
<xsl:with-param name="default" select="999" />
</xsl:call-template>
If you find the xsl:choose to be too verbose, you can rewrite GetIndex like this and get the same results:
<xsl:template name="GetIndex">
<xsl:param name="value" />
<xsl:param name="default" select="1" />
<xsl:variable name="foundIndex"
select="substring-before(
substring-after($value, '['), ']')" />
<xsl:value-of select="concat($foundIndex,
substring($default, 1,
not($foundIndex) *
string-length($default)))"/>
</xsl:template>
Personally, I think the version with xsl:choose is clearer, even if it is longer.
with this XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="myElem">
<xsl:choose>
<xsl:when test="translate(#bogen, '0123456789', '') != #bogen">
<myTransformedElem index="{translate(#bogen,translate(#bogen, '0123456789', ''), '')}"/>
</xsl:when>
<xsl:otherwise>
<myTransformedElem index="1"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when applied to this XML:
<root>
<myElem bogen="K"/>
</root>
produces
<myTransformedElem index="1"/>
and when applied to this XML:
<root>
<myElem bogen="K [2]"/>
</root>
produces
<myTransformedElem index="2"/>

Need custom order in flat file from XML using XSLT

I want to create a flat file from an xml using xslt with this output in the order as shown:
NM1*CC*1*Smith*John****34*999999999~
N3*100 Main Street~
From this XML:
<Claim>
<Claimant
lastName="Smith"
firstName="John"
middleName=""
suffixName=""
indentificationCodeQualifier="34"
identificationCode="999999999">
<ClaimantStreetLocation
primary="100 Main Street"
secondary=""/>
</Claimant>
</Claim>
With the XSLT I created I get the output in the reversed desired order as shown below due to the nature of how XSLT works as it traverses the input tree I'm assuming:
N3*100 Main Street~
NM1*CC*1*Smith*John****34*999999999~
What do I need to change/add to get the order I am looking for to the XSLT I've written as shown:
`
<xsl:template match="Claim/Claimant">
<xsl:apply-templates />
<xsl:text>NM1*CC*1*</xsl:text>
<xsl:value-of select="#lastName" />
<xsl:text>*</xsl:text>
<xsl:value-of select="#firstName" />
<xsl:text>*</xsl:text>
<xsl:value-of select="#middleName" />
<xsl:text>*</xsl:text>
<xsl:text>*</xsl:text>
<xsl:value-of select="#suffixName" />
<xsl:text>*</xsl:text>
<xsl:value-of select="#indentificationCodeQualifier" />
<xsl:text>*</xsl:text>
<xsl:value-of select="#identificationCode" />
<xsl:text>~</xsl:text>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="Claim/Claimant/ClaimantStreetLocation">
<xsl:apply-templates />
<xsl:text>N3*</xsl:text>
<xsl:value-of select="#primary" />
<xsl:text>~</xsl:text>
<xsl:text>
</xsl:text>
</xsl:template>`
Is there a way to do this without combining the two tags into one?
Any feedback would be appreciated.
I don't know if it matters, but I'm using xalan-java to process the xslt in code.
If you want to process the parent before the children, you should move apply-templates to the end of your parent template:
<xsl:template match="Claim/Claimant">
<xsl:text>NM1*CC*1*</xsl:text>
<xsl:value-of select="#lastName" />
<xsl:text>*</xsl:text>
<xsl:value-of select="#firstName" />
<xsl:text>*</xsl:text>
<xsl:value-of select="#middleName" />
<xsl:text>*</xsl:text>
<xsl:text>*</xsl:text>
<xsl:value-of select="#suffixName" />
<xsl:text>*</xsl:text>
<xsl:value-of select="#indentificationCodeQualifier" />
<xsl:text>*</xsl:text>
<xsl:value-of select="#identificationCode" />
<xsl:text>~</xsl:text>
<xsl:text>
</xsl:text>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="ClaimantStreetLocation">
<xsl:apply-templates />
<xsl:text>N3*</xsl:text>
<xsl:value-of select="#primary" />
<xsl:text>~</xsl:text>
<xsl:text>
</xsl:text>
</xsl:template>`
Update: What's happening here is:
The first element that is processed is Claim, but there are no templates matching it, so the default template applies, which process the templates for its children nodes.
There, the first child is Claimant, and you do have a template that matches it, so it is applied.
Next, that template is processed in order. But the critical point is that apply-templates omits the attributes in its default select (see
What is the default select of XSLT apply-templates?), so the only matched node there is the ClaimantStreetLocation element.
Given that you have a template that matches ClaimantStreetLocation, it is applied. So, if you want to process first the attributes, you should delay the apply-templates until they are selected, in your case, manually.

xsl variable initialization, method doesn't get called

I'm new to xslt and have a question.
I have a validate class that contains all necessary setters and getters. For example it has such method:
public void setProducer(String producer) {
this.producer = producer;
System.out.println("TEST");
}
When I start my app I see that this method was not called.
I only see in console my test message, when I add in my xsl file such code:
<xsl:value-of name="producerName" />
So where is my mistake or xsl:variable initialized during first use?
I have this code:
<xsl:param name="category-name" />
<xsl:param name="subcategory-name" />
<xsl:param name="producer" />
<xsl:param name="model" />
<xsl:param name="color" />
<xsl:param name="date_of_issue" />
<xsl:param name="price" />
<xsl:param name="not_in_stock" />
<xsl:variable name="validator" select="validation:new()" />
<xsl:template match="/">
<xsl:for-each select="products/category">
<xsl:if test="name() = 'category' and #name=$category-name">
<xsl:apply-templates select="subcategory[#name=$subcategory-name]" />
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="subcategory">
<xsl:apply-templates select="good" />
<xsl:variable name="errorSize"
select="validation:getErrorListSize($validator)" />
<xsl:if test="$errorSize = 0">
<xsl:variable name="producerName"
select="validation:setProducer($validator, $producer)" />
<xsl:variable name="setModel"
select="validation:setModel($validator, $model)" />
<xsl:variable name="setColor"
select="validation:setColor($validator, $color)" />
<xsl:variable name="setDateOfIssue"
select="validation:setDateOfIssue($validator, $date_of_issue)" />
<xsl:if test="$not_in_stock != null">
<xsl:variable name="setPrice"
select="validation:setPrice($validator, $price)" />
</xsl:if>
<xsl:variable name="validationResult"
select="validation:validateAllFields($validator)" />
VALIDATION FINISHED
<xsl:variable name="errors"
select="validation:getErrorListSize($validator)" />
<xsl:value-of select="$errors"/>
</xsl:if>
<xsl:if test="$errorSize != 0">
REDIRECT. ERROR EXISTS
</xsl:if>
</xsl:template>
First point: extension functions in XSLT are highly product-dependent, so you can't really ask us this question without telling us what XSLT processor you are using.
Mixing a declarative language and a procedural language is always going to be tricky (and the best thing would be to avoid it). Certainly in XSLT an optimizing processor will evaluate variables lazily, which means that if a variable isn't referenced then it won't be evaluated.
In Saxon you can generally get away with calling Java methods that have side-effects if you "pretend" to use the result of the method in your result tree; you can achieve that by calling the method from an xsl:value-of instruction.
Some XSLT processors such as Saxon use lazy evaluation, so the variable will not be evaluated untill the it's actually needed.
So, u haven't use such approach, there are other possibilities to call methods.
If variable is useless in your xsl document, use something like this instead:
<xsl:value-of select="validation:setProducer($validator, $producer)"/>

Categories

Resources