当父有其他子节点时,计算文本节点的字符串长度

时间:2013-01-24 19:05:33

标签: xslt xsl-fo docbook

我坚持使用XSLT 1.0制作用于逐字输出到PDF的连字脚本。 (注意它是PDF所以我不能使用聪明的CSS来帮助我。 我希望能够访问text()节点和子text()节点,以比较stringlength作为脚本的一部分。

例如:

<programlisting>This text will be displayed without word-wrap
I can measure length of string between end line chars '&#10;'
and break long lines. That works great until

<emphasis>this gets added</emphasis> then my counting loop doesn't
count the text in the emphasis tags.
</programlisting>

我使用名为screen的模板循环遍历text()。它检测行尾字符并确定行是否太长。长行被打破,其余部分通过模板递归,直到余数小于最大行长...然后到下一行文本... ...效果很好,但现在我有一些孩子了,我无法弄清楚如何同时访问text()和任何子节点的text()来计算字符串长度。

对于长示例代码抱歉...

要匹配的模板看起来像这样......

<xsl:template match="programlisting/text()"> 
   <xsl:variable name="max_width">100</xsl:variable>
   <xsl:variable name="min_width">80</xsl:variable>
          <xsl:call-template name="screen">
            <xsl:with-param name="text" select="."/>
            <xsl:with-param name="max_width" select="$max_width"/>
            <xsl:with-param name="min_width" select="$min_width"/>
          </xsl:call-template>
</xsl:template>

决定换行前字符串是否太长的模板:

<xsl:template name="screen"> 
    <xsl:param name="text" select="."/>
    <xsl:param name="max_width"/>
    <xsl:param name="min_width"/>
    <xsl:variable name="fixed_text"> <!-- add end linebreak if missing -->
        <xsl:choose>
            <xsl:when test="substring($text,string-length($text),1) = '&#10;'">
                <xsl:value-of select="$text"/>  
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="concat($text,'&#10;')"/>  
            </xsl:otherwise>
        </xsl:choose> 
     </xsl:variable>
     <xsl:variable name="first_line">
                <xsl:value-of select="substring-before(concat($fixed_text,'&#10;'),'&#10;')"/>
    </xsl:variable>
    <xsl:variable name="other_lines">
                <xsl:value-of select="substring-after($fixed_text,concat($first_line,'&#10;'))"/>
    </xsl:variable>
    <xsl:choose>
        <xsl:when test="string-length(normalize-space($first_line)) &lt; 1"><!-- blank line (just trim and copy)-->
            <xsl:value-of select="concat($verbatim_padding,substring($first_line,1,100),'&#10;')"/>
        </xsl:when>
        <xsl:when test="string-length($first_line) &lt; 100"> <!-- short line (just copy)-->
            <xsl:value-of select="concat($verbatim_padding,$first_line,'&#10;')"/>
        </xsl:when>
        <xsl:otherwise>
            <!--  Line is too long!!  -->
            <xsl:variable name="wrapped_lines">
               <xsl:call-template name="break_line"> 
                    <xsl:with-param name="long_string" select="$first_line"/>
                    <xsl:with-param name="max_chars" select="100"/>
                    <xsl:with-param name="min_chars" select="80"/>
                    <xsl:with-param name="break_chars" select="' ,/;'"/>
                    <xsl:with-param name="linebreak_string" select="'~'"/>
                </xsl:call-template> 
            </xsl:variable>
            <xsl:value-of select="concat($verbatim_padding,$wrapped_lines)"/>
        </xsl:otherwise>
    </xsl:choose>
    <xsl:if test="string-length($other_lines) &gt; 0">
          <xsl:call-template name="screen">
            <xsl:with-param name="text" select="$other_lines"/>
            <xsl:with-param name="max_width" select="$max_width"/>
            <xsl:with-param name="min_width" select="$min_width"/>
          </xsl:call-template>
    </xsl:if>
</xsl:template>

打破长行的模板:

<xsl:template name="break_line"> 
    <xsl:param name="long_string"/>
    <xsl:param name="max_chars"/> <!--max chars allowed on a line -->
    <xsl:param name="min_chars"/> <!--max char position foa soft linebreak (else we hard break at the max chars!) -->
    <xsl:param name="break_chars"/>   <!-- chars used for soft breaking -->
    <xsl:param name="linebreak_string"/>  <!-- add this to end of a broken line -->
    <xsl:choose>
      <xsl:when test="(string-length($long_string) &lt; $max_chars) or (string-length($long_string) &lt; $min_chars) or (string-length($long_string) &lt; 1)">
          <xsl:value-of select="concat($long_string,'&#10;')"/>
       </xsl:when>
      <xsl:otherwise>
            <xsl:variable name="trim_x_by">
                    <xsl:call-template name="CheckLastChar">
                        <xsl:with-param name="string" select="$long_string"/>
                        <xsl:with-param name="start" select="$max_chars"/>
                        <xsl:with-param name="stop" select="$min_chars"/>
                        <xsl:with-param name="break_chars" select="$break_chars"/>
                        <xsl:with-param name="max_chars" select="$max_chars"/>
                     </xsl:call-template>
            </xsl:variable>
            <xsl:variable name="first_x_chars"><xsl:value-of select="substring($long_string,1,$trim_x_by)"/></xsl:variable>
            <xsl:variable name="remaining_chars"><xsl:value-of select="substring-after($long_string,$first_x_chars)"/></xsl:variable>

            <xsl:value-of select="concat($first_x_chars,' ',$linebreak_string)"/>

            <xsl:call-template name="break_line"> 
                <xsl:with-param name="long_string" select="$remaining_chars"/>
                <xsl:with-param name="max_chars" select="$max_chars"/>
                <xsl:with-param name="min_chars" select="$min_chars"/>
                <xsl:with-param name="break_chars" select="$break_chars"/>
                <xsl:with-param name="linebreak_string" select="$linebreak_string"/>
            </xsl:call-template> 
      </xsl:otherwise>
    </xsl:choose>
</xsl:template>

最后递归检查字符串字符以查看它是否是一个安全的换行符(软断点)

   <xsl:template name="CheckLastChar">
        <xsl:param name="string"/>
        <xsl:param name="stop" select="1"/>
        <xsl:param name="start" select="string-length($string)"/>
        <xsl:param name="count" select="$start"/>
        <xsl:param name="break_chars"/>
        <xsl:param name="max_chars"/>
        <xsl:choose>
          <xsl:when test="($count &lt; $stop) or (string-length($string) &lt; $stop)"> 
                <!-- gone so far into the line that a linebreak would look weird! So return the max-length and that will
                force a 'hard' linebreak exactly at the max-length point regardless of the character -->
                <xsl:value-of select="$max_chars"/> 
          </xsl:when>
          <xsl:otherwise>
                <xsl:variable name="last_char"><xsl:value-of select="substring($string,$count,1)"/></xsl:variable>
                <xsl:choose>
                  <xsl:when test="contains($break_chars,$last_char)">
                        <xsl:value-of select="$count"/>
                  </xsl:when>
                 <xsl:otherwise>
                        <!-- keep looking -->
                        <xsl:call-template name="CheckLastChar">
                            <xsl:with-param name="string" select="$string"/>
                            <xsl:with-param name="stop" select="$stop"/>
                            <xsl:with-param name="start" select="$start"/>
                            <xsl:with-param name="count" select="$count - 1"/>
                            <xsl:with-param name="break_chars" select="$break_chars"/>
                            <xsl:with-param name="max_chars" select="$max_chars"/>
                        </xsl:call-template>
                  </xsl:otherwise>
                </xsl:choose>
          </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

1 个答案:

答案 0 :(得分:1)

要获取元素中的所有文本(包括其后代的文本),只需使用string(Element)

<xsl:template match="programlisting"> 
   <xsl:variable name="max_width">100</xsl:variable>
   <xsl:variable name="min_width">80</xsl:variable>
          <xsl:call-template name="screen">
            <xsl:with-param name="text" select="string(.)"/>
            <xsl:with-param name="max_width" select="$max_width"/>
            <xsl:with-param name="min_width" select="$min_width"/>
          </xsl:call-template>
</xsl:template>

请注意从text()属性中删除match

XPath Spec

  

元素节点的字符串值是文档顺序中元素节点的所有文本节点后代的字符串值的串联。

     

string()函数将对象转换为字符串,如下所示:   通过返回文档顺序中第一个节点集中节点的字符串值,将节点集转换为字符串。如果节点集为空,则返回空字符串。