Decodificar & amp; voltar para & em JavaScript

229

Eu tenho cordas como

var str = 'One & two & three';

renderizado em HTML pelo servidor da web. Eu preciso transformar essas cordas em

'One & two & three'

Atualmente, é isso que estou fazendo (com a ajuda do jQuery):

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

No entanto, tenho uma sensação perturbadora de que estou fazendo errado. eu tentei

unescape("&")

mas parece que não funciona, nem decodeURI / decodeURIComponent.

Existem outras maneiras mais nativas e elegantes de fazer isso?

Arte
fonte
A enorme função incluída neste artigo parece funcionar bem: blogs.msdn.com/b/aoakley/archive/2003/11/12/49645.aspx Não acho que seja a solução mais inteligente, mas funcione.
Matias
1
Como cadeias de caracteres que contêm entidades HTML são algo diferente de cadeias de caracteresescape d ou URI , essas funções não funcionam.
Marcel Korpel
1
@ Matias note que novas entidades nomeadas foram adicionadas ao HTML (por exemplo, através da especificação do HTML 5) desde que a função foi criada em 2003 - por exemplo, ela não reconhece 𝕫. Este é um problema com uma especificação em evolução; como tal, você deve escolher uma ferramenta que está sendo mantida para resolvê-la.
Mark Amery
1
@MarkAmery sim, concordo totalmente! É uma boa experiência voltar a essas perguntas depois de alguns anos, obrigado!
Matias

Respostas:

104

Uma opção mais moderna para interpretar HTML (texto e outros) do JavaScript é o suporte HTML na DOMParserAPI ( veja aqui no MDN ). Isso permite que você use o analisador HTML nativo do navegador para converter uma string em um documento HTML. É suportado em novas versões de todos os principais navegadores desde o final de 2014.

Se quisermos apenas decodificar algum conteúdo de texto, podemos colocá-lo como o único conteúdo no corpo de um documento, analisá-lo e retirá-lo .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 no rascunho da especificaçãoDOMParser que o JavaScript não está ativado para o documento analisado, para que possamos realizar essa conversão de texto sem preocupações de segurança.

O parseFromString(str, type)método deve executar estas etapas, dependendo do tipo :

  • "text/html"

    Analise str com um HTML parsere retorne o recém-criado Document.

    O sinalizador de script deve ser definido como "desativado".

    NOTA

    scriptelementos são marcados como não executáveis ​​e o conteúdo de noscripté analisado como marcação.

Está além do escopo desta pergunta, mas observe que, se você estiver pegando os nós DOM analisados ​​(não apenas o conteúdo de texto) e os movendo para o documento ao vivo DOM, é possível que seus scripts sejam reativados, e pode haver preocupações de segurança. Eu não pesquisei, então tenha cuidado.

Jeremy Banks
fonte
5
alguma alternativa para os NodeJs?
CodificadorInrRain
284

Você precisa decodificar todas as entidades HTML codificadas ou apenas &amp;ela mesma?

Se você precisar apenas lidar com &amp;isso, poderá fazer o seguinte:

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

Se você precisar decodificar todas as entidades HTML, poderá fazê-lo sem o jQuery:

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

Observe os comentários de Mark abaixo, que destacam as brechas de segurança em uma versão anterior desta resposta e recomenda o uso, em textareavez de divatenuar as possíveis vulnerabilidades do XSS. Essas vulnerabilidades existem se você usa jQuery ou JavaScript simples.

LukeH
fonte
16
Cuidado! Isso é potencialmente inseguro. Se encoded='<img src="bla" onerror="alert(1)">'o snippet acima mostrará um alerta. Isso significa que, se o texto codificado for proveniente da entrada do usuário, decodificá-lo com esse trecho pode apresentar uma vulnerabilidade XSS.
Mark Amery
@MarkAmery não eu um especialista em segurança, mas parece que se você conjunto imediato a div para nulldepois de receber o texto, o alerta na img não é acionado - jsfiddle.net/Mottie/gaBeb/128
Mottie
4
@Ottie, verifique se o navegador funcionou para você, mas alert(1)ainda é acionado por mim no Chrome no OS X. Se você quiser uma variante segura desse hack, tente usar umtextarea .
Mark Amery
+1 para a regexp simples substitui a alternativa por apenas um tipo de entidade html. Use isso se você espera que os dados html sejam interpolados de, por exemplo, um aplicativo de frasco python para um modelo.
OzzyTheGiant #
Como fazer isso no servidor Node?
Mohammad Kermani
44

