Azúcar de reemplazo de cuerdas múltiples

¿Cuántas veces ha necesitado ejecutar múltiples operaciones de reemplazo en la misma cadena? No es tan malo, pero puede volverse un poco tedioso si escribe mucho código como este.

str = str.
	replace( /&(?!#?\w+;)/g , '&'    ).
	replace( /"([^"]*)"/g   , '“$1”'     ).
	replace( /</g           , '&lt;'     ).
	replace( />/g           , '&gt;'     ).
	replace( /…/g           , '&hellip;' ).
	replace( /“/g           , '&ldquo;'  ).
	replace( /”/g           , '&rdquo;'  ).
	replace( /‘/g           , '&lsquo;'  ).
	replace( /’/g           , '&rsquo;'  ).
	replace( /—/g           , '&mdash;'  ).
	replace( /–/g           , '&ndash;'  );

Un truco común para acortar dicho código es buscar valores de reemplazo utilizando un objeto como tabla hash. Aquí hay una implementación simple de esto.

var hash = {
	'<' : '&lt;'    ,
	'>' : '&gt;'    ,
	'…' : '&hellip;',
	'“' : '&ldquo;' ,
	'”' : '&rdquo;' ,
	'‘' : '&lsquo;' ,
	'’' : '&rsquo;' ,
	'—' : '&mdash;' ,
	'–' : '&ndash;'
};

str = str.
	replace( /&(?!#?\w+;)/g , '&amp;' ).
	replace( /"([^"]*)"/g   , '“$1”'  ).
	replace( /[<>…“”‘’—–]/g , function ( $0 ) {
		return hash[ $0 ];
	});

Sin embargo, este enfoque tiene algunas limitaciones.

  • Los patrones de búsqueda se repiten en la tabla hash y en la clase de caracteres de expresión regular.
  • Tanto la búsqueda como el reemplazo se limitan a texto sin formato. Es por eso que el primer y segundo reemplazo tuvieron que permanecer separados en el código anterior. El primer reemplazo usó un patrón de búsqueda de expresiones regulares y el segundo usó una referencia inversa en el texto de reemplazo.
  • Los reemplazos no se suceden en cascada. Esta es otra razón por la cual la segunda operación de reemplazo tuvo que permanecer separada. Quiero texto como "this" para ser reemplazado primero con “this” , y eventualmente terminará como &ldquo;this&rdquo; .
  • No funciona en Safari 2.x y otros navegadores antiguos que no admiten el uso de funciones para generar texto de reemplazo.

Con unas pocas líneas de String.prototype azúcar, puedes lidiar con todos estos problemas.

String.prototype.multiReplace = function ( hash ) {
	var str = this, key;
	for ( key in hash ) {
		str = str.replace( new RegExp( key, 'g' ), hash[ key ] );
	}
	return str;
};

Ahora puedes usar un código como este:

str = str.multiReplace({
	'&(?!#?\\w+;)' : '&amp;'   ,
	'"([^"]*)"'    : '“$1”'    ,
	'<'            : '&lt;'    ,
	'>'            : '&gt;'    ,
	'…'            : '&hellip;',
	'“'            : '&ldquo;' ,
	'”'            : '&rdquo;' ,
	'‘'            : '&lsquo;' ,
	'’'            : '&rsquo;' ,
	'—'            : '&mdash;' ,
	'–'            : '&ndash;'
});

Si le importa el orden de los reemplazos, debe tener en cuenta que la especificación de JavaScript actual no requiere un orden de enumeración particular al recorrer las propiedades del objeto con for..in . Sin embargo, las versiones recientes de los cuatro grandes navegadores (IE, Firefox, Safari, Opera) utilizan el orden de inserción, lo que permite que esto funcione como se describe (de arriba a abajo). Las propuestas de ECMAScript 4 indican que la convención de orden de inserción se codificará formalmente en ese estándar.

Si necesita preocuparse por las propiedades no autorizadas que aparecen cuando la gente se mete con Object.prototype, puede actualizar el código de la siguiente manera:

String.prototype.multiReplace = function ( hash ) {
	var str = this, key;
	for ( key in hash ) {
		if ( Object.prototype.hasOwnProperty.call( hash, key ) ) {
			str = str.replace( new RegExp( key, 'g' ), hash[ key ] );
		}
	}
	return str;
};

Llamando al hasOwnProperty método en Object.prototype en lugar de en el hash object directamente permite que este método funcione incluso cuando está buscando la cadena "hasOwnProperty".

Déjame saber si crees que esto es útil.