Извлечь текст, заключенный в квадратные скобки (с разделителем между свойствами в скобках)

Оригинал:

Чтобы не усложнять, вот чего я пытаюсь достичь:

оригинал:

[category - subcategory] [some text - more text] [2018-12-31] text title here

желаемый результат:

category
subcategory
some text
more text
2018-12-31
text title here

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

[category - subcategory] [some text - more text] [2018-12-31] text title here

[category - subcategory] [some text] [2018-12-31] text title here more text

[category] [some text - more text - even more] [2018-12-31] text title here more text

Таким образом, текст внутри первых двух [] [] будет разделен -

Вчера я впервые попробовал использовать регулярное выражение, и это было немного головной болью. Возможно ли то, что я пытаюсь сделать?


person ggg    schedule 17.07.2019    source источник
comment
Вероятно, вы захотите немного подробнее объяснить желаемый результат, так как в настоящее время его можно получить, заменив все не альфа / пробелы на '' (но вы, вероятно, захотите что-то вроде \[([^-]+)\s*-\s*([^\]]+)\]\s+\[([^-]+)\s*-\s*([^\]]+)\]\s\[([^\]]+)\]\s*(.+))   -  person Tibrogargan    schedule 17.07.2019


Ответы (6)


Я бы решил эту проблему в два этапа.

Во-первых, используйте это регулярное выражение для извлечения блоков между квадратными скобками и после:

\[(.*?)\]\s*\[(.*?)\]\s*\[(.*?)\]\s*(.*)

Предполагая, что квадратные скобки не разрешены в другом месте ввода, это даст вам четыре групповых совпадения для категории, текста, даты и свободного текста.

Подробности:

  • \[ и \] соответствуют буквальным квадратным скобкам.
  • (.*?) не жадно сопоставляет текст между квадратными скобками, что позволяет избежать использования более неудобного набора символов ([^][]*) для их исключения.
  • \s* допускает любое количество пробелов между блоками. Вы также можете использовать только один пробел, если узор всегда состоит из одного пробела.
  • (.*) в конце просто захватит все, что осталось на линии.

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

PS: поскольку вы не указали язык программирования, я привожу ваш описательный псевдокод; вам нужно будет узнать, как получить доступ к группам совпадений и выполнить разделение на вашем языке.

ДЕМО

person joanis    schedule 17.07.2019

Да, это возможно, но довольно сложно с некоторыми выражениями, которые могут быть похожи на:

\[\s*(\s*\d{4}\s*-\s*\d{2}\s*-\s*\d{2}\s*)\s*\]|(?<=\[|-)\s*(.*?)\s*(?=-|\])|([A-Za-z].*)

Сначала мы зафиксируем дату, используя

\[\s*(\s*\d{4}\s*-\s*\d{2}\s*-\s*\d{2}\s*)\s*\]

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

(?<=\[|-)\s*(.*?)\s*(?=-|\])

и последнее предложение:

([A-Za-z].*)

например. Мы можем добавить другие символы в этот класс char

[A-Za-z]

если это будет необходимо.

Выражение объясняется в верхней правой панели этой демонстрации, если вы хотите изучить / упростить / изменить его.

Демо

В этой демонстрации вы можете увидеть, как работают группы захвата:

const regex = /\[\s*(\s*\d{4}\s*-\s*\d{2}\s*-\s*\d{2}\s*)\s*\]|(?<=\[|-)\s*(.*?)\s*(?=-|\])|([A-Za-z].*)/gm;
const str = `[category - subcategory] [some text   -   more text  ] [2018-12-31] text title here
[category - subcategory] [some text] [  2018 - 12 -31  ] text title here more text
[category] [some text - more text - even more] [2018-12-31] text title here more text
[category] [some text - more text - even more - some text - more text   -   even more  ] [2018-12-31] text title here more text`;
let m;

