1

This morning I was working on implementing xsl-fo formatting for the xhtml <cite> tag. Typically you want citations to be italicized:

<xsl:template match="cite">
  <fo:inline font-style="italic"><xsl:apply-templates/></fo:inline>
</xsl:template>

Of course there is this complication: if the surrounding text is already italicized, then you want the citation font-style to be normal for the purposes of contrast. The first, somewhat naive attempt at addressing this can be found in Doug Tidwell's IBM tutorial (http://www.ibm.com/developerworks/library/x-xslfo2app/#cite):

<xsl:template match="cite">
  <xsl:choose>
    <xsl:when test="parent::i">
      <fo:inline font-style="normal">
        <xsl:apply-templates select="*|text()"/>
      </fo:inline>
    </xsl:when>
    <xsl:otherwise>
      <fo:inline font-style="italic">
        <xsl:apply-templates select="*|text()"/>
      </fo:inline>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

This would be OK, except that xsl-fo allows for the font-style attribute in any block or inline tag. So imagine that in a somewhat modified flavor of html, the font-style attribute can appear in any (xhtml strict) block or inline tag which can contain text or children containing text (in addition to appearing in the xsl-fo output). The puzzle I'm trying to solve is how can you then determine whether or not any particular citation should be italicized?

My first stab at solving the problem:

<xsl:template match="cite">
  <fo:inline>
    <xsl:choose>
      <xsl:when test="ancestor::i">
        <xsl:variable name="font-style" select="'normal'"/>
      </xsl:when>
      <xsl:when test="parent::*[@font-style='italic']">
        <xsl:variable name="font-style" select="'normal'"/>
      </xsl:when>
      <xsl:when test="parent::*[@font-style='normal']">
        <xsl:variable name="font-style" select="'italic'"/>
      </xsl:when>
      <xsl:when test="ancestor::*[@font-style='italic']">
        <xsl:variable name="font-style" select="'normal'"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:variable name="font-style" select="'italic'"/>
      </xsl:otherwise>

      <xsl:attribute font-style=$font-style/>
      <xsl:apply-templates/>
  </fo:inline>
</xsl:template>

Of course it's clear that this doesn't work in every case:

<div font-style="italic">
  <p font-style="normal">Old, but good science fiction, such as
    <b><cite>Stephenson, Neil "Snowcrash"</cite></b>
    more text....
  </p></div>

The template above would set the font-style of the citation to normal because this condition would be the first one met:

<xsl:when test="ancestor::*[@font-style='italic']">

I've thought about something like

test="count(ancestor::*[@font-style='italic']) - count(ancestor::*[@font-style='normal') mod 2 = 0"

but this doesn't work, either, as the order of tags matters; i.e. it's perfectly legal to do something like this:

<div font-style="italic> ... <p font-style="italic"> ...</p></div>

I know how I would do this with a procedural language: count backwards through the ancestor list incrementing/decrementing an "italic" count variable, but I can't think of any way of doing this in XSL, given that variables are immutable.

pgoetz
  • 848
  • 1
  • 8
  • 18
  • I am confused as to whether that `font-style` attribute is part of your XML input tree or part of the result tree you want. As for 'count backwards through the ancestor list incrementing/decrementing an "italic" count variable', write a recursive function that walks the ancestor axis and has a count parameter that it returns at the end. – Martin Honnen Nov 26 '14 at 17:48
  • It's both! Think of the input language as xhtml+, basically xhtml, but with some xsl-fo style attributes (such as font-style) thrown in. Thanks for the tip on using the count parameter -- I will have to think about this. – pgoetz Nov 26 '14 at 17:57

1 Answers1

1

It sounds like you really only need to know if the first ancestor that contains @font-style has a @font-style value of italic. You could use a moded template to go back up the tree until it finds an ancestor that has a @font-style.

Example...

<xsl:template match="cite">
    <xsl:variable name="isItalic" as="xs:boolean">
        <xsl:choose>
            <xsl:when test="not(ancestor::*[@font-style[string()]])">
                <xsl:sequence select="false()"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:apply-templates select="." mode="isItalic"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <fo:inline font-style="{if ($isItalic) then 'normal' else 'italic'}">
        <xsl:apply-templates/>
    </fo:inline>
