xsl variable initialization, method doesn't get called - java

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)"/>

Related

XSLT How to iterate a Java List with XALAN

I am trying to iterate over an array using XSLT in order to get a final text looking like this:
array1.firstElement
array1.secondElement
I have this in my XSLT:
<xsl:param name="nombreEmpresaDebe" />
And I put this in my transform method:
transformer.setParameter("nombreEmpresaDebe", listaNombreEmpresaDebe);
renglones/reglon is the name of my node and I am already iterating over it with the following code:
<xsl:for-each select='renglones/renglon'>
<label>
<xsl:value-of select="rubro" />
</label>
<label>
<xsl:value-of select="$nombreEmpresaDebe"/>
</label>
<xsl:for-each>
Now I have to get $nombreEmpresaDebe elements which is a java.util.List but I have no clue about doing it in the same for-each. Does anyone know how to do it?
I need something like this:
<renglon1>
<rubro>rubro1</rubro>
<nombreEmpresaDebe>firstElement</nombreEmpresaDebe>
</renglon1>
<renglon2>
<rubro>rubro2</rubro>
<nombreEmpresaDebe>secondElement</nombreEmpresaDebe>
</renglon2>
You can accomplish this by calling a recursive template. For example
<xsl:call-template name="outputList">
<xsl:with-param name="list" select="$nombreEmpresaDebe"/>
<xsl:with-param name="index" select="0"/>
</xsl:call-template>
Where the template is defined like
<xsl:template name="outputList">
<xsl:param name="list"/>
<xsl:param name="index"/>
<xsl:if test="number($index) < java:size($list)">
<label>
<xsl:value-of select="$nombreEmpresaDebe"/>
</label>
<xsl:call-template name="outputList">
<xsl:with-param name="list" select="$list"/>
<xsl:with-param name="index" select="number($index)+1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
I should note that my answer does not combine this with the iteration of the other nodelist you are handling, but this is the basic List iterating technique that could be combined with that. Also, I realize this is 3 years late, but someone else my find this question and benefit from having an answer.

XSL - using variables in nodepath

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.

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.

How to insert text with XSLT v1.0 instead of using an XSLT v2.0 regex?

I have an xml file which describes (among other things) elements with attribute values that describe fully qualified java class names. I am trying to write an XSLT transformation to modify the class names described in this file, such that (for example) ocurrances of com.example.MyClass will become com.example.MockMyClass.
Here's that example again in the context of a fragment of the original file:
<event type="node-enter">
<action name="MyActionName" class="com.example.MyClass">
<bodyTemplate>
templates/MyTemplate.vm
</bodyTemplate>
</action>
</event>
I want the result to be:
<event type="node-enter">
<action name="MyActionName" class="com.example.MockMyClass">
<bodyTemplate>
templates/MyTemplate.vm
</bodyTemplate>
</action>
</event>
I'm doing this transformation using the Java JAXP API, and had written a lovely XSLT 2.0 compliant regex routine to get the results I want, only to discover that Java 5 doesn't support XSLT 2.0, which is required for regex support.
So my question is, what is the best way to achieve this using the archaic JAXP XSLT 1.0 API? That is, without the use of regular expressions. I looked for similar problems, but the requirement for backreferencing regex groups seems to make this a tricky one. This question is a start, but I need to insert text, within a matching string, rather than just replacing.
For reference, here is my regex (XSLT 2.0) attempt:
<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:template match='/'>
<xsl:analyze-string select='action/#class' regex='([A-Za-z0-9]+[$\.])+([A-Za-z0-9]+)'>
<xsl:matching-substring>
<xsl:value-of select='regex-group(1)'/>
<xsl:text>Mock</xsl:text>
<xsl:value-of select='regex-group(2)'/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select='.'/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
</xsl:stylesheet>
How about the following?
<xsl:template name="classname">
<xsl:param name="class"/>
<xsl:choose>
<xsl:when test="contains($class,'.')">
<xsl:value-of select="concat(substring-before($class,'.'),'.')"/>
<xsl:call-template name="classname">
<xsl:with-param name="class"
select="substring-after($class,'.')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('Mock',$class)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This takes a classname as an input parameter and adds "Mock" after the final ".". You can call it with, for example,
<xsl:call-template name="classname">
<xsl:with-param name="class" select="#class"/>
</xsl:call-template>
(I just gave it a quick try in Firefox, you might find you need to do some tidying up of white space.)
The following seems long, however it uses ready parts (the strRev template is provided by FXSL and needs not be re-written). Also, nearly half of the code is the identity template and passing params to <xsl:call-template>. This is much shorted in XSLT 2.0.
When we have ready smaller parts/functions like the strRev template / reverse() function, then this solution doesn't require writing long and error-prone home-made recursive code.
The basic idea is that the last '.' character in a string is the first '.' character in the reversed string.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pPrepend" select="'Mock'"/>
<xsl:variable name="vRevPrepend">
<xsl:call-template name="strRev">
<xsl:with-param name="pText" select="$pPrepend"/>
</xsl:call-template>
</xsl:variable>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="action/#class">
<xsl:variable name="vRevText">
<xsl:call-template name="strRev"/>
</xsl:variable>
<xsl:variable name="vRevNew" select=
"concat(substring-before($vRevText,'.'), $vRevPrepend,
'.', substring-after($vRevText,'.'))"/>
<xsl:variable name="vNewText">
<xsl:call-template name="strRev">
<xsl:with-param name="pText" select="$vRevNew"/>
</xsl:call-template>
</xsl:variable>
<xsl:attribute name="class">
<xsl:value-of select="$vNewText"/>
</xsl:attribute>
</xsl:template>
<xsl:template name="strRev">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length($pText)">
<xsl:call-template name="strRev">
<xsl:with-param name="pText" select="substring($pText,2)"/>
</xsl:call-template>
<xsl:value-of select="substring($pText,1,1)"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<event type="node-enter">
<action name="MyActionName" class="com.example.MyClass">
<bodyTemplate>
templates/MyTemplate.vm
</bodyTemplate>
</action>
</event>
the wanted, correct result is produced:
<event type="node-enter">
<action name="MyActionName" class="com.example.MockMyClass">
<bodyTemplate>
templates/MyTemplate.vm
</bodyTemplate>
</action>
</event>
II. XSLT 2.0 solution:
Exactly the same algorithm, but in XSLT 2.0 is really short:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes"/>
<xsl:param name="pPrepend" select="'Mock'"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="action/#class">
<xsl:attribute name="class" select=
"my:strRev(concat(substring-before(my:strRev(.),'.'),
my:strRev($pPrepend),'.',
substring-after(my:strRev(.),'.')
)
)
"/>
</xsl:template>
<xsl:function name="my:strRev" as="xs:string">
<xsl:param name="pText" as="xs:string"/>
<xsl:sequence select=
"codepoints-to-string(reverse(string-to-codepoints($pText)))
"/>
</xsl:function>
</xsl:stylesheet>

Categories

Resources