I have a node like this in an XML file that I transform with XSLT 2.0:
<h2><span class='sun'>☼☼</span> my text (G1.2)</h2>
which includes some HTML special characters, as you see.
Now I need to generate XHTML like this:
my text
So I have to strip out the span and the stuff between () and use the rest to generate the h2 header.
To strip the (), I have this:
<xsl:value-of select="normalize-space(replace( . ,'\([^\)]*\)' ,''))"/>
which works OK. But to strip the span, I cannot use
<xsl:template match="span[#class='sun']"/>
Because I do not apply templates after the xsl:value-of anymore. So the span-template is never applied.
Can I strip the span in the same line? If not, how can I strip the span also?
Or can I replace the special characters in the same replace function somehow? Then I would be left with an empty span element, but that's not a problem.
You can match the child::text() contents of <h1> separately from the child::span elements. This should work:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="span[#class='sun']"/>
<xsl:template match="h2/text()">
<a href="">
<xsl:value-of select="normalize-space(replace( . ,'\([^\)]*\)' ,''))"/>
</a>
</xsl:template>
</xsl:stylesheet>
Here is the template thus far. It (toct) generates a table of contents from a HTML h1/h2/h3 structure recursively.
It is applied like this:
<xsl:call-template name="toct">
<xsl:with-param name="nodes" select="document(file)//(h1|h2|h3)"/>
<xsl:with-param name="file" select="replace (file,'xml', 'xhtml')"/>
</xsl:call-template>
<xsl:template name="toct">
<xsl:param name="nodes"/>
<xsl:param name="file"/>
<xsl:if test="count($nodes) > 0">
<xsl:for-each-group select="$nodes" group-starting-with="*[local-name() = name($nodes[1])]">
<li>
<!-- do not include empty header tags in the TOC -->
<a>
<xsl:choose>
<xsl:when test="text()">
<xsl:if test="name($nodes[1])='h1'">
<xsl:attribute name="id"><xsl:value-of select="$nodes[1]/#id"/></xsl:attribute>
</xsl:if>
<xsl:attribute name="href"><xsl:value-of select="$file"/>#<xsl:value-of select="#id"/></xsl:attribute>
<xsl:value-of select="normalize-space(replace(replace( string-join(node(),'') , '[☼*]' ,'') ,'\([^\)]*\)' ,''))"/>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="class">invisible</xsl:attribute>
<xsl:text>-</xsl:text>
</xsl:otherwise>
</xsl:choose>
</a>
<xsl:if test="current-group()[2]">
<ol>
<xsl:call-template name="toct">
<!-- strip the first node in the list. This is why you can use $nodes[1] to find out
which level of <h_> tags you are at -->
<xsl:with-param name="nodes" select="current-group() except ."/>
<xsl:with-param name="file" select="$file"/>
</xsl:call-template>
</ol>
</xsl:if>
</li>
</xsl:for-each-group>
</xsl:if>
</xsl:template>
Related
I'm new to XSL programming and facing an issue while trying to remove the HTML tags from the XML file.
My Input XML file is: Just adding the tag that I'm interested in
<Ingredients>
<p><b>INGREDIENTS:</b> Reduced Fat Cheese (84%) [Reduced Fat<strong>Milk</strong>, Salt, Starter Cultures, Enzyme (Animal Rennet, Lipase)],<strong>Milk</strong> Solids, Water, Emulsifying Salt (331), Salt, Acidity Regulator (330), Preservative (200), Natural Colours (100, 160b).</p>
</Ingredients>
My XSL file is:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="http://www.micros.com/creations/core/domain/dto/v1p0/full" xmlns:ns2="http://www.micros.com/creations/core/domain/dto/v1p0/simple" exclude-result-prefixes="ns1 ns1">
<xsl:template name="replace-string">
<xsl:param name="text"/>
<xsl:param name="replace"/>
<xsl:param name="with"/>
<xsl:choose>
<xsl:when test="contains($text,$replace)">
<xsl:value-of select="substring-before($text,$replace)"/>
<xsl:value-of select="$with"/>
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="substring-after($text,$replace)"/>
<xsl:with-param name="replace" select="$replace"/>
<xsl:with-param name="with" select="$with"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="strip-html-tags">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text, '<')">
<xsl:value-of select="substring-before($text, '<')"/>
<xsl:call-template name="strip-html-tags">
<xsl:with-param name="text" select="substring-after($text, '>')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="$text"/>
<xsl:with-param name="replace" select="' '" />
<xsl:with-param name="with" select="''"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="/">
<ItemDetails>
<SourceSystem>
<xsl:text>Fusion</xsl:text>
</SourceSystem>
<ActionType>
<xsl:text>Create</xsl:text>
</ActionType>
<CreateDateTime>
<xsl:text>2021-11-10T08:00:00</xsl:text>
</CreateDateTime>
<Ingredients>
<xsl:call-template name="strip-html-tags">
<xsl:with-param name="text" select="ns1:productSpecificationFullDTO/ns1:specificationSectionDetail/ns1:specificationSectionFoodRecipeAndRawMaterialsSection/ns1:onPackIngredientsList"/>
<!--<xsl:with-param name="replace" select="' '"/>
<xsl:with-param name="with" select="''"/>-->
</xsl:call-template>
</Ingredients>
</ItemDetails>
</xsl:template>
</xsl:stylesheet>
Using the above XSL file I'm trying to remove the HTML tags and also trying to replace the special character like with empty. Hence I'm calling 2 templates.
<xsl:template name="strip-html-tags"> strips the other tags but not " "
So created a <xsl:template name="replace-string"> to replace " " with ''.
However this is not working. either of the templates works when invoked first.
If <xsl:template name="strip-html-tags"> is invoked first, it removes all the HTML tags except " "
If <xsl:template name="replace-string"> is invoked first, it replace the " " with '' but the other HTML tags are not removed.
I am calling these templates in the when clause.
How can this issue be solved? I need all the HTML tags to be removed. Is there a way to do it at single go or is it something that I'm missing?
If you want to use the output of one template as the input to the other template, then call the first template within the xsl:with-param instruction of the second template - for example:
<xsl:call-template name="replace-string">
<xsl:with-param name="text">
<xsl:call-template name="strip-html-tags">
<xsl:with-param name="text" select="Ingredients"/>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="replace" select="' '" />
<xsl:with-param name="with" select="''"/>
</xsl:call-template>
Simplified demo: https://xsltfiddle.liberty-development.net/eiorv1s
Ensure that you are scrubbing the value that is between the tags, not just selecting it. Instead of xsl:value-of, run it through the replace-string template.
Also, you may want to replace with a space ' ' instead of empty string. Otherwise, you will get ReducedFat instead of Reduced Fat
<xsl:when test="contains($text, '<')">
<!--<xsl:value-of select="substring-before($text, '<')"/>-->
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="substring-before($text, '<')"/>
<xsl:with-param name="replace" select="' '" />
<xsl:with-param name="with" select="' '"/>
</xsl:call-template>
<xsl:call-template name="strip-html-tags">
<xsl:with-param name="text" select="substring-after($text, '>')"/>
</xsl:call-template>
</xsl:when>
I need to replace standalone attributes width="<>" and height="<>" with style="width:<>; height:<>;.
A typical input is the following
<img width="32pt" alt="PIC" class="graphics" height="32pt" src="images/about.png"/>
Thus the result should be
<img style="width:32pt; height:32pt;" alt="PIC" class="graphics" src="images/about.png"/>
The problem is that I've never worked with xsl before.
What I know, as far, is that
this can catch the img element
<xsl:template match="xh:img">
this can catch the width attribute
<xsl:template match="#width">
and I know how to add an attribute or element.
But I don't know how to store the value of the width and height and write both in a single line.
Any help would be appreciated.
You could use:
<xsl:template match="img">
<img style="width:{#width}; height:{#height};">
<xsl:copy-of select="#*[not(name()='width' or name()='height')]"/>
</img>
</xsl:template>
This is assuming every img has both width and height attributes, and no child nodes. If your source XML places the img elements in a namespace (not shown in your question), add the appropriate prefix to the template's match pattern.
For reference, see: https://www.w3.org/TR/xslt/#attribute-value-templates
You (or other readers) may be interested in this more general (XSLT 2.0) solution taken from a production stylesheet:
<xsl:template name="style-attributes">
<xsl:choose>
<xsl:when test="#style | #class">
<xsl:copy-of select="#style | #class"/>
</xsl:when>
<xsl:when test="#*[f:is-style-attribute(.)]">
<xsl:attribute name="style">
<xsl:for-each select="#*[f:is-style-attribute(.)]">
<xsl:if test="position() gt 1">; </xsl:if>
<xsl:apply-templates select="." mode="style-attribute"/>
</xsl:for-each>
</xsl:attribute>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:function name="f:is-style-attribute" as="xs:boolean">
<xsl:param name="att" as="attribute(*)"/>
<xsl:variable name="n" select="local-name($att)"/>
<xsl:sequence select="namespace-uri($att) = '' and $n = ('class', 'style', 'border', 'width', 'cellspacing', 'padding', 'cellpadding', 'align', 'valign')"/>
</xsl:function>
<xsl:function name="f:units" as="xs:string">
<xsl:param name="value" as="xs:string"/>
<xsl:sequence select="if ($value castable as xs:integer) then concat($value, 'px') else $value"/>
</xsl:function>
<xsl:template match="#border" mode="style-attribute">border:<xsl:value-of select="f:units(.)"/> solid</xsl:template>
<xsl:template match="#width" mode="style-attribute">width:<xsl:value-of select="f:units(.)"/></xsl:template>
<xsl:template match="#align" mode="style-attribute">text-align:<xsl:value-of select="."/></xsl:template>
<xsl:template match="#valign" mode="style-attribute">vertical-align:<xsl:value-of select="."/></xsl:template>
<xsl:template match="#cellspacing" mode="style-attribute">border-spacing:<xsl:value-of select="."/></xsl:template>
<xsl:template match="#padding" mode="style-attribute">padding:<xsl:value-of select="f:units(.)"/></xsl:template>
<xsl:template match="#cellpadding" mode="style-attribute">padding:<xsl:value-of select="f:units(.)"/></xsl:template>
... etc (as required)
I want to add a new tag <br\> to my XML each 10 characters. (without including inner nodes)
For example if my input is:
<main>
01234567890123
<a>
link
</a>
45678901234567901234
</main>
I expect is to be somthing like:
<main>
0123456789<br\>0123
<a>
link
</a>
456789<br\>012345679<br\>01234
</main>
I wrote this function:
<!-- Add new "HTML Break" Every X chars -->
<xsl:template name="Add-HTML-Break-After-X-Chars">
<xsl:param name="text" />
<xsl:param name="max-length" />
<xsl:choose>
<xsl:when test="string-length($text) > $max-length">
<!-- Show the X length of the text -->
<xsl:copy-of select="substring($text,1,$max-length)" />
<!-- Adding the special tag -->
<br/>
<!-- continue to add the rest of the text -->
<xsl:call-template name="Add-HTML-Break-After-X-Chars">
<xsl:with-param name="text" select="substring($text, $max-length + 1)" />
<xsl:with-param name="max-length" select="$max-length" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- Show the text (length is less then X) -->
<xsl:copy-of select="$text" />
</xsl:otherwise>
</xsl:choose>
The problem is that I'm getting the output without the inner tags:
<main>
0123456789<br\>0123
link
45<br\>6789012345<br\>67901234
</main>
I'm using this code to call the template:
<xsl:call-template name="Add-HTML-Break-After-X-Chars">
<xsl:with-param name="text" select="main" />
<xsl:with-param name="max-length" select="10" />
</xsl:call-template>
I tried also "value-of" instead of "copy-of".
Is there a way I can keep the tags?
If so, how can I keep them and make a "safe" insert of the new tag?
This is difficult to do in XSLT, because it processes each text node individually, with no information being carried over from the preceding text nodes.
Here's a possible approach:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="main/text()" name="wrap">
<xsl:param name="text" select="."/>
<xsl:param name="line-length" select="10"/>
<xsl:param name="carry">
<xsl:variable name="lengths">
<xsl:for-each select="preceding-sibling::text()">
<length>
<xsl:value-of select="string-length()"/>
</length>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="sum(exsl:node-set($lengths)/length) mod $line-length"/>
</xsl:param>
<xsl:value-of select="substring($text, 1, $line-length - $carry)"/>
<br/>
<xsl:if test="$carry + string-length($text) > $line-length">
<!-- recursive call -->
<xsl:call-template name="wrap">
<xsl:with-param name="text" select="substring($text, $line-length - $carry + 1)"/>
<xsl:with-param name="carry" select="0"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When this is applied to the following test input:
XML
<main>A123456789B123<a>link1</a>456789C123456789D12345678<a>link2</a>9E123456789F1234567</main>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<main>A123456789<br/>B123<a>link1</a>456789<br/>C123456789<br/>D12345678<a>link2</a>9<br/>E123456789<br/>F1234567</main>
There have been similar questions but none working with SharePoint and the XSL editor.
I have an RSS feed which is pulling in article titles, along with accompanying html tags as shown below:
<i>Wall Street Journal</i> Article on Competition in the Dental Industry
I would like to have those <i> (and any other HTML tags) be read as HTML, not text..i.e an end result of:
Wall Street Journal Article on Competition in the Dental Industry
I can read and semi-reverse engineer XML, but writing it is another story.
Any help will be greatly appreciated.
A sample of my code where I think the changes is needed, below:
<xsl:template name="RSSMainTemplate.body" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:param name="Rows"/>
<xsl:param name="RowCount"/>
<xsl:for-each select="$Rows">
<xsl:variable name="CurPosition" select="position()" />
<xsl:variable name="RssFeedLink" select="$rss_WebPartID" />
<xsl:variable name="CurrentElement" select="concat($RssFeedLink,$CurPosition)" />
<xsl:if test="($CurPosition <= $rss_FeedLimit)">
<div class="item link-item" >
<a href="{concat("javascript:ToggleItemDescription('",$CurrentElement,"')")}" >
<xsl:value-of select="title"/>
</a>
<xsl:if test="$rss_ExpandFeed = true()">
<xsl:call-template name="RSSMainTemplate.description">
<xsl:with-param name="DescriptionStyle" select="string('display:block;')"/>
<xsl:with-param name="CurrentElement" select="$CurrentElement"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="$rss_ExpandFeed = false()">
<xsl:call-template name="RSSMainTemplate.description">
<xsl:with-param name="DescriptionStyle" select="string('display:none;')"/>
<xsl:with-param name="CurrentElement" select="$CurrentElement"/>
</xsl:call-template>
</xsl:if>
</div>
</xsl:if>
</xsl:for-each>
</xsl:template>
Figured it out.
The key to this issue is the disable-output-escaping attribute. I was correct about the location of the issue. Below is my adjusted code. Notice lines 10-15
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:param name="Rows"/>
<xsl:param name="RowCount"/>
<xsl:for-each select="$Rows">
<xsl:variable name="CurPosition" select="position()" />
<xsl:variable name="RssFeedLink" select="$rss_WebPartID" />
<xsl:variable name="CurrentElement" select="concat($RssFeedLink,$CurPosition)" />
<xsl:if test="($CurPosition <= $rss_FeedLimit)">
<div class="item link-item" >
<a href="{concat("javascript:ToggleItemDescription('",$CurrentElement,"')")}" >
<xsl:variable name="SafeHtml">
<xsl:call-template name="GetSafeHtml">
<xsl:with-param name="Html" select="title"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$SafeHtml" disable-output-escaping="yes"/>
</a>
<xsl:if test="$rss_ExpandFeed = true()">
<xsl:call-template name="RSSMainTemplate.description">
<xsl:with-param name="DescriptionStyle" select="string('display:block;')"/>
<xsl:with-param name="CurrentElement" select="$CurrentElement"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="$rss_ExpandFeed = false()">
<xsl:call-template name="RSSMainTemplate.description">
<xsl:with-param name="DescriptionStyle" select="string('display:none;')"/>
<xsl:with-param name="CurrentElement" select="$CurrentElement"/>
</xsl:call-template>
</xsl:if>
</div>
</xsl:if>
</xsl:for-each>
<div class="description">
<h4 class="ms-rteElement-H4B">
<span class="ms-rteStyle-Emphasis">
<br></br>View More Articles <img alt="more-icon-1.png" src="/BHiveImageGallery/Stock-Images/more-icon-1.png"/>
</span>
<span class="ms-rteStyle-Emphasis"> </span>
</h4>
</div>
My full code is too long and formatted too uniquely to get it to display as I want in the window. It is 3/20/2015 right now, if you message me within the next 5 years i'll be able to send you the code, beyond that, no guarantees haha.
Generally though I used the SharePoint 2013 standard RSS viewer webpart code, and only modified the piece above
I have this xslt which is working:
<xsl:when test="area_of_expertise">
<div>
<xsl:value-of select="area_of_expertise"/>
</div>
</xsl:when>
but what i need is along the lines of:
<xsl:when test="area_of_expertise">
<div id="<xsl:value-of select="area_of_expertise"/>">
<xsl:value-of select="area_of_expertise"/>
</div>
</xsl:when>
However the second example has errors.. does anyone know why?
Btw Is there a way we can transform the node's name area_of_expertise into areaOfExperiseLabel and insert that as the id? the output that i really need is this:
<div id="areaOfExpertiseLabel">
asasdasdasd
</div>
The reason it errors out is because it's no longer valid XML.
To do what you're trying to do:
<xsl:when test="title">
<div id="{title}">
<xsl:value-of select="title"/>
</div>
</xsl:when>
You can put any sort of selector inside of the {} tags, or even reference variables if you have something complex.
<xsl:variable name="some_complex_variable">
<xsl:value-of select="title"/>
</xsl:variable>
<xsl:when test="title">
<div id="{$some_complex_variable}">
<xsl:value-of select="title"/>
</div>
</xsl:when>
A 3rd, long-winded way of doing it is to dynamically attach the attribute with xsl:attribute:
<xsl:when test="title">
<div>
<xsl:attribute name="id" select="title"/>
</div>
</xsl:when>
For 2nd part try using this template:
<xsl:template name="parse">
<xsl:param name="input"/>
<xsl:param name="position"/>
<xsl:if test="$position <= string-length($input)">
<xsl:choose>
<xsl:when test="substring($input, $position, 1) = '_'">
<xsl:value-of select="translate(substring($input, $position + 1, 1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
<xsl:call-template name="parse">
<xsl:with-param name="input" select="$input"/>
<xsl:with-param name="position" select="$position + 2"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($input, $position, 1)"/>
<xsl:call-template name="parse">
<xsl:with-param name="input" select="$input"/>
<xsl:with-param name="position" select="$position + 1"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
Usage:
<xsl:call-template name="parse">
<xsl:with-param name="input" select="'area_of_expertise'"/>
<xsl:with-param name="position" select="1"/>
</xsl:call-template>
For the second part, converting from underscores to camel case, you may want to look at String Processing in the XSLT Standard Library. With str:subst() to split at underscores, str:to-camelcase() to change letter case suitably, and concat() to add the "Label" suffix, you should be set.
For the 1st part you could use:
<xsl:when test="area_of_expertise">
<div>
<xsl:attribute name="id">
<xsl:value-of select="area_of_expertise"/>
</xsl:attribute>
<xsl:value-of select="area_of_expertise"/>
</div>
</xsl:when>
It might be a silly answer, but it seems that you need this simple trace:
<xsl:when test="area_of_expertise">
<div id="areaOfExperiseLabel">
<xsl:value-of select="area_of_expertise"/>
</div>
</xsl:when>
Otherwise, why are you intersted in <xsl:value-of select="area_of_expertise"/> for #id if you then need another string?