Я написал систему управления контентом, которая использует регулярное выражение на стороне сервера, чтобы избежать амперсандов в ответе страницы непосредственно перед его отправкой в браузер клиента. Регулярное выражение учитывает амперсанды, которые уже были экранированы или являются частью объекта 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 && b) doSomething();
// ]]>
</script>
что, конечно, движки JavaScript не понимают.
У меня такой вопрос: есть ли способ изменить регулярное выражение, чтобы оно действовало точно так же, как сейчас, за исключением того, что текст внутри раздела CDATA остается нетронутым?
Поскольку регулярное выражение не так просто с самого начала, на этот вопрос, возможно, будет легче ответить: можно ли написать регулярное выражение, которое изменит все буквы на точку, кроме букв между «<» и «>»? Например, тот, который заменит "some <words> are < safe! >" на ".... <words> ... < safe! >"?
Как пользователь покажет фактическую строку '& amp;' если бы они хотели? (например, в примере HTML)





Я сделал нечто подобное здесь:
Лучший способ кодировать текстовые данные для XML
К счастью, в моем случае проблема с CDATA не возникла.
Проблема в том, что вы должны быть осторожны, чтобы выражение не было жадным, иначе вы получите что-то вроде этого:
.... <words> are < safe! >
Я серьезно сомневаюсь, что то, что вы пытаетесь сделать, можно сделать, используя только регулярное выражение. Регулярные выражения, как известно, плохо справляются с вложением.
Вероятно, вам будет лучше использовать синтаксический анализатор XML и не экранировать содержимое CDATA.
Не используйте для этого регулярные выражения. Это ужасная, ужасная идея. Вместо этого просто HTML-кодируйте все, что вы выводите, что может содержать символ. Как это:
require 'cgi'
print CGI.escape("All of this is HTML encoded!")
Разве это не приведет к двойному кодированию уже экранированных сущностей? (например, & -> &amp;?)
Я не хочу избегать всего по нескольким причинам, одна из которых (как сказал Бен Бланк) & amp; станет & amp; amp; но также потому, что я не хочу, чтобы символы во встроенном JavaScript экранировались, поэтому необходимо исключить разделы CDATA.
Ой. Вместо этого я должен был сказать «unescape».
Ты просил об этом! : 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/…
«Я думаю, это говорит само за себя. Увидимся в следующий раз!» : D
Это сработало! В Рубулярный мне пришлось изменить параметры с /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 & d</p>
<script type = "text/javascript">
// <![CDATA[
if (a && b) doSomething('a & b & c');
// ]]>
</script>
<p>a & b</p>
<p>c & d</p>
Совпадают только два амперсанда - a & b вверху и a & b внизу. Амперсанды уже экранированы как &, а все амперсанды (экранированные или нет) между <![CDATA[ и ]]> остаются в покое.
Итак, мой последний код выглядит следующим образом:
html.gsub(/&(?!(?:[a-zA-Z][a-zA-Z0-9]*|#\d+);)(?!(?>(?:(?!<!\[CDATA\[|\]\]>).)*)\]\]>)/m, '&')
Большое спасибо, Алан. Это именно то, что мне было нужно.
Ах! Я все время забываю о Ruby, использующем модификатор «m» для обозначения того, для чего все остальные используют «s». Я исправлю это.
В PHP вам нужно использовать параметр / s (PCRE_DOTALL). PCRE с разрывами строк или пробелами у меня не работал, даже при использовании параметров / m (PCRE_MULTILINE) и / или / x (PCRE_EXTENDED).
Я был бы удивлен, если бы это можно было решить, используя только регулярные выражения, поэтому мне тем более хочется увидеть, как кто-то ответит на этот вопрос :-)