spec - Truncar XML con XSLT
html5 whatwg (5)
Aquí está mi versión. Lo he probado sobre tu muestra de XML y funciona.
Para invocarlo, use <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 < $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 <= $c">
<xsl:value-of select="substring(., 1, ($limit - $p))"/>
<xsl:text>…</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>
Tengo una pregunta para la gente inteligente de la comunidad SO.
A continuación se muestra un fragmento de XML generado por Symphony CMS .
<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>
Lo que tengo que hacer es tomar una parte del elemento <body>
, basada en una longitud específica, para mostrar en el estilo de blog de:
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed malesuada auctor magna. Vivamus urna justo, pulvinar nec, sagittis malesuada, accums en, massa. Quisque mi purus, gravida eget, ultricies a, porta in, sem ... más
... donde más es un enlace a la noticia completa. Sé que puedo seleccionar párrafos específicos y también sé que puedo usar la función de subcadena para traer un número específico de caracteres. Sin embargo, necesito preservar el formato del texto, es decir, las etiquetas HTML dentro del elemento <body>
.
Me doy cuenta de que esto plantea problemas de cierre de etiquetas, pero seguramente debe haber una manera. Con suerte, alguien con más experiencia con XSLT puede arrojar algo de luz sobre este tema.
Este será un episodio de dolor usando XSLT. Recomiendo utilizar un lenguaje de scripting como Perl / Python para intentar esto.
Lo que estás preguntando es un generador de elipsis XSLT.
Puede ser que esta plantilla xslt 1.0 pueda darle una idea:
Aquí está la esencia principal de esto:
<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) > $max-text-length">
<!-- add an ellipsis if necessary -->
<xsl:text>...</xsl:text>
</xsl:if>
<xsl:text>"</xsl:text>
</text>
</xsl:template>
Nota:
- tendrá que reemplazar las elipsis por un enlace, pero la idea principal está allí.
- esto representa solo un pequeño extracto del script
- es posible que no necesite todo: si necesita "
<use xlink:href="...
", debe declarar el espacio de nombres xlink
Aquí hay una transformación completa de XSLT 1.0 que resuelve exactamente el problema.
Esta transformación 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 < $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>
cuando se aplica en el documento XML fuente original :
<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>
produce el resultado deseado :
<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>
Tenga en cuenta lo siguiente:
La hoja de estilo de escaneo de la biblioteca FXSL se importa. Esta plantilla se usa comúnmente para acumular datos del procesamiento de una lista de elementos. La función (la plantilla que coincide con
myAdd:*
) que hace el procesamiento real se pasa como un parámetro a la plantilla descanl
. El otro parámetro que se le debe pasar es el valor "inicial" del procesamiento, que debe devolverse si la lista aprobada de elementos está vacía.El parámetro global
$pTruncateLength
contiene la longitud máxima de cadena que excede el texto truncado
Después de mucho piratear, llegué a esta solución:
<?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) <= $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 > $length and $node-sum <= $length">
<p>
<xsl:value-of select="substring(.,1,$length - $node-sum)"/>
<xsl:text>…</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 < $length">
<xsl:copy-of select="."/>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Aunque usaría la solución de Chaotic Pattern, es más elegante ;-)