Is is possible to get all the xPaths used in an XSLT file?
For example:
XSLT File:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>My CD Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Title</th>
<th>Artist</th>
</tr>
<xsl:for-each select="/catalog/cd">
<tr>
<td>
<xsl:value-of select="title"/>
</td>
<td>
<xsl:value-of select="artist"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
As output I want a list like this:
/catalog/cd
/catalog/cd/title
/catalog/cd/artist
Maybe by making a custom post processor that outputs a line everytime an xPaths has matched/not been matched?
Ideas are welcome, because I'm very hopeless :)
Thanks!
You can use this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="*">
<xsl:for-each select="ancestor-or-self::*">
<xsl:text>/</xsl:text>
<xsl:value-of select="local-name(.)"/>
</xsl:for-each>
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
It's difficult because template rule matching in XSLT is very dynamic: if you have a template with match="cities" that calls xsl:apply-templates, and if you have another template with match="city", then the XSLT compiler can't know that there is a path /cities/city.
Internally Saxon has the capability to build a "path map" rather as you describe, and it's used to implement "document projection" in XQuery, but it's of very little use in XSLT because of dynamic template rule despatch.
So static analysis to determine the paths isn't going to get you very far. You also suggest that dynamic analysis - capturing the paths visited at run-time - might also be of interest. In principle you can do that in Saxon with a TraceListener. The difficulty is in determining exactly what you mean by "visited" - for example do you consider xsl:copy-of select="/" as visiting every node in the document, or only the root node?
Related
I'm a beginning user of xslt and xpath. Using an xpath on a command line (Ubuntu 14.04) with a xml file works, but the very same xpath in an xslt file returns nothing. I'm working with Juniper Junos xml files. Any suggestions?
Thanks,
George
The xml file begins with:
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:junos="http://xml.juniper.net/junos/12.3R8/junos">
<interface-information xmlns="http://xml.juniper.net/junos/12.3R8/junos-interface" junos:style="normal">
<physical-interface>
<name>fe-0/1/0</name>
<logical-interface>
<name>fe-0/1/0.0</name>
...
The command line that works in Ubuntu 14.04 is:
xpath -e "/rpc-reply/interface-information/physical-interface/logical-interface/name" interfaces.xml
The xslt file is:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>Interfaces</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Name</th>
</tr>
<xsl:for-each select="/rpc-reply/interface-information/physical-interface/logical-interface">
<tr>
<td><xsl:value-of select="name"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
The problem here is quite simple. You are not using namespaces in your XPaths. Apparently your command line utility doesn't care, or evaluates XPaths based on the elements' QNames ignores default namespaces and does some other non-standard handling of namespaces as well.
The solution:
Declare prefixes at the top of your stylesheet:
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:nbase="urn:ietf:params:xml:ns:netconf:base:1.0"
xmlns:junosi="http://xml.juniper.net/junos/12.3R8/junos-interface"
>
use those prefixes in your XPaths:
<xsl:for-each select="/nbase:rpc-reply/junosi:interface-information
/junosi:physical-interface/junosi:logical-interface">
<tr>
<td><xsl:value-of select="junosi:name"/></td>
</tr>
</xsl:for-each>
Input xml is
<getArtifactContentResponse>
<return>
<![CDATA[
<metadata>
<overview>
<name>scannapp</name>
<developerId>developer702</developerId>
<stateId>2</stateId>
<serverURL>abc.com</serverURL>
<id>cspapp1103</id>
<description>scann doc</description>
<hostingTypeId>1</hostingTypeId>
</overview>
</metadata>
]]>
</return>
</getArtifactContentResponse>
Below is the stylesheet which I have developed. I am able to retrieve the XML inside Cdata but not able to fetch the elements value.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" >
<xsl:output method="html" encoding="utf-8" omit-xml-declaration="no" indent="no"/>
<xsl:template match="/">
<html>
<body>
<h1>Company Details</h1>
<table border="1">
<tr>
<th>name</th>
<th>developerId</th>
<th>Id</th>
</tr>table
<xsl:variable name ="data" select="//getArtifactContentResponse/return/node()" />
<tr>
<td>
<xsl:value-of select="$data/metadata/overview/name" disable-output-escaping="yes"/>
</td>
<td>
<xsl:value-of select="$data/metadata/overview/developerId" />
</td>
<td>
<xsl:value-of select="$data/metadata/overview/Id" />
</td>
</tr>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Output coming as
<html><body><h1>Company Details</h1><table border="1"><tr><th>name</th><th>developerId</th><th>serverURL</th></tr>table
<tr><td></td><td></td><td></td></tr></table></body></html>
Expected output
<html><body><h1>Company Details</h1><table border="1"><tr><th>name</th><th>developerId</th><th>serverURL</th></tr>table
<tr><td>scannapp</td><td>developer702</td><td>cspapp1103</td></tr></table></body></html>
I want to take the value name,developerId,Id and print to HtML. How to do that Please help me. Using XSLT version1.0.
AFAIK, there's no way to parse CDATA as XML.
Using an extension function to parse the section as text would be nice, but not really necessary. Here's an example that extracts the three items you're after:
...
<xsl:variable name ="cdata" select="/getArtifactContentResponse/return" />
<name>
<xsl:value-of select="substring-before(substring-after($cdata, '<name>'), '</name>')"/>
</name>
<developerId>
<xsl:value-of select="substring-before(substring-after($cdata, '<developerId>'), '</developerId>')"/>
</developerId>
<id>
<xsl:value-of select="substring-before(substring-after($cdata, '<id>'), '</id>')"/>
</id>
...
I am trying to extract data out of the following XML fragment:
<?xml version= "1.0" ?>
<Stmts xmlns="http://tempuri.org/Statement.xsd" Generation="2011-08-01T12:41:41" >
<StatementDetail AccountStatus="Open" CompanyID="" TransactionCount="182" >
<Transactions>
<Manual.../>
...
</Transactions>
</StatementDetail>
</Stmts>
Notice that the element has a xmlns attribute.
When I try to use the following XSL I get no data.
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>CabCharge</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Batch</th>
<th>TransNo</th>
</tr>
<xsl:for-each select="Stmt/StatementDetail/Transactions/Manual">
<tr>
<td><xsl:value-of select="#Batch"/></td>
<td><xsl:value-of select="#TransNo"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
BUT! If I remove the XMLNS attribute from the element, I do get data.
What do I need to specifiy in the XSL to recognise the namespace???
Thanks.
Make sure to declare the default namespace of the document in your stylesheet, like:
<xsl:stylesheet version="1.0"
xmlns="http://tempuri.org/Statement.xsd"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
Otherwise the XSLT processor will consider the elements referenced in your stylesheet as belonging to default namespace null.
Moreover, if you want the namespace exlcuded from the output document, you need to declare null namespace for the root literal element html, like:
<xsl:template match="/">
<html xmlns="">
<!-- your stuff -->
</html>
</xsl:template>
Do note also that:
In the xsl:for-each you are selecting the wrong element (Stmt in place of Stmts)
The attributes Batch and TransNo do not exist in your input documents.
This is for a category index of blog posts-I only want to show the category once using xsl v1.0. There will be multiple posts in each category. The desired result is:
Cat Name 1
cat Name 2
Cat Name 3
I assume grouping the items and only showing the first one in a group (using the cat name as a key) would work but the Muenchian method is a bit beyond my abilities. So a simpler method or a simple explanation of the Muenchian method would be most appreciated.
The xml
<Root>
<Schema>
<Field Type="Lookup" DisplayName="Category name" Required="FALSE" ShowField="Category_x0020_name" Name="Category_x0020_name" Group="" />
<Field ReadOnly="TRUE" Type="Computed" Name="LinkTitle" DisplayName="Post number" />
</Schema>
<Data ItemCount="1">
<Row Category_x0020_name="" LinkTitle="" />
</Data>
</Root>
The xsl:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" />
<xsl:template match="/">
<table border="0" cellpadding="0" cellspacing="0">
<h3>Categories</h3>
<xsl:for-each select="//Data/Row">
<xsl:if test="./#Category_x0020_name !=''">
<tr valign="top"> <td>
<a href="/cat{./#LinkTitle}.aspx">
<xsl:value-of select="./#Category_x0020_name" /></a></td> </tr>
</xsl:if>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Do not be afraid of Meunchian method. Use it once, and you will be able to apply it whenever you need it.
collect the wanted data to group in a key
apply the templates to only one node with same key using a predicate like
generate-id()=generate-id(key(...)[1])
That's what you need know to use Meunchian grouping. Here to get you started:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="Cat" match="Data/Row" use="#Category_x0020_name"/>
<xsl:template match="/*/Data">
<xsl:apply-templates select="Row
[generate-id()
= generate-id(key('Cat',#Category_x0020_name)[1])]"/>
</xsl:template>
<xsl:template match="Row">
<xsl:value-of select="concat(#Category_x0020_name,'
')"/>
</xsl:template>
</xsl:stylesheet>
Since you don't need to list the members of each category, and assuming your data sets are not very large so performance is not a big factor, you can forego Muenchian grouping for something a little less elegant. Just change your <xsl:if test> to:
<xsl:if test="./#Category_x0020_name !='' and
not(./#Category_x0020_name = preceding::Row/#Category_x0020_name)">
In other words, only output a category name the first time it occurs.
Incidentally, you can remove the ./ wherever it occurs at the beginning of an XPath expression. It's redundant. It means "starting from the context node," but you're already starting from the context node. If you want to leave it in for readability or something, that's OK.
Then with input like
<Root>...
<Data ItemCount="1">
<Row Category_x0020_name="foo" LinkTitle="Foo" />
<Row Category_x0020_name="bar" LinkTitle="Bar" />
<Row Category_x0020_name="foo" LinkTitle="Foo" />
</Data>
</Root>
You get this output:
<table border="0" cellpadding="0" cellspacing="0">
<h3>Categories</h3>
<tr valign="top">
<td>foo</td>
</tr>
<tr valign="top">
<td>bar</td>
</tr>
</table>
I'm new to XSLT. I'm trying to change the font size of a specific text in XML file using XSLT. For eg- I have the CDCatalog.xml file with following data.
<?xml version="1.0" encoding="ISO-8859-1" ?>
<?xml-stylesheet type="text/xsl" href="cdcat.xsl"?>
<catalog>
<cd>
<title>Empire Burlesque</title>
<artist><SmallText>Bob Dylan</SmallText><LineBreak/>*</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
</catalog>
and the cdCat.XSL file is-
<?xml version="1.0" encoding="ISO-8859-1" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:include href="cdCatalog.xsl" /> <!-- I added this -->
<xsl:template match="/">
<html>
<body>
<h2>My CD Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th align="left">Title</th>
<th align="left">Artist</th>
</tr>
<xsl:for-each select="catalog/cd">
<tr>
<td>
<xsl:value-of select="title" />
</td>
<td>
<xsl:value-of select="artist" />
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
I added a new xsl file cdCatalog.XSL file with following details-
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="LineBreak">
<br/>
</xsl:template>
<xsl:template match="Superscript">
<sup>
<xsl:value-of select="."/>
</sup>
</xsl:template>
<xsl:template match="SmallText">
<font size="1">
<xsl:value-of select="."/>
</font>
</xsl:template>
</xsl:stylesheet>
and included this file in the CDCat.xsl file.and added the tags - <smallText>, <LineBreak> in the CdCatalog.xml file. now when I open the xml file i dont see the LineBreak nor the font size difference. Can anyone please suggest if I'm missing something.
Thanks in advance
Sai
You need to use apply-templates to indicate where your template matches should take effect.
XML says nothing about presentation, that's the whole point. It's a data format.
If you want your XSLT to output to something where presentation matters I suggest you transform to HTML and get let HTML/CSS handle the styling.
Having seen your actual code now (hint: use the formatting when creating questions) don't use the font tag. What you want semantically and in practice is just headers <h1>, <h2>, <h3> etc, and I'd still suggest you add a CSS link in there. Oh and <xsl:output method="html" />
In-between these two opening tags:
<html>
<body>
...I'd place a link to a style sheet that defines the font sizes. Alternatively (and useful if you want a self contained HTML file to email around) you could put a style block there instead.