How can I use processing instruction value in a different XSL template? - xslt

I am unable to use processing-instruction attribute values in a different template that is already used to add topic details in the header of the output file.
<task xml:lang="en-us" id="_01FDEB11">
<?ASTDOCREVINFO __docVerName="1.6" __docVerDesc="Description goes here" __docVerUser="Leroy" __docVerDate="Sep 25, 2017 10:44:44 AM"?>
I've created a template to pull values from a processing instruction but the variables don't keep the values in a different template.
<xsl:template match="processing-instruction('ASTDOCREVINFO')">
Version: <xsl:value-of select="substring-before(substring-after(., '__docVerName="'), '"')"/>
Date: <xsl:value-of select="substring-before(substring-after(., '__docVerDate="'), '"')"/>
<xsl:variable name="astVersion" select="substring-before(substring-after(., '__docVerName="'), '"')"/>
<xsl:variable name="astDate" select="substring-before(substring-after(., '__docVerDate="'), '"')"/>
Variable Version: <xsl:value-of select="$astVersion"/>
Variable Date: <xsl:value-of select="$astDate"/>
</xsl:template>
I'm unable to get this to work in a different template already used to pull topic info into the header of the output file.
<xsl:template
match="*[contains(#class, ' topic/topic ')][not(parent::*[contains(#class, ' topic/topic ')])]/*[contains(#class, ' topic/title ')]">
How can I add "processing-instruction('ASTDOCREVINFO')" to this template match?

You cannot pass information from one template match to another as XSLT is side effect free but in your second template you can use XPath to match the processing instruction which is a child of the root element. Something like:
<xsl:template
match="*[contains(#class, ' topic/topic ')][not(parent::*[contains(#class, ' topic/topic ')])]/*[contains(#class, ' topic/title ')]">
<!-- /* => means the root element of the XML document -->
<xsl:variable name="astoriaPI" select="/*/processing-instruction('ASTDOCREVINFO')"/>
<xsl:variable name="astVersion" select="substring-before(substring-after($astoriaPI, '__docVerName="'), '"')"/>
<xsl:variable name="astDate" select="substring-before(substring-after($astoriaPI, '__docVerDate="'), '"')"/>
</xsl:template>

Related

How use more than one value for attribute in XSLT

I want to use more than one value for the attribute in XSLT.
Input:
<contrib contrib-type="author">
<name>
<surname>Khorana</surname>
<given-names>Alok A.</given-names>
</name>
<degrees>MD</degrees>
</contrib>
<contrib contrib-type="author">
<name>
<surname>Holand</surname>
<given-names>Gamak J.</given-names>
</name>
<degrees>PhD</degrees>
</contrib>
The output should be:
<fieldSet name="Author" value="Alok A. Khorana, MD Gamak J. Holand, PhD"/>
Tried code:
<xsl:template name="take-author">
<tps:fieldSet name="Author">
<xsl:attribute name="value">
<xsl:value-of select="concat(descendant::contrib[#contrib-type='author']/name/given-names,descendant::contrib[#contrib-type='author']/name/surname)"/>
</xsl:attribute>
</tps:fieldSet>
</xsl:template>
But I am getting the following error when trying the above code
A sequence of more than one item is not allowed as the first argument
of concat()
The fact you get that message suggests you are using XSLT 2.0, which does not allow a sequence of more than one element as parameter to a function that expects a string argument.
If indeed you are using XSLT 2.0 (or above) you can do this...
<xsl:value-of select="descendant::contrib[#contrib-type='author']/concat(name/given-names, ' ', name/surname, ', ', degrees)" separator=" " />
(Note, the default separator is space anyway, so in this case you could omit the separator attribute, but I left it in just to demonstrate its use).
Also note, in XSLT 1.0, xsl:value-of will only return the value of the first node in a node-set, so you would have to write it like so:
<xsl:for-each select="descendant::contrib[#contrib-type='author']">
<xsl:if test="position() > 1"><xsl:text> </xsl:text></xsl:if>
<xsl:value-of select="concat(name/given-names, ' ', name/surname, ', ', degrees)" />
</xsl:for-each>

How to call template from another template in xslt 1.0

