Decodificar & amp; volver a & en JavaScript

230

Tengo cuerdas como

var str = 'One & two & three';

renderizado en HTML por el servidor web. Necesito transformar esas cadenas en

'One & two & three'

Actualmente, eso es lo que estoy haciendo (con la ayuda de jQuery):

$(document.createElement('div')).html('{{ driver.person.name }}').text()

Sin embargo, tengo la inquietante sensación de que lo estoy haciendo mal. Yo he tratado

unescape("&")

pero no parece funcionar, tampoco decodeURI / decodeURIComponent.

¿Hay alguna otra forma más nativa y elegante de hacerlo?

Arte
fuente
La gran función incluida en este artículo parece funcionar bien: blogs.msdn.com/b/aoakley/archive/2003/11/12/49645.aspx No creo que sea la solución más inteligente, pero funciona.
Matias
1
Como las cadenas que contienen entidades HTML son algo diferentes a las cadenas codificadas por Uescape o URI , esas funciones no funcionarán.
Marcel Korpel
1
@Matias observa que se han agregado nuevas entidades con nombre a HTML (por ejemplo, a través de la especificación HTML 5) desde que esa función se creó en 2003; por ejemplo, no reconoce 𝕫. Este es un problema con una especificación en evolución; como tal, debe elegir una herramienta que realmente se esté manteniendo para resolverlo.
Mark Amery
1
@ MarkAmery sí, estoy totalmente de acuerdo! Es una buena experiencia volver a estas preguntas después de un par de años, ¡gracias!
Matias

Respuestas:

105

Una opción más moderna para interpretar HTML (texto y otros) desde JavaScript es el soporte HTML en la DOMParserAPI ( ver aquí en MDN ). Esto le permite utilizar el analizador HTML nativo del navegador para convertir una cadena en un documento HTML. Ha sido compatible con las nuevas versiones de todos los principales navegadores desde finales de 2014.

Si solo queremos decodificar algún contenido de texto, podemos ponerlo como el único contenido en el cuerpo de un documento, analizar el documento y extraerlo .body.textContent.

var encodedStr = 'hello & world';

var parser = new DOMParser;
var dom = parser.parseFromString(
    '<!doctype html><body>' + encodedStr,
    'text/html');
var decodedString = dom.body.textContent;

console.log(decodedString);

Podemos ver en el borrador de la especificaciónDOMParser que JavaScript no está habilitado para el documento analizado, por lo que podemos realizar esta conversión de texto sin problemas de seguridad.

El parseFromString(str, type)método debe ejecutar estos pasos, según el tipo :

  • "text/html"

    Analiza str con an HTML parsery devuelve el recién creado Document.

    El indicador de secuencias de comandos debe establecerse en "deshabilitado".

    NOTA

    scriptlos elementos se marcan como no ejecutables y el contenido de noscriptse analiza como marcado.

Está más allá del alcance de esta pregunta, pero tenga en cuenta que si toma los nodos DOM analizados (no solo su contenido de texto) y los mueve al DOM del documento en vivo, es posible que sus scripts se vuelvan a habilitar, y podría Ser preocupaciones de seguridad. No lo he investigado, así que tenga cuidado.

Jeremy Banks
fuente
55
alguna alternativa para NodeJs?
coderInrRain
285

¿Necesita decodificar todas las entidades HTML codificadas o solo a &amp;sí mismo?

Si solo necesita manejar &amp;, puede hacer esto:

var decoded = encoded.replace(/&amp;/g, '&');

Si necesita decodificar todas las entidades HTML, puede hacerlo sin jQuery:

var elem = document.createElement('textarea');
elem.innerHTML = encoded;
var decoded = elem.value;

Tome nota de los comentarios de Mark a continuación que destacan los agujeros de seguridad en una versión anterior de esta respuesta y recomiendan usar en textarealugar de divmitigar las posibles vulnerabilidades de XSS. Estas vulnerabilidades existen si usa jQuery o JavaScript simple.

