how to iterate through XML param in XSLT - xslt

I have an XML file that I am transforming via XSLT. I am passing an XML as parameter to the XSLT via C#. The parameter's name is attachment and it contains XML. It is written as follows:
StringWriter sw = new StringWriter();
XmlTextWriter w = new XmlTextWriter(sw);
w.WriteStartElement("root");
if (!string.IsNullOrEmpty(sWordFileName)) {
w.WriteStartElement("mylink", sWordFileName);
w.WriteEndElement();
}
if (!string.IsNullOrEmpty(sPDFFileName)) {
w.WriteStartElement("mylink", sPDFFileName);
w.WriteEndElement();
}
w.Close();
XPathDocument doc = new XPathDocument(new StringReader(sw.ToString()));
XPathNavigator nav = doc.CreateNavigator();
_exportSet[currentExportSet].Format.ParamList["attachment"] = nav.Select("./*");
My xml parameter looks like
<root><attachment xmlns=file1><attachment xmlns=file2></root>
Now in XSLT I need to iterate through this XML param and create a link.
Here is my XSLT
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:my-scripts="urn:my-scripts"
xmlns="factiva.com/fcs/schemas/newsSummaries">
<xsl:param name="attachment"/>
<xsl:for-each select="$attachment">
<a target="_blank" href="#"><xsl:copy-of select="."/></a>
</xsl:for-each>
</xsl:stylesheet>
But it doesn't create a link.

