xmlxsltxslt-2.0xsl-fo

Replacing text using XSL-FO


I recently asked a similar question Convert text string to xml tagging using xsl pt.2 and received the solution, which has helped me put together some XSL-FO code.

Now, the XML I'm trying to convert is <p>The @c cmdname is a utility and @v varname is an option.</p>

to something like:

The cmdname is a utility and varname is an option.

I use the following XSL-FO to convert the word following '@c' (cmdname) to monospace font, and the word following '@v' (varname) to italics. But, after building the PDF with a sentence like the XML example, only the font for @c (cmdname) changes. Note that the <p> tag in the example can be any XML tag.

It's a long story, but I can't just use XML tags, like <i> to make the font italic.

My XSL-FO code looks like:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   version="2.0">

<xsl:template match="text()[contains(., '@c')]">
   <xsl:analyze-string select="." regex="@c\s+([!-~]+)">
      <xsl:matching-substring>
         <fo:inline font-family="RobotoMono-Regular">
            <xsl:value-of select="regex-group(1)"/>
         </fo:inline>
      </xsl:matching-substring>
      <xsl:non-matching-substring>
         <xsl:value-of select="."/>
      </xsl:non-matching-substring>
   </xsl:analyze-string>
</xsl:template>

<xsl:template match="text()[contains(., '@v')]">
   <xsl:analyze-string select="." regex="@v\s+([!-~]+)">
      <xsl:matching-substring>
         <fo:inline font-family="Roboto" font-style="italic">
            <xsl:value-of select="regex-group(1)"/>
         </fo:inline>
      </xsl:matching-substring>
      <xsl:non-matching-substring>
         <xsl:value-of select="."/>
      </xsl:non-matching-substring>
   </xsl:analyze-string>
</xsl:template>

</xsl:stylesheet>

How can I modify this XSL-FO code to work with any '@*' tagging combination?


Solution

  • Your two templates are the same priority, so for the text node that contains both @c and @v, you're processing the@c and getting the string value of the rest of the text node. (I'd be surprised if your XSLT processor isn't warning you about the two possible matching templates.)

    You can connect the two either by having nested xsl:analyze-string in a template matching on text() or by having one template call the other:

    <xsl:template match="text()[contains(., '@c')]" priority="5">
      <xsl:analyze-string select="." regex="@c\s+([!-~]+)">
        <xsl:matching-substring>
          <fo:inline font-family="RobotoMono-Regular">
            <xsl:value-of select="regex-group(1)"/>
          </fo:inline>
        </xsl:matching-substring>
        <xsl:non-matching-substring>
          <xsl:call-template name="varname" />
        </xsl:non-matching-substring>
      </xsl:analyze-string>
    </xsl:template>
    
    <xsl:template match="text()[contains(., '@v')]" name="varname">
      <xsl:param name="string" select="." />
      <xsl:analyze-string select="$string" regex="@v\s+([!-~]+)">
        <xsl:matching-substring>
          <fo:inline font-family="Roboto" font-style="italic">
            <xsl:value-of select="regex-group(1)"/>
          </fo:inline>
        </xsl:matching-substring>
        <xsl:non-matching-substring>
          <xsl:value-of select="."/>
        </xsl:non-matching-substring>
      </xsl:analyze-string>
    </xsl:template>