Matthias Bynens tem uma biblioteca para isso: https://github.com/mathiasbynens/he

Exemplo:

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

Sugiro favorecê-lo em relação a hacks que envolvem a configuração do conteúdo HTML de um elemento e a leitura do conteúdo do texto. Tais abordagens podem funcionar, mas são enganosamente perigosas e apresentam oportunidades de XSS se usadas em entradas não confiáveis ​​do usuário.

Se você realmente não consegue carregar uma biblioteca, pode usar o textareahack descrito nesta resposta para uma pergunta quase duplicada, que, diferentemente de várias abordagens semelhantes sugeridas, não possui falhas de segurança que eu conheço:

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

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

Mas tome nota dos problemas de segurança, afetando abordagens semelhantes a essa, listadas na resposta vinculada! Essa abordagem é um hack, e futuras alterações no conteúdo permitido de um textarea(ou bugs em navegadores específicos) podem levar ao código que depende repentinamente de um furo no XSS um dia.

Mark Amery
fonte
A biblioteca de Matthias Bynens heé absolutamente ótima! Muito obrigado pela recomendação!
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
    };
})();

Isto é do código fonte ExtJS.

WaiKit Kung
fonte
4
-1; isso falha ao lidar com a grande maioria das entidades nomeadas. Por exemplo, htmlEnDecode.htmlDecode('&euro;')deve retornar '€', mas retorna '&euro;'.
Mark Amery
17

element.innerText também faz o truque.

avg_joe
fonte
15

Você pode usar a função de desbloqueio / escape do Lodash https://lodash.com/docs/4.17.5#unescape

import unescape from 'lodash/unescape';

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

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

Eu sou eu
fonte
1
provavelmente é melhor fazer "importar _unescape de 'lodash / unescape';" por isso não entre em conflito com a função javascript obsoleto com o mesmo nome: unescape
Rick Penabella
14

Caso você esteja procurando, como eu - enquanto isso, há um método JQuery agradável e seguro.

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

Você pode f.ex. digite isso no seu console:

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

Portanto, $ .parseHTML (x) retorna uma matriz e, se você tiver uma marcação HTML em seu texto, o array.length será maior que 1.

cslotty
fonte
Funcionou perfeitamente para mim, era exatamente isso que eu estava procurando, obrigado.
Jonathan Nielsen
1
Se xtiver um valor <script>alert('hello');</script>acima, irá falhar. No jQuery atual, na verdade, ele não tenta executar o script, mas [0]produz undefinedcomo resultado a chamada para textContentfalhará e seu script será interrompido por aí. $('<div />').html(x).text();parece mais seguro - via gist.github.com/jmblog/3222899
Andrew Hodgkinson
@AndrewHodgkinson sim, mas a pergunta era "Decodificar de volta para & em JavaScript" - para testar primeiro o conteúdo de x ou garantir que você o use apenas nos casos corretos.
cslotty
Realmente não vejo como isso se segue. O código acima funciona em todos os casos. E como exatamente você "asseguraria" o valor de x necessário consertar? E se o exemplo de script acima alertou '& amp;' para que realmente precisasse de correção? Não temos ideia de onde vêm as sequências do OP, portanto, é necessário considerar as entradas maliciosas.
Andrew Hodgkinson
@AndrewHodgkinson Gosto da sua consideração, mas essa não é a questão aqui. Sinta-se livre para responder a essa pergunta, no entanto. Eu acho que você pode remover tags de script, f.ex.
cslotty
8

