I have several unordered lists in an XML file that I need to convert to HTML with an XSL file. These lists inside my XML look like:
<Food>
<FoodItem ID="1">
<Content><![CDATA[<ul><li>Apples</li><li>Pears</li><li>Oranges</li><ul>]]></Content>
</FoodItem>
</Food>
I would like to grab the unordered lists from the XML file and output them in my XSL file like so that I can stlye them with CSS. The result should be like this:
<div class="food">
<ul class="fooditems">
<li class="fooditem">Apples</li>
<li class="fooditem">Pears</li>
<li class="fooditem">Oranges</li>
</ul>
</div>
However, <xsl:value-of select="key('fooditem', 1)/Content
only gave me a string of <ul><li>Apples</li><li>Pears</li><li>Oranges</li></ul>
.
The key is defined as <xsl:key name="fooditem" match="FoodItem" use="@ID" />
.
How can I get the individual list tags including their content out of the <![CDATA[]]>
tag?
First of all, the ul
/li
elements are not as nodes in your input XML but rather as escaped markup in a CDATA section. So you would need to parse that markup before being able to transform nodes. With XSLT 3 (as supported by Saxon 9.8 and later or AltovaXML 2017 and later) you can use the parse-xml
or parse-xml-fragment
XPath 3 function to parse a string with an XML document or an XML fragment into nodes.
However, at least in your question's sample, the escaped markup is not well-formed XML as it doesn't have a properly closed ul
element, it is <ul><li>Apples</li><li>Pears</li><li>Oranges</li><ul>
where it should be <ul><li>Apples</li><li>Pears</li><li>Oranges</li></ul>
to be parseable as XML.
So depending on the real sample, if that is well-formed, you can use
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="html" indent="yes" html-version="5"/>
<xsl:template match="FoodItem">
<div class="food">
<xsl:apply-templates select="parse-xml-fragment(Content)/node()" mode="food"/>
</div>
</xsl:template>
<xsl:mode name="food" on-no-match="shallow-copy"/>
<xsl:template match="ul" mode="food">
<ul class="fooditems">
<xsl:apply-templates select="@* , node()" mode="#current"/>
</ul>
</xsl:template>
<xsl:template match="li" mode="food">
<li class="fooditem">
<xsl:apply-templates select="@* , node()" mode="#current"/>
</li>
</xsl:template>
</xsl:stylesheet>
as shown in https://xsltfiddle.liberty-development.net/3NJ38ZH/1 and get your wanted output
<div class="food">
<ul class="fooditems">
<li class="fooditem">Apples</li>
<li class="fooditem">Pears</li>
<li class="fooditem">Oranges</li>
</ul>
</div>
How can I get the individual list tags including their content out of the tag?
For an XSLT-1.0 solution, you have to use a two step approach:
- Get the items out of the CDATA section.
- Apply the templates you want to apply.
One problem is that the CDATA sample is not well-formed XML. So you have to change your input XML (changing the last <ul>
to </ul>
) to the following:
<Food>
<FoodItem ID="1">
<Content><![CDATA[<ul><li>Apples</li><li>Pears</li><li>Oranges</li></ul>]]></Content>
</FoodItem>
</Food>
For the first step you can use this (assuming that your XSLT processor does support the disable-output-escaping attribute):
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<!-- Identity template -->
<xsl:template match="node()[not(self::text())]|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:value-of select="." disable-output-escaping="yes" />
</xsl:template>
</xsl:stylesheet>
Now you can apply your XSLT template to the output of the above stylesheet.
The result of this transformation should be:
<?xml version="1.0" encoding="UTF-8"?>
<Food>
<FoodItem ID="1">
<Content>
<ul>
<li>Apples</li>
<li>Pears</li>
<li>Oranges</li>
<ul>
</Content>
</FoodItem>
</Food>
Now, the second step is applying a different XSLT-1.0 stylesheet to this output.
So apply this second XSLT-1.0 stylesheet:
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/Food/FoodItem/Content/ul">
<div class="food">
<ul class="fooditems">
<xsl:apply-templates select="*" />
</ul>
</div>
</xsl:template>
<xsl:template match="li">
<li class="fooditem"><xsl:value-of select="." /></li>
</xsl:template>
</xsl:stylesheet>
The result of this second step is:
<div class="food">
<ul class="fooditems">
<li class="fooditem">Apples</li>
<li class="fooditem">Pears</li>
<li class="fooditem">Oranges</li>
</ul>
</div>
which is as desired.
SOLVED!
With newer versions of XSLT processors (3.0 and above) you can (possibly) do both steps in one stylesheet.