We are trying to transform an HTML template with placeholders into the final HTML using XSLT.
The (simplified) HTML template looks like:
<p>
<span class="condition" id="v6">Some text here
<span class="placeholder" id="v1" />
</span>
</p>
The transformation should
replace every span element with placeholder class;
hide or show each span element that contains a condition class
The (simplified) XSLT we have is:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:local="urn:local"
xmlns:s0="http://www.w3.org/1999/xhtml">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<!-- Take the HTML template -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!--Replace every placeholder in the HTML template with the value from the XML data-->
<xsl:template match="span[#class='placeholder']">
<xsl:variable name="this" select="current()/#id"/>
<xsl:value-of select="'replaced'" />
</xsl:template>
<!-- Handle conditions based on custom logic -->
<xsl:template match="span[#class='condition']">
<xsl:variable name="this" select="current()/#id"/>
<xsl:if test="$this = 'v6'">
<xsl:value-of select="current()" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
If the placeholder span is not nested into the condition span, everything works fine. However, in the HTML example above, it doesn't work, the output is:
<p>Some text here</p>
We would like it to be:
<p>
Some text here
replaced
</p>
It seems as if the condition is executed, but the placeholder is not executed or somehow overwritten; so basically, the placeholder span elements are not being replaced.
Does anyone has an explanation for this, and what we are doing wrong?
Thanks!
Meanwhile found a solution:
<!-- Handle conditions based on custom logic -->
<xsl:template match="span[#class='condition']">
<xsl:variable name="this" select="current()/#id"/>
<xsl:if test="$this = 'v6'">
<xsl:apply-templates select="node()"/> <<<<----------------
</xsl:if>
</xsl:template>
Instead of taking the current text, I just re-apply the node.
Related
I have an XML file with a series of pairings like the following:
<metamark function="let-stand" spanTo="#meta-93"/>some text between the two empty nodes<anchor xml:id="meta-93"/>
In other words, the text is always preceded with a metamark tag with #function='let-stand' and a spanTo with a unique value. And the text is always followed with an anchor tag whose #xml:id value match that of the #spanTo value on the metamark.
When transforming such text via XSLT into HTML, I would like to wrap it in a span tag as follows:
<span class="dotted">some text between the two empty nodes</span>
How can I achieve this? Note that the text between the two empty nodes will always be siblings. The value I've put on the span #class is arbitrary. I'm just using "dotted" for demonstration purposes here.
The basic idea is that for each metamark:
create span tag,
get following siblings of the current metamark,
which as a following sibling have anchor tag with proper id (end point, exclusive),
and apply templates to them.
Of course, you have to block "normal" template application within the parent tag of your metamark tags.
Try the following transformation:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="html" doctype-public="XSLT-compat"
encoding="UTF-8" indent="yes" />
<xsl:template match="metamark">
<xsl:element name="span">
<xsl:attribute name="class" select="'dotted'"/>
<xsl:variable name="termId" select="substring(#spanTo, 2)"/>
<xsl:variable name="srcRange" select="following-sibling::node()
[following-sibling::anchor[#xml:id=$termId]]"/>
<xsl:apply-templates select="$srcRange"/>
</xsl:element>
<xsl:text>
</xsl:text>
</xsl:template>
<!-- In "main" process only "metamark" tags -->
<xsl:template match="main">
<xsl:apply-templates select="metamark"/>
</xsl:template>
<!-- HTML envelope -->
<xsl:template match="/">
<html>
<body>
<xsl:text>
</xsl:text>
<xsl:apply-templates />
</body>
</html>
</xsl:template>
<!-- Identity transform -->
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:transform>
I tried it for the following XML sample:
<?xml version="1.0" encoding="utf-8"?>
<main>
<metamark function="let-stand" spanTo="#meta-93"/>Aaaaaa bbbbbbb<anchor xml:id="meta-93"/>
<metamark function="let-stand" spanTo="#meta-94"/>Eeeeee <b>bbb</b> ccc<anchor xml:id="meta-94"/>
<metamark function="let-stand" spanTo="#meta-95"/>Ffffff bbbbbbb<anchor xml:id="meta-95"/>
</main>
and got result:
<!DOCTYPE html PUBLIC "XSLT-compat">
<html>
<body>
<span class="dotted">Aaaaaa bbbbbbb</span>
<span class="dotted">Eeeeee <b>bbb</b> ccc</span>
<span class="dotted">Ffffff bbbbbbb</span>
</body>
</html>
I have an XML document containing this:
<d1/>
<p1>...</p1>
<p2>...</p2>
<d2/>
<p3>...</p3>
<d3/>
Where pn are elements with possibly subelements and other stuff, and dn indicates where an HTML DIV tag wrapping the p tags should begin, but without a corresponding closing tag, this is only indicated implicitly by the next dn tag. The desired HTML output is this:
<div>
<p1>...</p1>
<p2>...</p2>
</div>
<div>
<p3>...</p3>
</div>
I have written an XSLT to introduce the <div> and </div> tags on the fly, using the following:
<xsl:text disable-output-escaping="yes"><div></xsl:text>
and
<xsl:text disable-output-escaping="yes"></div></xsl:text>
and this works on Safari, but it fails on FireFox, which makes me suspect that it's not the right way to do it.
Do you have a better idea that will work on every browser?
Thanks a lot in advance.
Firefox does not support disable-output-escaping because it does not serialize the result tree. The problem is a grouping problem, one way to solve it is to use a key:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<xsl:key name="group" match="body/*[not(starts-with(local-name(), 'd'))]" use="generate-id(preceding-sibling::*[starts-with(local-name(), 'd')][1])"/>
<xsl:template match="body">
<xsl:copy>
<xsl:apply-templates select="*[starts-with(local-name(), 'd')]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(local-name(), 'd')]">
<div>
<xsl:copy-of select="key('group', generate-id())"/>
</div>
</xsl:template>
</xsl:transform>
That would create an empty div however at the end of your sample, so you might want to change the last template to
<xsl:template match="*[starts-with(local-name(), 'd')]">
<xsl:if test="key('group', generate-id())">
<div>
<xsl:copy-of select="key('group', generate-id())"/>
</div>
</xsl:if>
</xsl:template>
You could use a technique known as "sibling recursion".
Given a well-formed input such as:
XML
<root>
<d1/>
<p1>a</p1>
<p2>b</p2>
<d2/>
<p3>c</p3>
<d3/>
</root>
the following stylesheet:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/root">
<body>
<xsl:apply-templates select="*[starts-with(name(), 'd')][position()!=last()]"/>
</body>
</xsl:template>
<xsl:template match="*[starts-with(name(), 'd')]">
<div>
<xsl:apply-templates select="following-sibling::*[1][not(starts-with(name(), 'd'))]"/>
</div>
</xsl:template>
<xsl:template match="/root/*[not(starts-with(name(), 'd'))]">
<xsl:copy-of select="."/>
<xsl:apply-templates select="following-sibling::*[1][not(starts-with(name(), 'd'))]"/>
</xsl:template>
</xsl:stylesheet>
will return:
<body>
<div>
<p>a</p>
<p>b</p>
</div>
<div>
<p>c</p>
</div>
</body>
I am trying to make an XSLT transformation from XML, I want to transform font style tags into HTML tags, but my I am doing something wrong.
My XML file is like this one :
<root>
<p>
<span>
<i/>
italic
</span>
<span>
<i/>
<b/>
bold-italic
</span>
<span>
normal
</span>
</p>
</root>
What I want is HTML with the same tags but my XSLT transformation does not work:
HTML:
<p>
<i>italic</i>
<i><b>bold-italic</b></i>
normal
<p>
I was trying xsl:if condition but it does not work,i do not know what I am doing wrong:
XSLT:
<xsl:template match="p">
<p>
<xsl:for-each select="span">
<xsl:if test="i">
<i>
<xsl:value-of select="."/>
</i>
</xsl:if>
<xsl:if test="b">
<b>
<xsl:value-of select="."/>
</b>
</xsl:if>
</xsl:for-each>
</p>
</xsl:template>
Do you know how to repair my code ?
Can you have more than just b and i elements? It may be possible to do this with a generic solution, that creates a nested element for each child element of a span element.
This solution uses a recursive template, that matches span, but with a parameter contain the index number of the child element that needs to be output. When this index exceeds the number of child elements, the text is output.
Try this XSLT too:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="span">
<xsl:param name="num" select="1"/>
<xsl:variable name="childElement" select="*[$num]"/>
<xsl:choose>
<xsl:when test="$childElement">
<xsl:element name="{local-name($childElement)}">
<xsl:apply-templates select=".">
<xsl:with-param name="num" select="$num + 1"/>
</xsl:apply-templates>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This does assume that all the span element only contain elements you want to nest, in addition to the text.
You can test the contents of span element using an XPath expression with a predicate which tests for its contents, and match different templates for each situation. Since you need b and i for bold-italic, you should use that expression in one of your predicates.
The stylesheet below does the transformation using only templates (without the need of a for-each). I'm assuming the contents of your <span> elements is text (not mixed content):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:strip-space elements="*"/>
<xsl:template match="p">
<p><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="span[i]">
<i><xsl:value-of select="."/></i>
</xsl:template>
<xsl:template match="span[b]">
<b><xsl:value-of select="."/></b>
</xsl:template>
<xsl:template match="span[i and b]">
<i><b><xsl:value-of select="."/></b></i>
</xsl:template>
</xsl:stylesheet>
I have a fairly simple xsl stylesheet for transforming an xml doc that defines our html into an html format (please don't ask why, it's just the way we have to do it...)
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="HtmlElement">
<xsl:element name="{ElementType}">
<xsl:apply-templates select="Attributes"/>
<xsl:value-of select="Text"/>
<xsl:apply-templates select="HtmlElement"/>
</xsl:element>
</xsl:template>
<xsl:template match="Attributes">
<xsl:apply-templates select="Attribute"/>
</xsl:template>
<xsl:template match="Attribute">
<xsl:attribute name="{Name}">
<xsl:value-of select="Value"/>
</xsl:attribute>
</xsl:template>
The issue came up when I ran across this little bit of HTML requiring transformation:
<p>
Send instant ecards this season <br/> and all year with our ecards!
</p>
the <br/> in the middle breaks the logic of the transformation and gives me only the first half of the paragraph block: Send instant ecards this season <br></br>. The XML attempting to be transformed looks like:
<HtmlElement>
<ElementType>p</ElementType>
<Text>Send instant ecards this season </Text>
<HtmlElement>
<ElementType>br</ElementType>
</HtmlElement>
<Text> and all year with our ecards!</Text>
</HtmlElement>
Suggestions?
You can simply add a new rule for Text elements and then match both HTMLElements and Texts:
<xsl:template match="HtmlElement">
<xsl:element name="{ElementName}">
<xsl:apply-templates select="Attributes"/>
<xsl:apply-templates select="HtmlElement|Text"/>
</xsl:element>
</xsl:template>
<xsl:template match="Text">
<xsl:value-of select="." />
</xsl:template>
You could make the stylesheet a bit more generic in order to handle additional elements by adjusting the template for HtmlElement to ensure that it applies templates first to the Attributes element, and then to all elements except for the Attributes and HtmlElement elements by using a predicate filter in the select attribute of the xsl:apply-templates.
The built-in templates will match the Text element and will copy the text() to the output.
Also, the template for the root node that you currently have declared (i.e. match="/") can be removed. It simply re-defines what is already handled by the built-in template rules and does not do anything to change behavior, just clutters your stylesheet.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<!-- <xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>-->
<xsl:template match="HtmlElement">
<xsl:element name="{ElementType}">
<xsl:apply-templates select="Attributes"/>
<!--apply templates to all elements except for ElementType and Attributes-->
<xsl:apply-templates select="*[not(self::Attributes|self::ElementType)]"/>
</xsl:element>
</xsl:template>
<xsl:template match="Attributes">
<xsl:apply-templates select="Attribute"/>
</xsl:template>
<xsl:template match="Attribute">
<xsl:attribute name="{Name}">
<xsl:value-of select="Value"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
I have looked up solutions on stackflow, but none of them seem to work for me. Here is my question. Lets say I have the following text :
Source:
<greatgrandparent>
<grandparent>
<parent>
<sibling>
Hey, im the sibling .
</sibling>
<description>
$300$ <br/> $250 <br/> $200! <br/> <p> Yes, that is right! <br/> You can own a ps3 for only $200 </p>
</description>
</parent>
<parent>
... (SAME FORMAT)
</parent>
... (Several more parents)
</grandparent>
</greatgrandparent>
Output:
<newprice>
$300$ <br/> $250 <br/> $200! <br/> Yes, that is right! <br/> You can own a ps3 for only $200
</newprice>
I can't seem to find a way to do that.
Current XSL:
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="greatgrandparents">
<xsl:apply-templates />
</xsl:template>
<xsl:template match = "grandparent">
<xsl:for-each select = "parent" >
<newprice>
<xsl:apply-templates>
</newprice>
</xsl:for-each>
</xsl:template>
<xsl:template match="description">
<xsl:element name="newprice">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="p">
<xsl:apply-templates/>
</xsl:template>
Use templates to define behavior on specific elements
<!-- after standard identity template -->
<xsl:template match="description">
<xsl:element name="newprice">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="p">
<xsl:apply-templates/>
</xsl:template>
The first template says to swap description with newprice. The second one says to ignore the p element.
If you're unfamiliar with the identity template, take a look here for a few examples.
EDIT: Given the new example, we can see that you want to only extract the description element and its contents. Notice that the template action starts with the match="/" template. We can use this control where our stylesheet starts and thus skip much of the riffraff we want to filter out.
change the <xsl:template match="/"> to something more like:
<xsl:template match="/">
<xsl:apply-templates select="//description"/>
<!-- use a more specific XPath if you can -->
</xsl:template>
So altogether our solution looks like this:
<xsl:stylesheet
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
exclude-result-prefixes="xs">
<xsl:template match="/">
<xsl:apply-templates select="//description" />
</xsl:template>
<!-- this is the identity template -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="description">
<xsl:element name="newprice">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="p">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
Shouldn't the contents of be inside a CDATA element? And then probably disable output encoding on xsl:value-of..
You should look into xsl:copy-of.
You would probably wind up with somthing like:
<xsl:template match="description">
<xsl:copy-of select="."/>
</xsl:template>
Probably the shortest solution is this one:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="description">
<newprice>
<xsl:copy-of select="node()"/>
</newprice>
</xsl:template>
<xsl:template match="text()[not(ancestor::description)]"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML document, the wanted result is produced:
<newprice>
$300$ <br /> $250 <br /> $200! <br /> <p> Yes, that is right! <br /> You can own a ps3 for only $200 </p>
</newprice>
Do note:
The use of <xsl:copy-of select="node()"/> to copy all the subtree rooted in description, without the root itself.
How we override (with a specific, empty template) the XSLT built-in template, preventing any text nodes that are not descendents of a <description> element, to be output.