Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Westi on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

XPath "following-sibling" does not include text nodes? 1

Status
Not open for further replies.

thenewa2x

Programmer
Dec 10, 2002
349
US
I'm trying to create a template that will make inline references look like this:

My sentence.(1) My other sentence. (2, 3)

Using this tempate:
Code:
  <!-- Match Ref -->
  <xsl:template match="ref">
    <fo:inline baseline-shift="4" font-size="7.5pt">
      <xsl:if test="not( preceding-sibling::ref )">
        <fo:inline>
          <xsl:text>(</xsl:text>
        </fo:inline>
      </xsl:if>
      <fo:inline>
        <xsl:copy-of select="text()" />
      </fo:inline>
      <xsl:if test="following-sibling::ref">
        <xsl:text>, </xsl:text>
      </xsl:if>
      <xsl:if test="not( following-sibling::ref )">
        <fo:inline>
          <xsl:text>)</xsl:text>
        </fo:inline>
      </xsl:if>
    </fo:inline>
  </xsl:template>

Unfortunately the references end up looking like this:

My sentence.(1, My other sentence. 2, 3)

I think this is because the XPath I'm using does not take into account text nodes. How can I modify the test attributes in [tt]<xsl:if />[/tt] to account for the text in between the references.

 
Sorry about that.

Code:
<document>
  My sentence.<ref>1</ref>  My other sentence.<ref>2</ref><ref>3</ref>
</document>

 
You have a couple things wrong in your XPath expressions. Here is a working (reduced) stylesheet.
Code:
<xsl:stylesheet version="1.0" xmlns:xsl="[URL unfurl="true"]http://www.w3.org/1999/XSL/Transform">[/URL]
<xsl:output method="text"/>

<xsl:template match="/">
	<xsl:apply-templates/>
</xsl:template>

<xsl:template match="document">
<xsl:apply-templates/>
</xsl:template>

<xsl:template match="ref">
      <xsl:if test="local-name(preceding-sibling::node()[1]) != 'ref'">
          <xsl:text>(</xsl:text>
      </xsl:if>
      <xsl:copy-of select="text()" />
      <xsl:choose>
      <xsl:when test="local-name(following-sibling::node()[1]) = 'ref'">
          <xsl:text>, </xsl:text></xsl:when>
      <xsl:otherwise>
          xsl:text>)</xsl:text></xsl:otherwise>
      </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

It produces this:
Code:
  My sentence.(1)  My other sentence.(2, 3)

The principal node type for the *-sibling axes (as with most of the axes) is element. However, you can use the node test node() to override the principal node type so you can see all the nodes, including the text nodes.

Then it is reasonable to explicitly test the immediate neighbors; that is the reason for the '[1]' portion of the expression.

You were using the formulation preceding-sibling::ref. This will develop a nodeset of elements the local name of which is 'ref' which is not what you want. Therefore, the correct way to test if the neighbor node is a 'ref' node is to test the local-name of the neighbor node.

Finally, the text nodes are copied through to the output by the built-in template rules.

Hope this helps... [bigsmile]

Tom Morrison
 
Thank you very much.

This is the new version of the template I now have working:

Code:
  <xsl:template match="ref">
    <fo:inline baseline-shift="4" font-size="7.5pt">
      <xsl:if test="local-name( preceding-sibling::node( ) ) != 'ref'">
        <fo:inline>
          <xsl:text>(</xsl:text>
        </fo:inline>
      </xsl:if>
      <fo:inline>
        <xsl:copy-of select="text()" />
      </fo:inline>
      <xsl:if test="local-name( following-sibling::node( ) ) = 'ref'">
        <xsl:text>, </xsl:text>
      </xsl:if>
      <xsl:if test="local-name( following-sibling::node( ) ) != 'ref'">
        <fo:inline>
          <xsl:text>)</xsl:text>
        </fo:inline>
      </xsl:if>
    </fo:inline>
  </xsl:template>

 
I was a bit unsatisfied with the stylesheet provided in my previous example. While it works for the sample provided, and may serve the end purpose well, I feel that it is just a bit to delicate for my tastes.

For example, what happens if there is a whitespace-only text node between two <ref> nodes? The previous example does not suppress the whitespace-only node. Indeed, it would be very difficult to do something relatively simple like that.

So, presume an input document that has whitespace introduced between two (otherwise) adjacent <ref> elements.
Code:
<document>
  My sentence.<ref>1</ref>  My other sentence.<ref>2</ref>[COLOR=white red]    [/color]<ref>3</ref>
</document>

Herewith a recursive stylesheet that solves this modest change in assumptions. I represent that the recursive model is also a better platform for additional changes when you discover other changes imposed on your input document.
Code:
<xsl:stylesheet version="1.0" xmlns:xsl="[URL unfurl="true"]http://www.w3.org/1999/XSL/Transform">[/URL]
<xsl:output method="text"/>

<xsl:template match="/">
	<xsl:apply-templates/>
</xsl:template>

<xsl:template match="document">
<xsl:call-template name="formatRefs">
	<xsl:with-param name="nodeList" select="child::node()"/>
</xsl:call-template>
</xsl:template>

<xsl:template name="formatRefs">
	<xsl:param name="nodeList"/>
	<xsl:param name="isInitial" select="true()"/>
	<xsl:param name="previousNodeIsRef" select="false()"/>

	<xsl:variable name="thisNode" select="$nodeList[1]"/>
	<xsl:variable name="remainingNodes" select="$nodeList[position() != 1]"/>

	<xsl:choose>
	<xsl:when test="$thisNode">
		<xsl:choose>
		<xsl:when test="local-name($thisNode) = 'ref'">
		<!-- process a ref element -->
			<xsl:choose>
			<xsl:when test="$previousNodeIsRef"><xsl:text>, </xsl:text></xsl:when>
			<xsl:otherwise><xsl:text>(</xsl:text></xsl:otherwise>
			</xsl:choose>
			<xsl:value-of select="$thisNode"/>
			<xsl:call-template name="formatRefs">
				<xsl:with-param name="nodeList" select="$remainingNodes"/>
				<xsl:with-param name="isInitial" select="false()"/>
				<xsl:with-param name="previousNodeIsRef" select="true()"/>
			</xsl:call-template>
		</xsl:when>
		<xsl:otherwise>
			<xsl:choose>
			<xsl:when test="normalize-space($thisNode)">
				<xsl:if test="$previousNodeIsRef"><xsl:text>)</xsl:text></xsl:if>
				<xsl:value-of select="$thisNode"/>
				<xsl:call-template name="formatRefs">
					<xsl:with-param name="nodeList" select="$remainingNodes"/>
					<xsl:with-param name="isInitial" select="false()"/>
					<xsl:with-param name="previousNodeIsRef" select="false()"/>
				</xsl:call-template>
			</xsl:when>
			<xsl:otherwise>
				[COLOR=white red]<!-- ignore whitespace node -->[/color]
				<xsl:call-template name="formatRefs">
					<xsl:with-param name="nodeList" select="$remainingNodes"/>
					<xsl:with-param name="isInitial" select="$isInitial"/>
					<xsl:with-param name="previousNodeIsRef" select="$previousNodeIsRef"/>
				</xsl:call-template>
			</xsl:otherwise>
			</xsl:choose>
		</xsl:otherwise>
		</xsl:choose>
	</xsl:when>
	<xsl:otherwise>
		<xsl:if test="$previousNodeIsRef"><xsl:text>)</xsl:text></xsl:if>
	</xsl:otherwise>
	</xsl:choose>
</xsl:template>

</xsl:stylesheet>

Tom Morrison
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top