xslt: Use variables in select and match - html

I need to specify the output order from a html file to a text file. Therefore I use the xsl:apply-templates select approach.
It works ok but in order to fine tune the output of the different nodes I need a corresponding template, not just a general one. This also works ok but I need to repeat the select pattern in the match pattern for the template.
I like to define a variable that holds the pattern so it only needs to be defined once.
Below is my simplified style sheet and simplified html which does not work but gives an idea of what I want to accomplish.
Is it possible to use variables like this? I can use both xslt 1.0 and 2.0 if needed.
<xsl:stylesheet ...>
...
<xsl:variable name="first">div[#class='one']</xsl:variable>
<xsl:variable name="second">div[#class='two']</xsl:variable>
<xsl:template match="/*">
<xsl:apply-templates select="//$first"/>
<xsl:apply-templates select="//$second"/>
...
</xsl:template>
<xsl:template match="//$first">
<xsl:text>Custom text for class one:</xsl:text><xsl:value-of select="text()"/>
</xsl:template>
<xsl:template match="//$second">
<xsl:text>Custom text for class two:</xsl:text><xsl:value-of select="text()"/>
</xsl:template>
</xsl:stylesheet>
The html:
...
<div class="two">text from two</div>
<div class="one">text from one </div>
...
Desired output:
Custom text for class one: text from one
Custom text for class two: text from two

There is no way to use variables like that in XSLT 1 or 2. The only way would be to write a stylesheet producing a second stylesheet and execute that separately.
In XSLT 3 there are new features called static variables/parameters and shadow attributes that could help or there you could use the transform function to execute a newly generated stylesheet directly with XSLT instead of in a separate step with a host language.
But using XSLT 2 you can shorten the
<xsl:apply-templates select="//div[#class='one']"/>
<xsl:apply-templates select="//div[#class='two']"/>
to
<xsl:apply-templates select="//div[#class='one'], //div[#class='two']"/>
For completeness here is the XSLT 3 approach with two static parameters used in shadow attributes:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="3.0">
<xsl:param name="first" static="yes" as="xs:string" select=""div[#class='one']""/>
<xsl:param name="second" static="yes" as="xs:string" select=""div[#class='two']""/>
<xsl:template match="/*">
<xsl:apply-templates _select="//{$first}, //{$second}"/>
</xsl:template>
<xsl:template _match="{$first}">
<xsl:text>Custom text for class one:</xsl:text><xsl:value-of select="text()"/>
</xsl:template>
<xsl:template _match="{$second}">
<xsl:text>Custom text for class two:</xsl:text><xsl:value-of select="text()"/>
</xsl:template>
</xsl:stylesheet>

Variables in XSLT hold values, not fragments of expressions. (In other words, XSLT is not a macro language).
As an alternative to Martin's solution, which requires XSLT 3.0, you could consider using what are sometimes called "meta-stylesheets" - do a transformation as a pre-processing step on the stylesheet itself. You could even write the generic stylesheet to use the XSLT 3.0 syntax with shadow attributes like _match, and do an XSLT preprocessing phase to convert this to regular XSLT 1.0 or 2.0 syntax for execution.

Related

XSLT: What is the best way to apply a floating text that will display the full expression for acronyms from an external dictionary file?

I'm new to XSLT.
I need to apply floating text that displays a setting for acronyms, as soon as the mouse cursor hovers over a word that is abbreviated.
My input file is XML, and every word that should display acronyms is represented in the following format:
<abbreviation Id="E.G."/>
When the mouse cursor hovers over the word EG, the floating text will be displayed: "for example".
I thought to apply the floating text using the HTML abbr tag
And the code I wrote is:
<xsl:template match="abbreviation">
<abbr title="for example.">
<xsl:value-of select="#Id"/>
</abbr>
</xsl:template>
I want the "title" attribute to be given as a parameter the definition of the main boxes from an external file that will contain a dictionary of related abbreviations and explanations.
I would love to know how to apply my issue.
And also get ideas about the external dictionary file - what kind of file should you create? For example, I would like a good structure of the file.
Note:
I use OXYGEN EDITOR,
And I believe I can also get solutions in XSLT version 2 and 3
Suppose your external dictionary looks like:
dict.xml
<dictionary>
<entry abbr="C.V.">course of life</entry>
<entry abbr="E.G.">for example</entry>
<entry abbr="N.B.">note well</entry>
</dictionary>
You can then use a key to transform an input like:
XML
<root>
<abbreviation Id="E.G."/>
</root>
using:
XSLT 2.0 (untested)
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="path-to-dictionary" select="'dict.xml'"/>
<xsl:key name="abbr-lookup" match="entry" use="#abbr" />
<xsl:template match="/root">
<html>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="abbreviation">
<abbr title="{key('abbr-lookup', #Id, document($path-to-dictionary))}">
<xsl:value-of select="#Id"/>
</abbr>
</xsl:template>
</xsl:stylesheet>
to get:
Result
<html>
<body>
<abbr title="for example">E.G.</abbr>
</body>
</html>
To understand the syntax used, read about Attribute Value Templates.
Well, XSLT does not float any text, it is a programming language to transform XML (or with XSLT 2 or 3, other input formats) to XML, (X)HTML, plain text. You seem to want to transform your XML to HTML in the hope the HTML user agent or browser displays a tooltip of the title attribute.
As for using an XML file as a secondary input file, if you have abbrvs.xml with
<root><abbr key="E.G.">for example.</abbr>...</root>
then in XSLT you can use
<abbr title="{key('abbr', #Id, doc('abbrvs.xml'))}">
<xsl:value-of select="#id"/>
</abbr>
to pull that title attribute value from the secondary input file if the XSLT declares a key
<xsl:key name="abbr" match="abbr" use="#key"/>

Call custom function inside xslt 2

I want to call a function written inside xslt
this way i call it
<xsl:value-of select='foo:compareCI()'/>
this way is defined
<xsl:function name='foo:compareCI'>
<xsl:value-of select='jkhjkhjk'/>
</xsl:function>
and now i have to add it to header but i what is properly way to do it?
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
version='2.0'
xmlns:foo='http://whatever'>
Can someone help with this?
Make sure you use an XSLT 2.0 processor if you want to use xsl:function, a minimal example for a function returning a string constant is
<xsl:function name='foo:compareCI'>
<xsl:sequence select="'jkhjkhjk'"/>
</xsl:function>
with a namespace declared with e.g.
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:foo="http://example.com/foo">
you can then call the function with e.g.
<xsl:value-of select="foo:compareCI()"/>

XML to XHTML transformation

I need to transform a XML to XHTML. Within the XML are multiple paragraphs and embedded quotations e.g.
<para>SomeText</para>
<para><quote>SomeText</quote></para>
<para>SomeText</para>
I tried this:
<xsl:choose>
<xsl:when test="//text/para">
<xsl:for-each select="//text">
<xsl:for-each select="//para">
<p><xsl:value-of select="text()"/></p>
</xsl:for-each>
</xsl:for-each>
</xsl:when>
<xsl:when test="//text/para[quote]">
<xsl:for-each select="//text">
<xsl:for-each select="//para/quote">
<p><q><xsl:value-of select="text()"/></q></p>
</xsl:for-each>
</xsl:for-each>
</xsl:when>
</xsl:choose>
The second condition simply gets ignored however.
As #LarsH indicates, avoid <xsl:for-each>. Use template matching.
This simple transformation:
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="para">
<p><xsl:apply-templates /></p>
</xsl:template>
<xsl:template match="quote">
<q><xsl:apply-templates /></q>
</xsl:template>
</xsl:transform>
will turn this:
<text>
<para>SomeText</para>
<para><quote>SomeText</quote></para>
<para>SomeText</para>
</text>
into
<p>SomeText</p>
<p><q>SomeText</q></p>
<p>SomeText</p>
Further reading here on SO:
What are the differences between 'call-template' and 'apply-templates' in XSL?
Why does XSLT output all text by default?
What is the default select of XSLT apply-templates?
The problem is you're using XPath expressions that ignore context (aside from the context document), i.e. expressions that start with //. So if you have any <para> element anywhere in the document that has a <quote> child element, the first condition will always be true and the second condition will never be reached.
Really you want to move the for-each loop (or probably better, a set of templates with match patterns and apply-templates) outside of the choose/when conditions. In fact you will probably not need a choose/when at all, once you have the right templates.
(Moving in some info from comments)
XSLT and XPath are sufficiently complex that it's really worth learning the basics before trying to get something working by trial-and-error. You'll save a lot of time that way. For a general introduction I would recommend
How XSLT Works
Then for more advanced discussion of xsl:for-each vs. xsl:apply-templates, see
Jeni Tennison's blog post Matching templates, named templates or for-each? My rules of thumb
differences between for-each and templates in xsl?

How to get html as xml for XSLT conversion usage?

I would like to do some XSLT conversion with the HTML page with YQL. The following line is used to get HTML:
select * from html where url="http://example.com/somepage" and
xpath='//div[#class="article-text"]'
How can I apply select * from xslt where ... to the previous result?
Not sure as I haven't used YQL before, but I guess you have to go the other way round: using XSLT to get the result out of the HTML and than apply the YQL-Query to get the XML as result:
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:apply-templates select="//div[#class='article-text']" />
</xsl:template>
<xsl:template match="div[#class='article-text']">
<articletext>
<xsl:value-of select="."/>
</articletext>
</xsl:template>
YQL query:
select * from xslt where stylesheet="url/name-of.xsl" and
url="http://example.com/somepage"
This should result in
<results>
<articletext>Text of article</articletext>
</results>
As I don't know YQL but was used working with XSLT/XPath, I just googled about it and found this recommendable SO example: YQL column projection using XPATH . Instead of just pasting the link I adjusted the XSLT-Part of the example provided there to match your query.
Note that HTML is not an XML-based language (though XHTML is). If you want to operate on HTML using XML tools, you will need to either find an HTML parser (such as nekohtml, which is based on Apache Xerces) or preconvert the HTML to XHTML using something like the W3C's tidy tool.

XML to XML using XSLT

I am trying to create a new XML file from an exisiting one using XSL. When writing the new file, I want to mask data appearing in the accountname field.
This is how my XML looks like:
<?xml version="1.0" encoding="UTF-8"?>
<Sumit>
<AccountName>Sumit</AccountName>
<CCT_datasetT id="Table">
<row>
<CCTTitle2>Title</CCTTitle2>
</row>
</CCT_datasetT>
</Sumit>
Here is my XSL Code:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" omit-xml-declaration="no" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute namespace="{namespace-uri()}" name="{name()}"/>
</xsl:template>
<xsl:template match="AccountName">
<AccountName>acc_no</AccountName>
</xsl:template>
</xsl:stylesheet>
When I apply the XSL code to my XML, I get the following output:
<?xml version="1.0" encoding="UTF-16"?>
<Sumit>
<AccountName>acc_no</AccountName>
<CCT_datasetT id="">
<row>
<CCTTitle2>Title</CCTTitle2>
</row>
</CCT_datasetT>
</Sumit>
with the following issues:
1) It creates the output using UTF-16 encoding
2) The output of the second line is:
<CCT_datasetT id="">
The attribute value(Table) is missing.
Can anyone please tell me how do I get rid of these two issues. Many thanks.
#Evan Lenz:
Here is the javascript code:
var oArgs = WScript.Arguments;
if (oArgs.length == 0)
{
WScript.Echo ("Usage : cscript xslt.js xml xsl");
WScript.Quit();
}
xmlFile = oArgs(0) + ".xml";
xslFile = oArgs(1) + ".xsl";
var xml = new ActiveXObject("Microsoft.XMLDOM")
xml.async = false
xml.load(xmlFile)
// Load the XSL
var xsl = new ActiveXObject("Microsoft.XMLDOM")
xsl.async = false
xsl.load(xslFile)
// Transform
var msg = xml.transformNode(xsl)
var fso = new ActiveXObject("Scripting.FileSystemObject");
// Open the text file at the specified location with write mode
var txtFile = fso.OpenTextFile("Output.xml", 2, false, 0);
txtFile.Write(msg);
txtFile.close();
It creates the output in a new file "Output.xml", but I don't know why the encoding is getting changed. I am more concerned about it, because of the following reason:
My input XML containg the following code:
<Status></Status>
And in the output it appears as
<Status>
</Section>
A carriage return is introduced for all empty tags. I am not sure, if it has something to do with the encoding. Please suggest.
Many Thanks.
Remove your second template rule. The first template rule (the identity rule) will already copy attributes for you. By including the second one (which has the explicit <xsl:attribute> instruction), you're creating a conflict--an error condition, and the XSLT processor is recovering by picking the one that comes later in your stylesheet. The reason the "id" attribute is empty is that your second rule is creating a new attribute with the same name but with no value. But again, that second rule is unnecessary anyway, so you should just delete it. That will solve the missing attribute value issue.
As for the output encoding, it sounds like your XSLT processor is not honoring the <xsl:output> directive you've given it, or it's being invoked in a context (such as a server-side framework?) where the encoding is determined by the framework, rather than the XSLT code. What XSLT processor are you using and how are you invoking it?
UPDATE (re: character encoding):
The save Method (DOMDocument) documentation says this:
Character encoding is based on the encoding attribute in the XML declaration, such as <?xml version="1.0" encoding="windows-1252"?>. When no encoding attribute is specified, the default setting is UTF-8.
I would try using transformNodeToObject() and save() instead of outputting to a string.
I haven't tested this, but you probably want something like this:
var result = new ActiveXObject("Microsoft.XMLDOM")
// Transform
xml.transformNodeToObject(xsl, result);
result.save("Output.xml");
UPDATE (re: unwanted whitespace):
If you want to have ultimate control over what whitespace appears in the result, you should not specify indent="yes" on the <xsl:output> element. Try removing that.
Try this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" omit-xml-declaration="no" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- You don't actually need this template -->
<!-- but I think this was what you were trying to do -->
<xsl:template match="#*" priority="2">
<xsl:attribute namespace="{namespace-uri()}" name="{name()}"><xsl:value-of select="."/></xsl:attribute>
</xsl:template>
<xsl:template match="AccountName" priority="2">
<AccountName>acc_no</AccountName>
</xsl:template>
</xsl:stylesheet>
As for the UTF issue, you are doing the right thing.
From www.w3.org/TR/xslt:
The encoding attribute specifies the preferred encoding to use for outputting the result tree. XSLT processors are required to respect values of UTF-8 and UTF-16.