This is not the browser type XSLT, this is for processing data (SAP B1 Integration Framework). Suppose we have two SQL Tables, HEADER and LINE and we want to avoid the kind of work where we first SELECT from the HEADER and then launch a separate select for the lines for each, because that requires "visual programming", and we like writing code more than connecting arrows. So we are sending the server a query like SELECT * FROM HEADER, SELECT * FROM LINES and we get an XML roughly like this:
<ResultSets>
<ResultSet>
<Row><MHeaderNum>1</MHeaderNum></Row>
<Row><MHeaderNum>2</MHeaderNum></Row>
<Row><MHeaderNum>3</MHeaderNum></Row>
</ResultSet>
<ResultSet>
<Row><LineNum>1</LineNum> <HeaderNum>1</HeaderNum></Row>
<Row><LineNum>2</LineNum> <HeaderNum>2</HeaderNum></Row>
<Row><LineNum>1</LineNum> <HeaderNum>3</HeaderNum></Row>
<Row><LineNum>2</LineNum> <HeaderNum>1</HeaderNum></Row>
<Row><LineNum>1</LineNum> <HeaderNum>2</HeaderNum></Row>
<Row><LineNum>2</LineNum> <HeaderNum>3</HeaderNum></Row>
</ResultSet>
so we think we are imperative, procedural programmers and pull a
<xsl:for-each select="//ResultSets/Resultset[1]/Row">
do stuff with header data
<xsl:for-each select="//ResultSets/Resultset[2]/Row[HeaderNum=MHeaderNum]">
do stiff with the line data beloning to this particular header
</xsl:for-each>
</xsl:for-each>
And of course this does not blinkin' work because MHeaderNum went out of context like grunge went out of fashion, and we cannot save it into a variable either because we will not be update that variable, as XSLT is something sort of an immutable functional programming language.
But fear not, says an inner voice, because XSLT gurus can solve things like that with templates. Templates, if I understand it, are sort of XSLT's take on functions. They can be recursive and stuff like that. So can they be used to solve problems like this?
And of course we are talking about XSLT 1.0 because I don't know whether Java ever bothered to implement the later versions, but SAP certainly did not bother to used said, hypothetical implementation.
Or should I really forget about this and just connect my visual arrows? The thing is, SQL is not supposed to be used in such an iterate through headers then iterate through lines ways. What I am trying to do is what makes an SQL database a happy, get a big ol' chunk of data out of it and then process it somewhere else, not bother it with seventy zillion tiny queries. And in our case the somewhere else is sadly XSLT, although technically I could try JavaScript as well as SAP added a Nashorn to this pile of mess as well, but maybe it is solvable in "pure" XSL?
Whether XSLT 1 or later and whether with templates and for-each, the current() function exists: //ResultSets/Resultset[2]/Row[HeaderNum=current()/MHeaderNum].
The best way to resolve cross-references is by using a key.
For example, the following stylesheet:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="line-by-header" match="ResultSet[2]/Row" use="HeaderNum" />
<xsl:template match="/ResultSets">
<output>
<xsl:for-each select="ResultSet[1]/Row">
<header num="{MHeaderNum}">
<xsl:for-each select="key('line-by-header', MHeaderNum)">
<line>
<xsl:value-of select="LineNum"/>
</line>
</xsl:for-each>
</header>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
when applied to the following input:
XML
<ResultSets>
<ResultSet>
<Row><MHeaderNum>1</MHeaderNum></Row>
<Row><MHeaderNum>2</MHeaderNum></Row>
<Row><MHeaderNum>3</MHeaderNum></Row>
</ResultSet>
<ResultSet>
<Row><LineNum>1</LineNum> <HeaderNum>1</HeaderNum></Row>
<Row><LineNum>2</LineNum> <HeaderNum>2</HeaderNum></Row>
<Row><LineNum>3</LineNum> <HeaderNum>3</HeaderNum></Row>
<Row><LineNum>4</LineNum> <HeaderNum>1</HeaderNum></Row>
<Row><LineNum>5</LineNum> <HeaderNum>2</HeaderNum></Row>
<Row><LineNum>6</LineNum> <HeaderNum>3</HeaderNum></Row>
</ResultSet>
</ResultSets>
will return:
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>
<header num="1">
<line>1</line>
<line>4</line>
</header>
<header num="2">
<line>2</line>
<line>5</line>
</header>
<header num="3">
<line>3</line>
<line>6</line>
</header>
</output>
You can try the following XSLT. It is using three XSLT templates.
Because desired output is unknown, I placed some arbitrary processing for each of the header and line item templates.
XSLT
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<root>
<xsl:apply-templates/>
</root>
</xsl:template>
<xsl:template match="/ResultSets/ResultSet[1]">
<ResultSet1>
<xsl:for-each select="Row">
<r>
<xsl:value-of select="MHeaderNum"/>
</r>
</xsl:for-each>
</ResultSet1>
</xsl:template>
<xsl:template match="/ResultSets/ResultSet[2]">
<ResultSet2>
<xsl:for-each select="Row">
<xsl:copy-of select="."/>
</xsl:for-each>
</ResultSet2>
</xsl:template>
</xsl:stylesheet>
It's more of a clarification that I am in need ..
as per this answer on a question, XSLT variables are cheap! My question is: Is this statement valid for all the scenarios? The instant variables which get created and get destroyed withing 4 line code aren't bothersome but loading a root node or child entities, in my opinion is indeed bad practice..
I have two XSLT files, designed for same input and output requirement:
XSLT1 (without unnecessary variable):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<Collection>
<xsl:for-each select="CATALOG/CD">
<DVD>
<Cover>
<xsl:value-of select="string(TITLE)"/>
</Cover>
<Author>
<xsl:value-of select="string(ARTIST)"/>
</Author>
<BelongsTo>
<xsl:value-of select="concat(concat(string(COUNTRY), ' '), string(COMPANY))"/>
</BelongsTo>
<SponsoredBy>
<xsl:value-of select="string(COMPANY)"/>
</SponsoredBy>
<Price>
<xsl:value-of select="string(number(string(PRICE)))"/>
</Price>
<Year>
<xsl:value-of select="string(floor(number(string(YEAR))))"/>
</Year>
</DVD>
</xsl:for-each>
</Collection>
</xsl:template>
</xsl:stylesheet>
XSLT2 (with unnecessary variable "root" in which whole XML is loaded):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="root" select="."/>
<Collection>
<xsl:for-each select="$root/CATALOG/CD">
<DVD>
<Cover>
<xsl:value-of select="string(TITLE)"/>
</Cover>
<Author>
<xsl:value-of select="string(ARTIST)"/>
</Author>
<BelongsTo>
<xsl:value-of select="concat(concat(string(COUNTRY), ' '), string(COMPANY))"/>
</BelongsTo>
<SponsoredBy>
<xsl:value-of select="string(COMPANY)"/>
</SponsoredBy>
<Price>
<xsl:value-of select="string(number(string(PRICE)))"/>
</Price>
<Year>
<xsl:value-of select="string(floor(number(string(YEAR))))"/>
</Year>
</DVD>
</xsl:for-each>
</Collection>
</xsl:template>
</xsl:stylesheet>
Approach-2 exists in realtime and infact the XML would be several KBs to few MBs, In XSLT usage of variables is extended to child entities as well..
To put-forth my proposal to change the approach, I need to verify the theory behind it..
As per my understanding incase of approach-2, system is reloading the XML data over and over in memory (incase of usage of multiple variables to load child entities the situation turns worst) and thereby slowing down the transformation process.
Before posting this question here I tested the performance of two XSLTs using timer. First approach takes few milliseconds lesser than approach-2. (I used copy-XML files to test two XSL files to avoid complexity with system cache). But again system cache might play huge confusing role here ..
Despite of this analysis of mine I still have a question in mind! Do we really need to avoid usage of variables. And as far as my system is concerned, how worthy is it to modify the realtime XSLT files, so as to use 'approach-1'?
OR Is it like XSLT variables are different than other programming languages (Incase if I'm not aware) .. Say for example, XSLT variables don't actually store the data when you do select="." but they kind of point to the data! or something like this..? AND HENCE continue using XSLT variables without hesitation..
What is your suggestion on this?
Quick Info on current system:
Host Programming Language or System: Siebel (C++ is the backend code)
XSLT Processor: Xalan (Unless Saxon is used explicitely)
I agree with the comments made that you need to measure performance with your particular XSLT processor.
But your descriptions or expectations like "approach-2, system is reloading the XML data over and over in memory" seem wrong to me. The XSLT processor builds an input tree of the primary input XML document anyway and I can't imagine that any implementation then with <xsl:variable name="root" select="."/> does anything like loading the document completely again, it would even be wrong, as node identity and generate-id would not work. The variable will simply keep a reference to the document node of the existing input tree.
Of course in your sample where you have a single input document and a single template where the current node is the document anyway the use of the variable you have is superfluous. But there are cases where you need to store the document node of the primary input document, in particular when you deal with multiple documents.
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.
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>