while ((m = regex.exec(str)) !== null) {
    // This is necessary to avoid infinite loops with zero-width matches
    if (m.index === regex.lastIndex) {
        regex.lastIndex++;
    }
    
    // The result can be accessed through the `m`-variable.
    m.forEach((match, groupIndex) => {
        console.log(`Found match, group ${groupIndex}: ${match}`);
    });
}

person Emma    schedule 17.07.2019

Вы также можете применить sed, чтобы получить результат в желаемом формате.

echo [category - subcategory] [some text - more text] [2018-12-31] text title here \
| sed -e $'s/\] /\\\n/g' -e $'s/ \- /\\\n/g' -e 's/\[//g'

Вывод:

 category
 subcategory
 some text
 more text
 2018-12-31
 text title here

Сначала преобразование ](space) и (space)-(space) в новую строку, а затем замена [ на empty

person minhazur    schedule 17.07.2019

Попробуйте шаблон \[.+?(?(?<= - ) - |\])

Объяснение:

\[ - совпадать [ буквально

.+? - соответствует одному или нескольким любым символам (нежадный)

(?(?<= - ) - |\]) - условно: если положительный внешний вид (?<= - ) (буквальное совпадение -) удовлетворяется, тогда соответствует -, иначе соответствует ] буквально с \]

введите здесь описание ссылки

person Michał Turczyn    schedule 17.07.2019

Сделайте себе одолжение и напишите свой собственный парсер, например с Python (язык еще не отмечен?), это может быть parsimonious:

from parsimonious.grammar import Grammar
from parsimonious.nodes import NodeVisitor

data = ["[category - subcategory] [some text - more text] [2018-12-31] text title here",
        "[category - subcategory] [some text] [2018-12-31] text title here more text",
        "[category] [some text - more text - even more] [2018-12-31] text title here more text",
        "[category - subcategory] [some text - more text] [2018-12-31] text title here"]


class TextVisitor(NodeVisitor):
    grammar = Grammar(
        r"""
        content = (section / text)+

        section = lpar notpar (sep notpar)* rpar ws*
        text    = ~"[^][]+"

        lpar    = "["
        rpar    = "]"
        notpar  = ~"(?:(?! - )[^][])+"
        sep     = " - "
        ws      = ~"\s+"
        """
    )

    def generic_visit(self, node, visited_children):
        return visited_children or node

    def visit_section(self, node, visited_children):
        _, cat1, catn, *_ = visited_children

        categories = [cat1.text] + [cat[1].text for cat in catn]
        return categories

    def visit_text(self, node, visited_children):
        return [node.text]

    def visit_content(self, node, visited_children):
        result = [textnode
                  for child in visited_children
                  for subchild in child
                  for textnode in subchild]
        return result


for datapoint in data:
    tv = TextVisitor()
    result = tv.parse(datapoint)
    print("\n".join(result))
    print("###")

Это дает

category
subcategory
some text
more text
2018-12-31
text title here
###
category
subcategory
some text
2018-12-31
text title here more text
###
category
some text
more text
even more
2018-12-31
text title here more text
###
category
subcategory
some text
more text
2018-12-31
text title here
###
person Jan    schedule 17.07.2019

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

(?:\[|\G(?!^))([^-\][\s]+(?:[ -][^-\][\s]+)*)(?: - )?(?=[^[\]]*\])

Матчи находятся в первой группе захвата.

Объяснение

  • (?: Non capturing group
    • \[ Match [
    • | Or
    • \G(?!^) Утверждать позицию в предыдущем совпадении, а не в начале
  • ) Закрыть группу без захвата
  • ( Capture group 1
    • [^-\][\s]+ Match 1+ any char except -, ], [ and a whitespace char
    • (?:[ -][^-\][\s]+)* Повторить 0+ раз, как в предыдущем шаблоне, только с пробелом или дефисом в начале
  • ) Закрыть группу
  • (?: - )? Можно сопоставить - между пробелами
  • (?= Positive lookahead, assert what is on the right is
    • [^[\]]*\] Match 0+ times any char except [ and ]
  • ) Близкий взгляд вперед

Демонстрация Regex

person The fourth bird    schedule 17.07.2019