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

Есть ли способ указать, что два или более регулярных выражений могут встречаться в любом порядке? Например, атрибуты 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>/
Другие вопросы по тегам