Расшифровать & amp; вернуться к & в JavaScript

230

У меня есть такие строки, как

var str = 'One & two & three';

визуализируется в HTML веб-сервером. Мне нужно преобразовать эти строки в

'One & two & three'

В настоящее время это то, что я делаю (с помощью jQuery):

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

Однако у меня тревожное чувство, что я делаю это неправильно. я пытался

unescape("&")

но это, похоже, не работает, как и decodeURI / decodeURIComponent.

Есть ли другие, более родные и элегантные способы сделать это?

Искусство
источник
Кажется, что огромная функция, включенная в эту статью, работает нормально: blogs.msdn.com/b/aoakley/archive/2003/11/12/49645.aspx Я не думаю, что это самое умное решение, но оно работает.
Матиас
1
Поскольку строки, содержащие сущности HTML, отличаются от строк в кодировкеescape d или URI , эти функции работать не будут.
Марсель Корпель
1
@Matias отмечает, что новые именованные объекты были добавлены в HTML (например, через спецификацию HTML 5) с тех пор, как эта функция была создана в 2003 году - например, она не распознает 𝕫. Это проблема с развивающейся спецификацией; как таковой, вы должны выбрать инструмент, который на самом деле поддерживается, чтобы решить его с помощью.
Марк Эмери
1
@MarkAmery да, я полностью согласен! Это хороший опыт, чтобы вернуться к этим вопросам через пару лет, спасибо!
Матиас

Ответы:

105

Более современным вариантом для интерпретации HTML (текста и других) из JavaScript является поддержка HTML в DOMParserAPI ( см. Здесь в MDN). ). Это позволяет использовать собственный анализатор HTML браузера для преобразования строки в документ HTML. Он поддерживается в новых версиях всех основных браузеров с конца 2014 года.

Если мы просто хотим декодировать некоторый текстовый контент, мы можем поместить его в качестве единственного содержимого в теле документа, проанализировать документ и извлечь его .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);

Мы можем видеть в проекте спецификации,DOMParser что JavaScript не включен для проанализированного документа, поэтому мы можем выполнить это преобразование текста без проблем с безопасностью.

parseFromString(str, type)Метод должен выполнить следующие действия, в зависимости от типа :

  • "text/html"

    Parse str с HTML parserи вернуть только что созданноеDocument .

    Флаг сценария должен быть установлен на «отключен».

    НОТА

    scriptэлементы помечаются как неисполняемые, а содержимое noscriptразбирается как разметка.

Этот вопрос выходит за рамки этого вопроса, но, пожалуйста , обратите внимание, что если вы берете проанализированные узлы DOM (а не только их текстовое содержимое) и перемещаете их в DOM документа, работающего в режиме реального времени, возможно, что их сценарии будут повторно включены, и может быть проблемы безопасности. Я не исследовал это, поэтому, пожалуйста, будьте осторожны.

Джереми Бэнкс
источник
5
любая альтернатива для NodeJs?
coderInrRain
285

Вам нужно декодировать все закодированные сущности HTML или только &amp;себя?

Если вам нужно только обработать, &amp;то вы можете сделать это:

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

Если вам нужно декодировать все сущности HTML, вы можете сделать это без jQuery:

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

Пожалуйста, обратите внимание на комментарии Марка ниже, которые выделяют дыры в безопасности в более ранней версии этого ответа и рекомендуют использовать, textareaа не divсмягчать против потенциальных уязвимостей XSS. Эти уязвимости существуют независимо от того, используете ли вы jQuery или простой JavaScript.