I have two templates like this ...one to create normal package name and another to append "-Non" with the name if it passes certain conditions as shown
<xsl:template match="text()" name="checkForAgency">
<xsl:param name="Name"/>
<xsl:param name="Agency"/>
<xsl:if test="contains($Agency,'AG') ">
<xsl:value-of select="concat($Name,' - Non')"/>
</xsl:if>
</xsl:template>
<xsl:template match="text()" name="createPackageName">
<xsl:param name="Type"/>
<xsl:if test="contains($Type,'NEW')">
<xsl:value-of select="concat('New',' Goal')"/>
</xsl:if>
<xsl:if test="contains($Type,'AMD')">
<xsl:value-of select="concat('Amended',' Goal')"/>
</xsl:if>
<xsl:if test="contains($Type,'REN') or contains($Type,'ARN')">
<xsl:value-of select="concat('Renewal',' Goal')"/>
</xsl:if>
</xsl:template>
Calling these templates from
<xsl:variable name="NonAgencyPackageName">
<xsl:call-template name="createPackageName">
<xsl:with-param name="Type" select="ID"/>
</xsl:call-template>
</xsl:variable>
<xsl:element name="PackageName">
<xsl:call-template name="checkForAgency">
<xsl:with-param name="Name" select="$NonAgencyPackageName"/>
<xsl:with-param name="Agency" select="Output"/>
</xsl:call-template>
</xsl:element>
If ID has 'New', the package name should be New Goal and if the Output has 'AG', then name should be New Goal- Non.
Else name should be new goal. I want it two separate templates only. I am getting empty value now. Please help me in achieving this and how to correct it?
At a guess, the XPaths ID and Output are not selecting what you expect, so all conditionals are failing. Since you don't provide any XML input, it's impossible to be sure.
Ask yourself several questions:
When this code is evaluated, what is the current element?
Does this element have a child element named ID?
What is the string value of that element?
Does it have a child element named Output?
What is its string value?
The PackageName element you generate will be non-empty only if there is an Output child and its string value contains the sequence 'AG'. It will contain the string 'New Goal - Non' only if the Output child contains 'AG' and the ID child contains NEW. No power in creation could make it have the value 'New Goal', since the template CheckForAgency either produces a value ending with ' - Non' or no output at all. (You may wish to read about the choose/when instruction in XSLT.)

xslt assigning variable value to string literal

I have
<xsl:for-each select="ancestor-or-self::*">
<xsl:variable name="expression" select="name()" ></xsl:variable>
</xsl:for-each>
below I have href and i want to set the value this expression variable in href #######
Delete
I also tried with :
Delete
None of them worked.
Can Anybody help me out how to do that?
Basically my task is to find the expression for current node and send the expression for the same in href?
Adding More Info :
<br/><xsl:for-each select="ancestor-or-self::*">
<xsl:variable name="expression" select="name()" />
</xsl:for-each>
<b>Click Me</b>
when above xsl code is transformed it is giving below error:
Variable or parameter 'expression' is undefined.
In XSLT1.0, you could try setting the variable like so:
<xsl:variable name="expression">
<xsl:for-each select="ancestor-or-self::*">
<xsl:text>/</xsl:text>
<xsl:variable name="name" select="name()"/>
<xsl:value-of select="$name"/>
</xsl:for-each>
</xsl:variable>
So, assuming the following XML structure
<As>
<a>
<Bs>
<b>5</b>
</Bs>
</a>
<a>
<Bs>
<b>9</b>
</Bs>
</a>
<a/>
<a>
<Bs>
<b>12</b>
<b>14</b>
<b>15</b>
</Bs>
</a>
</As>
If you were positioned on the b element with the value of 14, then expression would be set to /As/a/Bs/b
However, this does not take into account multiple nodes with the same name, and so would not be sufficient if you wanted accurate XPath to select the node.
Instead, you could try the following:
<xsl:variable name="expression">
<xsl:for-each select="ancestor-or-self::*">
<xsl:text>/</xsl:text>
<xsl:variable name="name" select="name()"/>
<xsl:value-of select="$name"/>
<xsl:text>[</xsl:text>
<xsl:value-of select="count(preceding-sibling::*[name() = $name]) + 1"/>
<xsl:text>]</xsl:text>
</xsl:for-each>
</xsl:variable>
This would return /As[1]/a[4]/Bs[1]/b[2], which may be what you want.
Very simply, variables are scoped in XSL and exist only within the containing tag. Thus the variable named expression exists only within the for-each block. Also, variables can only be set once. Attempting to set a variable value a second time has no effect.
Therfore you have to declare the variable at or above the level where you want to use it, and put all the code to generate the value inside the variable declaration. If you can use XSLT2, the following will do what you want:
string-join(for $n in ancestor-or-self::* return name($n), '/')
I know it can also be done in XSLT 1 with a recursive template, but I don't have an example handy.

How do I render a comma delimited list using xsl:for-each

