Spec-Zone.ru › Dart 2

dart:core

Класс RegExp

Шаблон регулярного выражения.

Регулярные выражения (сокращенно regex или regexp) состоят из последовательности символов, определяющих алгоритм проверки совпадений во входном тексте. Применение регулярного выражения к входному тексту приводит либо к совпадению (принятию текста), либо к отклонению текста. Когда регулярное выражение совпадает с текстом, оно также предоставляет некоторую информацию о том, как именно произошло совпадение.

Регулярные выражения в Dart имеют тот же синтаксис и семантику, что и регулярные выражения в JavaScript. Дополнительные сведения о регулярных выражениях в JavaScript см. по адресу ecma-international.org/ecma-262/9.0/#sec-regexp-regular-expression-objects.

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

На основе этой базовой проверки совпадения можно построить все остальные методы RegExp.

Наиболее распространенное использование регулярного выражения — это поиск совпадения во входных данных. Эту функциональность предоставляет метод firstMatch. Данный метод ищет в строке первую позицию, в которой совпадает регулярное выражение. Опять же, если совпадение найдено, Dart возвращает подробную информацию о нем в виде RegExpMatch.

Следующий пример находит первое совпадение регулярного выражения в строке.

RegExp exp = RegExp(r'(\w+)');
String str = 'Parse my string';
RegExpMatch? match = exp.firstMatch(str);
print(match![0]); // "Parse"

Используйте allMatches для поиска всех совпадений регулярного выражения в строке.

Следующий пример находит все совпадения регулярного выражения в строке.

RegExp exp = RegExp(r'(\w+)');
String str = 'Parse my string';
Iterable<RegExpMatch> matches = exp.allMatches(str);
for (final m in matches) {
  print(m[0]);
}

Результат работы примера:

Parse
my
string

В предыдущих примерах используется неформатированная строка (raw string), особый тип строк, строковый литерал которых предваряется префиксом r. Используйте raw-строку, чтобы каждый символ в строке, включая \ и $, воспринимался буквально как символ. Затем каждый символ передается в парсер RegExp. Рекомендуется использовать raw-строку в качестве аргумента конструктора RegExp.

Примечание о производительности: регулярные выражения не решают проблемы магическим образом. Любой может написать регулярное выражение, которое работает неэффективно на определенных входных строках. Часто такое регулярное выражение достаточно хорошо работает на небольших или типичных входных данных, но демонстрирует патологическое падение производительности на больших и необычных входных данных. Такое непостоянное поведение затрудняет обнаружение проблем с производительностью при тестировании.

Регулярное выражение может находить текст ничуть не быстрее, чем использование операций String для проверки строки. Сила регулярных выражений заключается в возможности описывать довольно сложные шаблоны с помощью всего нескольких символов. Такие регулярные выражения обеспечивают разумную эффективность в большинстве типичных случаев. Однако эта лаконичность достигается за счет читаемости. Из-за синтаксической сложности регулярные выражения нельзя считать самодокументируемыми.

Регулярные выражения в Dart реализуют спецификацию ECMAScript RegExp. Эта спецификация обеспечивает общепринятое и хорошо известное поведение регулярных выражений. При компиляции Dart для веба скомпилированный код может использовать встроенную в браузер реализацию регулярных выражений.

Спецификация определяет поведение регулярных выражений ECMAScript с использованием поиска с возвратом (бэктрекинга). Когда у регулярного выражения есть выбор между несколькими способами сопоставления, оно пробует каждый вариант в порядке, указанном в шаблоне. Например: RegExp(r"(foo|bar)baz") должно проверить совпадение с foo или bar, поэтому сначала оно проверяет foo. Если продолжение по этому пути не дает совпадения с входными данными, реализация регулярного выражения выполняет возврат. Реализация сбрасывается до исходного состояния, предшествовавшего проверке foo, забывая всю выполненную после этого работу, а затем пробует следующий вариант — bar в данном примере.

Спецификация определяет эти варианты выбора и порядок, в котором они должны проверяться. Если регулярное выражение может совпасть с входными данными более чем одним способом, порядок вариантов определяет, какое именно совпадение вернет регулярное выражение. В часто используемых регулярных выражениях варианты сопоставления упорядочиваются так, чтобы гарантировать определенный результат. Спецификация регулярных выражений ECMAScript ограничивает то, как Dart может реализовывать регулярные выражения. Это должна быть реализация с возвратом, проверяющая варианты выбора в определенном порядке. Dart не может использовать другую реализацию регулярных выражений, так как в противном случае сопоставление работало бы иначе.

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

Широко известный опасный паттерн регулярных выражений возникает из-за вложенных квантификаторов, таких как *:

