2

I have some HTML containing styled spans like these:

<p><span class="foo">unstyled span</span></p>
<p><span style="font-style: italic;">italic</span></p>
<p><span style="font-weight: bold; font-style: italic;">bold italic</span></p>
<p><span style="text-decoration: underline; font-style: italic; font-weight: bold;">underline italic bold</span></p>

Spans are also used there to set font and background color and few more things. Basically I have to replace span tags with em, strong, etc. where possible, keep some styles and remove everything else (unneeded styles and classes). For the above input, the desired output is:

<p>unstyled span</p>
<p><em>italic</em></p>
<p><em><strong>bold italic</strong></em></p>
<p><em><strong><span style="text-decoration: underline;">underline italic bold</span></strong></em></p>

With my very limited XSLT skills I was able to write the following transformation that does the job but looks ugly:

<?xml version="1.0" encoding="UTF-8"?>

<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="html" omit-xml-declaration="yes"/>

  <xsl:template match="span">
    <xsl:call-template name="startStyleTests" select="." />
  </xsl:template>

  <xsl:template name="startStyleTests">
    <xsl:call-template name="testItalic" select="." />
  </xsl:template>

  <xsl:template name="testItalic">
    <xsl:choose>
      <xsl:when test="contains(./@style, 'italic')">
        <xsl:element name="em"><xsl:call-template name="testBold" select="." /></xsl:element>
      </xsl:when>
      <xsl:otherwise><xsl:call-template name="testBold" select="." /></xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="testBold">
    <xsl:choose>
      <xsl:when test="contains(./@style, 'bold')">
        <xsl:element name="strong"><xsl:call-template name="testUnderline" select="." /></xsl:element>
      </xsl:when>
      <xsl:otherwise><xsl:call-template name="testUnderline" select="." /></xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="testUnderline">
    <xsl:choose>
      <xsl:when test="contains(./@style, 'underline')">
        <xsl:element name="span">
          <xsl:attribute name="style">text-decoration: underline;</xsl:attribute>
          <xsl:call-template name="endStyleTests" select="." />
        </xsl:element>
      </xsl:when>
      <xsl:otherwise><xsl:call-template name="endStyleTests" select="." /></xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="endStyleTests">
      <xsl:apply-templates />
  </xsl:template>

  <xsl:template match="@*|node()">
    <xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy>
  </xsl:template>

</xsl:transform>

(a few "testSomething" templates removed for readability)

How should it really be done with XSLT 1.0?

intert
  • 23
  • 2

1 Answers1

0

What you could do is create a variable that holds a "mapping" of the styles to elements that you want

<xsl:variable name="styles">
   <style match="italic" element="em" />
   <style match="bold" element="strong" />
   <style match="underline" element="span" style="text-decoration: underline;" />
</xsl:variable>

Then, rather than have separate templates for each style mapping, you can have one template, that recursively checks each of the mappings in turn.

Note that in XSLT 1.0 the styles variable actually contains what is known as a "Result Tree Fragment", so you will need an extension function to process them as nodes.

Try this XSLT

<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
               xmlns:exslt="http://exslt.org/common"
               exclude-result-prefixes="exslt">

  <xsl:output method="html" omit-xml-declaration="yes"/>

  <xsl:variable name="styles">
    <style match="italic" element="em" />
    <style match="bold" element="strong" />
    <style match="underline" element="span" style="text-decoration: underline;" />
  </xsl:variable>

  <xsl:template match="span">
    <xsl:call-template name="startStyleTests" /> 
  </xsl:template>

  <xsl:template name="startStyleTests">
    <xsl:param name="styleNumber" select="1" />
    <xsl:variable name="style" select="exslt:node-set($styles)/style[position() = $styleNumber]" />
    <xsl:choose>
      <xsl:when test="not($style)">
          <xsl:call-template name="endStyleTests" />
      </xsl:when>
      <xsl:when test="contains(@style, $style/@match)">
        <xsl:element name="{$style/@element}">
         <xsl:if test="$style/@style">
           <xsl:attribute name="style">
             <xsl:value-of select="$style/@style" />
           </xsl:attribute>
         </xsl:if>
         <xsl:call-template name="startStyleTests">
            <xsl:with-param name="styleNumber" select="$styleNumber + 1" />
         </xsl:call-template>
        </xsl:element>
      </xsl:when>
      <xsl:otherwise>
       <xsl:call-template name="startStyleTests">
          <xsl:with-param name="styleNumber" select="$styleNumber + 1" />
       </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="endStyleTests">
      <xsl:apply-templates />
  </xsl:template>

  <xsl:template match="@*|node()">
    <xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy>
  </xsl:template>
</xsl:transform>

Alternatively, you could put the style mappings in a separate XML document, and then use the document() function to access it. That would not need an extension function.

Tim C
  • 70,053
  • 14
  • 74
  • 93
  • Thank you for the answer and the `document()` tip, Tim. I have also found [this answer](http://stackoverflow.com/a/3880709/7763334) on using a node set as a parameter and followed it to create a version that does not need an extension and keeps style mappings in the same document. – intert Mar 25 '17 at 12:51