I am rendering a list of tickers to html via xslt and I would like for the list to be comma deliimited. Assuming I was going to use xsl:for-each...
<xsl:for-each select="/Tickers/Ticker">
<xsl:value-of select="TickerSymbol"/>,
</xsl:for-each>
What is the best way to get rid of the trailing comma? Is there something better than xsl:for-each?
<xsl:for-each select="/Tickers/Ticker">
<xsl:if test="position() > 1">, </xsl:if>
<xsl:value-of select="TickerSymbol"/>
</xsl:for-each>
In XSLT 2.0 you could do it (without a for-each) using the string-join function:
<xsl:value-of select="string-join(/Tickers/Ticker, ',')"/>
In XSLT 1.0, another alternative to using xsl:for-each would be to use xsl:apply-templates
<xsl:template match="/">
<!-- Output first element without a preceding comma -->
<xsl:apply-templates select="/Tickers/Ticker[position()=1]" />
<!-- Output subsequent elements with a preceding comma -->
<xsl:apply-templates select="/Tickers/Ticker[position()>1]">
<xsl:with-param name="separator">,</xsl:with-param>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Ticker">
<xsl:param name="separator" />
<xsl:value-of select="$separator" /><xsl:value-of select="TickerSymbol" />
</xsl:template>
I know you said xsl 2.0 is not an option and it has been a long time since the question was asked, but for all those searching for a posibility to do what you wanted to achieve:
There is an easier way in xsl 2.0 or higher
<xsl:value-of separator=", " select="/Tickers/Ticker/TickerSymbol" />
This will read your /Tickers/Ticker elements and insert ', ' as separator where needed
If there is an easier way to do this I am looking forward for advice
Regards Kevin

Using xsl:variable in a xsl:foreach select statement

I'm trying to iterate through an xml document using xsl:foreach but I need the select=" " to be dynamic so I'm using a variable as the source. Here's what I've tried:
...
<xsl:template name="SetDataPath">
<xsl:param name="Type" />
<xsl:variable name="Path_1">/Rating/Path1/*</xsl:variable>
<xsl:variable name="Path_2">/Rating/Path2/*</xsl:variable>
<xsl:if test="$Type='1'">
<xsl:value-of select="$Path_1"/>
</xsl:if>
<xsl:if test="$Type='2'">
<xsl:value-of select="$Path_2"/>
</xsl:if>
<xsl:template>
...
<!-- Set Data Path according to Type -->
<xsl:variable name="DataPath">
<xsl:call-template name="SetDataPath">
<xsl:with-param name="Type" select="/Rating/Type" />
</xsl:call-template>
</xsl:variable>
...
<xsl:for-each select="$DataPath">
...
The foreach threw an error stating: "XslTransformException - To use a result tree fragment in a path expression, first convert it to a node-set using the msxsl:node-set() function."
When I use the msxsl:node-set() function though, my results are blank.
I'm aware that I'm setting $DataPath to a string, but shouldn't the node-set() function be creating a node set from it? Am I missing something? When I don't use a variable:
<xsl:for-each select="/Rating/Path1/*">
I get the proper results.
Here's the XML data file I'm using:
<Rating>
<Type>1</Type>
<Path1>
<sarah>
<dob>1-3-86</dob>
<user>Sarah</user>
</sarah>
<joe>
<dob>11-12-85</dob>
<user>Joe</user>
</joe>
</Path1>
<Path2>
<jeff>
<dob>11-3-84</dob>
<user>Jeff</user>
</jeff>
<shawn>
<dob>3-5-81</dob>
<user>Shawn</user>
</shawn>
</Path2>
</Rating>
My question is simple, how do you run a foreach on 2 different paths?
Try this:
<xsl:for-each select="/Rating[Type='1']/Path1/*
|
/Rating[Type='2']/Path2/*">
Standard XSLT 1.0 does not support dynamic evaluation of xpaths. However, you can achieve your desired result by restructuring your solution to invoke a named template, passing the node set you want to process as a parameter:
<xsl:variable name="Type" select="/Rating/Type"/>
<xsl:choose>
<xsl:when test="$Type='1'">
<xsl:call-template name="DoStuff">
<xsl:with-param name="Input" select="/Rating/Path1/*"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$Type='2'">
<xsl:call-template name="DoStuff">
<xsl:with-param name="Input" select="/Rating/Path2/*"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
...
<xsl:template name="DoStuff">
<xsl:param name="Input"/>
<xsl:for-each select="$Input">
<!-- Do stuff with input -->
</xsl:for-each>
</xsl:template>
The node-set() function you mention can convert result tree fragments into node-sets, that's correct. But: Your XSLT does not produce a result tree fragment.
Your template SetDataPath produces a string, which is then stored into your variable $DataPath. When you do <xsl:for-each select="$DataPath">, the XSLT processor chokes on the fact that DataPath does not contain a node-set, but a string.
Your entire stylesheet seems to be revolve around the idea of dynamically selecting/evaluating XPath expressions. Drop that thought, it is neither possible nor necessary.
Show your XML input and specify the transformation your want to do and I can try to show you a way to do it.