7

我有一个问题要问 SO 社区的聪明人。

下面是Symphony CMS生成的 XML 片段。

   <news>
        <entry>
            <title>Lorem Ipsum</title>
            <body>
                <p><strong>Lorem Ipsum</strong></p>
                <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed malesuada auctor magna. Vivamus urna justo, pulvinar nec, sagittis malesuada, accumsan in, massa. Quisque mi purus, gravida eget, ultricies a, porta in, sem. Maecenas justo elit, elementum vel, feugiat vulputate, pulvinar nec, velit. Fusce vel ante et diam bibendum euismod. Nunc vel nulla non lorem dignissim placerat. Nulla magna massa, auctor et, tempor nec, auctor sit amet, turpis. Quisque odio lacus, auctor at, posuere id, suscipit eget, dui. Phasellus aliquam. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin varius. Phasellus cursus. Cras mattis adipiscing turpis. Sed.</p>
                <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed malesuada auctor magna.</p>
            </body>
        </entry>
    </news>

我需要做的是<body>根据指定的长度取一部分元素,以博客样式显示:

Lorem ipsum dolor sit amet, consectetur adipiscing elit。sed malesuada auctor magna。Vivamus urna justo、pulvinar nec、sagittis malesuada、accumsan in、massa。Quisque mi purus, gravida eget, ultricies a, porta in, sem...更多

...更多是指向完整新闻项目的链接。我知道我可以选择特定的段落,我也知道我可以使用 substring 函数带来指定数量的字符。但是,我需要保留文本的格式,即<body>元素中的 HTML 标记。

我意识到这会引发标签关闭的问题,但肯定有办法。希望对 XSLT 更有经验的人可以对这个问题有所了解。

4

5 回答 5

5

这是完全解决问题的完整 XSLT 1.0 转换。

这个 XSLT 转换:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common"
 xmlns:f="http://fxsl.sf.net/"
 xmlns:myAdd="f:myAdd"
 xmlns:myParam="f:myParam"
 exclude-result-prefixes="ext f myAdd myParam"
>
 <xsl:import href="scanl.xsl"/>
 <!--                                         -->
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>
 <!--                                         -->
 <myAdd:myAdd/>
 <myParam:myParam>0</myParam:myParam>
 <!--                                         -->
 <xsl:param name="pTruncateLength" select="772"/>
 <!--                                         -->
   <xsl:variable name="vFun" select="document('')/*/myAdd:*[1]"/>
   <xsl:variable name="vZero" select="document('')/*/myParam:*[1]"/>
 <!--                                         -->
   <xsl:variable name="vrtfScanResults">
           <xsl:call-template name="scanl">
             <xsl:with-param name="pFun" select="$vFun"/>
             <xsl:with-param name="pQ0" select="$vZero" />
             <xsl:with-param name="pList" select="/*/*/body//text()"/>
           </xsl:call-template>
   </xsl:variable>
 <!--                                         -->
   <xsl:variable name="vScanResults"
        select="ext:node-set($vrtfScanResults)"/>
   <xsl:variable name="vindNode" select=
    "count($vScanResults/*[. > $pTruncateLength][1]
                                   /preceding-sibling::*)"/>
 <!--                                         -->
   <xsl:variable name="vrtfTruncInfo">
       <xsl:for-each select="/*/*/body//text()">
 <!--                                         -->
         <xsl:variable name="vPos" select="position()"/>
         <tNode id="{generate-id()}">
           <xsl:attribute name="preserve">
             <xsl:if test="$vPos &lt; $vindNode">
               <xsl:value-of select="string-length(.)"/>
             </xsl:if>
             <xsl:if test="$vPos > $vindNode">
               <xsl:value-of select="0"/>
             </xsl:if>
             <xsl:if test="$vPos = $vindNode">
               <xsl:value-of select=
               "$vScanResults/*[$vindNode+1]
               -
                $pTruncateLength"/>
             </xsl:if>
           </xsl:attribute>
         </tNode>
       </xsl:for-each>
   </xsl:variable>
 <!--                                         -->
   <xsl:variable name="vTruncInfo" select="ext:node-set($vrtfTruncInfo)"/>
 <!--                                         -->
 <xsl:template match="node()|@*">
   <xsl:copy>
     <xsl:apply-templates select="node()|@*"/>
   </xsl:copy>
 </xsl:template>
 <!--                                         -->
 <xsl:template match="text()[ancestor::body]">
   <xsl:variable name="vAllowedLength"
        select="$vTruncInfo/*[@id = generate-id(current())]/@preserve"
   />
 <!--                                         -->
   <xsl:value-of select="substring(.,1,$vAllowedLength)"/>

   <xsl:if test="string-length(.) > $vAllowedLength
               and
                 $vAllowedLength > 0
                ">
     <strong> ...more</strong>
   </xsl:if>
 </xsl:template>
 <!--                                         -->
 <xsl:template match="myAdd:*" mode="f:FXSL">
   <xsl:param name="pArg1"/>
   <xsl:param name="pArg2"/>
   <xsl:value-of select="$pArg1 + string-length($pArg2)"/>
 </xsl:template>