An XSLT parameter is different than an XML tag name. Parameters are passed using the tag as described here.
As stated in the comments below, this problem is not too different from what is provided in the link above.
StringWriter sw = new StringWriter();
XmlTextWriter w = new XmlTextWriter(sw);
w.WriteStartElement("root");
if (!string.IsNullOrEmpty(sWordFileName)) {
w.WriteStartElement("attachment", sWordFileName);
w.WriteAttributeString("name", sWordFileName);
w.WriteEndElement();
}
if (!string.IsNullOrEmpty(sPDFFileName)) {
w.WriteStartElement("attachment");
w.WriteAttributeString("name", sPDFFileName);
w.WriteEndElement();
}
w.WriteEndElement();
w.Close();
XPathDocument doc = new XPathDocument(new StringReader(sw.ToString()));
XPathNavigator nav = doc.CreateNavigator();
XsltArgumentList xslArg = new XsltArgumentList();
xslArg.AddParam("attachment","",nav);
Here would be XSL to match per Accessing parameters which contain mark-up:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:my-scripts="urn:my-scripts"
xmlns="factiva.com/fcs/schemas/newsSummaries">
<xsl:param name="attachment" />
<xsl:template match="/">
<xsl:apply-templates select="$attachment"/>
</xsl:template>
<xsl:template match="attachment">
<a target="_blank" href="{#name}">{#name}</a>
</xsl:template>
</xsl:stylesheet>

You can pass any XPath/XSLT data type as parameters. How to do that entirely depends on the XSLT processor implementation.
As proof this stylesheet, with any input (not used):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="attachment" select="document('parameter.xml')/root"/>
<xsl:template match="/">
<xsl:apply-templates select="$attachment"/>
</xsl:template>
<xsl:template match="attachment">
<a target="_blank" href="{#href}">Link</a>
</xsl:template>
</xsl:stylesheet>
And parameter.xml resource as:
<root>
<attachment href="file1"/>
<attachment href="file2"/>
</root>
Output:
<a target="_blank" href="file1">Link</a>
<a target="_blank" href="file2">Link</a>

It should read <xsl:for-each select="attachment">.... There is no $ sign because attachment is the name of an XML element, not a variable.
EDIT after you've given the full XSLT and XML.
There are several problems with your XML:
All tags should be closed.
You may not use the xmlns for anything else that it's meant for — namespaces.
You must have double quotes around the attribute values
So a correct version of the XML file would be (for instance):
<root>
<attachment ptr="file1" />
<attachment ptr="file2" />
</root>
The XSLT file has some issues too:
The xsl namespace should be bound to the exact URI http://www.w3.org/1999/XSL/Transform.
You must have at least a template so that the XSLT transform processes your input XML document.
A correct version would be, for instance:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/root">
<xsl:for-each select="attachment">
<a target="_blank" href="{#ptr}"><xsl:value-of select="#ptr" /></a>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I'm not sure it is exactly what you want, but for the above document it produces the following fragment:
<a target="_blank" href="file1">file1</a>
<a target="_blank" href="file2">file2</a>

You would want to put the value of your attribute that has the link in it like so:
<xsl:value-of select="#YourAttribute"/>
This selects an attribute for the current xml element.

The code you posted is somewhat incorrect. Where are the quotes, what is $attachment?
You probably forgot to mention namespace, to select correctly, you need to write select="//file1:attachment" or sth like that.

Related

How to copy all child nodes of any type of a template context element

I am transforming XML into HTML using XSLT.
I have the following XML structure:
<root>
<element>
<subelement>
This is some html text which should be <span class="highlight">displayed highlighted</span>.
</subelement>
</element>
</root>
I use the following template for the transformation:
<xsl:template name="subelement">
<xsl:value-of select="." />
</xsl:template>
Unfortunately, I lose the <span>-tags.
Is there a way to keep them so the HTML is displayed correctly (highlighted)?
The correct way to get the all the contents of the current matching node (text nodes included) is:
<xsl:template match="subelement">
<xsl:copy-of select="node()"/>
</xsl:template>
This will copy everything descendent.
Try using <xsl:copy-of... instead of <xsl:value-of... for example:
<xsl:template name="subelement">
<xsl:copy-of select="*" />
</xsl:template>
Note the * which will stop the <subelement></subelement> bits being output to the results, rather than using . which will include the <subelement></subelement> bits .
For example, the xsl stylesheet:
<?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"
exclude-result-prefixes="xs"
version="2.0">
<xsl:template match="root/element">
<output>
<xsl:apply-templates select="subelement"/>
</output>
</xsl:template>
<xsl:template match="subelement">
<xsl:copy-of select="*"/>
</xsl:template>
</xsl:stylesheet>
when applied to your example xml file returns:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<span class="highlight">displayed highlighted</span>
</output>
The <xsl:value-of> declaration takes the concatenated contents of all text nodes within the element, in sequential order, and doesn't output any elements at all.
I'd recommend using <xsl:apply-templates> instead. Where it finds a text node, it will output the contents as-is, but you would need to define a template for handling span tags to convert them to html ones. If that span tag IS an html tag, then strictly speaking, you should have separate namespaces for your own document structure and html.

XSL namespaces and xsl:for-each

I'm currently stuck trying to use XSL to transform a XML document into HTML. The XML document uses namespaces and I don't really have too much experience with XSL let alone namespaces. Basically all I want to do is grab every instance of s:treatment and output this as a list. I've changed data so not to expose the website I'm doing this for. I'm using Classic ASP (can't update to ASP.NET) to transform the XML on the server, so the XSL has to be version 1 :(
Any help would be really appreciated here as I just can't figure out what's going wrong.
Here's the XML:
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title type="text">a</title>
<id>a</id>
<rights type="text">© Crown Copyright 2009</rights>
<updated>2011-01-19T11:23:25Z</updated>
<category term="Service"/>
<author>
<name>c</name>
<uri>http://www.meh.com</uri>
<email>erwt</email>
</author>
<complete xmlns="http://purl.org/syndication/history/1.0"/>
<entry>
<id>http://meh.com/services</id>
<title type="text">title</title>
<updated>2010-06-18T19:52:12+01:00</updated>
<link rel="self" title="title" href="meh"/>
<link rel="alternate" title="title" href="id"/>
<content type="application/xml">
<s:service xmlns:s="http://syndication.nhschoices.nhs.uk/services">
<s:type code="S">h</s:type>
<s:deliverer>j</s:deliverer>
<s:parent>k</s:parent>
<s:treatments>
<s:treatment>fissure</s:treatment>
<s:treatment>fistula</s:treatment>
<s:treatment>liver</s:treatment>
<s:treatment>pancreas</s:treatment>
<s:treatment>Cirrhosis</s:treatment>
<s:treatment>Coeliac disease</s:treatment>
<s:treatment>Crohn's disease</s:treatment>
<s:treatment>Diagnostic endoscopy of the stomach</s:treatment>
<s:treatment>Diverticular problems</s:treatment>
<s:treatment>Gastrectomy</s:treatment>
<s:treatment>Gastroenteritis</s:treatment>
<s:treatment>Gastroenterology</s:treatment>
<s:treatment>Gastroesophageal reflux disease(GORD)</s:treatment>
<s:treatment>Hepatitis</s:treatment>
<s:treatment>Hepatitis A</s:treatment>
<s:treatment>Hepatitis B</s:treatment>
<s:treatment>Hepatitis C</s:treatment>
<s:treatment>Hernia hiatus</s:treatment>
<s:treatment>Ileostomy</s:treatment>
<s:treatment>Irritable bowel syndrome</s:treatment>
<s:treatment>Liver disease (alcoholic)</s:treatment>
<s:treatment>Obesity</s:treatment>
<s:treatment>Pancreatitis</s:treatment>
<s:treatment>Peptic ulcer</s:treatment>
<s:treatment>Peritonitis</s:treatment>
<s:treatment>Primary biliary cirrhosis</s:treatment>
<s:treatment>Surgery for haemorrhoids</s:treatment>
<s:treatment>Therapeutic endoscopy on the stomach</s:treatment>
<s:treatment>Ulcerative colitis</s:treatment>
</s:treatments>
<s:phone>020 8</s:phone>
<s:fax>020 8</s:fax>
<s:email>jj</s:email>
<s:website>oiyi</s:website>
</s:service>
</content>
</entry>
</feed>
As you can see it uses the Atom namespace, Purl syndication namespace and NHS Choices namespace only the NHS Choices namespace actually uses the prefix though, this is what is confusing me really. How would I declare the other namespaces and do I even need to?
Here's my XSL:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:s="http://syndication.nhschoices.nhs.uk/services">
<ul>
<xsl:template match="/">
<xsl:for-each select="feed/entry/content/s:service/s:treatments/s:treatment">
<li><xsl:text></xsl:text></li>
</xsl:for-each>
</xsl:template>
</ul>
</xsl:stylesheet>
This XSL was taken from the w3schools example so apologies if it's bare.
Any ideas what I need to do to make this work?
Thanks,
Colin
First of all your XSLT is not valid at all (uloutside xsl:template is not valid XSL).
Further more as there is a default namespace on your feed tag you have also to define this in your xslt. You also better make usage of mathcing templates intead of for-each loops in CSLT.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:s="http://syndication.nhschoices.nhs.uk/services"
xmlns:a="http://www.w3.org/2005/Atom" exclude-result-prefixes="a s">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<ul>
<xsl:apply-templates
select="a:feed/a:entry/a:content/s:service/s:treatments/s:treatment" />
</ul>
</xsl:template>
<xsl:template match="s:treatment">
<li>
<xsl:value-of select="." />
</li>
</xsl:template>
</xsl:stylesheet>

Problem with XSLT getting values from tags with namespace prefixes

I have a specific problem getting values for width and height out of some XML that has namespace prefixes defined. I can get other values such as SomeText from RelatedMaterial quite easily using normal xpath with namespace "n:" but unable to get values for width and height.
Sample XML:
<Description>
<Information>
<GroupInformation xml:lang="en">
<BasicDescription>
<RelatedMaterial>
<SomeText>Hello</SomeText>
<t:ContentProperties>
<t:ContentAttributes>
<t:Width>555</t:Width>
<t:Height>444</t:Height>
</t:ContentAttributes>
</t:ContentProperties>
</RelatedMaterial>
</BasicDescription>
</GroupInformation>
</Information>
</Description>
Here is an extract from the XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:n="urn:t:myfoo:2010" xmlns:tva2="urn:t:myfoo:extended:2008"
<xsl:apply-templates select="n:Description/n:Information/n:GroupInformation"/>
<xsl:template match="n:GroupInformation">
<width>
<xsl:value-of select="n:BasicDescription/n:RelatedMaterial/t:ContentProperties/t:ContentAttributes/t:Width"/>
</width>
</xsl:template>
The above XSLT does not work for getting the width. Any ideas?
I'm not sure you have realised that both your input and XSLT is invalid, it's always better to provide working examples.
Anyway, if we look at the XPath expression n:BasicDescription/n:RelatedMaterial/t:ContentProperties/t:ContentAttributes/t:Width you're using a prefix n which is mapped to urn:t:myfoo:2010 but when the data infact is in the default namespace. The same goes for the t prefix which isn't defined at all in neither the input data nor XSLT.
You need to define the namespaces on "both sides", in the XML data and the XSLT transformation and they need to be the same, not the prefixes, but the URI.
Somebody else could probably explain this better than me.
I've corrected your example and added a few things to make this work.
Input:
<?xml version="1.0" encoding="UTF-8"?>
<Description
xmlns="urn:t:myfoo:2010"
xmlns:t="something...">
<Information>
<GroupInformation xml:lang="en">
<BasicDescription>
<RelatedMaterial>
<SomeText>Hello</SomeText>
<t:ContentProperties>
<t:ContentAttributes>
<t:Width>555</t:Width>
<t:Height>444</t:Height>
</t:ContentAttributes>
</t:ContentProperties>
</RelatedMaterial>
</BasicDescription>
</GroupInformation>
</Information>
</Description>
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:n="urn:t:myfoo:2010"
xmlns:t="something...">
<xsl:template match="/">
<xsl:apply-templates select="n:Description/n:Information/n:GroupInformation"/>
</xsl:template>
<xsl:template match="n:GroupInformation">
<xsl:element name="width">
<xsl:value-of select="n:BasicDescription/n:RelatedMaterial/t:ContentProperties/t:ContentAttributes/t:Width"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="UTF-8"?>
<width>555</width>

Looping over distinct values

Given a variable which returns a list of distinct States using the distinct-values() function, is there a way to tokenize the variable in a for-each loop?
<States>
<State>AL</State>
<State>AL</State>
<State>NM</State>
</States>
The following variable returns AL and NM, but I can't iterate over it using for-each. Is there a way around this?
<xsl:variable name="FormStates" select="distinct-values(States/State)"/>
<xsl:for-each select="$FormStates">
XSLT 2.0 ok.
The distinct-values() function returns a sequence of values which you should be able to iterate over. The result is so to speak "tokenized".
fn:distinct-values('AL', 'AL', 'NL') returns the sequence ('AL', 'NL').
If you output the variable with xsl:value-of it will return the string "AL NL" only because the default sequence separator for xsl:value-of is a single space character. This is something you could change with the #separator attribute:
Input
<?xml version="1.0" encoding="UTF-8"?>
<States>
<State>AL</State>
<State>AL</State>
<State>NM</State>
</States>
XSLT
<?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:variable name="FormStates" select="distinct-values(States/State)"/>
<xsl:comment>xsl:value-of</xsl:comment>
<xsl:value-of select="$FormStates" separator=":"/>
<xsl:comment>xsl:for-each</xsl:comment>
<xsl:for-each select="$FormStates">
<xsl:value-of select="."/>
<xsl:text>:</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output
<?xml version="1.0" encoding="UTF-8"?>
<!--xsl:value-of-->
AL:NM
<!--xsl:for-each-->
AL:NM:
Here's an XSLT 1.0 solution that I've used in the past.
<xsl:template match="/">
<ul>
<xsl:for-each select="//State[not(.=preceding::*)]">
<li>
<xsl:value-of select="."/>
</li>
</xsl:for-each>
</ul>
</xsl:template>
Returns:
<ul xmlns="http://www.w3.org/1999/xhtml">
<li>AL</li>
<li>NM</li>
</ul>
In theory it should work; are you sure the XPath given to the distinct-values function is correct? The code you've given requires that the States element is a sibling of the forms element.
You could insert <xsl:value-of select="count($FormStates)"> immediately after the variable declaration to confirm if it is being set properly.

XPath: Selecting nodes included by an <xsl:include>

There's an XSL that includes another XSL:
<xsl:include href="registered.xsl"/>
That included file has a list of nodes:
<g:registered>
<node1/>
<node2/>
</g:registered>
Documentation says that "the children of the <xsl:stylesheet> element in this document replace the element in the including document", so I would think that, given the include directive has worked, I can select g:registered nodes like if they always belonged to the inluding document:
select="document('')/xsi:schema/g:registered"
That returns an empty nodeset though.
However, this:
select="document('registered.xsl')/xsi:schema/g:registered"
does select what is required, but that, as I suppose, means opening the included file for the second time which doesn't seem nice to me.
So how do I select those includes without opening the file second time?
EDIT
Requested document structure:
Included document:
<?xml version='1.0' encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:g="http://www.sample.com/ns">
<g:registered-templates>
<SampleTemplate/>
<WrongTemplate/>
</g:registered-templates>
<xsl:include href="Sample Template.xsl" />
<xsl:include href="Wrong Template.xsl" />
</xsl:stylesheet>
Including document:
<?xml version='1.0' encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:g="http://www.sample.com/ns">
<xsl:output method="text"/>
<xsl:include href="Label Registration.xsl"/>
<!-- How do I refer to just loaded inclusion without directing engine to the file again? -->
<xsl:variable name="template-names" select="document('Label Registration.xsl')/xsl:stylesheet/g:registered-templates"/>
<xsl:template match="Job">
<xsl:for-each select="WorkItem">
<xsl:apply-templates select="$template-names/*[local-name()=current()/#name]">
<xsl:with-param name="context" select="." />
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Selecting into your variable template-names queries the transformation source document - not your included stylesheet.
If you want to refer to g:registered-templates you have to point to the file like a second source document.
EDIT
I'm not really sure. but it looks like you want to create an element according to the attribute value.
In that case this post will be interesting for you.
<xsl:for-each select="WorkItem">
<xsl:element name="{Type}" >
<xsl:value-of select="current()/#name"/>
</xsl:element>
</xsl:for-each>
Ok, my understanding was wrong.
The document('') function opens the file anyway, so it has no advantages, performance-wise, over document('registered.xsl'). And since it queries the file, not the now-modified DOM model of current document, the result does not include my includes.
And it is not possible to query DOM model of the transformation template itself, as far as I'm concerned.