Регулярное выражение для экранирования амперсандов HTML при соблюдении CDATA - PullRequest
8 голосов
/ 20 января 2009

Я написал систему управления контентом, которая использует регулярное выражение на стороне сервера для экранирования амперсандов в ответе страницы непосредственно перед его отправкой в ​​браузер клиента. Регулярное выражение учитывает амперсанды, которые уже экранированы или являются частью сущности 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! >"?

Ответы [ 5 ]

7 голосов
/ 22 января 2009

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

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

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

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

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

РЕДАКТИРОВАТЬ: проблема в Rubular заключалась в том, что я использовал 's' в качестве модификатора (чтобы означать все совпадения точек), но Ruby для этого использует 'm'.

3 голосов
/ 21 января 2009

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

require 'cgi'
print CGI.escape("All of this is HTML encoded!")
1 голос
/ 22 января 2009

Это сработало! В Rubular мне пришлось изменить параметры с /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;')

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

0 голосов
/ 21 января 2009

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

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

0 голосов
/ 20 января 2009

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...