Почему мы должны предпочесть отрицательные классы символов?* в регулярных выражениях?


Я смотрел учебник по регулярному выражению.

Речь шла о том, как получить атрибут класса из этого фрагмента html

<pre class="ruby" name="code">

И используемое регулярное выражение было

<pre class="([^"]+)" name="code">

Они рекомендовали использовать вышеприведенный вместо

<pre class="(.+)" name="code">

" как это выходит за рамки цитаты."

Я не понимаю, что они имеют в виду. Это просто будет работать в любом случае, но тогда почему рекомендуется первое регулярное выражение. Я что-нибудь упустил? Пожалуйста, просвети меня.

Заранее благодарю.

4 4

4 ответа:

.+ спички жадно. например, в

<pre class="ruby" size="medium" name="code"> 

Он будет соответствовать ruby" size="medium. Еще хуже, если бы у вас было два тега на одной строке, они совпадали бы прямо через границы тегов:

<pre class="ruby" name="code">foo</pre> <pre class="python" name="code">bar</pre>

Приведет к ruby" name="code">foo</pre> <pre class="python!

Так что пока вы точно знаете, как будет выглядеть ваш HTML, .+ может работать, но как только он неожиданно изменится (как обычно делает HTML), ваше регулярное выражение не просто не сработает (как второе), но и будет соответствовать неправильному материалу.

Следовательно, второе регулярное выражение является более безопасным (так как оно более четко определяет, что именно разрешено сопоставлять). Обычно вы должны стараться избегать простого .+ или .* "соответствовать чему-либо", а вместо этого думать о том, что вы действительно хотите соответствовать.

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

Регулярное выражение обычно пытается соответствовать самому длинному регулярному выражению, которое оно может. Поэтому "([ ^ "]+) " соответствует только первой цитате , с которой она встречается. С другой стороны, " (.+ ) "будет соответствовать от первой цитаты до самой последней цитаты в строке.

Например, если мы применим их к вашему вопросу, первый будет соответствовать "ruby", потому что это первая цитируемая строка в вашем вопросе. Второй будет соответствовать всему пути от "ruby до beyond the quote", потому что это последний цитата в вопросе (и будет включать несколько других строк в кавычках между ними.

Рассмотрим следующий пример:

<pre class="scooby" name="not-code">
  content
</pre>

...other HTML...

<pre class="ruby" name="code">
  content
</pre>

С этим регулярным выражением [*]:

<pre class="(.+)" name="code">

...первая часть - <pre class=" - начинает соответствовать первому тегу, затем (.+) потребляет всю остальную часть документа. Но остальная часть регулярного выражения - " name="code"> - не может соответствовать там, поэтому он отступает, пока не найдет позицию, где он может-во втором теге. Результат: группа в конечном итоге захватывает все от scooby до ruby.

Это будет верно, даже если вы используете не жадный (.+?) вместо жадного (.+). Люди часто говорят, что не жадные кванторы заставляют регулярное выражение возвращать кратчайшее возможное совпадение, но это не так. Подобно жадному регулярному выражению, оно начинает сопоставление при первой же возможности; оно простопрекращает сопоставление, как только может. Ситуации, подобные этой, когда не жадные кванторы не приносят никакой пользы, не редки.

Еще одна вещь, о которой нужно подумать, - это когда совпадение невозможно,например, если есть теги <pre> с первым атрибутом class="~whatever~", но нет ни одного с атрибут name="code". На каждом из них жадный (.+) сожрет весь документ, а затем отступит, пока он не достигнет своей начальной точки, прежде чем сдаться. Ненасытный (.+?) не будет возвращаться назад, но он будет сканировать всю страницу, и он будет делать это гораздо медленнее (он эффективно делает lookahead для " name="code"> в каждой позиции).

С этим регулярным выражением:

<pre class="([^"]+)" name="code">

... он никогда не должен сканировать дальше конца тега, чтобы решить, соответствует ли он.

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

[*] я предполагаю, что матч выполняется в режиме DOTALL (он же однострочный режим) для иллюстрации.

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

Некоторое время назад Джефф Этвуд написал об этом интересное сообщение в блоге , где он приводит пример, казалось бы, невинного регулярного выражения: (x+x+)+y, которое может занять (почти) вечность, чтобы закончить обработку. Даже когда предмет такой крошечный, как этот: xxxxxxxxxxxxxxxxxxxx.

Дайте ему прочитать, это действительно довольно интересно.