LukeH
fuente
16
¡Tener cuidado! Esto es potencialmente inseguro. Si encoded='<img src="bla" onerror="alert(1)">'entonces el fragmento de arriba mostrará una alerta. Esto significa que si su texto codificado proviene de la entrada del usuario, decodificarlo con este fragmento puede presentar una vulnerabilidad XSS.
Mark Amery
@MarkAmery No soy un experto en seguridad, pero parece que si configuras el div inmediatamente nulldespués de recibir el texto, no se activa
Mottie
44
@Mottie tenga en cuenta en qué navegador funcionó para usted, pero alert(1)todavía me funciona en Chrome en OS X. Si desea una variante segura de este truco, intente usar atextarea .
Mark Amery
+1 para la alternativa simple de reemplazo de expresiones regulares para un solo tipo de entidad html. Use esto si espera que los datos html se interpolen desde, por ejemplo, una aplicación de matraz de Python a una plantilla.
OzzyTheGiant
¿Cómo hacer esto en el servidor Node?
Mohammad Kermani
44

Matthias Bynens tiene una biblioteca para esto: https://github.com/mathiasbynens/he

Ejemplo:

console.log(
    he.decode("J&#246;rg &amp J&#xFC;rgen rocked to &amp; fro ")
);
// Logs "Jörg & Jürgen rocked to & fro"

Sugiero favorecerlo sobre los hacks que implican configurar el contenido HTML de un elemento y luego volver a leer su contenido de texto. Dichos enfoques pueden funcionar, pero son engañosamente peligrosos y presentan oportunidades XSS si se utilizan en entradas de usuarios no confiables.

Si realmente no puede soportar cargar en una biblioteca, puede usar el textareatruco descrito en esta respuesta a una pregunta casi duplicada, que, a diferencia de varios enfoques similares que se han sugerido, no tiene agujeros de seguridad que conozca:

function decodeEntities(encodedString) {
    var textArea = document.createElement('textarea');
    textArea.innerHTML = encodedString;
    return textArea.value;
}

console.log(decodeEntities('1 &amp; 2')); // '1 & 2'

¡Pero tome nota de los problemas de seguridad, que afectan enfoques similares a este, que enumero en la respuesta vinculada! Este enfoque es un truco, y los cambios futuros en el contenido permitido de un textarea(o errores en navegadores particulares) podrían conducir a un código que depende de que de repente tenga un agujero XSS algún día.