O jQuery irá codificar e decodificar para você. No entanto, você precisa usar uma tag de área de texto, não uma 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
fonte
2
-1 porque há uma (surpreendente) falha de segurança nas versões antigas do jQuery, algumas das quais provavelmente ainda possuem uma base de usuários significativa - essas versões detectam e avaliam explicitamente os scripts no HTML passado para .html(). Assim, mesmo usando um textareanão é suficiente para garantir a segurança aqui; Sugiro não usar o jQuery para esta tarefa e escrever código equivalente com a API simples do DOM . (Sim, que o comportamento de idade por jQuery é louco e terrível.)
Mark Amery
Obrigado por apontar isso. No entanto, a pergunta não inclui um requisito para verificar a injeção de script. A pergunta pergunta especificamente sobre o html renderizado pelo servidor da web. O conteúdo html salvo em um servidor web provavelmente deve ser validado para injeção de script antes de salvar.
21817 Jason Williams
4

Primeiro crie um <span id="decodeIt" style="display:none;"></span>lugar no corpo

Em seguida, atribua a string a ser decodificada como innerHTML para isso:

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

Finalmente,

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

Aqui está o código geral:

var stringtodecode="<B>Hello</B> world<br>";
document.getElementById("decodeIt").innerHTML=stringtodecode;
stringtodecode=document.getElementById("decodeIt").innerText
Infoglaze.com
fonte
1
-1; isso é perigosamente inseguro para uso em entradas não confiáveis. Por exemplo, considere o que acontece se stringtodecodecontiver algo parecido <script>alert(1)</script>.
Mark Amery
2

uma solução javascript que captura os comuns:

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

este é o inverso de https://stackoverflow.com/a/4835406/2738039

Peter Brandt
fonte
Se você usar map[c] || ''os não reconhecidos, não serão mostrados comoundefined
#
Cobertura muito limitada; -1.
Mark Amery
2
+1, mais é unescapeHtml(str){ var map = {amp: '&', lt: '<', le: '≤', gt: '>', ge: '≥', quot: '"', '#039': "'"} return str.replace(/&([^;]+);/g, (m, c) => map[c]|| '') }
Trần Quốc Hoài new 2015
Cobertura manual. Não recomendado.
Sergio A.
2

Para caras de uma linha:

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

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

A questão não especifica a origem de, xmas faz sentido defender, se pudermos, contra entradas maliciosas (ou simplesmente inesperadas, de nosso próprio aplicativo). Por exemplo, suponhax tenha um valor de &amp; <script>alert('hello');</script>. Uma maneira simples e segura de lidar com isso no jQuery é:

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

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

Encontrado via https://gist.github.com/jmblog/3222899 . Não vejo muitos motivos para evitar o uso dessa solução, pois ela é pelo menos tão curta, se não menor, do que algumas alternativas e fornece defesa contra o XSS.

(Eu originalmente postei isso como um comentário, mas estou adicionando-o como uma resposta, pois um comentário subsequente no mesmo segmento solicitou que eu o fizesse).

Andrew Hodgkinson
fonte
1

Eu tentei de tudo para remover e de uma matriz JSON. Nenhum dos exemplos acima, mas https://stackoverflow.com/users/2030321/chris deu uma ótima solução que me levou a corrigir meu problema.

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

Eu não usei, porque não entendi como inseri-lo em uma janela modal que estava puxando dados JSON em uma matriz, mas tentei isso com base no exemplo e funcionou:

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

Gosto porque era simples e funciona, mas não sei por que não é amplamente utilizado. Pesquisei oi e baixo para encontrar uma solução simples. Continuo buscando a compreensão da sintaxe e se há algum risco em usá-la. Ainda não encontrei nada.

Digexart
fonte
Sua primeira proposta é um pouco complicada, mas funciona bem sem muito esforço. O segundo, por outro lado, usa apenas força bruta para decodificar caracteres; isso significa que pode levar muito esforço e tempo para realizar uma função completa de decodificação. É por isso que ninguém está usando essa maneira de resolver o problema do OP.
Sergio A.