XSLT XML to Excel in cell text formatting - html

I'm creating an excel document where certain text within a cell will be formatted in a different colour or bold.
for example my spreadsheet should be like this:
.
The text I want to format has got tags round it in my XML e.g. <strong>. I have written a function that picks up these tags and replaced them with
the colour or bold formatting e.g. <Font html:color="#0000FF"> text </Font> when creating the xml output to open in excel.
The problem is that when the spreadsheet is opened the text isn't coloured but instead surround by the formatting command <Font html:color="#0000FF">:
.
Here is my input XML
<?xml version="1.0" encoding="UTF-8"?>
<analysis>
<datasetList>
<dataset>
<datasetLabel>Subject Level Analysis</datasetLabel>
<datasetName>ADSL</datasetName>
<datasetOrdinal>1</datasetOrdinal>
<datasetStructure>One record per subject</datasetStructure>
<datasetContext>
<datasetClass>
<datasetClass>ADSL</datasetClass>
</datasetClass>
</datasetContext>
<columnList>
<column>
<columnLabel>Analysis Visit</columnLabel>
<columnName>AVISIT</columnName>
<columnDerivationList>
<columnDerivation>
<columnDerivationDescription>Set to collected visit name [EG.VISIT] <keepordrop>Set to 'POST-BASELINE MINIMUM'</keepordrop></columnDerivationDescription>
</columnDerivation>
<columnDerivation>
<columnDerivationDescription>Set to a re-defined visit range based on user-defined input. <strong>Set to 'POST-BASELINE MINIMUM'</strong></columnDerivationDescription>
</columnDerivation>
</columnDerivationList>
</column>
<column>
<columnLabel>Analysis Visit (N)</columnLabel>
<columnName>AVISITN</columnName>
<columnDerivationList>
<columnDerivation>
<columnDerivationDescription>Set to collected <edit>Set to 9997</edit></columnDerivationDescription>
</columnDerivation>
<columnDerivation>
<columnDerivationDescription>Set to a user defined numeric value <select>Set to 9997 wih the analysis visit </select></columnDerivationDescription>
</columnDerivation>
</columnDerivationList>
</column>
</columnList>
</dataset>
</datasetList>
</analysis>
Here is my complete XSLT
<?xml version="1.0" encoding="UTF-8"?>
<?mso-application progid="Excel.Sheet"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40" xmlns:gdsr="http://somethinghere.com" >
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<!--
***************************************************************************************
START OF WORKBOOK LAYOUT
***************************************************************************************
-->
<xsl:template match="/" >
<xsl:processing-instruction name="mso-application">progid="Excel.Sheet"</xsl:processing-instruction>
<ss:Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40">
<xsl:call-template name="document-properties"/>
<xsl:call-template name="document-styles"/>
<!-- ****************************************************************************************** -->
<!-- CREATE THE TABS IN THE WORKBOOK -->
<!-- ****************************************************************************************** -->
<xsl:call-template name="getDset"/>
<!-- ****************************************************************************************** -->
</ss:Workbook>
</xsl:template>
<!-- TEMPLATE FOR DATASET -->
<xsl:template name="getDset">
<xsl:for-each select="//dataset">
<ss:Worksheet ss:Name="{.//datasetName}"><!-- Add filters to the columns -->
<Names>
<NamedRange ss:Name="_FilterDatabase" ss:RefersTo="={.//datasetName}!R1C1:R1C2" ss:Hidden="1"/>
</Names>
<ss:Table ss:ExpandedColumnCount="2" ss:ExpandedRowCount="200" x:FullColumns="1" x:FullRows="1" ss:DefaultRowHeight="30">
<Column ss:Width="70"/>
<Column ss:Width="70"/>
<Row>
<Cell ss:StyleID="sHead">
<Data ss:Type="String">
Header1
</Data>
<NamedCell ss:Name="_FilterDatabase"/>
</Cell>
<Cell ss:StyleID="sHead">
<Data ss:Type="String">
Header2
</Data>
<NamedCell ss:Name="_FilterDatabase"/>
</Cell>
</Row>
<xsl:for-each select="columnList/column">
<xsl:if test="columnName !=' ' ">
<Row>
<Cell ss:StyleID="sBody" >
<Data ss:Type="String">
<xsl:value-of select="columnName"/>
</Data>
</Cell>
<!-- SET COLOR OF TEXT WHEN TAGGED -->
<xsl:variable name="columnDerivationDescription">
<xsl:value-of select="columnDerivationList/columnDerivation/columnDerivationDescription"/>
</xsl:variable>
<xsl:variable name="columnDerivationDescription1">
<xsl:value-of select="gdsr:set-font($columnDerivationDescription)"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="contains($columnDerivationDescription1, 'Font')">
<Cell ss:StyleID="sBody">
<ss:Data ss:Type="String" xmlns="http://www.w3.org/TR/REC-html40">
<xsl:for-each select="columnDerivationList/columnDerivation">
<xsl:if test="columnDerivationDescription !=' ' ">
<xsl:call-template name="LFsToBRs">
<xsl:with-param name="input" select="gdsr:set-font(columnDerivationDescription)"/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</ss:Data>
</Cell>
</xsl:when>
<xsl:otherwise>
<Cell ss:StyleID="sBody">
<Data ss:Type="String">
<xsl:for-each select="columnDerivationList/columnDerivation">
<xsl:if test="columnDerivationDescription !=' ' ">
<xsl:call-template name="LFsToBRs">
<xsl:with-param name="input" select="columnDerivationDescription"/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</Data>
</Cell>
</xsl:otherwise>
</xsl:choose>
<!-- COLOUR TAGGED. NICELY DONE -->
</Row>
</xsl:if>
</xsl:for-each>
</ss:Table>
<xsl:call-template name="worksheet-options"/>
<AutoFilter x:Range="R1C1:R1C2" xmlns="urn:schemas-microsoft-com:office:excel"/>
</ss:Worksheet>
</xsl:for-each>
</xsl:template>
<!--FUNCTIONS-->
<xsl:function name="gdsr:set-font">
<xsl:param name="text"/>
<xsl:variable name="blue-font"><Font html:color="#0000FF"></xsl:variable>
<xsl:variable name="blue-font-end"><xsl:text disable-output-escaping="no"></Font></xsl:text></xsl:variable>
<xsl:variable name="pink-font"><Font html:color="#7030A0"></xsl:variable>
<xsl:variable name="pink-font-end"><xsl:text disable-output-escaping="no"></Font></xsl:text></xsl:variable>
<xsl:variable name="var-1" select="replace($text,'<select>',$blue-font)"/>
<xsl:variable name="var-2" select="replace($var-1,'</select>',$blue-font-end)"/>
<xsl:variable name="var-3" select="replace($var-2,'<strong>','<b>')"/>
<xsl:variable name="var-4" select="replace($var-3,'</strong>','</b>')"/>
<xsl:variable name="var-5" select="replace($var-4,'<keepordrop>',$pink-font)"/>
<xsl:variable name="var-6" select="replace($var-5,'</keepordrop>',$pink-font-end)"/>
<xsl:variable name="var-7" select="replace($var-6,'<edit>',$blue-font)"/>
<xsl:variable name="var-8" select="replace($var-7,'</edit>',$blue-font-end)"/>
<xsl:variable name="var-81" select="replace($var-8,'<select><option>',$blue-font)"/>
<xsl:variable name="var-82" select="replace($var-81,'</option><option>','BLUEFONTENDOP1 | BLUEFONTSTARTOP2')"/>
<xsl:variable name="var-83" select="replace($var-82,'BLUEFONTENDOP1',$blue-font-end)"/>
<xsl:variable name="var-84" select="replace($var-83,'BLUEFONTSTARTOP2',$blue-font)"/>
<xsl:variable name="var-85" select="replace($var-84,'</option></select>',$blue-font-end)"/>
<xsl:variable name="var-9">
<xsl:analyze-string select="$var-85"
regex="({$blue-font})(.*?)({$blue-font-end}|{$blue-font})">
<xsl:matching-substring>
<xsl:choose>
<xsl:when test="regex-group(3)=$blue-font-end">
<xsl:value-of select="regex-group(0)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="regex-group(1)"/>
<xsl:value-of select="regex-group(2)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:variable>
<xsl:variable name="var-10">
<xsl:analyze-string select="$var-9"
regex="({$blue-font-end})(.*?)({$blue-font-end}|{$blue-font})">
<xsl:matching-substring>
<xsl:choose>
<xsl:when test="regex-group(3)=$blue-font">
<xsl:value-of select="regex-group(0)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="regex-group(2)"/>
<xsl:value-of select="regex-group(3)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:variable>
<xsl:value-of select="$var-10"/>
</xsl:function>
<!--
*************************************GENERAL TEMPLATES**************************************************
-->
<xsl:template name="LFsToBRs">
<xsl:param name="input"/>
<xsl:choose>
<xsl:when test="contains($input, '
')">
<xsl:value-of select="substring-before($input, '
')"/>
<xsl:text disable-output-escaping="yes">&#10;</xsl:text>
<xsl:call-template name="LFsToBRs">
<xsl:with-param name="input" select="substring-after($input, '
')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$input"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- EXCEL WORKSHEET OPTIONS -->
<xsl:template name="worksheet-options">
<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel">
<PageSetup>
<Header x:Margin="0.3"/>
<Footer x:Margin="0.3"/>
<PageMargins x:Bottom="0.75" x:Left="0.7" x:Right="0.7" x:Top="0.75"/>
</PageSetup>
<Selected/>
<FreezePanes/>
<FrozenNoSplit/>
<SplitHorizontal>1</SplitHorizontal>
<TopRowBottomPane>1</TopRowBottomPane>
<ActivePane>2</ActivePane>
<Panes>
<Pane>
<Number>3</Number>
<ActiveRow>18</ActiveRow>
<ActiveCol>2</ActiveCol>
</Pane>
</Panes>
<ProtectObjects>False</ProtectObjects>
<ProtectScenarios>False</ProtectScenarios>
</WorksheetOptions>
</xsl:template>
<!-- EXCEL DOCUMENT PROPERTIES -->
<xsl:template name="document-properties">
<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office">
<Author>Mason, Huw {MDBZ~Basel}</Author>
<LastAuthor>Mason, Huw {MDBZ~Basel}</LastAuthor>
<Created></Created>
<Company></Company>
<Version>1</Version>
</DocumentProperties>
<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">
<WindowHeight>14370</WindowHeight>
<WindowWidth>27795</WindowWidth>
<WindowTopX>480</WindowTopX>
<WindowTopY>120</WindowTopY>
<ProtectStructure>False</ProtectStructure>
<ProtectWindows>False</ProtectWindows>
</ExcelWorkbook>
</xsl:template>
<!-- EXCEL DOCUMENT STYLES -->
<xsl:template name="document-styles">
<ss:Styles>
<ss:Style ss:ID="Default" ss:Name="Normal">
<ss:Alignment ss:Vertical="Bottom" ss:WrapText="1"/>
<ss:Borders/>
<ss:Font ss:FontName="Arial" x:Family="Swiss" ss:Size="10" ss:Color="#000000"/>
<ss:Interior/>
<ss:NumberFormat/>
<ss:Protection/>
</ss:Style><!-- **STYLES CREATED TO REF LATER ON e.g. STYLE FOR COL HEADER**-->
<ss:Style ss:ID="sHead">
<Alignment ss:Horizontal="Center" ss:Vertical="Top" ss:WrapText="1"/>
<ss:Borders>
<ss:Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/>
</ss:Borders>
<Font ss:FontName="Arial" x:Family="Swiss" ss:Size="9" ss:Color="#FFFFFF"/>
<ss:Interior ss:Color="#538DD5" ss:Pattern="Solid"/>
</ss:Style>
<ss:Style ss:ID="sHeadAC">
<Alignment ss:Horizontal="Center" ss:Vertical="Top" ss:WrapText="1"/>
<ss:Borders>
<ss:Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/>
</ss:Borders>
<Font ss:FontName="Arial" x:Family="Swiss" ss:Size="9" ss:Color="#000000"/>
<ss:Interior ss:Color="#FFFF66" ss:Pattern="Solid"/>
</ss:Style>
<ss:Style ss:ID="sHeadCT">
<Alignment ss:Horizontal="Center" ss:Vertical="Top" ss:WrapText="1"/>
<ss:Borders>
<ss:Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/>
</ss:Borders>
<Font ss:FontName="Arial" x:Family="Swiss" ss:Size="9" ss:Color="#FFFFFF"/>
<ss:Interior ss:Color="#FF9900" ss:Pattern="Solid"/>
</ss:Style>
<ss:Style ss:ID="sHeadGD">
<Alignment ss:Horizontal="Center" ss:Vertical="Top" ss:WrapText="1"/>
<ss:Borders>
<ss:Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/>
</ss:Borders>
<Font ss:FontName="Arial" x:Family="Swiss" ss:Size="9" ss:Color="#DA9694"/>
<ss:Interior ss:Color="#000000" ss:Pattern="Solid"/>
</ss:Style>
<ss:Style ss:ID="sHeadPRM">
<Alignment ss:Horizontal="Center" ss:Vertical="Top" ss:WrapText="1"/>
<ss:Borders>
<ss:Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/>
</ss:Borders>
<Font ss:FontName="Arial" x:Family="Swiss" ss:Size="9" ss:Color="#FFFFFF"/>
<ss:Interior ss:Color="#92D050" ss:Pattern="Solid"/>
</ss:Style>
<ss:Style ss:ID="sHeadALG">
<Alignment ss:Horizontal="Center" ss:Vertical="Top" ss:WrapText="1"/>
<ss:Borders>
<ss:Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/>
</ss:Borders>
<Font ss:FontName="Arial" x:Family="Swiss" ss:Size="9" ss:Color="#FFFFFF"/>
<ss:Interior ss:Color="#92D050" ss:Pattern="Solid"/>
</ss:Style>
<ss:Style ss:ID="sBody">
<Alignment ss:Horizontal="Left" ss:Vertical="Top" ss:WrapText="1"/>
<ss:Borders>
<ss:Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/>
<ss:Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/>
</ss:Borders>
<ss:Interior ss:Pattern="Solid"/>
</ss:Style>
<Style ss:ID="SHyperlink" ss:Name="Hyperlink">
<Alignment ss:Horizontal="Left" ss:Vertical="Top" ss:WrapText="1"/>
<Borders>
<Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/>
<Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/>
<Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/>
<Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/>
</Borders>
<Font ss:FontName="Arial" x:Family="Swiss" ss:Size="10" ss:Color="#0000FF" ss:Underline="Single"/>
</Style>
</ss:Styles>
</xsl:template>
</xsl:stylesheet>
Here is a sample from my output XML
<Row>
<Cell ss:StyleID="sBody">
<Data ss:Type="String">AVISIT</Data>
</Cell>
<Cell ss:StyleID="sBody">
<ss:Data xmlns="http://www.w3.org/TR/REC-html40" ss:Type="String">Set to collected visit name [EG.VISIT] <Font html:color="#7030A0">Set to 'POST-BASELINE MINIMUM'</Font>Set to a re-defined visit range based on user-defined input. <b>Set to 'POST-BASELINE MINIMUM'</b></ss:Data>
</Cell>
</Row>
<Row>
<Cell ss:StyleID="sBody">
<Data ss:Type="String">AVISITN</Data>
</Cell>
<Cell ss:StyleID="sBody">
<ss:Data xmlns="http://www.w3.org/TR/REC-html40" ss:Type="String">Set to collected <Font html:color="#0000FF">Set to 9997</Font>Set to a user defined numeric value <Font html:color="#0000FF">Set to 9997 wih the analysis visit </Font></ss:Data>
</Cell>
</Row>
I've created an xsl and saved as XML spreadsheet 2003 to check the code and is as expected. When I replace < > with < and > I get the issue I am experiencing with my output
I don't know if is related to using < rather than < when creating the xml. Has anybody had a similar problem and solved it? Or can see where my problem lies?
EDIT:
I,ve made the updates to the XSLT inc. the html namespace, as suggested by Martin. This creates the XML as I would expect and looks fine.
The problem now is that when opened in excel the formatting doesn't display, I get plain black text.
Output without formatting
I've created an example of what it should look and saved as XML spreadsheet 2003, to compare the two and they are the same.
Interestingly: If I copy the code from the excel created example and paste it in to the xslt created XML then open it in excel, the formatting works.
If I copy the code from the xslt created xml and paste it in to my excel created example (that did work fine) the formatting now no longer works.
Excel created example
The XML code of the excel created value
<Row ss:AutoFitHeight="0">
<Cell ss:StyleID="s64"><Data ss:Type="String">AVISIT</Data></Cell>
<Cell ss:StyleID="s65"><ss:Data ss:Type="String"
xmlns="http://www.w3.org/TR/REC-html40">Set to collected visit name [EG.VISIT] <Font
html:Color="#7030A0">Set to 'POST-BASELINE MINIMUM'</Font>Set to a re-defined visit range based on user-defined input. <B>Set to 'POST-BASELINE MINIMUM'</B></ss:Data></Cell>
</Row>
<Row ss:AutoFitHeight="0">
<Cell ss:StyleID="s64"><Data ss:Type="String">AVISITN</Data></Cell>
<Cell ss:StyleID="s65"><ss:Data ss:Type="String"
xmlns="http://www.w3.org/TR/REC-html40">Set to collected <Font
html:Color="#0000FF">Set to 9997</Font>Set to a user defined numeric value<Font
html:Color="#0000FF">Set to 9997 wih the analysis visit</Font></ss:Data></Cell>
</Row>
This is how the xml created by my XSLT looks:
<Row>
<Cell ss:StyleID="sBody">
<Data ss:Type="String">AVISIT</Data>
</Cell>
<Cell ss:StyleID="sBody">
<ss:Data xmlns="http://www.w3.org/TR/REC-html40" ss:Type="String">Set to collected visit name [EG.VISIT] <Font html:Color="#7030A0">Set to 'POST-BASELINE MINIMUM'</Font>Set to a re-defined visit range based on user-defined input. <B>Set to 'POST-BASELINE MINIMUM'</B>
</ss:Data>
</Cell>
</Row>
<Row>
<Cell ss:StyleID="sBody">
<Data ss:Type="String">AVISITN</Data>
</Cell>
<Cell ss:StyleID="sBody">
<ss:Data xmlns="http://www.w3.org/TR/REC-html40" ss:Type="String">Set to collected <Font html:Color="#0000FF">Set to 9997</Font>Set to a user defined numeric value <Font html:Color="#0000FF">Set to 9997 wih the analysis visit </Font>
</ss:Data>
</Cell>
</Row>
So despite the fact that the xml code is the same, depending on how it was created defines whether it works or not.
Question:
Could there be some encoding differences that would explain this behavior?

