How can I simultaneously transform the attribute and content of a node? - xslt

I am converting an XML document to HTML, and am struggling to select both the attribute and content of the original element.
The XML document contains variables (inserted by the document editor) like this example:
<var styleclass="Interface b"><%BACKGROUND%></var>
I would like to both convert the text content and surround this with a span tag to apply the style.
My XSLT declarations are as follows:
<xsl:template match="var">
<xsl:choose>
<xsl:when test="#style='font-weight:bold;'">
<strong>
<xsl:apply-templates/>
</strong>
</xsl:when>
<xsl:when test="#styleclass='Interface'">
<span style="color:white; background-color: rgb(98, 98, 98);">
<xsl:apply-templates/>
</span>
</xsl:when>
<xsl:when test="#styleclass='Interface b'">
<span style="color: rgb(98, 98, 98); background-color: rgb(233, 180, 0);">
<xsl:apply-templates/>
</span>
</xsl:when>
<xsl:when test="#styleclass='Interface c'">
<span style="color: black; background-color: rgb(234, 235, 237);">
<xsl:apply-templates/>
</span>
</xsl:when>
<xsl:when test="#styleclass='Warning a'">
<span style="color:#e74c3c">
<xsl:apply-templates/>
</span>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="var[contains(text(),'BACKGROUND')]">
Background
</xsl:template>
<xsl:template match="var[contains(text(),'COLOR PICKER')]">
Color picker
</xsl:template>
<xsl:template match="var[contains(text(),'PARAGRAPH')]">
Paragraph
</xsl:template>
<xsl:template match="var[contains(text(),'IMPORT DATA')]">
Import data
</xsl:template>
However, the output contains the correctly converted text but without the surrounding span tag.

If you can use XSLT 2/3 I would consider using xsl:next-match e.g. along the lines of
<xsl:template match="var[contains(.,'BACKGROUND')]">
Background
</xsl:template>
<xsl:template match="var[#styleclass = 'Interface b']">
<span style="color:white; background-color: rgb(98, 98, 98);">
<xsl:next-match/>
</span>
</xsl:template>
<xsl:mode on-no-match="shallow-copy" on-multiple-match="use-last"/>
So for the attribute based match I have also used a template with the particular condition in a predicate and would suggest to write a new template for each new condition instead of those xsl:choose/xsl:when approach.
But even if you prefer that you can use xsl:next-match in there instead of xsl:apply-templates.

Related

Create a attribute dynamically

