Регулярное выражение для извлечения текста, ограниченного '/'

Мне нужно регулярное выражение для извлечения имен из файла GEDCOM. Формат:

Фред Джозеф /Смит/

Где текст, ограниченный /, является фамилией, а Фред Джозеф - именами. Сложность в том, что фамилия может быть в любом месте текста, а может и не быть вовсе. Мне нужно что-то, что будет извлекать фамилию и фиксировать все остальное как имена.

Это все, что у меня есть, и я попытался сделать группы необязательными с помощью ? квалификатор, но безрезультатно:

Что у меня есть

Как вы можете видеть, у него есть несколько проблем: если фамилия отсутствует, ничего не захватывается, имена иногда имеют начальные и конечные пробелы, и у меня есть 3 группы захвата, когда я действительно хотел бы 2. Еще лучше было бы, если бы группа захвата для фамилии не включала символы '/'.

Любая помощь приветствуется.


person Magic Bullet Dave    schedule 18.02.2017    source источник


Ответы (5)


Что касается вашей последней строки, я не уверен, что есть способ объединить группу 1 с группой 3 в одну группу.

Вот мое предлагаемое решение. Он не захватывает пробелы вокруг имен.

^(?:\h*([a-z\h]+\b)\h*)?(?:\/([a-z\h]+)\/)?(?:\h*([a-z\h]+\b)\h*)?$

Чтобы правильно сопоставить имена, используйте флаг нечувствительный, а если вы тестируете все строки сразу, используйте флаг многострочный.

Посмотреть демо

Объяснение

  • ^ начало строки
  • (?:\h*([a-z\h]+\b)\h*)? first non-capturing group that matches 0 or 1 time:
    • \h* 0 or more horizontal spaces
    • ([a-z\h]+\b) захватывает в группу буквы и пробелы, но останавливается в конце последнего слова
    • \h* соответствует возможным оставшимся пробелам без захвата
  • (?:\/([a-z\h]+)\/)? вторая незахватываемая группа, которая соответствует 0 или 1 раз имени в захватываемой группе, окруженному косой чертой
  • (?:\h*([a-z\h]+\b)\h*)? третья группа без захвата делает то же самое, что и первая, захватывая имена в третьей группе.
  • $ конец строки
person Niitaku    schedule 18.02.2017
comment
Вау, спасибо, Ниитаку. Неудивительно, что я не мог этого понять. Также оцените четкое объяснение. Спасибо. - person Magic Bullet Dave; 18.02.2017

Для ваших требований

([A-z a-z /])+\w*

Пример

person Sandeep Bhaskar    schedule 18.02.2017
comment
Сандип спасибо за быстрый ответ. Кажется, это не работает для меня. Добавил \ перед /, но все равно не захватил, как ожидалось. - person Magic Bullet Dave; 18.02.2017

Надеюсь, это поможет (.\*?)\\/(.\*?)\\/(.\*)

person user3507211    schedule 18.02.2017
comment
Отличный ответ на StackOverflow включает в себя больше, чем просто код. Вы можете улучшить свой ответ, объяснив, что происходит, чтобы люди могли извлечь из этого уроки. - person iblamefish; 18.02.2017

Попробуйте это: ^([^/]*)(/[^/]+/)?([^/]*)$

Это соответствует следующему:

  • ^ начало строки (или с модификатором multiline начало строки)
  • ([^/\n]*) anything other than / or new line zero or more times - this is captured as group 1
    • (/[^/\n]+/)? a single / followed by one or more non / or new line characters, then a single '/' character - this is captured as group 2, and is optional
    • ([^/\n]*) что угодно, кроме / или новой строки ноль или более раз — это фиксируется как группа 3
    • $ конец строки (или с многострочным модификатором конец строки)

Вы можете увидеть в действии текст вашего примера здесь: https://regex101.com/r/9kmKpy/1

Чтобы не захватывать косые черты, вы можете добавить группу без захвата, добавив ?: ко второму набору скобок, а затем добавив еще одну пару между косыми чертами: ^([^\/\n]*)(?:\/([^\/\n]+)\/)?([^\/\n]*)$

https://regex101.com/r/9kmKpy/2

person Theo    schedule 18.02.2017

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

(.*)(\/?.*\/?)(.*)

Это не значит, что это не дает вам группировки для КАЖДОГО имени, поскольку некоторые решения будут иметь несколько имен в одной группе.

Редактировать:

Расширяя решение Niitaku и рассматривая каждое отдельное имя в своей группе, вы можете использовать:

^\s*(?:\/?([a-z]+)\/?)\s*(?:\/?([a-z]+)\/?)\s*(?:\/?([a-z]+)\/?)\s*$

Однако, как объяснено, если использовать такой язык, как ruby, это будет просто:

ruby -pe '$_ = $_.scan(/\w+/)' file
person grail    schedule 18.02.2017
comment
Спасибо Грааль. Когда я это делаю, первая группа захвата захватывает все, включая, например, Фреда Джозефа /Смита/. Я использую NSRegularExpression, но тестирую с использованием regex101.com с вариантом pcre. - person Magic Bullet Dave; 18.02.2017
comment
Что-то я запутался, какой желаемый результат? Должны ли мы захватывать «/» как часть группы или вам просто нужны имена? - person grail; 18.02.2017
comment
Только имена. В идеале 1-й захват должен быть «Фред Джозеф», а 2-й захват — «Смит». ХТМС Дэйв - person Magic Bullet Dave; 18.02.2017
comment
Вам либо нужно учитывать все сценарии, если вы просто используете машину с регулярными выражениями, но если вы используете язык (например, рубин), я мог бы легко доставить все имена - person grail; 18.02.2017