</xsl:stylesheet>

当应用于原始源 XML 文档时

<news>
    <entry>
        <title>Lorem Ipsum</title>
        <body>
            <p>
                <strong>Lorem Ipsum</strong>
            </p>
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed malesuada auctor magna. Vivamus urna justo, pulvinar nec, sagittis malesuada, accumsan in, massa. Quisque mi purus, gravida eget, ultricies a, porta in, sem. Maecenas justo elit, elementum vel, feugiat vulputate, pulvinar nec, velit. Fusce vel ante et diam bibendum euismod. Nunc vel nulla non lorem dignissim placerat. Nulla magna massa, auctor et, tempor nec, auctor sit amet, turpis. Quisque odio lacus, auctor at, posuere id, suscipit eget, dui. Phasellus aliquam. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin varius. Phasellus cursus. Cras mattis adipiscing turpis. Sed.</p>
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed malesuada auctor magna.</p>
            <p>This text should not be displayed</p>
        </body>
    </entry>
</news>

产生想要的结果

<news>
   <entry>
      <title>Lorem Ipsum</title>
      <body>
         <p>
            <strong>Lorem Ipsum</strong>
         </p>
         <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed malesuada auctor magna. Vivamus urna justo, pulvinar nec, sagittis malesuada, accumsan in, massa. Quisque mi purus, gravida eget, ultricies a, porta in, sem. Maecenas justo elit, elementum vel, feugiat vulputate, pulvinar nec, velit. Fusce vel ante et diam bibendum euismod. Nunc vel nulla non lorem dignissim placerat. Nulla magna massa, auctor et, tempor nec, auctor sit amet, turpis. Quisque odio lacus, auctor at, posuere id, suscipit eget, dui. Phasellus aliquam. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin varius. Phasellus cursus. Cras mattis adipiscing turpis. Sed.</p>
         <p>Lorem <strong> ...more</strong>
         </p>
         <p/>
      </body>
   </entry>
</news>

请注意以下事项:

  1. FXSL 库中的scanl样式表被导入。此模板通常用于通过处理项目列表来累积数据。执行实际处理的函数(模板匹配)作为参数传递给模板。必须传递给它的另一个参数是处理中的“初始”值,如果传递的项目列表为空,则返回该值。myAdd:*scanl

  2. 全局参数$pTruncateLength保存文本必须被截断的最大字符串长度

于 2009-02-10T19:58:39.927 回答
5

这是我的版本。我已经在您的 XML 示例上对其进行了测试,并且可以正常工作。

要调用它,请使用<xsl:apply-templates select="path/to/body/*" mode="truncate"/>.

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

<xsl:strip-space elements="*"/>

<!-- limit: the truncation limit -->
<xsl:variable name="limit" select="250"/>

<!-- t: Total number of characters in the set -->
<xsl:variable name="t" select="string-length(normalize-space(//body))"/>

<xsl:template match="*" mode="truncate">
    <xsl:variable name="preceding-strings">
        <xsl:copy-of select="preceding::text()[ancestor::body]"/>
    </xsl:variable>

    <!-- p: number of characters up to the current node -->
    <xsl:variable name="p" select="string-length(normalize-space($preceding-strings))"/>

    <xsl:if test="$p &lt; $limit">
        <xsl:element name="{name()}">
            <xsl:apply-templates select="@*" mode="truncate"/>
            <xsl:apply-templates mode="truncate"/>
        </xsl:element>
    </xsl:if>
</xsl:template>