Mark Amery
fuente
¡La biblioteca de Matthias Bynens hees absolutamente genial! Muchas gracias por la recomendación!
Pedro A
23
var htmlEnDeCode = (function() {
    var charToEntityRegex,
        entityToCharRegex,
        charToEntity,
        entityToChar;

    function resetCharacterEntities() {
        charToEntity = {};
        entityToChar = {};
        // add the default set
        addCharacterEntities({
            '&amp;'     :   '&',
            '&gt;'      :   '>',
            '&lt;'      :   '<',
            '&quot;'    :   '"',
            '&#39;'     :   "'"
        });
    }

    function addCharacterEntities(newEntities) {
        var charKeys = [],
            entityKeys = [],
            key, echar;
        for (key in newEntities) {
            echar = newEntities[key];
            entityToChar[key] = echar;
            charToEntity[echar] = key;
            charKeys.push(echar);
            entityKeys.push(key);
        }
        charToEntityRegex = new RegExp('(' + charKeys.join('|') + ')', 'g');
        entityToCharRegex = new RegExp('(' + entityKeys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
    }

    function htmlEncode(value){
        var htmlEncodeReplaceFn = function(match, capture) {
            return charToEntity[capture];
        };

        return (!value) ? value : String(value).replace(charToEntityRegex, htmlEncodeReplaceFn);
    }

    function htmlDecode(value) {
        var htmlDecodeReplaceFn = function(match, capture) {
            return (capture in entityToChar) ? entityToChar[capture] : String.fromCharCode(parseInt(capture.substr(2), 10));
        };

        return (!value) ? value : String(value).replace(entityToCharRegex, htmlDecodeReplaceFn);
    }

    resetCharacterEntities();

    return {
        htmlEncode: htmlEncode,
        htmlDecode: htmlDecode
    };
})();

Esto es del código fuente ExtJS.

WaiKit Kung
fuente
44
-1; esto no logra manejar la gran mayoría de las entidades nombradas. Por ejemplo, htmlEnDecode.htmlDecode('&euro;')debería regresar '€', pero en cambio regresa '&euro;'.
Mark Amery
17

element.innerText También hace el truco.

avg_joe
fuente
15

Puede usar la función de escape / escape de Lodash https://lodash.com/docs/4.17.5#unescape

import unescape from 'lodash/unescape';

const str = unescape('fred, barney, &amp; pebbles');

str se convertirá 'fred, barney, & pebbles'

Soy L
fuente
1
probablemente mejor hacer "importar _unescape de 'lodash / unescape';" por lo que no entra en conflicto con la obsoleta función de JavaScript del mismo nombre: unescape
Rick Penabella
14

En caso de que lo esté buscando, como yo, mientras tanto, hay un método JQuery agradable y seguro.

https://api.jquery.com/jquery.parsehtml/

Puedes f.ex. escribe esto en tu consola:

var x = "test &amp;";
> undefined
$.parseHTML(x)[0].textContent
> "test &"

Entonces $ .parseHTML (x) devuelve una matriz, y si tiene un marcado HTML dentro de su texto, la longitud de la matriz será mayor que 1.

cslotty
fuente
Funcionó perfectamente para mí, esto era exactamente lo que estaba buscando, gracias.
Jonathan Nielsen
1
Si xtiene un valor de <script>alert('hello');</script>lo anterior se bloqueará. En jQuery actual, en realidad no intentará ejecutar el script, pero [0]cederá, undefinedpor lo que la llamada textContentfallará y su script se detendrá allí. $('<div />').html(x).text();parece más seguro - a través de gist.github.com/jmblog/3222899
Andrew Hodgkinson
@AndrewHodgkinson, sí, pero la pregunta era "Decodificar y volver a & en JavaScript", por lo que primero probaría el contenido de x o se aseguraría de usarlo solo en los casos correctos.
cslotty
Realmente no veo cómo eso sigue. El código anterior funciona en todos los casos. ¿Y cómo exactamente "se aseguraría" de que el valor de x fuera necesario arreglar? ¿Y si el ejemplo del script anterior alertara a '& amp;' para que realmente necesitara corrección? No tenemos idea de dónde provienen las cadenas del OP, por lo que se deben considerar las entradas maliciosas.
Andrew Hodgkinson, el
@ AndrewHodgkinson Me gusta su consideración, pero esa no es la pregunta aquí. Sin embargo, siéntase libre de responder esa pregunta. Supongo que podrías eliminar las etiquetas de script, por ejemplo, f.ex.
cslotty
8

jQuery codificará y decodificará por usted. Sin embargo, debe usar una etiqueta textarea, no un div.

var str1 = 'One & two & three';
var str2 = "One &amp; two &amp; three";
  
$(document).ready(function() {
   $("#encoded").text(htmlEncode(str1)); 
   $("#decoded").text(htmlDecode(str2));
});

function htmlDecode(value) {
  return $("<textarea/>").html(value).text();
}

function htmlEncode(value) {
  return $('<textarea/>').text(value).html();
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

<div id="encoded"></div>
<div id="decoded"></div>

Jason Williams
fuente
2
-1 porque hay un agujero de seguridad (sorprendente) aquí para las versiones antiguas de jQuery, algunas de las cuales probablemente todavía tienen una base de usuarios significativa: esas versiones detectarán y evaluarán explícitamente los scripts en el HTML pasado .html(). Por lo tanto, incluso usar un textareano es suficiente para garantizar la seguridad aquí; Sugiero no usar jQuery para esta tarea y escribir código equivalente con la API DOM simple . (Sí, ese viejo comportamiento de jQuery es loco y horrible.)
Mark Amery
Gracias por señalar eso. Sin embargo, la pregunta no incluye un requisito para verificar la inyección de script. La pregunta se refiere específicamente a html prestado por el servidor web. El contenido HTML guardado en un servidor web probablemente debería validarse para la inyección de script antes de guardarlo.
Jason Williams
4

Primero crea un <span id="decodeIt" style="display:none;"></span> lugar en el cuerpo

A continuación, asigne la cadena que se decodificará como innerHTML a esto:

document.getElementById("decodeIt").innerHTML=stringtodecode

Finalmente,

stringtodecode=document.getElementById("decodeIt").innerText

Aquí está el código general:

var stringtodecode="<B>Hello</B> world<br>";
document.getElementById("decodeIt").innerHTML=stringtodecode;
stringtodecode=document.getElementById("decodeIt").innerText
Infoglaze.com
fuente
1
-1; Esto es peligrosamente inseguro de usar en entradas no confiables. Por ejemplo, considere lo que sucede si stringtodecodecontiene algo como <script>alert(1)</script>.
Mark Amery
2

Una solución de JavaScript que atrapa los más comunes:

var map = {amp: '&', lt: '<', gt: '>', quot: '"', '#039': "'"}
str = str.replace(/&([^;]+);/g, (m, c) => map[c])

Este es el reverso de https://stackoverflow.com/a/4835406/2738039

Peter Brandt
fuente
Si usa los map[c] || ''no reconocidos no se mostrará comoundefined
Eldelshell
Cobertura muy limitada; -1.
Mark Amery
2
+1, más esunescapeHtml(str){ var map = {amp: '&', lt: '<', le: '≤', gt: '>', ge: '≥', quot: '"', '#039': "'"} return str.replace(/&([^;]+);/g, (m, c) => map[c]|| '') }
Trần Quốc Hoài nuevo 2015
Cobertura manual. No recomendado.
Sergio A.
2

Para chicos de una línea:

const htmlDecode = innerHTML => Object.assign(document.createElement('textarea'), {innerHTML}).value;

console.log(htmlDecode('Complicated - Dimitri Vegas &amp; Like Mike'));
Ninh Pham
fuente
2

La pregunta no especifica el origen de, xpero tiene sentido defender, si podemos, contra entradas maliciosas (o simplemente inesperadas, desde nuestra propia aplicación). Por ejemplo, supongamos que xtiene un valor de &amp; <script>alert('hello');</script>. Una forma segura y sencilla de manejar esto en jQuery es:

var x    = "&amp; <script>alert('hello');</script>";
var safe = $('<div />').html(x).text();

// => "& alert('hello');"

Encontrado a través de https://gist.github.com/jmblog/3222899 . No puedo ver muchas razones para evitar usar esta solución dado que es al menos tan corta, si no más corta que algunas alternativas y proporciona defensa contra XSS.

(Originalmente publiqué esto como un comentario, pero lo agrego como respuesta, ya que un comentario posterior en el mismo hilo solicitó que lo hiciera).

Andrew Hodgkinson
fuente
1

Intenté todo para eliminar & de una matriz JSON. Ninguno de los ejemplos anteriores, pero https://stackoverflow.com/users/2030321/chris dio una gran solución que me llevó a solucionar mi problema.

var stringtodecode="<B>Hello</B> world<br>";
document.getElementById("decodeIt").innerHTML=stringtodecode;
stringtodecode=document.getElementById("decodeIt").innerText

No lo usé, porque no entendía cómo insertarlo en una ventana modal que extraía datos JSON en una matriz, pero lo intenté en función del ejemplo, y funcionó:

var modal = document.getElementById('demodal');
$('#ampersandcontent').text(replaceAll(data[0],"&amp;", "&"));

Me gusta porque era simple y funciona, pero no estoy seguro de por qué no se usa ampliamente. Busqué hola y bajo para encontrar una solución simple. Sigo buscando la comprensión de la sintaxis y si existe algún riesgo de usarla. No he encontrado nada todavía.

Digexart
fuente
Su primera propuesta es un poco complicada, pero funciona bien sin mucho esfuerzo. El segundo, por otro lado, usa solo la fuerza bruta para decodificar personajes; Esto significa que podría tomar MUCHO esfuerzo y tiempo lograr una función de decodificación completa. Es por eso que nadie está usando esa manera para resolver el problema de OP.
Sergio A.