I'm writing an xsl script in which I need to create the attribute dynamically.
Here is my sample code.
<Table BORDERLINESTYLE="solid" BOTTOMMARGIN="12" NOTEGROUPSPACING="single" CELLPADDING="0" CELLSPACING="0" WIDTH="504"></Table>
Here BORDERLINESTYLE may or may not be available to all Table tags. and I want the output to be something like this.
<table style="border-style:solid; border-margin:12px; width:504px"></table>
Here are 2 things that I've tried.
1. Creating attributes
<table>
<xsl:if test="./#BORDERLINESTYLE">
<xsl:attribute name="border" select="'1px solid'"/>
</xsl:if>
<xsl:if test="./#WIDTH">
<xsl:attribute name="width" select="concat(./#WIDTH, 'px')"/>
</xsl:if>
<xsl:if test="./#BOTTOMMARGIN">
<xsl:attribute name="border" select="'1px solid'"/>
</xsl:if>
<xsl:apply-templates/>
</table>
the output that I get is as below.
<table border="1px solid" width="504px">
2. Inline adding
<table style="width:{current()/#WIDTH,'px'}; border:{./#BORDERLINESTYLE}; margin-bottom: {./#BOTTOMMARGIN, 'px'}; margin-top:{./#TOPMARGIN, 'px'}; "></table>
and the output is as below with some blank values like margin-top
<table style="width:504 px; border:solid; margin-bottom: 12 px; margin-top:px; ">
How can I add styles to style based on the attributes provided in the XML?
I'm using XSLT2.0.
I would use something like this:
<xsl:if test="#BORDERLINESTYLE|#WIDTH|#BOTTOMMARGIN|#TOPMARGIN">
<xsl:attribute name="style">
<xsl:if test="#BORDERLINESTYLE">
<xsl:text>border:1px solid;</xsl:text>
</xsl:if>
<xsl:if test="#WIDTH">
<xsl:value-of select="concat('width:',#WIDTH, 'px;')"/>
</xsl:if>
<xsl:if test="#BOTTOMMARGIN">
<xsl:value-of select="concat('margin-bottom:',#BOTTOMMARGIN, 'px;')"/>
</xsl:if>
<xsl:if test="#TOPMARGIN">
<xsl:value-of select="concat('margin-top:',#TOPMARGIN, 'px;')"/>
</xsl:if>
</xsl:attribute>
</xsl:if>
And depending on the business rules for mapping those source-attributes to style attributes change the code accordingly.
Btw.: ./# is the same as #
Use if else Function
For Example
margin-top: {if(//#TOPMARGIN!=0) then {./#TOPMARGIN, 'px'} else '12px'}
I need to create the attribute dynamically.
Actually, you want to populate the attribute dynamically - which could be done by applying a template to each input attribute you want to use:
<xsl:template match="Table">
<table>
<xsl:attribute name="style">
<xsl:apply-templates select="#BORDERLINESTYLE | #BOTTOMMARGIN | #WIDTH"/>
</xsl:attribute>
</table>
</xsl:template>
<xsl:template match="#BORDERLINESTYLE">
<xsl:text>border-style:</xsl:text>
<xsl:value-of select="."/>
<xsl:text>; </xsl:text>
</xsl:template>
<xsl:template match="#BOTTOMMARGIN">
<xsl:text>border-margin:</xsl:text>
<xsl:value-of select="."/>
<xsl:text>px; </xsl:text>
</xsl:template>
<xsl:template match="#WIDTH">
<xsl:text>width:</xsl:text>
<xsl:value-of select="."/>
<xsl:text>; </xsl:text>
</xsl:template>

Changing node value in XSLT depending on conditions

I'm new in XSLT and have a problem with converting values.
I have XML node:
<NPD>0</NPD
type in XSD:
<xs:element name="NPD" type="xs:boolean" minOccurs="0"/>
I've created XSLT visualisation using Altova StyleVision but now I have to change in NPD node value "0" to string "No" and value "1" to string "yes".
How can I obtain this effect?
<td colspan="3" style="padding-left:10px; width:1.40in; ">
<xsl:for-each select="$XML">
<xsl:for-each select="wnio:DD">
<xsl:for-each select="wnio:NPD">
<span style="color:#0024c0; ">
<xsl:apply-templates/>
</span>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
<span>
<xsl:text>  </xsl:text>
</span>
</td>
Use template rules:
<xsl:template match="wnio:DD[.='0']">No</xsl:template>
<xsl:template match="wnio:DD[.='1']">Yes</xsl:template>
You can use xsl:choose to perform if/switch like statements in XSLT. Here's an example which should work for your scenario:
<xsl:template match="NPD">
<xsl:choose>
<xsl:when test="./text()='0'">
<xsl:text>No</xsl:text>
</xsl:when>
<xsl:when test="./text()='1'">
<xsl:text>Yes</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="yes">The Yes/No value to be translated did not match expected input</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
See https://www.w3schools.com/xml/xsl_choose.asp for more.
That said, Michael Kay's answer's way better; beautifully elegant & to the point.

XSLT - Remove specific value from attribute but keep other values

I have following XML input:
<table>
<tbody>
<tr>
<td style="width: 10px; margin-left: 10px;">td text</td>
<td style="color: red; width: 25px; text-align: center; margin-left: 10px;">
<span>span text</span>
</td>
</tr>
</tbody>
</table>
Please note that I have other nodes in the same document that should not be touched.
I want to remove certain attribute values from an element (in this case from td).
Let's say I want to remove the width value within a style attribute.
I don't know where in the style-attribute the width-value is set, it could be anywhere.
The span in the td doesn't really matter (this and some other elements are there in the input).
I expect the output to be like this:
<table>
<tbody>
<tr>
<td style="margin-left: 10px;">td text</td>
<td style="color: red; text-align: center; margin-left: 10px;">
<span>span text</span>
</td>
</tr>
</tbody>
</table>
I prefer using XSLT1, I did not bring the replace() function to work yet (but maybe I am doing something wrong).
I tried using this XSLT:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="td/#style">
<xsl:attribute name="style">
<xsl:value-of select="replace(., 'width:.[[:digit:]]+px;', '')" />
</xsl:attribute>
<xsl:apply-templates select="node()" />
</xsl:template>
I am still a beginner in XSLT and this above doesn't work and I did not find a solution here.
Also, I don't know the width-value so I would need to replace the value with a regex (I used "width:.[[:digit:]]+px;") or something.
Is there maybe a easier method that can replace every specific value? So I could remove text-align aswell without having to think of a new regex?
I really hope that you can help me with this (surely easy) problem.
Thank you in advance!
Let's say I want to remove the width value within a style attribute. I
don't know where in the style-attribute the width-value is set, it
could be anywhere.
Try:
XSLT 1.0
<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:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="td/#style[contains(., 'width:')]">
<xsl:attribute name="style">
<xsl:value-of select="substring-before(., 'width:')" />
<xsl:value-of select="substring-after(substring-after(., 'width:'), ';')" />
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Note:
I want to remove certain attribute values from an element (in this
case from td).
Actually, what you want is to remove certain properties from the style attribute. The above will work for removing a single property; if you want to remove more than one, you'll have to use a recursive template to do it.
Added:
Will there be an issue if the style contains border-width:1px as
this become border-?
Yes, this could be a problem. A possible solution would be:
<xsl:template match="td/#style">
<xsl:variable name="style" select="concat(' ', .)" />
<xsl:choose>
<xsl:when test="contains($style, ' width:')">
<xsl:attribute name="style">
<xsl:value-of select="substring-before($style, ' width:')" />
<xsl:value-of select="substring-after(substring-after($style, ' width:'), ';')" />
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:copy/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
However, this assumes that the ; separator in the source document is always followed by a space (as it is in the given example). Otherwise it gets more complicated.
Assuming you are using XSLT 2.0 (as replace is not supported in 1.0) you can use \d to match a digit in regular expressions, so you can write your pattern like so:
<xsl:value-of select="replace(., '( | $)width:\s*\d*px;?', '')" />
Note the \s* is used to match zero or more characters of whitespace, so allow for width:10px or width: 10px. Also not ( | $) is used to ensure a space before width (or if it is at the start), so that properties like border-width are not matched.
If you wanted to handle units other than px you could do this...
<xsl:value-of select="replace(., '( | $)width:[^;]+;?', '')" />
Read up on regular expressions at http://www.xml.com/pub/a/2003/06/04/tr.html.

Docbook <bibliosource> gets lost in the transformation

I have the following structure
<informalfigure xmlns="http://docbook.org/ns/docbook">
<info>
<bibliosource>Photo: John Doe</bibliosource>
</info>
<mediaobject>
<imageobject>
<imagedata contentdepth="30mm" contentwidth="35mm" fileref="path/to/img.jpg"/>
</imageobject>
</mediaobject>
<caption>
<para>Here goes the caption for the image</para>
</caption>
</informalfigure>
<imagedata> and the <caption> get rendered but the <bibliosource> is gone after the transformation.
I'm using the docbook-xsl-1.77.0/xhtml/docbook.xsl for the transformation... I need some guidance on where/how to change the xslt so that <bibliosource> gets transformed properly.
Thanks!
Here is a simple solution. Add the following to your XHTML customization layer:
<xsl:template match="d:caption">
<div>
<xsl:apply-templates select="." mode="common.html.attributes"/>
<xsl:call-template name="id.attribute"/>
<xsl:if test="#align = 'right' or #align = 'left' or #align='center'">
<xsl:attribute name="align"><xsl:value-of select="#align"/></xsl:attribute>
</xsl:if>
<xsl:apply-templates/>
</div>
<div><xsl:value-of select="../d:info/d:bibliosource"/></div> <!-- This line added -->
</xsl:template>
The above works with the namespace-aware XSLT stylesheets (docbook-xsl-ns).
If you use the non-namespace-aware stylesheets (docbook-xsl), the corresponding customization becomes:
<xsl:template match="caption">
<div>
<xsl:apply-templates select="." mode="common.html.attributes"/>
<xsl:call-template name="id.attribute"/>
<xsl:if test="#align = 'right' or #align = 'left' or #align='center'">
<xsl:attribute name="align"><xsl:value-of select="#align"/></xsl:attribute>
</xsl:if>
<xsl:apply-templates/>
</div>
<div><xsl:value-of select="../blockinfo/bibliosource"/></div> <!-- This line added; note blockinfo -->
</xsl:template>
The text of the <bibliosource> element will appear in a separate <div> directly below the caption. The original template is in graphics.xsl.

XSLT get a variable in case of element name

I can get an xml with one of three nodes.
<root>
<headerA>
<!-- Content -->
</headerA>
</root>
in place of <headerA> there can be <headerB> or <headerС> node with similar content. In this case I want to have html output as:
<span> Type [X] </span>
Where [X] is A, B or C depending on the tag name of the element.
As Tomalak has already observed, your question is rather vague. It sounds as if you might be asking how to write either a single template for the three kinds of header:
<xsl:template match="headerA | headerB | headerC">
<span>
<xsl:apply-templates/>
</span>
</xsl:template>
or else how to write similar but distinct templates for them:
<xsl:template match="headerA">
<span> Type A </span>
</xsl:template>
<xsl:template match="headerB">
<span> Type B </span>
</xsl:template>
<!--* Template for headerC left as an exercise for the reader *-->
or else how to write a single template that does slightly different things based on what it matches:
<xsl:template match="headerA | headerB | headerC">
<span>
<xsl:choose>
<xsl:when test="self::headerA">
<xsl:text> Type A </xsl:type>
</xsl:when>
<xsl:when test="self::headerB">
<xsl:text> Type B </xsl:type>
</xsl:when>
<!--* etc. *-->
<xsl:otherwise>
<xsl:message terminate="yes"
>I thought this could not happen.</xsl:message>
</xsl:otherwise>
</xsl:choose>
<xsl:apply-templates/>
</span>
</xsl:template>
If you figure out which of these helps you, you will be closer to understanding what question you are trying to ask.
<xsl:template match="*[starts-with(name(), 'header')]">
<xsl:variable name="type" select="substring-after(name(), 'header')" />
<span>
<xsl:value-of select="concat(' Type ', $type, ' ')" />
</span>
</xsl:template>
This would work for any element named headerX, where can be X any string.