<xsl:template match="text()" mode="truncate">
    <xsl:variable name="preceding-strings">
        <xsl:copy-of select="preceding::text()[ancestor::body]"/>
    </xsl:variable>

    <!-- p: number of characters up to the current node -->
    <xsl:variable name="p" select="string-length(normalize-space($preceding-strings))"/>

    <!-- c: number of characters including current node -->
    <xsl:variable name="c" select="$p + string-length(.)"/>

    <xsl:choose>
        <xsl:when test="$limit &lt;= $c">
            <xsl:value-of select="substring(., 1, ($limit - $p))"/>
            <xsl:text>&#8230;</xsl:text>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="."/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template match="@*" mode="truncate">
    <xsl:attribute name="{name(.)}"><xsl:value-of select="."/></xsl:attribute>
</xsl:template>

</xsl:stylesheet>
于 2009-02-17T17:27:23.683 回答
3

您要问的是 XSLT省略号生成器。

这个xslt 1.0 模板可能会给你一些想法:

这是它的主要要点:

<xsl:template match="text()" mode="label">
    <xsl:param name="self-x"/>
    <xsl:param name="self-y"/>
    <xsl:variable name="text" select="normalize-space(.)"/>
    <!-- a quick and dirty way to avoid problems with line breaks -->
    <!-- replace the select attribute with this call
         if you want to use a fancier way to escape whitespace
         characters:
          <xsl:call-template name="escape-ws"
            <xsl:with-param name="text" select="." /
          </xsl:call-template
    -->
    <use xlink:href="#text-box" transform="translate({$self-x} 
 {$self-y})"/>
    <!-- text nodes are marked with a little box -->
    <text x="{$self-x + $writing-bump-over}"
          y="{$self-y - $writing-bump-up}"
          style="{$text-font-style}; stroke:none; fill:{$text-color}">
      <xsl:text>"</xsl:text>
      <xsl:value-of select="substring($text,1,$max-text-length)"/>
      <!-- truncate the text node to $max-text-length -->
      <xsl:if test="string-length($text) &gt; $max-text-length">
        <!-- add an ellipsis if necessary -->
        <xsl:text>...</xsl:text>
      </xsl:if>
      <xsl:text>"</xsl:text>
    </text>
  </xsl:template>

笔记:

于 2009-02-10T13:38:48.187 回答
0

经过多次黑客攻击,我得出了这个解决方案:

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

<!--
    Author: Neil Albrock
    Version: 1.0
    Description: Truncate by a character limit and retain HTML content.
    Usage: 
        <xsl:call-template name="truncate">
            <xsl:with-param name="data" select="path/to/your/body" />
            <xsl:with-param name="length" select="250" />
            <xsl:with-param name="link" select="'href'" />
        </xsl:call-template>
-->

<xsl:template name="truncate">

    <!-- The node set to be worked on. -->
    <xsl:param name="data"/>
    <!-- The desired truncate length. Default to length of data. -->
    <xsl:param name="length" select="string-length($data)"/>
    <!-- More link -->
    <xsl:param name="link"/>

    <xsl:choose>
        <!-- Return whole data if it's within length. -->
        <xsl:when test="string-length($data) &lt;= $length">
            <xsl:copy-of select="$data" />
        </xsl:when>
        <!-- Truncate to desired length. -->
        <xsl:otherwise>
            <xsl:for-each select="$data/*">
                <xsl:variable name="this-node" select="string-length(.)"/>
                <xsl:variable name="preceding-nodes">
                    <xsl:copy-of select="preceding-sibling::*"/>
                </xsl:variable>
                <xsl:variable name="node-sum" select="string-length(normalize-space($preceding-nodes))"/>
                <xsl:variable name="limit" select="$node-sum + $this-node"/>

                <xsl:choose>
                    <xsl:when test="$limit &gt; $length and $node-sum &lt;= $length">
                        <p>
                        <xsl:value-of select="substring(.,1,$length - $node-sum)"/>
                        <xsl:text>&#8230;</xsl:text>
                        <a>
                            <xsl:attribute name="href">
                                <xsl:value-of select="$link"/>
                            </xsl:attribute>
                            <xsl:text>more</xsl:text>
                        </a>
                        </p>
                    </xsl:when>
                    <xsl:when test="$limit &lt; $length">
                        <xsl:copy-of select="."/>
                    </xsl:when>
                    <xsl:otherwise/>
                </xsl:choose>

            </xsl:for-each>
        </xsl:otherwise>
    </xsl:choose>

</xsl:template>

</xsl:stylesheet>

我会使用混沌模式的解决方案,它更优雅;-)

于 2009-02-20T16:23:33.407 回答
-3

这将是使用 XSLT 的痛苦插曲。我强烈建议使用像 Perl/Python 这样的脚本语言来尝试这个。

于 2009-02-10T13:36:19.830 回答