LukeH
источник
16
Осторожно! Это потенциально небезопасно. Если encoded='<img src="bla" onerror="alert(1)">'тогда фрагмент выше покажет предупреждение. Это означает, что если ваш закодированный текст поступает от пользователя, его декодирование с помощью этого фрагмента может представлять уязвимость XSS.
Марк Амери
@MarkAmery Я не эксперт по безопасности, но похоже, что если сразу nullпосле получения текста установить div , оповещение в img не сработает
jsfiddle.net/Mottie/gaBeb/128
4
@ Обратите внимание, что браузер, который у вас работал, alert(1)все еще работает для меня в Chrome на OS X. Если вы хотите безопасный вариант этого хака, попробуйте использоватьtextarea .
Марк Амери
+1 для простого регулярного выражения заменить альтернативу только для одного вида HTML-сущности. Используйте это, если вы ожидаете, что html-данные будут интерполированы, скажем, из приложения Python-флаконов в шаблон.
OzzyTheGiant
Как это сделать на сервере Node?
Мохаммад Кермани
44

У Матиаса Биненса есть библиотека для этого: https://github.com/mathiasbynens/he

Пример:

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

Я предпочитаю отдавать предпочтение этому хаку, включая установку HTML-содержимого элемента и последующее чтение его текстового содержимого. Такие подходы могут работать, но они обманчиво опасны и представляют возможности XSS, если они используются для ненадежного пользовательского ввода.

Если вы действительно не можете загрузить библиотеку, вы можете использовать textareaхак, описанный в этом ответе, для почти дублированного вопроса, который, в отличие от предложенных различных похожих подходов, не имеет дыр в безопасности, о которых я знаю:

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

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

Но обратите внимание на проблемы безопасности, затрагивающие сходные подходы к этому, которые я перечислю в связанном ответе! Этот подход является хакерским, и будущие изменения в допустимом содержимом textarea(или ошибках в определенных браузерах) могут привести к тому, что код, который полагается на него, однажды вдруг обнаружит дыру в XSS.

Марк Эмери
источник
Библиотека Матиаса Биненса heпросто великолепна! Большое спасибо за рекомендацию!
Педро А
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
    };
})();

Это из исходного кода ExtJS.

Вайкит кунг
источник
4
-1; это не в состоянии справиться с подавляющим большинством именованных организаций. Например, htmlEnDecode.htmlDecode('&euro;')должен вернуться '€', но вместо этого возвращается '&euro;'.
Марк Амери
17

element.innerText также делает трюк.

avg_joe
источник
15

Вы можете использовать функцию unescape / escape Lodash https://lodash.com/docs/4.17.5#unescape

import unescape from 'lodash/unescape';

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

ул станет 'fred, barney, & pebbles'

Я это я
источник
1
вероятно, лучше сделать "импорт _unescape из 'lodash / unescape';" так что это не противоречит устаревшей функции javascript с тем же именем:
unescape
14

В случае, если вы ищете его, как я - тем временем есть хороший и безопасный метод JQuery.

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

Вы можете, например, введите это в вашей консоли:

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

Таким образом, $ .parseHTML (x) возвращает массив, и если в вашем тексте есть разметка HTML, то значение array.length будет больше 1.

cslotty
источник
Отлично сработало для меня, это было именно то, что я искал, спасибо.
Джонатан Нильсен
1
Если xимеет значение <script>alert('hello');</script>выше, произойдет сбой. В текущем jQuery он на самом деле не будет пытаться запустить скрипт, но [0]даст результат, undefinedтак что вызов textContentзавершится неудачно, и ваш скрипт остановится на этом. $('<div />').html(x).text();выглядит безопаснее - через gist.github.com/jmblog/3222899
Эндрю Ходжкинсон
@AndrewHodgkinson да, но вопрос был «Расшифровать и обратно в JavaScript» - так что вы сначала протестируете содержимое x или убедитесь, что используете его только в правильных случаях.
cslotty
Я действительно не вижу, как это следует. Код выше работает во всех случаях. И как именно вы «убедитесь», что значение х необходимо исправить? А что если в приведенном выше примере скрипта появилось предупреждение & amp; так что это действительно нужно исправить? Мы понятия не имеем, откуда берутся строки OP, поэтому необходимо учитывать злонамеренный ввод.
Эндрю Ходжкинсон
@AndrewHodgkinson Мне нравится ваше мнение, но здесь вопрос не в этом. Не стесняйтесь ответить на этот вопрос, хотя. Я думаю, вы могли бы удалить теги сценария, например.
cslotty
8

