Вы, похоже, неправильно понимаете, как работают классы персонажей (например, [a-f0-9]
или [^aeiouy]
). /[^abcd]/
не отменяет шаблон abcd
, он говорит "соответствует любому символу, который не 'a'
или 'b'
или 'c'
или 'd'
".
Если вы хотите сопоставить отрицание шаблона, используйте конструкцию /(?!pattern)/
. Это совпадение с нулевой шириной - это означает, что оно на самом деле не соответствует ни одному символу, оно соответствует позиции.
Аналогично тому, как /^/
и /$/
соответствуют началу и концу строки, или /\b/
соответствует границе слова. Например: /(?!xx)/
соответствует каждой позиции, где шаблон «xx» не начинается.
Как правило, после использования отрицания шаблона вам необходимо сопоставить некоторый символ для продвижения вперед в строке.
Итак, чтобы использовать ваш шаблон:
form = /\/\*\s+(\w+)\s+\((\d{4})\)[0]{2}\s+\*\//m
form_and_fields = /#{form}((?:(?!#{form}).)+)/m
text.scan(form_and_fields)
Изнутри (я буду использовать (?#comments)
)
(?!#{form})
отменяет исходный рисунок, поэтому он соответствует любой позиции, с которой исходный рисунок не может начаться.
(?:(?!#{form}).)+
означает сопоставить один символ после этого и повторить попытку столько раз, сколько возможно, но хотя бы один раз. (?:(?#whatever))
- это круглые скобки без записи - хорошо для группировки.
В irb это дает:
irb> text.scan(form_and_fields)
=> [["F004", "0309", " \n /* field 1 */ \n /* field 2 */ \n ", nil, nil], ["F004", "0409", " \n /* field 1 */ \n /* field 2 */ \n", nil, nil]]
Дополнительные nil
получены от групп захвата в form
, которые используются в шаблоне с отрицанием (?!#{form})
и поэтому ничего не фиксируют при успешном совпадении.
Это можно почистить немного:
form_and_fields = /#{form}\s*(.+?)\s*(?:(?=#{form})|\Z)/m
text.scan(form_and_fields)
Теперь вместо отрицательного просмотра с нулевой шириной мы используем положительный взгляд с нулевой шириной (?=#{form})
, чтобы соответствовать положению следующего вхождения form
. Таким образом, в этом регулярном выражении мы сопоставляем все до следующего вхождения form
( без , включая следующее вхождение в нашем матче). Это позволяет нам обрезать некоторые пробелы вокруг полей. Мы также должны проверить случай, когда мы достигли конца строки - /\Z/
, поскольку это тоже может произойти.
In irb:
irb> text.scan(form_and_fields)
=> [["F004", "0309", "/* field 1 */ \n /* field 2 */", "F004", "0409"], ["F004", "0409", "/* field 1 */ \n /* field 2 */", nil, nil]]
Обратите внимание, что последние два поля заполняются в первый раз - b / c захват паренсов в положительном прогнозе нулевой ширины соответствовал чему-то, даже если он не был помечен как «использованный» во время процесса - вот почему этот бит можно повторить во второй раз.