Here is how you could approach the problem using parse-xml-fragment (https://www.w3.org/TR/xpath-functions-31/#func-parse-xml-fragment, available in Saxon 9.7 (all editions) when using version="3.0" in your XSLT) and then templates to transform the elements you have inside of the escaped markup in the columnDerivationDescription elements:
<xsl:template name="getDset">
<xsl:for-each select="//dataset">
<ss:Worksheet ss:Name="{.//datasetName}"><!-- Add filters to the columns -->
<Names>
<NamedRange ss:Name="_FilterDatabase" ss:RefersTo="={.//datasetName}!R1C1:R1C2" ss:Hidden="1"/>
</Names>
<ss:Table ss:ExpandedColumnCount="2" ss:ExpandedRowCount="200" x:FullColumns="1" x:FullRows="1" ss:DefaultRowHeight="30">
<Column ss:Width="70"/>
<Column ss:Width="70"/>
<Row>
<Cell ss:StyleID="sHead">
<Data ss:Type="String">
Header1
</Data>
<NamedCell ss:Name="_FilterDatabase"/>
</Cell>
<Cell ss:StyleID="sHead">
<Data ss:Type="String">
Header2
</Data>
<NamedCell ss:Name="_FilterDatabase"/>
</Cell>
</Row>
<xsl:for-each select="columnList/column">
<xsl:if test="columnName !=' ' ">
<Row>
<Cell ss:StyleID="sBody" >
<Data ss:Type="String">
<xsl:value-of select="columnName"/>
</Data>
</Cell>
<!-- SET COLOR OF TEXT WHEN TAGGED -->
<Cell ss:StyleID="sBody">
<Data ss:Type="String" xmlns="http://www.w3.org/TR/REC-html40">
<xsl:apply-templates select="columnDerivationList/columnDerivation/columnDerivationDescription"/>
</Data>
</Cell>
<!-- COLOUR TAGGED. NICELY DONE -->
</Row>
</xsl:if>
</xsl:for-each>
</ss:Table>
<xsl:call-template name="worksheet-options"/>
<AutoFilter x:Range="R1C1:R1C2" xmlns="urn:schemas-microsoft-com:office:excel"/>
</ss:Worksheet>
</xsl:for-each>
</xsl:template>
<xsl:template match="columnDerivationDescription">
<xsl:apply-templates select="parse-xml-fragment(.)/node()"/>
</xsl:template>
<xsl:template match="strong">
<b>
<xsl:apply-templates/>
</b>
</xsl:template>
<xsl:template match="edit">
<Font html:color="#0000FF">
<xsl:apply-templates/>
</Font>
</xsl:template>
<!-- add further templates here for the other elements and transformations you need -->

Related

Appending nodes with similar names using xslt in wso2

I have some xml data with multiple Name nodes. Based on the presence or absence of id node, I need to segregate the nodes. On converting to JSON, I want all the similar nodes to be merged into an JSON array. Below is my XML data
<Names>
<CustName>
<Name>Name1</Name>
<id>3</id>
</CustName>
<CustName >
<Name>Name2</Name>
</CustName>
<CustName>
<Name>Name3</Name>
<id>32</id>
</CustName>
</Names>
The XSLT that I have tried is as follows. But this creates two nodes for Update and one node for Create. Whereas I want 1st and 3rd Name nodes to be under Update node and 2nd Name node under Create
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="no" method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<CustomerNames>
<xsl:for-each select="//Names/CustName">
<xsl:choose>
<xsl:when test="id !=''">
<Update>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
<id>
<xsl:value-of select="id"/>
</id>
</Update>
</xsl:when>
<xsl:otherwise>
<Create>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
</Create>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</CustomerNames>
</xsl:template>
</xsl:stylesheet>
Transformed xml should look like:
<CustomerNames>
<Update>
<CustName>Name1</CustName>
<id>3</id>
</Update>
<Update>
<CustName>Name3</CustName>
<id>32</id>
</Update>
<Create>
<Name>Name2</Name>
</Create>
</CustomerNames>
On converting to json, I want the similar nodes to be appended in an array. Like this
{
"CustomerNames":{
"Update":[
{
"CustName":"Name1",
"id":"3"
},
{
"CustName":"Name3",
"id":"32"
}
],
"Create":[
{
"Name":"Name2"
}
]
}
}
How can I achieve this in XSL 1.0?
It seems the order nodes are placed matters when auto-converting XML to a JSON. Hence update your XSLT to something like the one below.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="no" method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<CustomerNames>
<xsl:for-each select="//Names/CustName[id]">
<Update>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
<id>
<xsl:value-of select="id"/>
</id>
</Update>
</xsl:for-each>
<xsl:for-each select="//Names/CustName[not(id)]">
<Create>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
</Create>
</xsl:for-each>
</CustomerNames>
</xsl:template>
</xsl:stylesheet>
Alternatively, you can group the XML nodes by using XSL Sort
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="no" method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<CustomerNames xmlns="">
<xsl:for-each select="//Names/CustName">
<xsl:sort select="id"/>
<xsl:choose>
<xsl:when test="id !=''">
<Update>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
<id>
<xsl:value-of select="id"/>
</id>
</Update>
</xsl:when>
<xsl:otherwise>
<Create>
<CustName>
<xsl:value-of select="Name"/>
</CustName>
</Create>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</CustomerNames>
</xsl:template>
</xsl:stylesheet>
Another approach would be to use the Payloadfactry mediator after the XSLT mediator to group the nodes.Example below.
<payloadFactory media-type="xml">
<format>
<CustomerNames>
$1
$2
</CustomerNames>
</format>
<args>
<arg expression="//Update"/>
<arg expression="//Create"/>
</args>
</payloadFactory>

Sum of producst of subsums of (nodes with the same id) by a matching price

I add all the nodes' attributes which have the same index atrribute. I multiply this sum by a particular number (extracted from a secontary xml) and I have the step_1 product. I repeat the proceedure multiplying each time by a different particular number, and I add the new product to the product of the previous step and so on up to the last node. But in each step of the loop the sum functions looses the runningsum keeping only the current product. I would appreciate any help.
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="koinohrista.xsl"?>
<Features>
<Feature code="a" price="1"></Feature>
<Feature code="a" price="2"></Feature>
<Feature code="b" price="3"></Feature>
<Feature code="b" price="4"></Feature>
<Feature code="c" price="5"></Feature>
</Features>
The secondary xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="hiliosta.xsl"?>
<Features>
<Feature name="a1" koinoh="10" elev="20" heat="30"></Feature>
</Features>
The xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:variable name="Inventory" select="document('C:\Kapopoulos\Inventory.xml')"/>
<xsl:key name="KeyCode" match="Feature" use="#code"/>
<xsl:template match="Features">
<xsl:call-template name="sum">
<xsl:with-param name="nodes" select="Feature"/>
<xsl:with-param name="sum" select="0"/>
</xsl:call-template>
</xsl:template>
<!-- Sum Function -->
<xsl:template name="sum">
<xsl:param name="nodes"/>
<xsl:param name="sum"/>
<xsl:variable name="curr" select="$nodes[1]"/>
$sum1 = <xsl:value-of select="$sum"/><br/> <!-- for view reasons -->
<table>
<xsl:for-each select="Feature[generate-id() = generate-id(key('KeyCode',#code)[1])]">
<xsl:sort select="#code" order="ascending"/>
<xsl:if test="$curr">
Step1 = <xsl:value-of select="$sum"/> ||| <!-- for view reasons -->
<xsl:variable name="attrName">
<xsl:choose>
<xsl:when test="#code='a' or #code='c' or #code='d'">koinoh</xsl:when>
<xsl:when test="#code='b'">elev</xsl:when>
<xsl:when test="#code='e'">heat</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:variable name="runningsum" select="$sum + sum(key('KeyCode',#code)/#price)
* $Inventory/*/Feature[#name='a1']/#*[name()=$attrName] div sum($Inventory//Feature/#*[name()=$attrName])"/>
RunSum2 = <xsl:value-of select="$runningsum"/><br/> <!-- for view reasons -->
<xsl:call-template name="sum">
<xsl:with-param name="nodes" select="$nodes[position() > 1]"/>
<xsl:with-param name="sum" select="$runningsum"/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
<xsl:if test="not($curr)"> Final=
<xsl:value-of select="$sum"/>
</xsl:if>
</table>
</xsl:template>
</xsl:stylesheet>
Desired calculation steps and output
1) SumOfPrice for #code="a" : 1+2=3
2) ProductOfSum : 3 * "koinoh"=3*10=30
3) RunningSum = 30
4) SumOfPrice for #code="b" : 3+4=7
5) ProductOfSum : 7 * "#elev"=7*20=140
6) RunningSum = 30+140=170
7) SumOfPrice for #code="c" : 5
8) ProductOfSum : 5 * "#heat" = 5*30=150
6) RunningSum = 30+140+150=320
DesiredOutput = 320
To get only the final result of 320, you could skip the grouping part and just process each individual Feature on its own. However, it might be useful to minimize the lookups to the "Inventory" document, so let's keep the grouping:
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:param name="path-to-inventory">C:\Kapopoulos\Inventory.xml</xsl:param>
<xsl:variable name="inventory" select="document($path-to-inventory)/Features/Feature[#name='a1']"/>
<xsl:key name="feature-by-code" match="Feature" use="#code" />
<xsl:template match="/Features">
<result>
<xsl:call-template name="summarize">
<!-- select distinct codes only -->
<xsl:with-param name="items" select="Feature[count(. | key('feature-by-code', #code)[1]) = 1]"/>
</xsl:call-template>
</result>
</xsl:template>
<xsl:template name="summarize">
<xsl:param name="items" select="/.."/>
<xsl:param name="balance" select="0"/>
<xsl:choose>
<xsl:when test="$items">
<xsl:variable name="code" select="$items[1]/#code" />
<xsl:variable name="sum-price" select="sum(key('feature-by-code', $code)/#price)" />
<xsl:variable name="factor">
<xsl:choose>
<xsl:when test="$code='a'">
<xsl:value-of select="$inventory/#koinoh" />
</xsl:when>
<xsl:when test="$code='b'">
<xsl:value-of select="$inventory/#elev" />
</xsl:when>
<xsl:when test="$code='c'">
<xsl:value-of select="$inventory/#heat" />
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:variable name="product" select="$sum-price * $factor" />
<!-- recursive call -->
<xsl:call-template name="summarize">
<xsl:with-param name="items" select="$items[position() > 1]"/>
<xsl:with-param name="balance" select="$balance + $product"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$balance" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Xml to XSLT: getting attributes value and so on

I have this xml file:
<?xml version="1.0" encoding="UTF-8"?>
<interface id="RESTInterface">
.....
<abstract_method name="authenticateUser">
<access>public</access>
<parameters>
<argument type="String">account</argument>
<argument type="String">password</argument>
</parameters>
<throws>
<exception>RemoteException</exception>
<exception>SecurityException</exception>
</throws>
<return>boolean</return>
</abstract_method>
<abstract_method name="verifyUser">
<access>public</access>
<parameters>
<argument type="aURL">link</argument>
</parameters>
<return>void</return>
</abstract_method>
</interface>
And I am trying to create an xslt style sheet to create this result:
You could start with smth like this. (I made the assumption that you might be restricted to XSLT 1.0)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html" doctype-public="-//W3C//DTD HTML 4.0 Transitional//EN" doctype-system="_http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="interface"></xsl:apply-templates>
</body>
</html>
</xsl:template>
<xsl:template match="interface">
<h1><xsl:value-of select="#id"/></h1>
<table>
<thead>
<tr>
<td>Operation</td>
<td>Argument(s)</td>
<td>Return</td>
<td>Exception</td>
</tr>
</thead>
<xsl:apply-templates select="abstract_method"></xsl:apply-templates>
</table>
</xsl:template>
<xsl:template match="abstract_method">
<tr>
<td><xsl:value-of select="#name"/></td>
<td><p><xsl:apply-templates select="./parameters/argument"></xsl:apply-templates></p></td>
<td><p><xsl:value-of select="./return/text()"/></p></td>
<td><p><xsl:choose>
<xsl:when test="count(./throws/exception) > 0">Yes</xsl:when>
<xsl:otherwise>No</xsl:otherwise>
</xsl:choose></p></td>
</tr>
</xsl:template>
<xsl:template match="argument">
<span><xsl:value-of select="./text()"/>: <xsl:value-of select="#type"/>
<xsl:if test="following-sibling::argument">, </xsl:if></span>
</xsl:template>
</xsl:stylesheet>

Tranform XML to JSON using XSLT and remove a specific pair name in the JSON

I have an xml that needs to be converted to JSON and am using XSLT to transform it. In one of the element am not required to pass pair name, only values. See below
My XML:
<?xml version="1.0" encoding="UTF-8"?>
<map xmlns="http://www.w3.org/2005/xpath-functions">
<array key="cars">
<map>
<string key="doors">4</string>
<string key="price">6L</string>
</map>
<map>
<string key="doors">5</string>
<string key="price">13L</string>
</map>
</array>
</map>
Using XSL: https://xsltfiddle.liberty-development.net/b4GWVd
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:value-of select="translate(xml-to-json(., map { 'indent' : true() }),'Ø', 'ø')"/>
</xsl:template>
</xsl:stylesheet>
Gives output: Which is correct
{ "cars" :
[
{ "doors" : "4",
"price" : "6L" },
{ "doors" : "5",
"price" : "13L" } ] }
But then I would like to get a JSON with below structure, Without Pair Name(Reason: Its the structure required to be submitted to an API)
{ "cars" :
[
{ "4",
"6L" },
{ "5",
"13L" } ] }
As the XSLT 3.0 spec also provides an implementation of xml-to-json as an XSLT 3.0 package you could use that code and override templates where you want to eliminate the "keys" of JSON objects/XDM map items:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:j="http://www.w3.org/2013/XSLT/xml-to-json"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="#all"
version="3.0">
<xsl:use-package name="http://www.w3.org/2013/XSLT/xml-to-json" package-version="1.0">
<xsl:override>
<!-- Template rule for fn:map elements, representing JSON objects , overridden for content of cars array -->
<xsl:template match="fn:array[#key = 'cars']/fn:map" mode="indent">
<xsl:value-of>
<xsl:variable name="depth" select="count(ancestor::*) + 1"/>
<xsl:text>{</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>, </xsl:text>
<xsl:value-of select="j:indent($depth)"/>
</xsl:if>
<!--<xsl:apply-templates select="snapshot(#key)" mode="key-attribute"/>
<xsl:text> : </xsl:text>-->
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>}</xsl:text>
</xsl:value-of>
</xsl:template>
<xsl:template match="fn:array[#key = 'cars']/fn:map/*" mode="no-indent">
<xsl:value-of>
<xsl:text>{</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>,</xsl:text>
</xsl:if>
<!--<xsl:apply-templates select="snapshot(#key)" mode="key-attribute"/>
<xsl:text>:</xsl:text>-->
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>}</xsl:text>
</xsl:value-of>
</xsl:template>
</xsl:override>
</xsl:use-package>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:value-of select="j:xml-to-json(., map { 'indent' : true() })"/>
</xsl:template>
</xsl:stylesheet>
The above can be run with Saxon 10 HE and higher or Saxon 9.8 PE or EE and later by using the command line -s:your-xml.xml -xsl:above-xslt-xsl -lib:w3c-xml-to-json.xsl where the last option refers to the file https://www.w3.org/TR/xslt-30/xml-to-json.xsl linked from the XSLT 3 spec with one path/errata added, namely the declaration of the default mode with <xsl:mode name="j:xml-to-json"/> so it would look like
<xsl:package
name="http://www.w3.org/2013/XSLT/xml-to-json"
package-version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:j="http://www.w3.org/2013/XSLT/xml-to-json"
exclude-result-prefixes="xs fn j" default-mode="j:xml-to-json" version="3.0">
<xsl:variable name="quot" visibility="private">"</xsl:variable>
<xsl:param name="indent-spaces" select="2"/>
<!-- The static parameter STREAMABLE controls whether the stylesheet is declared as streamable -->
<xsl:param name="STREAMABLE" static="yes" as="xs:boolean" select="true()"/>
<!-- fix for https://github.com/w3c/qtspecs/blob/master/errata/xslt-30/errata.xml#L1154 -->
<xsl:mode name="j:xml-to-json"/>
<xsl:mode name="indent" _streamable="{$STREAMABLE}" visibility="public"/>
<xsl:mode name="no-indent" _streamable="{$STREAMABLE}" visibility="public"/>
<xsl:mode name="key-attribute" streamable="false" on-no-match="fail" visibility="public"/>
<!-- The static parameter VALIDATE controls whether the input, if untyped, should be validated -->
<xsl:param name="VALIDATE" static="yes" as="xs:boolean" select="false()"/>
<xsl:import-schema namespace="http://www.w3.org/2005/xpath-functions" use-when="$VALIDATE"/>
<!-- Entry point: function to convert a supplied XML node to a JSON string -->
<xsl:function name="j:xml-to-json" as="xs:string" visibility="public">
<xsl:param name="input" as="node()"/>
<xsl:sequence select="j:xml-to-json($input, map{})"/>
</xsl:function>
<!-- Entry point: function to convert a supplied XML node to a JSON string, supplying options -->
<xsl:function name="j:xml-to-json" as="xs:string" visibility="public">
<xsl:param name="input" as="node()"/>
<xsl:param name="options" as="map(*)"/>
<xsl:variable name="input" as="node()" use-when="$VALIDATE">
<xsl:copy-of select="$input" validation="strict"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$options('indent') eq true()">
<xsl:apply-templates select="$input" mode="indent">
<xsl:with-param name="fallback" as="(function(element()) as xs:string)?"
select="$options('fallback')" tunnel="yes"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="$input" mode="no-indent">
<xsl:with-param name="fallback" as="(function(element()) as xs:string)?"
select="$options('fallback')" tunnel="yes"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<!-- A document node is ignored -->
<xsl:template match="/" mode="indent no-indent">
<xsl:apply-templates mode="#current"/>
</xsl:template>
<!-- Template rule for fn:map elements, representing JSON objects -->
<xsl:template match="fn:map" mode="indent">
<xsl:value-of>
<xsl:variable name="depth" select="count(ancestor::*) + 1"/>
<xsl:text>{</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>, </xsl:text>
<xsl:value-of select="j:indent($depth)"/>
</xsl:if>
<xsl:apply-templates select="snapshot(#key)" mode="key-attribute"/>
<xsl:text> : </xsl:text>
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>}</xsl:text>
</xsl:value-of>
</xsl:template>
<xsl:template match="fn:map" mode="no-indent">
<xsl:value-of>
<xsl:text>{</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:apply-templates select="snapshot(#key)" mode="key-attribute"/>
<xsl:text>:</xsl:text>
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>}</xsl:text>
</xsl:value-of>
</xsl:template>
<!-- Template rule for fn:array elements, representing JSON arrays -->
<xsl:template match="fn:array" mode="indent">
<xsl:value-of>
<xsl:variable name="depth" select="count(ancestor::*) + 1"/>
<xsl:text>[</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>, </xsl:text>
<xsl:value-of select="j:indent($depth)"/>
</xsl:if>
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>]</xsl:text>
</xsl:value-of>
</xsl:template>
<xsl:template match="fn:array" mode="no-indent">
<xsl:value-of>
<xsl:text>[</xsl:text>
<xsl:for-each select="*">
<xsl:if test="position() gt 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:apply-templates select="." mode="#current"/>
</xsl:for-each>
<xsl:text>]</xsl:text>
</xsl:value-of>
</xsl:template>
<!-- Template rule for fn:string elements in which
special characters are already escaped -->
<xsl:template match="fn:string[#escaped='true']" mode="indent no-indent">
<xsl:sequence select="concat($quot, ., $quot)"/>
</xsl:template>
<!-- Template rule for fn:string elements in which
special characters need to be escaped -->
<xsl:template match="fn:string[not(#escaped='true')]" mode="indent no-indent">
<xsl:sequence select="concat($quot, j:escape(.), $quot)"/>
</xsl:template>
<!-- Template rule for fn:boolean elements -->
<xsl:template match="fn:boolean" mode="indent no-indent">
<xsl:sequence select="xs:string(xs:boolean(.))"/>
</xsl:template>
<!-- Template rule for fn:number elements -->
<xsl:template match="fn:number" mode="indent no-indent">
<xsl:value-of select="xs:string(xs:double(.))"/>
</xsl:template>
<!-- Template rule for JSON null elements -->
<xsl:template match="fn:null" mode="indent no-indent">
<xsl:text>null</xsl:text>
</xsl:template>
<!-- Template rule matching a key within a map where
special characters in the key are already escaped -->
<xsl:template match="fn:*[#key-escaped='true']/#key" mode="key-attribute">
<xsl:value-of select="concat($quot, ., $quot)"/>
</xsl:template>
<!-- Template rule matching a key within a map where
special characters in the key need to be escaped -->
<xsl:template match="fn:*[not(#key-escaped='true')]/#key" mode="key-attribute">
<xsl:value-of select="concat($quot, j:escape(.), $quot)"/>
</xsl:template>
<!-- Template matching "invalid" elements -->
<xsl:template match="*" mode="indent no-indent">
<xsl:param name="fallback" as="(function(element()) as xs:string)?"
tunnel="yes" required="yes"/>
<xsl:choose>
<xsl:when test="exists($fallback)">
<xsl:value-of select="$fallback(snapshot(.))"/>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="yes">>Inc</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Template rule matching (and discarding) whitespace text nodes in the XML -->
<xsl:template match="text()[not(normalize-space())]" mode="indent no-indent"/>
<!-- Function to escape special characters -->
<xsl:function name="j:escape" as="xs:string" visibility="final">
<xsl:param name="in" as="xs:string"/>
<xsl:value-of>
<xsl:for-each select="string-to-codepoints($in)">
<xsl:choose>
<xsl:when test=". gt 65535">
<xsl:value-of select="concat('\u', j:hex4((. - 65536) idiv 1024 + 55296))"/>
<xsl:value-of select="concat('\u', j:hex4((. - 65536) mod 1024 + 56320))"/>
</xsl:when>
<xsl:when test=". = 34">\"</xsl:when>
<xsl:when test=". = 92">\\</xsl:when>
<xsl:when test=". = 08">\b</xsl:when>
<xsl:when test=". = 09">\t</xsl:when>
<xsl:when test=". = 10">\n</xsl:when>
<xsl:when test=". = 12">\f</xsl:when>
<xsl:when test=". = 13">\r</xsl:when>
<xsl:when test=". lt 32 or (. ge 127 and . le 160)">
<xsl:value-of select="concat('\u', j:hex4(.))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="codepoints-to-string(.)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:value-of>
</xsl:function>
<!-- Function to convert a UTF16 codepoint into a string of four hex digits -->
<xsl:function name="j:hex4" as="xs:string" visibility="final">
<xsl:param name="ch" as="xs:integer"/>
<xsl:variable name="hex" select="'0123456789abcdef'"/>
<xsl:value-of>
<xsl:value-of select="substring($hex, $ch idiv 4096 + 1, 1)"/>
<xsl:value-of select="substring($hex, $ch idiv 256 mod 16 + 1, 1)"/>
<xsl:value-of select="substring($hex, $ch idiv 16 mod 16 + 1, 1)"/>
<xsl:value-of select="substring($hex, $ch mod 16 + 1, 1)"/>
</xsl:value-of>
</xsl:function>
<!-- Function to output whitespace indentation based on
the depth of the node supplied as a parameter -->
<xsl:function name="j:indent" as="text()" visibility="public">
<xsl:param name="depth" as="xs:integer"/>
<xsl:value-of select="'
', string-join((1 to ($depth + 1) * $indent-spaces) ! ' ', '')"/>
</xsl:function>
</xsl:package>
To run code with Java code you basically need to use an XsltCompiler created from Processor e.g.
Processor processor = new Processor(true);
XsltCompiler xsltCompiler = processor.newXsltCompiler();
XsltPackage xmlToJsonPackage = xsltCompiler.compilePackage(new StreamSource("w3c-xml-to-json.xsl"));
xsltCompiler.importPackage(xmlToJsonPackage);
XsltExecutable xsltExecutable = xsltCompiler.compile(new StreamSource("sheet.xsl"));
Xslt30Transformer xslt30Transformer = xsltExecutable.load30();
// now run stylesheet with e.g. transform() or applyTemplates()
xslt30Transformer.transform(new StreamSource("input.xml"), xslt30Transformer.newSerializer(System.out));

TEI-P5: how to add a css classname to html table tag?

I'd like to style a specific class of tables differently in transformed HTML via CSS.
For example in XML:
<table>
<row role="label">
<cell>
Manuscrit Bodmer
</cell>
<cell>
Manuscrit fr. 1457
</cell>
</row>
<row>
<cell>
<lb/>Début <supplied><locus from="1r">fol. 1</locus></supplied> :
</cell>
<cell>
<note><lb/><supplied>fol. 6vᵒ, ligne 15</supplied> :</note>
</cell>
</row>
</table>
In XSL:
<xsl:template match="tei:table[not(. = '')]">
<xsl:param name="sprache"/>
<xsl:element name="table">
<xsl:apply-templates><xsl:with-param name="sprache" select="$sprache"/></xsl:apply-templates>
</xsl:element>
</xsl:template>
To achieve that I'm thinking of applying a specific CSS class to that table eg. in generated HTML
<table class="comparison-table">
and style it in css.
Is this possible? And how can I achieve that exactly?
--- Update
Thanks to #zx485 I updated XML & XSL to:
<xsl:template match="tei:table[#rend='comparison-table']">
<xsl:param name="sprache"/>
<xsl:copy>
<xsl:attribute name="class"><xsl:text>comparison-table</xsl:text></xsl:attribute>
<xsl:apply-templates><xsl:with-param name="sprache" select="$sprache"/></xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="tei:table[not(. = '')]">
<xsl:param name="sprache"/>
<xsl:element name="table">
<xsl:apply-templates><xsl:with-param name="sprache" select="$sprache"/></xsl:apply-templates>
</xsl:element>
</xsl:template>
And in my XML:
<table rend="comparison-table">
<row role="label">
<cell>
Manuscrit Bodmer
</cell>
<cell>
Manuscrit fr. 1457
</cell>
</row>
....
But in the generated HTML file the tables don't have css classname "comparison-table".
=== Update 2: As #zx485 suggested in the discussion room I just had to change to
<xsl:template match="tei:table">
Change your template to
<xsl:template match="tei:table[not(. = '')]">
<xsl:param name="sprache"/>
<xsl:copy>
<xsl:attribute name="class"><xsl:text>comparison-table</xsl:text></xsl:attribute>
<xsl:apply-templates><xsl:with-param name="sprache" select="$sprache"/></xsl:apply-templates>
</xsl:copy>
</xsl:template>
I refactored zx485's solution with xsl:if, it's probably more readable and follows the DRY-principle:
<xsl:template match="tei:table">
<xsl:param name="sprache"/>
<xsl:element name="table">
<xsl:if test="#rend='comparison-table'">
<xsl:attribute
name="class"><xsl:text>comparison-table</xsl:text></xsl:attribute>
</xsl:if>
<xsl:apply-templates><xsl:with-param name="sprache"
select="$sprache"/></xsl:apply-templates>
</xsl:element>
</xsl:template>