JQuery будет кодировать и декодировать для вас. Однако вам нужно использовать тег textarea, а не 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 потому что здесь есть (удивительная) дыра в безопасности для старых версий jQuery, некоторые из которых, вероятно, все еще имеют значительную пользовательскую базу - эти версии будут обнаруживать и явно оценивать скрипты в передаваемом HTML-коде .html(). Таким образом, даже использования textareaнедостаточно для обеспечения безопасности здесь; Я предлагаю не использовать jQuery для этой задачи и писать эквивалентный код с простым API DOM . (Да, это старое поведение jQuery безумно и ужасно.)
Марк Эмери
Спасибо, что указали на это. Тем не менее, вопрос не включает в себя требование проверять наличие скриптов. Вопрос, в частности, задается относительно HTML, отображаемого веб-сервером. Содержание HTML, сохраненное на веб-сервере, вероятно, должно быть проверено на наличие скриптов перед сохранением.
Джейсон Уильямс
4

Сначала создайте <span id="decodeIt" style="display:none;"></span>где-нибудь в теле

Затем присвойте строку, которая будет декодирована как innerHTML:

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

В заключение,

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

Вот общий код:

var stringtodecode="<B>Hello</B> world<br>";
document.getElementById("decodeIt").innerHTML=stringtodecode;
stringtodecode=document.getElementById("decodeIt").innerText
Infoglaze.com
источник
1
-1; это опасно небезопасно для использования на ненадежных данных. Например, рассмотрим, что произойдет, если stringtodecodeсодержит что-то вроде <script>alert(1)</script>.
Марк Амери
2

решение JavaScript, которое ловит общие:

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

это обратная сторона https://stackoverflow.com/a/4835406/2738039

Питер Брандт
источник
Если вы используете map[c] || ''нераспознанные, они не будут отображаться какundefined
Eldelshell
Очень ограниченный охват; -1.
Марк Амери
2
+1, больше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
Ручное покрытие. Не рекомендуется.
Серхио А.
2

Для однострочных парней:

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

console.log(htmlDecode('Complicated - Dimitri Vegas &amp; Like Mike'));
Нин Фам
источник
2

Вопрос не определяет происхождение, xно имеет смысл защищать, если мы можем, от злонамеренного (или просто неожиданного, из нашего собственного приложения) ввода. Например, предположим, xимеет значение &amp; <script>alert('hello');</script>. Безопасный и простой способ справиться с этим в jQuery:

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

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

Найдено через https://gist.github.com/jmblog/3222899 . Я не вижу много причин, чтобы избегать использования этого решения, поскольку оно, по крайней мере, такое же короткое, если не короче, чем некоторые альтернативы и обеспечивает защиту от XSS.

(Первоначально я разместил это как комментарий, но добавляю его как ответ, поскольку последующий комментарий в той же ветке попросил меня сделать это).

Эндрю Ходжкинсон
источник
1

Я попробовал все, чтобы удалить & из массива JSON. Ни один из приведенных выше примеров, но https://stackoverflow.com/users/2030321/chris не дал отличного решения, которое привело меня к решению моей проблемы.

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

Я не использовал, потому что я не понимал, как вставить его в модальное окно, которое вытягивало данные JSON в массив, но я попробовал это на основе примера, и это сработало:

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

Мне нравится это, потому что это было просто, и это работает, но не уверен, почему это не широко используется. Искал привет и низ, чтобы найти простое решение. Я продолжаю искать понимание синтаксиса и есть ли риск его использования. Пока не нашли ничего.

Digexart
источник
Ваше первое предложение немного сложнее, но оно работает без особых усилий. Второй, с другой стороны, использует только грубую силу для декодирования символов; это означает, что для выполнения полной функции декодирования может потребоваться МНОГО усилий и времени. Вот почему никто не использует этот способ для решения проблемы ОП.
Серхио А.