I have the following XSL file from the DCM4CHEE DICOM project and I was trying to adjust it slightly. The code I'm actually trying to get working is commented out, but even the variable assignment seems to actually be returning null. The DCM4CHEE logs are throwing Java exceptions with a 'null' seeming to be coming from the XSL template when it compiles it.
<xsl:call-template name="attr">
<xsl:with-param name="tag" select="'00100040'"/>
<xsl:with-param name="vr" select="'CS'"/>
<xsl:variable name="testing" select="string(field[8]/text())" />
<xsl:with-param name="val" select="$testing" />
<!--
<xsl:variable name="sexString" select="string(field[8]/text())" />
<xsl:variable name="sex">
<xsl:choose>
<xsl:when test="$sexString='1'">M</xsl:when>
<xsl:when test="$sexString='2'">F</xsl:when>
<xsl:when test="$sexString='9'">U</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$sexString"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:with-param name="val" select="$sex" /> -->
</xsl:call-template>
The normal XSL is just one simple line:
<xsl:with-param name="val" select="string(field[8]/text())" />
I'm probably doing something very wrong, but can someone explain why I'm not able to assign field[8]/text() to a variable and then pass it to the with-param?
<xsl:call-template name="attr">
<xsl:with-param name="tag" select="'00100040'"/>
<xsl:with-param name="vr" select="'CS'"/>
<xsl:variable name="testing" select="string(field[8]/text())" />
<xsl:with-param name="val" select="$testing" />
</xsl:call-template>
I'm probably doing something very wrong, but can someone explain why
I'm not able to assign field[8]/text() to a variable and then pass it
to the with-param?
Yes, the code is so wrong that the XSLT processor should throw an error message without compiling/executing it.
According to the W3C XSLT 1.0 specification, the only allowed element as child of xsl:call-template is xsl:with-param.
The presented code clearly violates this syntactic rules by placing other elements (xsl:variable) as children of xsl:call-template).
Solution: Move the variables out (before) the xsl:call-template:
<xsl:variable name="testing" select="string(field[8]/text())" />
<xsl:call-template name="attr">
<xsl:with-param name="tag" select="'00100040'"/>
<xsl:with-param name="vr" select="'CS'"/>
<xsl:with-param name="val" select="$testing" />
</xsl:call-template>
The code above is syntactically correct.
Related
I am using Apache FOP to generate a PDF document, and to display a certain value I have to iterate over a number of nodes to determine a total price value, then sum that value. So far I have a function that iterates over an array and then retrieves the intended value, but the issue occurs when I try to sum the results.
<xsl:function name="foo:buildTotalValue">
<xsl:param name="items" />
<xsl:variable name="totals">
<xsl:for-each select="$items/charge">
<xsl:call-template name="getTotalPriceNode">
<xsl:with-param name="itemParam" select="." />
</xsl:call-template>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="sum(exsl:node-set($totals))" />
</xsl:function>
<xsl:template name="getTotalPriceNode">
<xsl:param name="itemParam" />
<xsl:choose>
<xsl:when test="$itemParam/Recurrance = 'OnceOff'">
<xsl:value-of select="$itemParam/TotalValue" />
</xsl:when>
<xsl:when test="$itemParam/Recurrance = 'Monthly'">
<xsl:value-of select="$itemParam/TotalValue * $itemParam/Months"/>
</xsl:when>
<xsl:otherwise><xsl:value-of select="0" /></xsl:otherwise>
</xsl:choose>
</xsl:template>
I'm hoping that when I pass in foo:buildTotalValue with entries like this:
<Charges>
<Charge>
<Recurrance>OnceOff</Recurrance>
<TotalValue>50.00</TotalValue>
</Charge>
<Charge>
<Recurrance>Monthly</Recurrance>
<TotalValue>10.00</TotalValue>
<Months>6</Months>
</Charge>
</Charges>
would return with the value 110.00, but instead I get the error:
Cannot convert string "50.0060.00" to double
I've tried adding a <value> or something in the templates and then using that as a selector for the exsl:node-set function but it doesn't seem to make a difference.
AFAICT, the problem with your function is that it builds a concatenated string of values returned by the called template, instead of a tree of nodes that can be converted into a node-set and summed.
Try changing:
<xsl:for-each select="$items/charge">
<xsl:call-template name="getTotalPriceNode">
<xsl:with-param name="itemParam" select="." />
</xsl:call-template>
</xsl:for-each>
to:
<xsl:for-each select="$items/charge">
<total>
<xsl:call-template name="getTotalPriceNode">
<xsl:with-param name="itemParam" select="." />
</xsl:call-template>
</total>
</xsl:for-each>
and:
<xsl:value-of select="sum(exsl:node-set($totals))" />
to:
<xsl:value-of select="sum(exsl:node-set($totals)/total)" />
Untested, because (see comment to your question).
I ended up using the suggestion from Martin from the comment - the xpath 2+ expression along the line of:
sum(Charge[Recurrance = 'OnceOff']/TotalValue | Charge[Recurrance = 'Monthly']/(TotalValue * Months))
which was able to achieve what I needed without the use of functions / templates / node-set (And in a lot less code)
I have the variable x which is a number. I have a line. ("<name>James</name>") I need to print this sentence number x times. Can I do it in an easy way? without being complex?
If you are using XSLT 2.0 then you can do this ...
<xsl:for-each select="for $i in 1 to $x return $i">
<name>James</name>
</xsl:for-each>
The following is untested...
<xsl:call-template name="show">
<xsl:with-param name="text"><name>James</name></xsl:with-param>
<xsl:with-param name="count">50</xsl:with-param>
</xsl:call-template>
<xsl:template name="show">
<xsl:param name="text"/>
<xsl:param name="count"/>
<xsl:value-of select="$text"/>
<xsl:if test="number($count)>0">
<xsl:call-template name="show">
<xsl:with-param name="text" select="$text"/>
<xsl:with-param name="count" select="number($count)-1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
Updated to have <name> and </name>.
Here is an XmlPlayground of the above working
You could adding the following somewhere in your stylesheet:
<mydata>
<x/><x/><x/><x/> <!-- to print four times -->
</mydata>
then
<xsl:for-each select="document()//mydata/x">
<name>James</name>
</xsl:for-each>
This takes advantage of the ability to include your own data in an XSLT program, and access it through the document function (no argument indicates the stylesheet itself).
Can anyone tell me how to select the URL from this:
<media:content url="http://feedproxy.google.com/~r/TEDTalks_video/~5/aZWq6PY05YE/TimBrown_2009G.mp4" fileSize="57985745" type="video/mp4" />
I want to:
Create a link to this file:
Trim the URL from:
http://feedproxy.google.com/~r/TEDTalks_video/~5/aZWq6PY05YE/TimBrown_2009G.mp4
to:
TimBrown_2009G
and then take: TimBrown_2009G and use it as part of a URL
Selecting the URL. You just need to make sure that you have the correct namespace URI.
<xsl:value-of xmlns:media="http://search.yahoo.com/mrss/"
select="media:content/#url"/>
Trimming down the URL. How to do this best depends on whether you are using XSLT 1 or 2, since the latter has better string handling functions from XPath 2.0.
If you are using XSLT 1, you might want to create a helper template to return the last segment out of a delimited string:
<xsl:template name="last-substring-after">
<xsl:param name="string"/>
<xsl:param name="separator"/>
<xsl:choose>
<xsl:when test="contains($string, $separator)">
<xsl:call-template name="last-substring-after">
<xsl:with-param name="string"
select="substring-after($string, $separator)"/>
<xsl:with-param name="separator"
select="$separator"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Then you can make use of that to extract the last segment of the URL, and proceed to extract the part before the dot. Assuming the URL is in variable url:
<xsl:variable name="name">
<xsl:call-template name="last-substring-after">
<xsl:with-param name="string" select="$url"/>
<xsl:with-param name="separator" select="'/'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="substring-before($name, '.')"/>
The following works for me:
<xsl:variable name="core" select="document('CoreMain_v1.4.0.xsd')" />
<xsl:variable name="AcRec" select="document('AcademicRecord_v1.3.0.xsd')" />
<xsl:template match="xs:element">
<xsl:variable name="prefix" select="substring-before(#type, ':')" />
<xsl:variable name="name" select="substring-after(#type, ':')" />
<xsl:choose>
<xsl:when test="$prefix = 'AcRec'">
<xsl:apply-templates select="$AcRec//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
<xsl:when test="$prefix = 'core'">
<xsl:apply-templates select="$core//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
</xsl:choose>
</xsl:template>
But I use the same logic to handle the lookup of elements in the current or other documents based on the prefix, matching the node name in numerous places within the stylesheet. So, after changing the stylesheet version to 2.0, I tried:
<xsl:template match="xs:element">
<xsl:value-of select="my:lookup(#type)" />
</xsl:template>
<xsl:function name="my:lookup">
<xsl:param name="attribute" />
<!-- parse the attribute for the prefix & name values -->
<xsl:variable name="prefix" select="substring-before($attribute, ':')" />
<xsl:variable name="name" select="substring-after($attribute, ':')" />
<!-- Switch statement based on the prefix value -->
<xsl:choose>
<xsl:when test="$prefix = 'AcRec'">
<xsl:apply-templates select="$AcRec//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
<xsl:when test="$prefix = 'core'">
<xsl:apply-templates select="$core//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
</xsl:choose>
</xsl:function>
In my reading, I have only found examples of functions that return text - none call templates. I have the impression that an xsl:function should always return text/output...
After more investigation, it is entering the my:lookup function and the variables (prefix & name) are getting populated. So it does enter the xsl:choose statement, and the hits the appropriate when test. The issue appears to be with the apply-templates - value-of is displaying the child values; copy-of does as well, which I think is odd (shouldn't the output include the xml element declarations?). Why would there be a difference if code that works in a template declaration is moved to an xsl:function?
It's been a while since I did any serious XSLT, but IIRC your problem is not in the function, but in your template:
<xsl:template match="xs:element">
<xsl:value-of select="my:lookup(#type)" />
</xsl:template>
The value-of statement won't inline the result tree returned by your function. Instead, it's going to try and reduce that result tree down into some kind of string, and inline that instead. This is why you're seeing the child values and not the elements themselves.
To inline the result tree returned by your function, you'll need to use some templates to copy the result tree into place.
So, your main template will need to change to this:
<xsl:template match="xs:element">
<xsl:apply-templates select="my:lookup(#type)" />
</xsl:template>
and you'll need some templates to do the recursive call. A quick google found a good discussion of the identity template that should do what you need.
(Please forgive any syntax errors, as I said, it's been a while ...)
Ok, I'm stumped. I would like test if a parameter sent to an XSLT template contains a period and to print out quotes if it does not. The parameter I would like to test is "value" in the template below. It seems the contains function should work, but for some reason the quotes always get outputted regardless the contents of "value". What am I doing wrong? Thanks
<!-- Add a JSON property -->
<xsl:template name="addProperty">
<xsl:param name="name" />
<xsl:param name="value" />
<xsl:value-of select="$name" />
<xsl:text>:</xsl:text>
<xsl:if test="not(contains($value,'.'))">'</xsl:if>
<xsl:value-of select="$value" />
<xsl:if test="not(contains($value,'.'))">'</xsl:if>
<xsl:text>,</xsl:text>
</xsl:template>
When I call your template, it works fine. How are you calling it? This is what I used:
<xsl:call-template name="addProperty">
<xsl:with-param name="name" select="'abc'"/>
<xsl:with-param name="value" select="'123'"/><!-- quoted number -->
</xsl:call-template>
<xsl:call-template name="addProperty">
<xsl:with-param name="name" select="'abc'"/>
<xsl:with-param name="value" select="123"/><!-- NOT quoted number -->
</xsl:call-template>
<xsl:call-template name="addProperty">
<xsl:with-param name="name" select="'xyz'"/>
<xsl:with-param name="value" select="'456.789'"/><!-- quoted number -->
</xsl:call-template>
<xsl:call-template name="addProperty">
<xsl:with-param name="name" select="'xyz'"/>
<xsl:with-param name="value" select="456.789"/><!-- NOT quoted number -->
</xsl:call-template>
And this is what I got as output:
abc:'123',abc:'123',xyz:456.789,xyz:456.789,
Could you not be passing in the values to the named template that you think you are passing in? What XSLT engine are you using?
A good way to test this is to add something like this to your named template and see what it produces, if you don't have a good debugger handy:
XXX<xsl:value-of select="$value"/>XXX
YYY<xsl:value-of select="contains($value, '.')"/>YYY
ZZZ<xsl:value-of select="not(contains($value, '.'))"/>ZZZ