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?


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.


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.


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.


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.


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


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'


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.


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>


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

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


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'));

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).


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.


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.
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.