Use Google as Your Gateway to the Mobile Internet
Safari does not URL-encode arguments of the document() function
Still, don't forget that URL encoding is the programmer's job and that the browsers only fix your errors when they do the encoding behind the scenes. However, properly encoding a URL string with the limited set of string-handling functions of XSLT 1.0 is close to Mission Impossible ... although of course it's been proven that it can be done even with a Turing machine ;)
And, last but not least, if you need a more detailed description of URL encoding - here it is.
Output UTF-8 text from ASP script
Sub OutputCSV(finalText)In my application, the complete CSV data was stored in the finalText variable. If that's not the case, replace the Response.Write statement with the CSV-generating logic.
Response.Clear
Response.Charset = "utf-8"
Response.ContentType = "text/plain"
Response.Codepage = 65001
Response.Write finalText
Response.End
End Sub
Using document function with URI in XSLT
The reason is very simple: the document function in XSLT can be a very problematic cross-site-scripting (XSS) tool if it's allowed to download a document from any URI. Therefore it should be limited to the domain of the XSLT stylesheet calling it, similar to the restrictions imposed on the XMLHttpRequest object. Internet Explorer 7 and Firefox 2.0.0.11 enforce this restriction and Opera decided not to support the document() function even when they've finally added XSLT support (and here is an ecstatic post claiming they do it now).
Enhance Your AJAX Applications with Visual Cues
Select XML elements in XSLT template
Text nodes in XSLT
In the example mentioned by Alexandar …
<input type="submit">… a single text node within the xsl:attribute element (as parsed by XML parser) includes the Login text as well as a few whitespace characters. To get rid of the extra whitespace characters (it looks like the target application hates having leading whitespaces in submit button names), you could write the whole value in-line (now there is no extra whitespace in the attribute value):
<xsl:attribute name="value">
Login
</xsl:attribute>
</input>
<xsl:attribute name="value">Login</xsl:attribute>Alternatively, you could split the contents of the xsl:attribute element into whitespace-only text nodes (which are not copied to the output) and a non-whitespace text node (containing the Login text). The easiest way to do it is to insert an xsl:text element within the xsl:attribute element:
<xsl:attribute name="value">
<xsl:text>Login</xsl:text>
</xsl:attribute>
This post is part of You've asked for it series of articles.
Search Engine Optimization in XML+XSLT designs
- Google can process XML data, but only stores it into supplementary index (lower rankings)
- If the server's XML output does not contain enough context (for example, product description in HTML-ish format), the search engines cannot make any sense out of it, so it would not be indexed appropriately.
- Search engines will not follow explicit (let alone implicit) links in XML documents, so you need a sitemap (classic HTML page, Atom/RSS feed or Google sitemap) to help search engines find the content pages
Dynamic output elements in XSLT
<book title="Sample book">… we'd like to generate HTML H1 heading for the chapter and H2 … Hx headings for the sections. It's obvious that the heading element's name can be computed easily as 'H'+number-of-ancestors. The corresponding XSLT template is of course a bit more complex and uses the xsl:element tag to generate the output element.
<chapter title="First chapter">
<section title="Section in first chapter">
<section title="Chunk of text within a section">
<para>My paragraph</para>
</section>
</section>
</chapter>
</book>
<xsl:template match="chapter|section">
<xsl:element name="{concat('h',count(ancestor::*))}">
<xsl:value-of select="@title" />
</xsl:element>
<xsl:apply-templates />
</xsl:template>
The xsl:element expects a QName (a string specifying the element's name) as its name attribute. To insert dynamically computed element's name, you have to use curly braces around the expression.
Add a truly modal dialog box to your web application
XSLT global replace
<xsl:template name="globalReplace">The xsl:choose is the if-then-else/select replacement. We test if the source substring is present in the value …
<xsl:param name="value"/>
<xsl:param name="from"/>
<xsl:param name="to"/>
<xsl:choose>… if it is, we do tail recursion first, replacing all the remaining values and storing the result in $rest …
<xsl:when test="contains($value,$from)">
<xsl:param name="rest">… and then concatenate the substring before the first from value with the to value and the results of the tail recursion.
<xsl:call-template name="globalReplace">
<xsl:with-param name="outputString" select="substring-after($value,$from)"/>
<xsl:with-param name="from" select="$from"/>
<xsl:with-param name="to" select="$to"/>
</xsl:call-template>
</xsl:param>
<xsl:value-of select="concat(substring-before($value,$from),$to,$rest)" />If the source string does not contain the substring to be replaced, we just return it (this also ends the tail recursion) …
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$outputString"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
A few must-read JavaScript documents
XSL: Copy input elements into output stream
<catalog>… and use a simplistic approach to the copy-of …
<product id="123">
<name>Sample product</name>
<text>A <strong>marvellous</strong>
<span class='red'>red</span> book</text>
</product>
</catalog>
<xsl:template match="product">… you'd end up with an extra non-HTML-compliant text node in the output stream …
<h3><xsl:value-of select="name" /></h3>
<xsl:if test="text">
<xsl:copy-of select="text" />
</xsl:if>
</xsl:template>
<h3>Sample product</h3>To get just the contents you'd like to get, you should still use the xsl:copy-of instruction, but select just the child element (child::*) and the text nodes (text()) of the text element. To add icing on the cake, we'll enclose the contents of the text element in a P tag:
<text>A <strong>marvellous</strong>
<span class="red">red</span> book </text>
<xsl:template match="product">
<h3><xsl:value-of select="name" /></h3>
<xsl:for-each select="text">
<p><xsl:copy-of select="text() | child::*" /></p>
</xsl:for-each>
</xsl:template>
Displaying element's ancestors
<book title="Sample book">
<chapter title="First chapter">
<section title="Section in first chapter">
<section title="Chunk of text within a section">
<para>My paragraph</para>
</section>
</section>
</chapter>
</book>
The section elements can be nested
You could use the following template to display the breadcrumbs in front of each heading:
<xsl:template match="chapter|section">
<p class='crumbs'>
Walk through all the ancestors, starting from the root element
<xsl:for-each select="ancestor::*">
If the current element has an ancestor, we've obviously printed something already, so insert a breadcrumb separator …
<xsl:if test="ancestor::*"><xsl:text>, </xsl:text></xsl:if>
… and display the title of the ancestor element.
<xsl:value-of select="@title" />
</xsl:for-each>
</p>
At the end, display the current heading and recursively process child elements.
<h2><xsl:value-of select="@title" /></h2>
<xsl:apply-templates />
</xsl:template>
Max function in ASP
Function Max(A,B)Things get a bit more complex if you want to take in account the NULL values (assuming non-NULL is always greater than NULL):
Max = A : If B > A Then Max = B
End Function
Function Max(A,B)However, I prefer the multi-value function that can take as many arguments as you like (unfortunately in an array, as VBScript functions can't take variable number of arguments):
Max = A : If IsNull(B) Then Exit Function
If B > A Or IsNull(A) Then Max = B
End Function
Function MaxArray(A)
Dim I
MaxArray = A(LBound(A))
For I = LBound(A) + 1 To UBound(A)
If A(I) > MaxArray Or IsNull(MaxArray) Then MaxArray = A(I)
Next
End Function
The code uses a VBScript quirk: function name without parenthesis when used within the function refers to the current function's value, not a recursive call
To call the MaxArray function, you don't have to construct an array beforehand, you can also use the array VBScript function, for example:
mxv = MaxArray(array(A,B,C,D))
Keyboard shortcuts in web user interface
Firefox vulnerabilities when using data: or jar: protocols
XSL function to find parent node name
- The local-name function returns the name of its argument (without the namespace) or the name of the current element if it's called without an argument;
- The parent:: axis selects the current element's parent;
- The parent::* matches any node that is the parent of the current element.
<book>… this XSL template will output “My parent is: section”
<chapter>
<section>
<title>My section</title>
<para>My paragraph</para>
</section>
</chapter>
</book>
<xsl:template match="para">
My parent is: <xsl:value-of select="local-name(parent::*)" />
</xsl:template>
This post is part of You've asked for it series of articles.
Fading background made simple
function setFading (e) {
e.style.backgroundColor = "#FFFFFF";
if (!e.xa) e.xa = new xAnimation();
e.xa.rgb(e,"background-color","#EDC226",1000,1,1);
}
Emulating activeElement property with DOM browsers
All the solutions I've found with Google were kludges along the lines of “scan all the elements and test each one to see whether it has focus”. It's much better to use the capture phase of the DOM event model and track the focus and blur events with the document object:
function _dom_trackActiveElement(evt) {
if (evt && evt.target) {
document.activeElement =
evt.target == document ? null : evt.target;
}
}
function _dom_trackActiveElementLost(evt) {
document.activeElement = null; }
if (document.addEventListener) {
document.addEventListener("focus",_dom_trackActiveElement,true);
document.addEventListener("blur",_dom_trackActiveElementLost,true);
}
This implementation is slightly over-pessimistic; if the browser window loses focus, the activeElement is set to null (as the input control loses focus as well). If your application needs the activeElement value even when the browser window doesn't have the focus, you could remove the blur event listener.
You cannot compare strings with XSLT
Before anyone rushes forward and tells me that XPATH 2.0 has compare function that does exactly what I need, let me remind you that XPATH 2.0 is only implemented in a handful of standalone XSLT translators and is (at least as of October 2007) completely useless when you need browser-based transformation.
And just in case someone starts explaining about the collating sequences, let me conclude this post by saying that you can sort on strings with the xsl:sort instruction in XPATH 1.0, but cannot compare them. I'm really wondering what the standard designers were thinking …
XML Handling in Microsoft SQL Server 2005
XSLT-generated documents are never rendered in quirks mode in Firefox
While this trick works nicely in Internet Explorer, Firefox produces standards mode layout (with TABLE contents condensed at the top of the screen) regardless of the HTML output the XSLT transformation generates. Probably this is due to the fact that the XSLT transformation in Firefox does not produce interim HTML string, but transforms DOM tree into another DOM tree.
Create your own animated …loading… indicator
Use xsl:key to link nodes in input XML document
<data>You can define a key that will select the target element or another one that will select the text child of the target element (in which case you have to be a bit more creative in the use part of xsl:key definition):
<source id="abc" ref="123" />
<target>
<text>Message</text>
<id num="123" />
</target>
</data>
<xsl:key name="target" match="target" use="id/@num" />Assuming you want to display the text child of the target node, you could use either key in your transformation. If you use the target key, it returns the target node, so you have to continue with the XPath expression to get its text child; using the ttext key gives you the text node immediately:
<xsl:key name="ttext" match="target/text" use="../id/@num" />
<xsl:template match="source">
<xsl:variable name="target" select="@target" />
Source <xsl:value-of select="@id" />
target <xsl:value-of select="key('target',@target)/text" />
ttext <xsl:value-of select="key('ttext',@target)" />
</xsl:template>
Floating misteries
Mouse event bubbling might produce unexpected results
<div onmouseover="d_a()" onmouseout="d_b()">When the mouse is moved from outside the DIV to the first line, d_a and p_a are called as expected. When the mouse moves from Line 1 to Line 2, p_b (expected), as well as p_a (highly unexpected) are called even though the mouse has not left the DIV. p_a is called due to event bubbling; the mouseover event bubbles from its target through all the target's ancestors toward the document object.
<p onmouseover="p_a()" onmouseout="p_b()">Line 1</p>
<p>Line 2</p>
</div>
Probably the best workaround is to compare the event target (browser-dependent, use xEvent to get browser-independent code) with the current element.
On-demand load of JavaScript libraries
function LoadScript(url)
{
var lib = document.createElement(’script’);
lib.type=’text/javascript’;
lib.src= url;
document.getElementsByTagName(’head’)[0].appendChild(s);
}
... or you could use a safer xLoadScript function from the X library.
The X library also provides a xSmartLoadScript function to stop you from loading the same library multiple times.
In a recent post on PeachPit.com, Kris Hadlock gives you a few ideas where this functionality might come handy.Link multiple elements from source XML data
<data>While it's easy to select the correct target node, it's harder to get the source attribute into the XPath expression; the only way to do it is to store the source attribute in a local variable and then use the variable value (which is context-independent) in the XPath expression:
<source id="abc" target="123" />
<target>
<text>Message</text>
<id num="123" />
</target>
</data>
<xsl:template match="source">The final XPath expression works as follows:
<xsl:variable name="target" select="@target" />
Source <xsl:value-of select="@id" />
is associated with
<xsl:value-of select="//target[id/@num = $target]/text" />
</xsl:template>
- It selects a target node anywhere in the source XML tree (the // path) such that the num attribute of its id child is equal to the local variable target
- When the target node is selected, the value of the XPath expression is the value of its text child, which is then rendered into a string (collapsing all its descendant text nodes into the final result).
This post is part of You've asked for it series of articles.
How to handle XSLT namespaces
Some XSLT best practices
Match a keyword in space-separated keyword list (attribute value)
- The original problem explanation is somewhat misleading; the @class = 'vcard' test will correctly match if the class attribute is set to vcard, but would not match if vcard is just one of several classes applied to an HTML tag.
- The Microformats wikipedia has code samples solving the same issue in multiple programming languages.
- The best function I've found so far in JavaScript world is the xGetElementsByClassName from the X library.
Storing XML Data in a Relational Database
Interesting: greater-than character does not need to be escaped in XML
DOM events don't have the “on” prefix
To make matters worse, the attachEvent method (Microsoft equivalent of the addEventListener DOM method) expects the event names with the “on” prefix.
Serve SQL data in XML format
- Creating the XML markup in the server-side script
- Using the SQL server to generate the XML markup
- Using server-side XSLT transformation in combination with SQL server-produced XML data
Firefox hides the ?xml processing instruction
For example, when the following XML document ...
<?xml version="1.0" encoding="windows-1250"?>... is transformed into a DOM tree and processed with this JavaScript function:
<?xml-stylesheet href="countryPage.xsl" type="text/xsl"?>
<Test />
function testPI(dom) {... Internet Explorer generates the following text:
var topNodes = dom.childNodes;
for (var i = 0; i < topNodes.length; i++) {
var node = topNodes[i];
wr("nodeType="+node.nodeType);
if (node.nodeType == 7) {
wr("PI="+node.target+" ... "+node.data);
}
}
nodeType=7... while Firefox skips the <?xml ?> processing instruction:
PI=xml ... version="1.0" encoding="windows-1250"
nodeType=7
PI=xml-stylesheet ... href="countryPage.xsl" type="text/xsl"
nodeType=1
nodeType=7
PI=xml-stylesheet ... href="countryPage.xsl" type="text/xsl"
nodeType=1
IP address to physical location mapping
Sometimes I would also like to figure out where the visitor causing the ASP error is coming from (more so if the errors are due to lack of resources, indicating unexpectedly high load). The usual tools (nslookup) fail if the ISPs don't enter their IP address space in reverse DNS lookup zones. In the past I've tried to use various Internet registries to figure out who the address is allocated to … and finally I've stumbled on the IP2Location web site, which does exactly what I was looking for: I enter the IP address and it tells me where the visitor is coming from (including a nice overview map for those who have no clue about geography).
Publish code samples in Blogger
use strict;
use Win32::Clipboard;
our $lcnt = 0;
our $result = "" ;
our $CLIP = Win32::Clipboard();
while (<>) {
if (/\n/) { chop; }
$_ =~ s/\&/&/gi;
$_ =~ s/\</</gi;
$_ =~ s/\>/>/gi;
while (/^\s/) { $_ =~ s/^(\s*)\s/$1 /gi; }
$result .= ($lcnt == 0 ? "<pre class='code'>" : "\n") ;
$result .= $_;
$lcnt ++;
}
$result .= "</pre>";
$CLIP -> Empty();
$CLIP -> Set($result);
print "Result is on the clipboard";
If you're not fluent in PERL, don't even try to understand what it does ... it works :)
Extract the default stylesheet from the DOM document
You can find a good case study on using XML and XSLT in my Inform-IT article Optimized Presentation of XML Content.
The following JavaScript function extracts the default XSLT stylesheet from the XML document (passed to the function as DOM Document object). It relies on the fact that the DOM Document object extends the Node object and thus has the childNodes property that contains the root element as well as all processing instructions.
function getXMLStylesheet(dom) {
var topNodes = dom.childNodes;
for (var i = 0; i < topNodes.length; i++) {
var node = topNodes[i];
if (node.nodeType == 7) {
if (node.target == "xml-stylesheet") {
var match = /href="(.*?)"/gi.exec(node.data);
if (match.index >= 0) return match[1];
}
}
}
}
A typical usage of this function is illustrated below:
<script src="/sarissa.js"></script>
...
<script>
var xrq = new XMLHttpRequest();
xrq.open("GET","getPI.xml",false);
xrq.send(null);
if (xrq.status == 200) {
var dom = xrq.responseXML;
var xsl = getXMLStylesheet(dom);
alert("stylesheet="+xsl);
}
Constants of the DOM Node object are not implemented in Internet Explorer
if (node.nodeType == node.TEXT_NODE) { ... }
... which would be largely self-documenting thus has to be written as ...
if (node.nodeType == 3) { ... }
... unless you define the same constants in your JavaScript.
Use JavaScript Progressive Enhancement to simplify HTML table formatting
Should I encode data as XML attributes or child nodes
- If your data is simple and non-hierarchical (for example, results of SQL query), using XML attributes will reduce the response size (due to shorter markup)
- If you have repeating values in your data set, you cannot use attributes, as XML allows only a single attribute with a certain name per tag. In this case, you have to use child tags to encode repeating values. However, you can still transfer the actual value as an attribute of the child tag, not as text in the tag.
- If you're transfering large character fields, using child tags will increase the readability of the XML response when you're inspecting it with a browser (you know, sometimes we have to do a bit of debugging :)
- You might hit platform-dependent limitations if you use long attribute values, so yet again you would be better off using child nodes in this case
- Obviously, you cannot use CDATA section in an attribute, the attribute value has to be properly quoted.
This post is part of You've asked for it series of articles.
How do I use xsl:choose
The Google query strings bringing visitors to my blog are sometimes amazing, like the one in the title of this post. Let's start with a tangential remark: if you're serious about XSLT, you should invest into a good book (XSLT Programmer's Reference is one of the best technical books I've ever read). If, on the other hand, you're just looking for a quick fix, here's the whole story
XSLT does not have if-then-else or select-case constructs like most other languages. You can implement both functions with the xsl:choose element that can take as many xsl:when children as needed. These are evaluated sequentially, the first one that succeeds is executed. You can also use xsl:otherwise as the last child in the list to specify the default behavior.
This post is part of You've asked for it series of articles.
Find processing instructions in XSLT
<?user Web admin (admin@something.net) {00000000-0000-0000-0000-000000000000}?>To get this string into an XSLT parameter, I use the following XSLT instruction:
<xsl:param name="username" select="/processing-instruction('user')" />
A processing instruction can appear anywhere in the XML document. To find processing instructions that are not children of the root element, use the appropriate XPath syntax.
You have to use cells array, not DOM children to select table cells
<table>... the first child of the TR element is the whitespace between the TR tag and the first TD tag. As the whitespace is allowed in that position by the HTML standard, you cannot rely on the TR.firstChild property. You should use TR.cells[0] to select the first cell.
<tr id='test'>
<td>Cell #1</td>
<td>Cell #2</td>
</tr>
</table>
Introduction to HIJAX
How do you write utf-8 data from ASP?
Obviously this is too simple, so it's hard to find an explicit answer in the online documentation:
- To select the output encoding you want to use in the ASP script, you have to set the response.codepage property or change the per-page default with the @Codepage directive. To use utf-8 encoding, set the response.codepage to 65001.
- The HTTP Content-type header should match the encoding you're using. Set the response.charset (second part of the content-type header) to "utf-8".
- If you use META tag in your HTML to set the content-type or encoding attribute in the xml pseudo-instruction, these have to match the HTTP encoding as well. You don't have to specify utf-8 encoding in XML, as it's the default XML encoding.
- Instead of setting response.codepage in every response, you can set session.codepage at the start of the session.
- Important: If you use HTML forms, you have to set the @Codepage in all ASP scripts processing the forms or change the AspCodepage metabase property, otherwise the script might misinterpret the input data.
This post is part of You've asked for it series of articles.
Which XML encoding should I use?
The short answer is utf-8. The long answer goes along these lines:
- utf-8 is the default XML encoding specified by the XML standard. If you use it, you don't have to define the encoding manually, thus reducing the chances of introducing errors. For example, some browsers get confused if the encoding specified in the xml pseudo-instruction is not the same as the one specified in the HTTP header.
- All XML parsers are required to recognize utf-8 encoding. By using it, you don't risk any future compatibility issues. For example, some of my applications break on some installations of Internet Explorer on Vista as they use windows-1250 encoding. They work fine within IE on Windows XP, Firefox on Vista (and sometimes even with IE on Vista).
- By using utf-8 you'll never encounter a character you cannot encode.
This post is part of You've asked for it series of articles.
You cannot disable output escaping in Firefox
Firefox does not generate any error when encountering the disable-output-escaping attribute (it's allowed by the XSLT standard) but simply ignores it and inserts the quoted < and > characters into the output stream.
CSV format specifications
Firefox document object lacks DOM properties in XSLT-generated pages
The buggy browser was Firefox: if you create a web page with local XSLT transformation (dictated by the xml-stylesheet instruction in the XML document returned from the server) in Firefox (up to at least 2.0.0.6), everything looks normal, but document.body is null and document.forms, document.links and other similar array are empty.
To make matters more confusing, all other HTML DOM calls work (it's a different bug from this one), so I had to substitute the links array with document.getElementsByTagName("A") and the body property with the document.getElementsById("body") (and used the id attribute on the body tag).
Testing individual XSLT features
- Install SAXON;
- Create a small XML document with structure that represents your input data;
- Create a small XSL stylesheet focusing on features you want to test;
- Use xml:output method='text' in the test stylesheet to produce the test results without XML garbage;
- Perform the tests with SAXON;
- Test the transformation in your target environment. If you're developing browser-side transformations, insert xsl-stylesheet pseudo-instruction in your XML document and open it with Internet Explorer and Firefox.
You can see how I've used this approach in this post.
Handling unexpected children in xsl:apply-templates
When you use xsl:apply-templates in a xsl:template, you might get extra text strings in the transformed results if your input XML data contains unexpected elements with text nodes (due to default XSLT template rules, the extra elements themselves and their attributes are ignored).
There are three ways you can handle this situation:
- Write an XML schema and validate input data against it before the transformation;
- List the children you expect in the xsl:apply-templates instruction;
- Define a low-priority default template the does nothing.
<?xml version="1.0" ?>... is processed with the stylesheet ...
<data>
<a>A1</a>
<b>B1</b>
<c>Unexpected</c>
<b>B2</b>
<a>A2</a>
</data>
<?xml version="1.0" ?>... you get the string Unexpected in the transformed results. You could either list the children you expect in the xsl:apply-templates, for example ...
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="data">
<xsl:apply-templates /></xsl:template>
<xsl:template match="a">
A: <xsl:value-of select="text()" />
</xsl:template>
<xsl:template match="b">
B: <xsl:value-of select="text()" />
</xsl:template>
</xsl:stylesheet>
<xsl:template match="data">... or you could define a default template that does nothing (overwriting the built-in default that outputs the child text nodes) ...
<xsl:apply-templates select="a|b" /></xsl:template>
<xsl:template match="*" priority="-100" />
This post is part of You've asked for it series of articles.
Another IE bug: transformed plain text displayed as HTML
<?xml version="1.0" ?>... is transformed with this stylesheet ...
<?xml-stylesheet href="t1.xsl" type="text/xsl"?>
<data>
<row>Sample</row>
</data>
<?xml version="1.0" ?>... Firefox displays the resulting text (<b>Sample</b>), but Internet Explorer displays Sample.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="row">
<b><xsl:value-of select="text()" /></b>
</xsl:template>
</xsl:stylesheet>
Yet another reason to use Saxon
<?xml version="1.0" ?>... Saxon would report ambiguous rule match (even documenting the input element that caused the error and the source line number), whereas all the other XSLT translators would silently produce "best effort" results. In fact, the stylesheet had a typo, the last template should match the b element and I would have lots of problems detecting that with MSXML (for example).
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="data">
<xsl:apply-templates /></xsl:template>
<xsl:template match="a">
A: <xsl:value-of select="text()" />
</xsl:template>
<xsl:template match="a">
B: <xsl:value-of select="text()" />
</xsl:template>
</xsl:stylesheet>
Why would I use select='text()'
<xsl:value-of select='text() /> is used when you want to select the text embedded between the opening and closing tag of the current element. However, in this context, the text() function selects only the first child text node. If you want to:
- select all embedded text (including text within the child elements), use <xsl:value-of select='.' /> as this renders the whole tree of descendants as a text string;
- select all text nodes within the current element but no text in descendant elements, use a <xsl:for-each select='text()' > loop.
<?xml version="1.0" ?>... transformed with this stylesheet ...
<?xml-stylesheet href="t1.xsl" type="text/xsl"?>
<data>
<row>Before <b>an embedded tag</b> and after it</row>
</data>
<?xml version="1.0" encoding="windows-1250" ?>... results in the following text:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="windows-1250" />
<xsl:template match="row">
Text(): <xsl:value-of select="text()" />
Current element: <xsl:value-of select="." />
Text loop: <xsl:for-each select="text()"><xsl:value-of select="." /></xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Text(): Before
Current element: Before an embedded tag and after it
Text loop: Before and after it
This post is part of You've asked for it series of articles.
XSL: Remove whitespace from source XML document
Test for empty attributes in XSLT
Testing for missing attributes in XML elements is different from testing for attributes with empty values. You can test for missing attributes with <xsl:if test="not(@attribute)">, but this test will never succeed if the attribute is present but empty. In that case, you have to use the <xsl:if test="@attribute = ''"> condition.
This post is part of You've asked for it series of articles.
Workaround: generate explicit closing tag in XSLT
The workaround provided in PHP documentation comes handy in any situation where you want to have an explicit closing tag for whatever reason. Instead of <tag></tag> which is usually output as <tag />, your XSLT transformation should contain <tag><xsl:comment /></tag>, which will generate the opening and closing tag.
Output XML data from ASP
These are the steps you need to take to output XML data from ASP script:
- Clear the output buffer to ensure no HTML tags are sent to the client;
- Set the content-type to text/xml;
- Optionally (but highly recommended) set the character set to utf-8 and change the ASP code page;
- Output the XML data (for example, writing a DOM object xml property to the output stream);
- Stop ASP script processing.
The sample ASP code is included below and you can get more in-depth description of the necessary operations in my InformIT article Optimized presentation of XML content.
Response.Clear
Response.ContentType = contentType
Response.Charset = "utf-8"
Response.Codepage = 65001
Response.Write txt
Response.End
Note: this post is part of You've asked for it series of articles.
Converting forms to AJAX
How to avoid common CSS mistakes
Using transformNode in IE7
What you should do instead is to use a wrapper library like Sarissa that gives you XSLTProcessor object in both Firefox and Internet Explorer environments. The XSLTProcessor API is described in Mozilla documentation (which also includes a simple example).
Client-side development environment
Scenario: XSLT deployment in a browser/server scenario with XSLT transformations performed on the browser (JavaScript or with <?xsl-stylesheet ?>)
Potential tools:
- Saxon is great for off-line testing (primarily while developing small bits of code or trying new programming techniques). It cannot fetch the information from the server, so you have to provide test data in local XML files
- Firefox is the best tool if you want to test client-server integration and use <?xsl-stylesheet ?> approach as it can recover from XSLT errors (which IE can't). However, it doesn't detect all errors, so the stylesheets working with Firefox might break in IE (but not due to an IE bug)
- IE is a nightmare when using <?xsl-stylesheet ?> directive. Whenever something breaks (error in stylesheet or even server-side error resulting in HTML response instead of XML response), its cache gets messed up and you have to restart the browser to continue the tests. I therefore use IE only in the final tests to verify that my XSLT has no weird errors not detected by Firefox.
- The best testing mechanism is probable the manual (JavaScript) client-side transformation. You can do it in IE or Firefox; in this case I would prefer IE, as it has better XSLT error reporting (MSXML is not that bad :).
Example: Use XSLT transformation in browser
Several readers were asking for an code sample that performs XSLT transformation in the browser, so here it is :) To start with, you should use a wrapper library to isolate yourself from browser version/platform inconsistencies. I'm using Sarissa to deal with XML, XSLT and HttpRequest and X library to deal with DHTML-related functions.
The most common XSLT-related operation that you do in your browser is this:
- Download XML document from the web server using XMLHttpRequest;
- Transform the XML into application-specific HTML markup with an XSLT transformation;
- Insert the resulting HTML markup into the web page.
Sarissa library has a function (Sarissa.updateContentFromURI) that does almost exactly what you need to get there, so you only need to perform the following steps:
- Load the XSLT stylesheet as XML document (sync load is easier to do).
- Import the loaded XML document into an XSL processor (special object that can perform the XSLT transformations).
- Call the updateContentFromURI function.
The resulting code is very simple:
// load XSLT stylesheet, done only once.
var xsl = loadURI("moveRoutes_lists.xsl");
if (!xsl) throw("Cannot load XSL stylesheet");
var xform = new XSLTProcessor();
xform.importStylesheet(xsl);
// the following call transforms a remote XML document
// into HTML fragment and inserts it into the target
// element
Sarissa.updateContentFromURI(remoteURI, xGetElementById(targetElement),xform);
Notes:
- sarissa.js as well as sarissa_dhtml.js libraries have to be included in the source document.
- xGetElementById function is part of the X library.
- loadURI function is documented here.
JavaScript progressive enhancement in practice
Server-side XSLT transformations
I would often use XSLT on the server to separate data (XML) from its presentation. Its usage also results in cleaner server-side code, as the markup generation is separated from the programming logic. It's also extremely useful if you want to deploy XML/XSLT-based solution that has to support low-end clients (for example, search engine spiders).
Error handling Sarissa wrapper
In a previous post I've included a simple Sarissa wrapper that could be used to load an XML document (for example, XSLT stylesheet). If you want to perform more error checking and generate sensible errors, you could use these two functions:
- loadFromServer function requests object load. If you provide a callback parameter, the load is asynchronous, otherwise it waits for the load to complete and return the wrapper object.
- checkLoadStatus checks the various error conditions and throws appropriate errors.
var AWS = new Object();
AWS.loadFromServer = function (url,cb) {
var lso = new Object() ;
function loadHandler() {
if (lso.request.readyState == 4) cb(lso); }
lso.savedURL = url ;
lso.request = new XMLHttpRequest() ;
lso.request.open("GET", lso.savedURL, cb ? true,false);
if (cb) lso.request.onreadystatechange = loadHandler;
lso.request.send(null);
return lso;
}
AWS.checkLoadStatus = function (lso) {
var url = lso.savedURL ;
var xr = lso.request ;
var msg;
if (!url) throw("AWS.checkLoadStatus expects loadFromServer object") ;
if (xr.status != 200) throw (url + " failed: "+xr.statusText);
try {
var de = xr.responseXML.documentElement;
if (!de) msg = url + ": response is not a valid XML document";
} catch(err) {
msg = url + ": response is not a valid XML document (" + err + ")";
}
if (msg) throw(msg);
return xr.responseXML;
}
Offline XSLT processor
I'm primarily using SAXON to test my XSLT code (or develop new programming techniques) before deploying it in a distributed environment. I was also using it to develop the WordML transformations that are now executed within the context of Save As ... function in Word.
Simple high-level wrapper for Sarissa
function loadURI(uri,async) {
var d = Sarissa.getDomDocument();
d.async = false;
d.load(uri);
if (d.parseError) throw("Error loading "+uri+": "+Sarissa.getParseErrorText(d));
return d;
}
Make pop-up windows visible to search engines
Recommended wrapper libraries
Note: I decided not to use a framework library. If you want to go down that route, these two would be way too low-level.
Automate the pagination of web pages
Using WordProcessingML to Generate Clean HTML from Word
MSXML is still only DOM-1 compliant
function getDomById(e,id) {Note: The procedure is doing breadth-first search as I was looking for IDs that were pretty high in the hierarchy of trees with many branches.
if (e.getElementById) { return e.getElementById(id); }
if (e.documentElement) e = e.documentElement;
var cn = e.childNodes;
for (var i = 0 ; i != cn.length; i++) {
var n = cn[i];
if (n.nodeType == 1) {
if (n.getAttribute("id") == id) return n ;
}
}
for (var i = 0 ; i != cn.length; i++) {
var n = cn[i];
if (n.nodeType == 1) {
var result = getDomById(n,id); if (result) return result;
}
}
return false;
}
Make Pop-Up Windows Visible to Search Engines
Optimized presentation of XML content
Firefox: HTML DOM fails if a local XSL transformation does not specify xsl:output
The results are weird: for example, whatever is produced with the XSL transformation behaves like a HTML DOM object, but if you create a new element, it lacks HTML-specific DOM calls.
And the obvious solution: make sure you're always including ...
<xsl:output method="html" encoding="utf-8"... in your XSL stylesheet.
doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
IE7 bug: addEventListener does not work with table row mouseover event
Firefox 1.5 bug: table.rows property is empty when using XSL
To make matters worse, Firefox does not generate an implicit TBODY element in the DOM tree, but IE7 and Opera do. Makes for a nice nightmare if you aim at cross-browser support ... the workaround code is included below:
function recurseChildNodes(parent,tag,callback) {
if (! parent.nodeName) return;
if (parent.nodeName.toLowerCase() == tag) { callback(parent); return; }
if (! parent.childNodes) return;
for (var i = 0; i < parent.childNodes.length; i++) {
recurseChildNodes(parent.childNodes[i],tag,callback);
}
}
recurseChildNodes(table,"tr",callbackFunction);