Синтаксис регулярных выражений с переменным порядком
Есть ли способ указать, что два или более регулярных выражений могут встречаться в любом порядке? Например, атрибуты XML могут быть записаны в любом порядке. Скажи, что у меня есть следующий XML:
<a href="home.php" class="link" title="Home">Home</a>
<a href="home.php" title="Home" class="link">Home</a>
Как мне написать совпадение, которое проверяет класс и заголовок и работает в обоих случаях? Я в основном ищу синтаксис, который позволяет мне проверять в любом порядке, а не просто сопоставлять класс и заголовок, как я могу это сделать. Есть ли способ, кроме как просто включить обе комбинации и соединить их с помощью "|"?
Редактировать: я бы предпочел сделать это в одном регулярном выражении, так как я создаю его программно, а также проверяю его модулем.
7 ответов
Нет, я считаю, что лучший способ сделать это с одним RE - это именно то, что вы описываете. К сожалению, будет очень грязно, когда ваш XML может иметь 5 разных атрибутов, давая вам большое количество разных RE для проверки.
С другой стороны, я бы не стал делать это с RE, поскольку они не предназначены для языков программирования. Что плохого в старомодном подходе использования библиотеки обработки XML?
Если вам требуется использовать RE, этот ответ, вероятно, не сильно поможет, но я верю в использование правильных инструментов для работы.
Вы рассматривали xpath? (где порядок атрибутов не имеет значения)
//a[@class and @title]
Выберу оба <a>
узлы как действительные совпадения. Единственное предостережение в том, что ввод должен быть xhtml (правильно сформированный xml).
Вы можете создать прогноз для каждого из атрибутов и вставить их в регулярное выражение для всего тега. Например, регулярное выражение для тега может быть
<a\b[^<>]*>
Если вы используете это на XML, вам, вероятно, понадобится что-то более сложное. Само по себе это базовое регулярное выражение будет соответствовать тегу с нулевым или большим количеством атрибутов. Затем вы добавляете заголовок для каждого атрибута, который хотите сопоставить:
(?=[^<>]*\s+class="link")
(?=[^<>]*\s+title="Home")
[^<>]*
позволяет сканировать атрибут вперед, но не позволяет ему выходить за пределы закрывающей угловой скобки. Сопоставление начальных пробелов в данном документе служит двум целям: оно более гибкое, чем сопоставление с базовым регулярным выражением, и гарантирует, что мы сопоставляем полное имя атрибута. Объединяя их, мы получаем:
<a\b(?=[^<>]*\s+class="link")(?=[^<>]*\s+title="Home")[^<>]+>[^<>]+</a>
Конечно, для ясности я сделал несколько упрощающих предположений. Я не учел пробелы вокруг знаков равенства, одинарные кавычки или отсутствие кавычек вокруг значений атрибутов или угловые скобки в значениях атрибутов (что, как я слышал, допустимо, но я никогда не видел, чтобы это было сделано). Затвор этих утечек (если вам нужно) сделает регулярное выражение более уродливым, но не потребует изменений в базовой структуре.
Вы можете использовать именованные группы, чтобы извлечь атрибуты из тега. Запустите регулярное выражение, а затем переберите группы, выполняя любые необходимые вам тесты.
Примерно так (непроверено, используется синтаксис регулярного выражения.net с \w для символов слова и \s для пробела):
<a ((?<key>\w+)\s?=\s?['"](?<value>\w+)['"])+ />
Самый простой способ - написать регулярное выражение, которое <a .... >
часть, а затем напишите еще два регулярных выражения, чтобы вытащить класс и заголовок. Хотя вы, вероятно, могли бы сделать это с помощью одного регулярного выражения, это было бы очень сложно и, вероятно, намного более подвержено ошибкам.
С одним регулярным выражением вам нужно что-то вроде
<a[^>]*((class="([^"]*)")|(title="([^"]*)"))?((title="([^"]*)")|(class="([^"]*)"))?[^>]*>
Это всего лишь предположение из первых рук без проверки, является ли оно действительным. Гораздо проще просто разделить и победить проблему.
Первое специальное решение может заключаться в следующем.
((class|title)="[^"]*?" *)+
Это далеко от совершенства, поскольку позволяет каждому атрибуту встречаться более одного раза. Я мог предположить, что это может быть решаемо с утверждениями. Но если вы просто хотите извлечь атрибуты, этого может быть достаточно.
Если вы хотите сопоставить перестановку набора элементов, вы можете использовать комбинацию обратных ссылок и отрицательного прямого совпадения нулевой ширины.
Скажем, вы хотите сопоставить любую из этих шести строк:
123-abc-456-def-789-ghi-0AB
123-abc-456-ghi-789-def-0AB
123-def-456-abc-789-ghi-0AB
123-def-456-ghi-789-abc-0AB
123-ghi-456-abc-789-def-0AB
123-ghi-456-def-789-abc-0AB
Вы можете сделать это с помощью следующего регулярного выражения:
/123-(abc|def|ghi)-456-(?!\1)(abc|def|ghi)-789-(?!\1|\2)(abc|def|ghi)-0AB/
Обратные ссылки (\1
, \2
), позвольте вам сослаться на ваши предыдущие совпадения, и совпадение с нулевой шириной вперед ((?!...)
) позволяет отменить позиционное совпадение, говоря, что не совпадают, если содержащиеся совпадения в этой позиции. Объединение этих двух элементов гарантирует, что ваше совпадение является допустимой перестановкой данных элементов, причем каждая возможность встречается только один раз.
Так, например, в ruby:
input = <<LINES
123-abc-456-abc-789-abc-0AB
123-abc-456-abc-789-def-0AB
123-abc-456-abc-789-ghi-0AB
123-abc-456-def-789-abc-0AB
123-abc-456-def-789-def-0AB
123-abc-456-def-789-ghi-0AB
123-abc-456-ghi-789-abc-0AB
123-abc-456-ghi-789-def-0AB
123-abc-456-ghi-789-ghi-0AB
123-def-456-abc-789-abc-0AB
123-def-456-abc-789-def-0AB
123-def-456-abc-789-ghi-0AB
123-def-456-def-789-abc-0AB
123-def-456-def-789-def-0AB
123-def-456-def-789-ghi-0AB
123-def-456-ghi-789-abc-0AB
123-def-456-ghi-789-def-0AB
123-def-456-ghi-789-ghi-0AB
123-ghi-456-abc-789-abc-0AB
123-ghi-456-abc-789-def-0AB
123-ghi-456-abc-789-ghi-0AB
123-ghi-456-def-789-abc-0AB
123-ghi-456-def-789-def-0AB
123-ghi-456-def-789-ghi-0AB
123-ghi-456-ghi-789-abc-0AB
123-ghi-456-ghi-789-def-0AB
123-ghi-456-ghi-789-ghi-0AB
LINES
# outputs only the permutations
puts input.grep(/123-(abc|def|ghi)-456-(?!\1)(abc|def|ghi)-789-(?!\1|\2)(abc|def|ghi)-0AB/)
Для перестановки из пяти элементов это будет:
/1-(abc|def|ghi|jkl|mno)-
2-(?!\1)(abc|def|ghi|jkl|mno)-
3-(?!\1|\2)(abc|def|ghi|jkl|mno)-
4-(?!\1|\2|\3)(abc|def|ghi|jkl|mno)-
5-(?!\1|\2|\3|\4)(abc|def|ghi|jkl|mno)-6/x
Для вашего примера, регулярное выражение будет
/<a href="home.php" (class="link"|title="Home") (?!\1)(class="link"|title="Home")>Home<\/a>/