var re = RegExp(r"^(a*|b)*c");
print(re.hasMatch("aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));

Шаблон регулярного выражения не совпадает с входной строкой, состоящей только из a, поскольку входная строка не содержит обязательного c. Существует экспоненциальное количество различных способов, которыми (a*|b)* может сопоставить все a. Реализация регулярных выражений с возвратом перебирает все из них, прежде чем определить, что ни один из вариантов не может привести к полному совпадению. Каждый дополнительный символ a, добавленный во входные данные, удваивает время, необходимое регулярному выражению для возврата false. (Когда возврат имеет такой экспоненциальный потенциал, это называется «катастрофическим возвратом»).

Последовательные квантификаторы представляют собой еще один опасный паттерн, но они дают «всего лишь» полиномиальную сложность.

// Like `\w*-\d`, but check for `b` and `c` in that order.
var re = RegExp(r"^\w*(b)?\w*(c)?\w*-\d");
print(re.hasMatch("a" * 512));

Входные данные снова не совпадают, но RegExp должно опробовать n3 способов сопоставить n символов a до того, как определить это. Удвоение длины входных данных увеличивает время возврата false в восемь раз. Эта степень увеличивается с ростом количества последовательных квантификаторов.

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

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

Гарантированного решения этой проблемы не существует. Будьте осторожны и не используйте регулярные выражения со сверхлинейным поведением там, где программа может проверять регулярное выражение на входных данных без гарантированного совпадения.

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

  • Всякий раз, когда у регулярного выражения есть выбор, старайтесь сделать так, чтобы решение можно было принять на основе следующего символа (или очень ограниченного просмотра вперед). Это снижает необходимость выполнения большого объема вычислений по обоим вариантам ветвления.
  • При использовании квантификаторов убедитесь, что одна и та же строка не может совпадать как с одной, так и с более чем одной итерацией регулярного выражения квантификатора. (Для (a*|b)* строка "aa" может совпадать как с (a*|b){1}, так и с (a*|b){2}.)
  • В большинстве случаев регулярные выражения в Dart используются для поиска совпадения, например с помощью firstMatch. Если вы не привязываете шаблон к началу строки или входных данных с помощью ^, такой поиск работает так, будто регулярное выражение начинается с неявного [^]*. Начало вашего фактического регулярного выражения с .* в таком случае приводит к потенциальному квадратичному поведению при поиске. Используйте привязки или matchAsPrefix там, где это уместно, либо избегайте начала регулярного выражения с шаблона с квантификатором.
  • Только для экспертов: ни в Dart, ни в ECMAScript нет общей «атомарной группировки». Другие диалекты регулярных выражений используют ее для ограничения возврата. Если атомарная группа захвата сработала один раз, регулярное выражение не может впоследствии вернуться к тому же совпадению. Поскольку просмотры вперед и назад также служат атомарными группами, нечто подобное можно реализовать с помощью опережающей проверки: var re = RegExp(r"^(?=((a*|b)*))\1d"); В предыдущем примере выполняется такое же неэффективное сопоставление (a*|b)*. Как только регулярное выражение сопоставило как можно дальше, оно завершает положительную опережающую проверку. Затем оно пропускает то, что совпало с опережающей проверкой, используя обратную ссылку. После этого оно больше не может выполнять возврат и пробовать другие комбинации символов a.

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

Реализованные типы
  • Pattern

Конструкторы

RegExp(String source, {bool multiLine = false, bool caseSensitive = true, @Since("2.4") bool unicode = false, @Since("2.4") bool dotAll = false})
factory
Создает регулярное выражение.

Свойства

hashCode → int
read-onlyinherited
Хеш-код этого объекта.
isCaseSensitive → bool
read-only
Указывает, чувствительно ли это регулярное выражение к регистру.
isDotAll → bool
read-only
Указывает, соответствует ли "." в этом регулярном выражении признакам конца строки.
isMultiLine → bool
read-only
Указывает, выполняет ли это регулярное выражение сопоставление в многострочном режиме.
isUnicode → bool
read-only
Указывает, использует ли это регулярное выражение режим Юникода.
pattern → String
read-only
Исходный текст шаблона регулярного выражения для этого RegExp.
runtimeType → Type
read-onlyinherited
Представление типа объекта во время выполнения.

Методы

allMatches(String input, [int start = 0]) → Iterable<RegExpMatch>
override
Многократно сопоставляет этот шаблон со строкой.
firstMatch(String input) → RegExpMatch?
Находит первое совпадение регулярного выражения в строке input.
hasMatch(String input) → bool
Проверяет, есть ли у этого регулярного выражения совпадение в input.
matchAsPrefix(String string, [int start = 0]) → Match?
inherited
Сопоставляет этот шаблон с началом string.
noSuchMethod(Invocation invocation) → dynamic
inherited
Вызывается при обращении к несуществующему методу или свойству.
stringMatch(String input) → String?
Находит подстроку первого совпадения этого регулярного выражения в input.
toString() → String
inherited
Строковое представление этого объекта.

Операторы

operator ==(Object other) → bool
inherited
Оператор равенства.

Статические методы

escape(String text) → String
Создает синтаксис регулярного выражения, который совпадает с входным text.

© 2012 the Dart project authors
Licensed under the BSD 3-Clause "New" or "Revised" License.
https://api.dart.dev/stable/2.19.6/dart-core/RegExp-class.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API