Using a variable as part of a XPath selection - variables

I'm looking to use a variable as part of an XPath expression.
My problem might be the msxsl node-set function... not sure. But don't let that cloud your judgement... read on...
I'm using a .NET function to load up the content file, which is passed in via bespoke XML content. The #file results in an XML file.
The bespoke XML the sits on the page looks like :
<control name="import" file="information.xml" node="r:container/r:group[#id='set01']/r:item[1]" />
The XSL looks like :
<xsl:variable name="document">
<xsl:copy-of select="ext:getIncludedContent(#file)" />
</xsl:variable>
I'm then translating this to a node-set so I can query the document
<xsl:variable name="preset-xml" select="msxsl:node-set($document)" />
The source file I am loading in looks like :
<container>
<group id="set01">
<item>value01</item>
<item>value02</item>
<item>value03</item>
</group>
<group id="set02">
<item>value04</item>
<item>value05</item>
</group>
</container>
It works up until this point. I can see the source file being brought thru and output as XML.
My problem comes in when I am trying to query the source file with an XPath expression fed in from the content.
I've tried :
<xsl:value-of select="$preset-xml/#node" />
Clearly that doesn't work as it looks for #node as a direct child of the loaded in XML.
I've tried :
<xsl:variable name="node" select="#node" />
<xsl:value-of select="$preset-xml/$node" />
But it doesn't like the second variable.
I've tried concat($preset-xml,'/',$node), but that still draws out the entire document in the result.
The only way I can get this working at the moment is to write out the full expression in the template :
<xsl:value-of select="$preset-xml/r:container/r:group[#id='set01']/r:item[1]" />
Which correctly brings thru value01
But that is then a hard coded solution.
I want to develop a solution which can be manipulated from the content to suit any sort of imported content, with the parameters declared in the content file.
p.s. I'm using XSLT 1.0 because the tech admin won't change the Microsoft parser to support later versions of XSL, so any solution would need to be written with that in mind.

