Регулярное выражение для избежания амперсандов HTML при соблюдении CDATA

Я написал систему управления контентом, которая использует регулярное выражение на стороне сервера, чтобы избежать амперсандов в ответе страницы непосредственно перед его отправкой в ​​браузер клиента. Регулярное выражение учитывает амперсанды, которые уже были экранированы или являются частью объекта HTML. Например, следующее:

a & b, c & d, © 2009

изменяется на это:

a & b, c & d, © 2009

(Изменен только первый &.) Вот регулярное выражение, которое было взято и изменено из помощника Rails:

html.gsub(/&(?!([a-zA-Z][a-zA-Z0-9]*|(#\d+));)/) { |special| ERB::Util::HTML_ESCAPE[special] }

Хотя это отлично работает, у него есть проблема. Регулярному выражению не известны какие-либо <![CDATA[ или ]]>, которые могли бы окружать неэкранированные амперсанды. Это необходимо для того, чтобы встроенный JavaScript оставался нетронутым. Например, это:

<script type = "text/javascript">
  // <![CDATA[
  if (a && b) doSomething();
  // ]]>
</script>

к сожалению отображается так:

<script type = "text/javascript">
  // <![CDATA[
  if (a &amp;&amp; b) doSomething();
  // ]]>
</script>

что, конечно, движки JavaScript не понимают.

У меня такой вопрос: есть ли способ изменить регулярное выражение, чтобы оно действовало точно так же, как сейчас, за исключением того, что текст внутри раздела CDATA остается нетронутым?

Поскольку регулярное выражение не так просто с самого начала, на этот вопрос, возможно, будет легче ответить: можно ли написать регулярное выражение, которое изменит все буквы на точку, кроме букв между «<» и «>»? Например, тот, который заменит "some <words> are < safe! >" на ".... <words> ... < safe! >"?

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

David Hanak 21.01.2009 00:03

Как пользователь покажет фактическую строку '& amp;' если бы они хотели? (например, в примере HTML)

orip 11.05.2010 12:12
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
8
2
5 912
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Я сделал нечто подобное здесь:
Лучший способ кодировать текстовые данные для XML

К счастью, в моем случае проблема с CDATA не возникла.

Проблема в том, что вы должны быть осторожны, чтобы выражение не было жадным, иначе вы получите что-то вроде этого:

.... <words> are < safe! >

Я серьезно сомневаюсь, что то, что вы пытаетесь сделать, можно сделать, используя только регулярное выражение. Регулярные выражения, как известно, плохо справляются с вложением.

Вероятно, вам будет лучше использовать синтаксический анализатор XML и не экранировать содержимое CDATA.

Не используйте для этого регулярные выражения. Это ужасная, ужасная идея. Вместо этого просто HTML-кодируйте все, что вы выводите, что может содержать символ. Как это:

require 'cgi'
print CGI.escape("All of this is HTML encoded!")

Разве это не приведет к двойному кодированию уже экранированных сущностей? (например, &amp; -> &amp;amp;?)

Ben Blank 22.01.2009 06:47

Я не хочу избегать всего по нескольким причинам, одна из которых (как сказал Бен Бланк) & amp; станет & amp; amp; но также потому, что я не хочу, чтобы символы во встроенном JavaScript экранировались, поэтому необходимо исключить разделы CDATA.

Nick 22.01.2009 21:55

Ой. Вместо этого я должен был сказать «unescape».

Evan Fosmark 23.01.2009 07:59
Ответ принят как подходящий

Ты просил об этом! : D

/&(?!(?:[a-zA-Z][a-zA-Z0-9]*|#\d+);)
 (?!(?>(?:(?!<!\[CDATA\[|\]\]>).)*)\]\]>)/xm

Первая строка - это ваше исходное регулярное выражение. Предварительный просмотр соответствует, если впереди есть последовательность закрытия CDATA (]]>), если нет последовательности открытия (<!CDATA[) между здесь и там. Предполагая, что документ минимально правильно сформирован, это должно означать, что текущая позиция находится внутри раздела CDATA.

Ой, у меня было это задом наперед: используя положительный просмотр вперед, я сопоставлял «голые» амперсанды только в разделах CDATA. Я изменил его на негативный взгляд вперед, так что теперь он работает правильно.

Кстати, это регулярное выражение работает в RegexBuddy в режиме Ruby, но не в рублевый сайт. Я подозреваю, что Rubular использует старую версию Ruby с менее мощной поддержкой регулярных выражений; кто-нибудь может это подтвердить? (Как вы уже догадались, я не программист на Ruby.)

Обновлено: проблема в Rubular заключалась в том, что я использовал 's' в качестве модификатора (для обозначения точек-совпадений-всего), но Ruby использует для этого 'm'.

Хорошее решение. На это у меня ушло довольно много времени. Вот подробное объяснение, если кому-то еще интересно: bitkickers.blogspot.com/2009/01/…

Chase Seibert 31.01.2009 19:46

«Я думаю, это говорит само за себя. Увидимся в следующий раз!» : D

Alan Moore 01.02.2009 03:07

Это сработало! В Рубулярный мне пришлось изменить параметры с /xs на /m (и я удалил пробелы, разделяющие две части регулярного выражения, как вы показали выше).

Вы можете увидеть это регулярное выражение в действии вместе с образцом строки в http://www.rubular.com/regexes/5855.

Если постоянная ссылка Rubular не является постоянной, вот что я ввел для регулярного выражения:

/&(?!(?:[a-zA-Z][a-zA-Z0-9]*|#\d+);)(?!(?>(?:(?!<!\[CDATA\[|\]\]>).)*)\]\]>)/m

А вот и тестовая строка:

<p>a & b</p>
<p>c &amp; d</p>
<script type = "text/javascript">
  // <![CDATA[
  if (a && b) doSomething('a & b &amp; c');
  // ]]>
</script>
<p>a & b</p>
<p>c &amp; d</p>

Совпадают только два амперсанда - a & b вверху и a & b внизу. Амперсанды уже экранированы как &amp;, а все амперсанды (экранированные или нет) между <![CDATA[ и ]]> остаются в покое.

Итак, мой последний код выглядит следующим образом:

html.gsub(/&(?!(?:[a-zA-Z][a-zA-Z0-9]*|#\d+);)(?!(?>(?:(?!<!\[CDATA\[|\]\]>).)*)\]\]>)/m, '&amp;')

Большое спасибо, Алан. Это именно то, что мне было нужно.

Ах! Я все время забываю о Ruby, использующем модификатор «m» для обозначения того, для чего все остальные используют «s». Я исправлю это.

Alan Moore 23.01.2009 04:59

В PHP вам нужно использовать параметр / s (PCRE_DOTALL). PCRE с разрывами строк или пробелами у меня не работал, даже при использовании параметров / m (PCRE_MULTILINE) и / или / x (PCRE_EXTENDED).

feeela 01.04.2011 17:28

Другие вопросы по теме