</xsl:template>

<xsl:template match="*" mode="isItalic" as="xs:boolean">
    <xsl:choose>
        <xsl:when test="parent::*[@font-style[string()]]">
            <xsl:sequence select="if (parent::*/@font-style='italic') then 
                true() else false()"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:apply-templates select=".." mode="isItalic"/>
        </xsl:otherwise>
    </xsl:choose>            
</xsl:template>

Here's another way to do it in XSLT 1.0...

<xsl:template match="cite">
    <xsl:variable name="font-style">
        <xsl:choose>
            <xsl:when test="not(ancestor::*[@font-style[string()]])">
                <xsl:text>italic</xsl:text>
            </xsl:when>
            <xsl:otherwise>
                <xsl:apply-templates select="." mode="getFontStyle"/>                    
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <fo:inline font-style="{$font-style}">
        <xsl:apply-templates/>
    </fo:inline>
</xsl:template>

<xsl:template match="*" mode="getFontStyle">
    <xsl:choose>
        <xsl:when test="parent::*[@font-style[string()]]/@font-style='italic'">
            <xsl:text>normal</xsl:text>
        </xsl:when>
        <xsl:when test="parent::*[@font-style[string()]]">
            <xsl:text>italic</xsl:text>
        </xsl:when>
        <xsl:otherwise>
            <xsl:apply-templates select=".." mode="getFontStyle"/>
        </xsl:otherwise>
    </xsl:choose>            
</xsl:template>
Daniel Haley
  • 51,389
  • 6
  • 69
  • 95
  • 1
    Are you sure the `xsl:value-of` shouldn't be `xsl:sequence`? Currently the template returns a text node which the `as` would always convert to true. – Martin Honnen Nov 26 '14 at 20:15
  • This is a very elegant solution -- thanks! Now I just need to map this idea to XSL 1.0 syntax. – pgoetz Nov 26 '14 at 20:16
  • @MartinHonnen - I'm not sure if `xsl:value-of` needs to change or not. In my testing, the `as` is converting the result of the template to either true() or false(). Maybe it's processor specific? I'm using Saxon-HE 9.5. I added a test example that illustrates what I'm seeing. I always use Saxon and I've always used `xsl:value-of` like this, but if this doesn't work in other 2.0 processors I'd like to know so I can change. Thanks! – Daniel Haley Nov 26 '14 at 20:56
  • @pgoetz - Let me know if you have any issues converting to 1.0. I wouldn't have added a 2.0 answer if the question wasn't tagged as such. – Daniel Haley Nov 26 '14 at 20:57
  • 1
    The xsl:value-of constructs a text node whose string value is "true" or "false". The xsl:variable with as="xs:boolean" causes this to be atomized to a string "true" or "false", then cast to a boolean true or false. So this works, but it would avoid four or five conversions if you used xsl:sequence to return a boolean in the first place. – Michael Kay Nov 26 '14 at 21:12
  • @MichaelKay - Thanks for the explanation. I will update the answer and keep that in mind in the future. – Daniel Haley Nov 26 '14 at 21:17
  • I think your approach is fine, it looks as my doubt was based on a misunderstanding. – Martin Honnen Nov 26 '14 at 21:18
  • @MichaelKay - so the previous solution using `` only worked precisely because of the `as="xs:boolean"` Schema attribute in ``, as per the paragraph beginning with "However" on p. 552 of your book? – pgoetz Nov 26 '14 at 22:26
  • @pgoetz - I added a 1.0 example. I also moved the `not(ancestor::*[@font-style[string()]])` test to the `cite` template in both examples. That way it's only run once per `cite`. – Daniel Haley Nov 26 '14 at 23:09
  • @DanielHaley - Again, awesome solution. I learned some new tricks from this. For my particular use case, I made this minor modification: `test="not(ancestor::i or ancestor::*[@font-style[string()]])"` which carries down into the moded recursion as well. – pgoetz Nov 27 '14 at 10:04