Resumen HTML automático / Teaser

Al generar un avance o resumen de contenido HTML, muchas personas simplemente eliminan todas las etiquetas antes de seleccionar la n más a la izquierda. caracteres. Recientemente, en el blog del desarrollador de ColdFusion, Ben Nadel, abordó el problema de cerrar etiquetas XHTML en una cadena truncada usando ColdFusion y sus métodos subyacentes de Java. Después de ver esto, creé una versión de JavaScript más o menos equivalente y agregué algunas funciones adicionales. Específicamente, el siguiente código trunca adicionalmente la cadena (según un número de caracteres especificado por el usuario), y en el proceso solo cuenta el texto fuera de las etiquetas HTML hacia la longitud, evita terminar la cadena en medio de una etiqueta o palabra y evita agregar etiquetas de cierre para elementos únicos como <br> o <img> .

function getLeadingHtml (input, maxChars) {
	// token matches a word, tag, or special character
	var	token = /\w+|[^\w<]|<(\/)?(\w+)[^>]*(\/)?>|</g,
		selfClosingTag = /^(?:[hb]r|img)$/i,
		output = "",
		charCount = 0,
		openTags = [],
		match;

	// Set the default for the max number of characters
	// (only counts characters outside of HTML tags)
	maxChars = maxChars || 250;

	while ((charCount < maxChars) && (match = token.exec(input))) {
		// If this is an HTML tag
		if (match[2]) {
			output += match[0];
			// If this is not a self-closing tag
			if (!(match[3] || selfClosingTag.test(match[2]))) {
				// If this is a closing tag
				if (match[1]) openTags.pop();
				else openTags.push(match[2]);
			}
		} else {
			charCount += match[0].length;
			if (charCount <= maxChars) output += match[0];
		}
	}

	// Close any tags which were left open
	var i = openTags.length;
	while (i--) output += "</" + openTags[i] + ">";
	
	return output;
};

Todo esto es bastante sencillo, pero pensé que también podría transmitirlo.

Aquí hay un ejemplo de la salida:

var input = '<p><a href="http://www.realultimatepower.net/">Ninjas</a> are mammals<br>who <strong><em>love</em> to <u>flip out and cut off people\'s heads all the time!</u></strong></p>';
var output = getLeadingHtml(input, 40);

/* Output:
<p><a href="http://www.realultimatepower.net/">Ninjas</a> are mammals<br>who <strong><em>love</em> to <u>flip out </u></strong></p>
*/

Editar: En una nota relacionada, aquí hay una expresión regular que publiqué anteriormente en el sitio de Ben que coincide con los primeros 100 caracteres de una cadena, a menos que termine en medio de una etiqueta HTML, en cuyo caso coincidirá hasta el final de la etiqueta (utilice este con el modificador "punto coincide con nueva línea"):

^.{1,100}(?:(?<=<[^>]{0,99})[^>]*>)?

Eso debería funcionar con los motores .NET, Java y JGsoft regex. No funcionará en la mayoría de los demás debido al {0,99} en la mirada atrás. Tenga en cuenta que .NET y JGsoft en realidad admiten la búsqueda de longitud infinita, por lo que con esos dos podría reemplazar el {0,99} cuantificador con * . Dado que los motores .NET y JGsoft también admiten condicionales basados ​​en la búsqueda, puede guardar dos caracteres más escribiéndolos como ^.{1,100}(?(?<=<[^>]{0,99})[^>]*>) .

Si desea imitar el lookbehind en JavaScript, puede usar lo siguiente:

// JavaScript doesn't include a native reverse method for strings,
// so we need to create one
String.prototype.reverse = function() {
	return this.split("").reverse().join("");
};
// Mimic the regex /^[\S\s]{1,100}(?:(?<=<[^>]*)[^>]*>)?/ through
// node-by-node reversal
var regex = /(?:>[^>]*(?=[^>]*<))?[\S\s]{1,100}$/;
var output = input.reverse().match(regex)[0].reverse();