There is no feature similar to reflection in XSLT. MS XSLT engine, in particular, compiles XPath queries when the XSLT was loaded, not when it was applied to your XML document. There is a reason to this: it separates the code (XSLT) from the data (input XML).
What are you trying to achieve by mixing up code and data; are you sure you really need to execute an arbitrary XPath expression from the input XML? Think, for example, what if the user will pass in the #name equal to e.g. //*, and then you'll send to the output the content of the entire input XML document, which might contain some sensitive data.
If you really need to do the thing you described, you may write your own XSLT extension object, which will accept the document root and the XPath query and will return the latter applied to the former. Then you'll be able to call it like that:
<xsl:value-of select="myExtensionNs:apply-xpath($preset-xml, #node)"/>
As for your original attempt with concat($preset-xml,'/',$node), concat is the string concatenating function. You supply some arguments to it, and it returns the concatenation of its string representations. So, concat($preset-xml,'/',$node) will return the string value of $preset-xml, concatenated with /, concatenated with the string value of $node. If you were trying to write a needed XPath query, concat('$preset-xml','/',$node) would seem more logical (note the quotes); still, there is no way to evaluate the resulted string (e.g. $preset-xml/r:container/r:group[#id='set01']/r:item[1]) to the value of corresponging XPath query; it could only be sent to output.

One possible trick is to call an parametrized xsl:template which carries out the crucial select comparison part and evaluate it's output as string.
I had a similar problem (count all nodes of an arbitrary attribute value) which I solved this way (btw: $sd = 'document($filename)' so I load a secondary xml files content):
<xsl:template match="/">
<xsl:variable name="string_cntsuccess">
<xsl:for-each select="./Testcase">
<xsl:variable name="tcname" select="#location"/>
<xsl:for-each select="$sd//TestCase[#state='Passed']">
<xsl:call-template name="producedots">
<xsl:with-param name="name" select="$tcname"/>
</xsl:call-template>
</xsl:for-each>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="cntsuccess" select="string-length($string_cntsuccess)"/>
</xsl:template>
<xsl:template name="producedots">
<xsl:param name="name"/>
<xsl:variable name="ename" select="#name"/>
<xsl:if test="$name = $ename">
<xsl:text>.</xsl:text>
</xsl:if>
</xsl:template>

Related

Dynamically call parameters using concat(), select element name

I want to dynamically call parameters in a template based on another paramaters value, something like:
Let's say I have a list of Values that are dermined in several ways:
<xsl:param name="One"/>
<xsl:param name="Two"/>
<xsl:param name="Three"/>
from which I'd like to create matching elements from such as:
<One>1111</One>
<Two>2222</Two>
<Three>3333</Three>
Instead of creating all of the elements individually I like to create a seperate template or function to do that.
I was attempting something like:
<xsl:template name="AddElement">
<xsl:param name="Name" select="/foo/bar/text()"/>
...
<xsl:variable name="Value" select="concat('$', $Name)"/>
<xsl:element name="$ElementName">
<xsl:value-of select="$Value"/>
</xsl:element>
...
</xsl:template>
I want to avoid doing that multiple times the same way:
<xsl:if test="string-length($One) != 0">
<One>
<xsl:value-of select="$One"/>
</One>
</xsl:when>
Ideally it could just be
<xsl:call-template name="AddElement">
<xsl:with-param name="ElementName">One</xsl:with-param>
</xsl:call-template>
I think Change following Code:-
<xsl:element name="{$ElementName}">
No, you can't construct variable references (or other XPath expressions) dynamically as strings and then evaluate them. XSLT isn't a macro language.
In XSLT 3.0 you could put the data in a map and select dynamically from the map. I tried to put together an example for you, but I really couldn't get to the bottom of what your hypothetical code was trying to achieve.

Using Xsl <fo:external-graphic> to hand over data

Is there a way in XSL 1.0 to hand over variables or parameters using XSL fo:external-graphic like I would do when I'm using xsl:call-template
I know how I could work around the problem but I just wanted to know if there is a way I am not seeing.
If your SVGs are small enough, you could use fo:instream-foreign-object. (If the SVGs are very large, the size of the XSL-FO file might become a problem.)
main.xsl:
<xsl:import href="svg/svg_graphic.xsl" />
<xsl:template match="some/context">
<fo:instream-foreign-object>
<xsl:call-template name="make-svg">
<xsl:with-param name="param-a" select="..." />
</call-template>
</fo:instream-foreign-object>
</xsl:template>
svg_graphic.xsl:
<xsl:template name="make-svg">
<xsl:param name="param-a" select="..." />
<svg:svg>
...
</svg:svg>
</xsl:template>
<fo:external-graphic src="svg/svg_graphic.xsl" /> is not going to work. Inside your XSLT stylesheet, the elements in the XSL-FO namespace are just literal result elements. They are copied to the result tree, and they are not otherwise acted on by the XSLT processor. XSLT-specific attributes on literal result elements (where the attribute in the XSLT namespace and is defined as meaning something when used on literal result elements) are acted on by the XSLT processor. Attribute value templates ({...}) in attribute values of literal attributes (and some XSLT-defined attributes) are acted on by the XSLT processor.
There is no XSLT 1.0 way to get the XSLT processor to run another stylesheet based on the value of an XSL-FO-defined attribute.
There's also no XSLT 1.0 way to generate multiple result documents from one run of a stylesheet. Your XSLT processor probably has a processor-specific (or EXSLT) way to do that. The extension, if it exists, might not let you generate part of the XSL-FO result document, generate an SVG result document, and then go back to generating more of the XSL-FO document.

XSLT Function to pass a node argument concatenate a string then return element value

I'm fairly new to xslt but I searched through the topics to see if there is a match for mine but since I can't really understand most of the language I can't find them useful.
Here's what I'm trying to achieve. Basically, I want a custom function that can concatenate a string to a node path and return the element value without a template match. Maybe you can provide some example of function code that can do that and I can review it.
Basically what I want to do is pass the </root/AccountDetails/plantype> node to a function like a <xsl:value-of select=”testlang(/root/AccountDetails/plantype)”/>
In the function, it will do the language test and return the element value of the node (Please note there are a lot of English and French nodes nested in different levels) that why I'm passing the whole path. I'm limited to XSLT 1.0 and can't change the XML passed
XML Sample
<root>
<AccountDetails>
<plantype>RRSP</plantype>
<plantype_fr>CELI</plantype_fr>
</AccountDetails>
<ClientDetails>
<salutation>Mr.</salutation>
<salutation_fr>Monseiur</salutation_fr>
</ClientDetails>
<lang>fr</lang>
</root>
Possible function code
<xsl:function name=”testlang”>
<xsl:param name=”str”/>
<xsl:choose>
<xsl:when test=”/root/lang[.=’fr’]>
<xsl:value-of select=”concat($str,’_fr’)”/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select=”$str”/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>

XSLT Attribute name needs to be separated

I am working backwards to create an XML based on a client's desired outcome. I have an attribute in my XSL that I want the end result to look like this
<AlternateId idType="ADID">XYZ</AlternateId>
I have it incorrectly as
<xsl:attribute name="idType">ADID<xsl:value-of select="add:XYZ"/></xsl:attribute>
This, of course, groups ADID and XYZ together.
How do I get them separated and looking like what I want them to look like?
The value-of needs to go outside the attribute as you want it to be element content rather than part of the attribute value.
<xsl:element name="AlternateId">
<xsl:attribute name="idType">ADID</xsl:attribute>
<xsl:value-of select="add:XYZ"/>
</xsl:element>
But if the attribute name is a fixed string you might as well use a literal result element instead:
<AlternateId idType="ADID">
<xsl:value-of select="add:XYZ"/>
</AlternateId>

Call XSLT template and save all output to variable

I want to call and template and save the output to a variable. I want to save ALL of the output including HTML markup, but that's not what happens.
For example, take this simple XSLT:
<xsl:call-template name="demonstration">
<xsl:template name="demonstration">
<p>Just a test</p>
</xsl:template>
This simple template will output <p>Just a test</p> to the HTML page. If I view the page source, I see that (of course). Now take this example using the same template, but instead of just outputting to HTML I want to save the output to a variable:
<xsl:variable name="test">
<xsl:call-template name="demonstration">
</xsl:variable>
<xsl:value-of select="$test"/>
<xsl:template name="demonstration">
<p>Just a test</p>
</xsl:template>
Viewing the variable shows that the only output now is Just a test.
Where did the HTML markup go? I'd like to be able to save the output of a call-template to a variable, but I need the HTML markup too.
Is there a way of avoiding the loss of HTML tags when calling a template like this? Is there a different way of writing my template? Maybe a setting I'm missing? I've tried disable-escape-encoding, but that makes no difference (in Safari at least).
I prefer to use the template for both needs: I'd like to be able to just call it and have the output in an HTML page for viewing. I'd also like to be able to wrap the call in a variable, but it's important that both call methods including all of the HTML tags/markup as specificed in the template.
EDIT:
I've tried both of the posted answers so far, but copy-of gives me the same result as value-of. Actually, I'm not using value-of, I was only showing how to duplicate the problem. Here is a more thorough explanation of what I'm trying to do.
Have a stylesheet that is used to transform a rather large XML received from a REST response. The stylesheet has its output method set for html.
One of the templates in this stylesheet does a lot of decision-making on how to display as many as 4 rows of data in a table. There are 2 columns, one is for a label, the other is for data.
The decision-making that's done includes what the text of the label is and what class - sometimes the label is green, sometimes its red, etc. The data column can contain text and/or numbers. Some of the text might be bold, some might be colorized. There are a bunch of prerequisites that determine these attributes.
If I were displaying a details of a single item, I'd be done with this template, but one of these items can have a multitude of attributes chosen. For example, there might be a size and a color. Depending on which size or color a user chooses, the price can be different, the particular item might be out of stock, if it has a different price then it will have a different savings to the user. Different items might have free shipping, or might be available for preorder only. There are LOTS of attributes.
And so I have a template that anylizes all of these prerequisites and constructs a <tr><td></td><td></td></tr> filled with firly simple text and data derived by some not-so-simple logic.
I've modularized this template so that I can call it for any one of the items. It's paramertized so I can specify the label text, data, class, and a couple of other things.
When the web page loads, the default/main item is displayed along with general info - like the range of prices and a range of savings, etc.
But when a user selects a color or size, these table rows need to be updated with the correct data. I've already processed the data from the XML and it would be very costly to make another server request, so what I have done is created a JSON string array containing all of the data for all of the different kinds of items. I save this JSON in the value attribute of a hidden input control. There's other ways of doing it, yes, but this works and it seems manageable to me.
Since the logic to create these table rows is in the stylesheet on the server, I perform all this logic on all items and then pass the calculated strings to the client. Here is an example of what this looks like:
<input id="hfData" type="hidden" value="[
{"ProductID": "00001", "Color": "Beige", "Size": "14"},
{"ProductID": "00002", "Color": "Black", "Size": "14"},
{"ProductID": "00003", "Color": "Blue", "Size": "10"},
{"ProductID": "00004", "Color": "Pink", "Size": "10"},
{"ProductID": "00005", "Color": "Yellow", "Size": "10"}
]"
/>
I then have a small JQuery script that is triggered any time the user changes a dropdownlist selection, or changes an attribute. The script parses the above JSON and determines what the currently configured ProductID is.
Note: Since the value attribute of the input control would not be valid if I had double quotes scattered through it like this, they are actually all ". I'm showing double quotes here to make it easy to look at.
Once the ProductID is determined, the page gets updated with lots of different details as described earlier in this edit. These details come from yet another JSON object I've created - again, since all of the product details are known in the XSLT Stylesheet, that is where I am creating the JSON string array.
<input id="hfDetails" type="hidden" value="[
{"ProductID": "00001", "ListPrice": "<tr class="collapsed"><td class="datalabel">List Price:</td><td class="datainfo"></td></tr>", "YourPrice": "<tr><td class="datalabel">Price:</td><td class="datainfo">$23.99 & is elegible for <span class="freeshipping">FREE</span> shipping Details</td></tr>"},
... etc, etc, etc ...
]"
/>
As you can see, the hidden input just above has a value attribute containing a JSON object which contains HTML markup. I've seen this work, but in order to get JSON to work correctly I have to escape all of the tags. Ampersands have to be &, then there's <, > and also escaping all quotes with a backslash. I've coded it all that way and it works - albeit nasty to look at, it works and it prevents me from having to make round trips to the server - it prevents me from putting all the logic to create these strings on the client side.
None of this has anything to do with the problem I'm having, but I'm hoping to get rid of all the comments from JSON and XSL/XML professors who challenge me for having JSON and XSLT (or HTML) in a single sentence...
Now, I hope I can show (very simply) the precise problem I am having. To start, it has really VERY LITTLE to do with JSON. It has virtually nothing to do with using xsl:value-of over xsl:copy-of.
Here is where I create a hidden input field with the value attribute containing a JSON string:
<input>
<xsl:attribute name="id">
<xsl:value-of select="$id"/>
</xsl:attribute>
<xsl:attribute name="type">
<xsl:text>hidden</xsl:text>
</xsl:attribute>
<xsl:attribute name="runat">
<xsl:text>server</xsl:text>
</xsl:attribute>
<xsl:attribute name="value">
<xsl:text>[</xsl:text>
<xsl:for-each select="Items/Item">
<xsl:call-template name="JSONItemDetails">
<xsl:with-param name="offerNode" select="Offers"/>
<xsl:with-param name="lastitem" select="position() = last()"/>
</xsl:call-template>
</xsl:for-each>
<xsl:text>]</xsl:text>
</xsl:attribute>
</input>
<xsl:template name="JSONItemDetails">
<xsl:param name="offerNode" select="."/>
<xsl:param name="attributesNode" select="ItemAttributes"/>
<xsl:param name="listprice" select="0"/>
<xsl:param name="lastitem" select="false()"/>
<!-- Product ID -->
<xsl:text>{</xsl:text>
<xsl:text>"ProductID": </xsl:text>
<xsl:text>"</xsl:text>
<xsl:value-of select="./ProductID"/>
<xsl:text>",</xsl:text>
<xsl:for-each select="msxml:node-set($offerNode)">
<!-- Title -->
<xsl:text>"Title": </xsl:text>
<xsl:text>"</xsl:text>
<xsl:call-template name="escapeQuote">
<xsl:with-param name="pText">
<xsl:call-template name="title">
<xsl:with-param name="node" select="$attributesNode" />
</xsl:call-template>
</xsl:with-param>
</xsl:call-template>
<xsl:text>",</xsl:text>
<!-- List Price -->
<xsl:text>"ListPrice": </xsl:text>
<xsl:text>"</xsl:text>
<xsl:call-template name="escapeQuote">
<xsl:with-param name="pText">
<xsl:call-template name="DataTableRow">
<xsl:with-param name="label" select="'List Price:'" />
<xsl:with-param name="data" select="./Price/FormattedPrice" />
<xsl:with-param name="dataid" select="'listprice'" />
</xsl:call-template>
</xsl:with-param>
</xsl:call-template>
<xsl:text>"</xsl:text>
</xsl:for-each>
<xsl:text>}</xsl:text>
<xsl:if test="$lastitem != true()">
<xsl:text>,</xsl:text>
</xsl:if>
The DataTableRow template does a lot of things and which mostly provide consistency, but also clean up all the stray HTML tags used to create rows and columns. Here is that template.
<xsl:template name="DataTableRow">
<xsl:param name="label" select="''"/>
<xsl:param name="data" select="''"/>
<xsl:param name="dataid" select="''"/>
<xsl:param name="concat" select="''"/>
<xsl:param name="class" select="''"/>
<tr>
<xsl:choose>
<xsl:when test="$data = not(string(.))">
<xsl:attribute name="style">
<xsl:text>display:none</xsl:text>
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="style">
<xsl:text>display:block</xsl:text>
</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
<!-- Label Column -->
<td>
<div>
<xsl:choose>
<xsl:when test="$class = 'bigmaroon'">
<xsl:attribute name="class">
<xsl:text>datalabel maroon</xsl:text>
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="class">
<xsl:text>datalabel</xsl:text>
</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="$label"/>
</div>
</td>
<!-- Data Column -->
<td class="datainfo">
<xsl:attribute name="id">
<xsl:value-of select="$dataid"/>
</xsl:attribute>
<xsl:choose>
<xsl:when test="$class = 'strike'">
<strike>
<xsl:value-of select="$data" />
</strike>
<xsl:value-of select="$concat"/>
</xsl:when>
<xsl:when test="$class = 'maroon'">
<span class="maroon">
<xsl:value-of select="$data" />
</span>
<xsl:value-of select="$concat"/>
</xsl:when>
<xsl:when test="$class = 'bigmaroon'">
<span class="bigmaroon">
<xsl:value-of select="$data" />
</span>
<xsl:value-of select="$concat"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$data" />
<xsl:value-of select="$concat" />
</xsl:otherwise>
</xsl:choose>
</td>
</tr>
The DataTableRow template has a bit of logic that would be messy to do everytime I needed to write one of these rows. First off, All of these rows have to exist in the HTML. If there is no data, then they are set with a style of display:none. This is important since I won't be able to fill them in with data from my JQuery script unless there is a valid selector...
Problem is, after all of this, DataTableRow works just fine when it is called directly like this:
<xsl:call-template name="DataTableRow"/>
In the above call, the HTML tags are all output to my web page. They look fine, their classes and styles are correct. I can call it like this from anywhere in my template and it works just fine. I'm sure there is a bug here or there, and I might see the light on a better/more efficient way of coding this, but it basically works and works well for HTML.
THE PROBLEM: I am unable to get any of the HTML tags when I build my hidden input fields above. The only value that gets stored in my JSON strings are the innerhtml. DataTableRow is called from JSONItemDetails template and instead of getting a result of:
<tr><td>Some Label</td><td>Some Data</td></tr>
I get a result of
Some LabelSome Data
This was an awfult lot of information to ask such a simple question, but I've been getting responses that seem to hint I'm not doing things right if I'm dealing with JSON within XSLT/HTML.
Can anyone help with my problem? Why are the HTML tags being stripped from the output of DataTableRow when I call it in JSONItemDetails to create my JSON string?
EDIT #2:
I've got several things going on with my code that have been causing this problem of HTML tags being stripped. Am finally getting a handle on what's been causing the problem and now I've been trying to figure out how to resolve the issue. Here are additional notes.
Am able to confirm, xsl:value-of is stripping my HTML tags from the output of a template call. xsl:copy-of shows me the tags I expect. This is a big help for me in understanding where the problem is at, but it also allows me to identify other problems.
xsl-copy cannot be used to output under the tag's value= attribute. I understand why, though I'm clueless of how to handle this problem if I proceed in the direction I was going.
Even if I figure out a solution to #1, I have to figure out how to escape the double quotes in my HTML markup. Ie: A double quote isn't valid within the value= attribute. I understand that an apostrophe would work, but these double quotes are created from using xsl:attribute under certain tags to specify a class or occassional style. I have a template that escapes double quotes with a backslash, but calling it also strips out my HTML tags and I can't see why - so I don't know how to fix it. I'll post the code for this template below.
If I had a workaround for the above 2 problems I could proceed in the direction I've been going, but I'm open to hearing advice on why I shouldn't be mixing data with display in my JSON.
I mix the two because I don't like the idea of putting the display logic in my JQuery script. I'd like my script to remain ignorant of this logic. It's simple in JQuery using these JSON objects to drill down to a ProductID and replace a table row like this:
var details = $.parseJSON($("#[id*='hfDetails']").val());
var filteredDetails = $.grep(details, function (n) {
return n.ProductID == ProductID;
});
if (filteredDetails.length > 0) {
var product = filteredDetails[0];
$("div#title").html(product.Title);
$("td#listprice").parent().html(product.ListPrice);
$("td#price").parent().html(product.Price);
$("td#yousave").parent().html(product.YouSave);
}
If I remove display from my JSON strings as Dimitre is advising, all the sudden I have to put a lot of logic in my jquery script to not only provide <tr> and <td> formatting (classes), but also logic to wrap the actual data, like <strike, <strong>, color specifications and font sizes or even whether a particular table row is a display:block or a display:none. I absolutely do not want to code any of that logic in JQuery script on the client.
It's a very simple xsl:for-each in my XSLT template to create these strings on the server and to stuff the results in my JSON object so that I can use the above script. Admittingly, it's ugly data to look at.
Another note, no matter if I seperate display from data or not, the current XSLT template I use to process my XML will still be needed in the event there is only one item with a single possibility of display. In the event there is only a single product, JSON doesn't come into the picture since there are no controls displayed on the page to change the product's attributes (color, size, etc).
I definitely want to code this "right", so I certainly appreciate hearing from experience. I just don't think anyone can effectively advise me for or against the end result without knowing all of the considerations made that got me there
Here is (was) the template I used to escape double quotes:
<!--
Escape quotes with \ for JSON strings
-->
<xsl:template name="escapeQuote">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length($pText) >0">
<xsl:copy-of select="substring-before(concat($pText, '"'), '"')"/>
<xsl:if test="contains($pText, '"')">
<xsl:text>\"</xsl:text>
<xsl:call-template name="escapeQuote">
<xsl:with-param name="pText" select="substring-after($pText, '"')"/>
</xsl:call-template>
</xsl:if>
</xsl:if>
</xsl:template>
This template strips HTML tags from the input string. I call it like this:
<xsl:variable name="output3">
<xsl:call-template name="escapeQuote">
<xsl:with-param name="pText">
<xsl:call-template name="DataTableRow">
... with-params ...
</xsl:call-template>
</xsl:with-param>
</xsl:call-template>
</xsl:variable>
Immediately doing:
<xsl:copy-of select="$output3"/>
Shows that HTML tags no longer exist and therefore, no double quotes exist either. However, doing this reveals all of my HTML tags:
<xsl:variable name="output3">
<xsl:call-template name="DataTableRow">
... with-params ...
</xsl:call-template>
</xsl:variable>
<xsl:copy-of select="$output3"/>
Thanks for reading all of this - and btw, I see other posts where XSL code is formatted nicely and colorized. It's a LOT easier to read, but I can't get it to work with the code I'm posting. Everything is black text and left-justified. I've tried correcting that, but it's not working for me.
EDIT #3:
Dimitre commented that a quote could be used literally in the value attribute of a hidden input field, so I did some experimenting with that in mind. There seems to be a gotcha everywhere I look.
<input value='something containing " literally'/>
To get my value= contents wrapped by apostrophes instead of double quotes, I tried creating my hidden <input> like this:
<input>
<xsl:attribute name="id">
<xsl:value-of select="$id"/>
</xsl:attribute>
<xsl:attribute name="type">
<xsl:text>hidden</xsl:text>
</xsl:attribute>
<xsl:attribute name="runat">
<xsl:text>server</xsl:text>
</xsl:attribute>
<xsl:text> value='[</xsl:text>
<xsl:copy-of select="$output"/>
<xsl:text>]'</xsl:text>
</input>
This doesn't work because the <input...> tag gets rendered with the value= attribute (and it's contents) AFTER the closing </input>.
So I removed the xsl:attribute elements and formed it with xsl:text. Unfortunately, using a < inside a xsl:text isn't valid and generates an error. So I changed the < to < as well as the corresponding >. This didn't generate any errors, but causes the entire <input> and its contents to be displayed as a string when the page is rendered. (I'm viewing the page in Firefox).
I haven't tried coding this with CDATA because I'm pretty sure that's not going to work either and I'll get the same results as the latter - everything being displayed as a string.
shrug
EDIT #4:
OJay recommended dropping the idea of putting my JSON in the value= attribute of a hidden field. I've considered this all along, but wasn't sure how much I'd have to change my code. When he showed examples of how little I'd have to change, I decided to go for it.
Unfortunately, there's another gotcha.
<script type="text/javascript">
var hfDetails = [{ProductID: '000001',Title: '<h3>American Apparel Sheer Jersey Chemise X-Small-Asphalt</h3>',Condition: 'New',ListPrice: '<tr style="display:block">
<td>
<div class="datalabel">0List Price:</div>
</td>
<td class="datainfo" id="listprice">$24.99</td>
</tr>',Price: '<tr style="display:block">
<td>
<div class="datalabel maroon">Sale:</div>
</td>
<td class="datainfo" id="price"><span class="bigmaroon">$15.49</span></td>
</tr>',YouSave: '<tr style="display:block">
<td>
<div class="datalabel">You Save:</div>
</td>
<td class="datainfo" id="yousave"><span class="maroon">$9.50 (38%)</span></td>
</tr>'},
....
]
</script>
This is a paste from the rendered web page. I'm getting an error of "Unterminated string literal" and it's pointing to the apostrophe right before the very first <tr> - on the second line.
I'm assuming this is because the string has whitespace spreading it over several lines. The whitespace is as-is from making the call-template. I'm not sure how to disable this whitespacing or if that'll even make the difference. Am going to do some googling on this, but does anyone know if there is a way to turn whitespace off for a call-template?
Try <xsl:copy-of select="$test"> instead of <xsl:value-of ... />
Also note, <xsl:value-of /> (and <xsl:copy-of /> for that matter), should be inside a template, not at the root level of the <xsl:stylesheet >....</xsl:stylesheet> - throws an error in my XSLT debugger
so test that worked for me was
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="test">
<xsl:call-template name="demonstration" />
</xsl:variable>
<xsl:template match="/">
<xsl:copy-of select="$test"/>
</xsl:template>
<xsl:template name="demonstration">
<p>Just a test</p>
</xsl:template>
</xsl:stylesheet>
Value-of would be getting the value of the variable, and becuase we are in XML the value would be the same as the value of a node, i.e the data between the tags. Copy of would output exactly that a complete copy , tags and all
EDIT
As per my started comment, what I was meaning was something like this:
out from the XSLT
<script type="text/javascript">
var hfDetails = [{"ProductID": "00001", "ListPrice": '<tr class="collapsed"><td class="datalabel">List Price:</td><td class="datainfo"></td></tr>', "YourPrice": '<tr><td class="datalabel">Price:</td><td class="datainfo">$23.99 & is elegible for <span class="freeshipping">FREE</span> shipping Details</td></tr>'},
... etc, etc, etc ...
];
</script>
instead of the hidden input field. What this will do is create a global javascript array object.
in essences jumps the step required by
var details = $.parseJSON($("#[id*='hfDetails']").val());
you could then just do
var details = hfDetails;
and no more code needs to change.
If you are concerned about the escaping of apostrophe and quotes. Either can be used to define a string in javascript, and then the other can safely be used in the string i.e.
"ListPrice" : '<tr class="collapsed"><td class="datalabel">List Price:</td><td class="datainfo"></td></tr>'
is valid and so is
"ListPrice" : "<tr class='collapsed'><td class='datalabel'>List Price:</td><td class='datainfo'></td></tr>"
also note, you don't have to enclose the properties of the object in quotes i.e.
"ProductID" : "00001"
can just be
ProductID : "00001"
as long as you don't have spaces in your property names
<xsl:value-of select="$test"/>
You must use xsl:copy-of (or xsl:sequence is recommended in XSLT 2.0) -- instead of xsl:value-of.
By definition, xsl:value-of outputs the string value of the result of evaluating the expression in its select attribute.
<xsl:copy-of> outputs (in document order) all the nodes of the node-set specified in its select attribute. Or, to quote the W3C XSLT 1.0 specification again:
"The xsl:copy-of element can be used to copy a node-set over to the
result tree without converting it to a string"
In XSLT 2.0 xsl:sequence may be used to output the nodes of a node-set in any desired order.