使用部分标题上的正则表达式匹配将文本文档拆分为多个部分

时间:2018-02-13 15:45:56

标签: python regex

我所拥有的文件部分很好地用标题表示。我想使用这些标题将文档分成几个部分。例如:

1.1 Lorem Ipsum

Blah blah blah
9 (page break, never will have a period in it though)
Bleh bleh bleh as referenced in Section 1.3 hey hey hey

1.2 Lorem Ipsumus

Blah blah blah

我想要一个正则表达式,可以获取标题和文本,直到出现下一个标题。因此,该示例的期望结果将是

1.1 Lorem Ipsum Blah blah blah bleh bleh bleh as referenced in Section 1.3 hey hey hey

1.2 Lorem Ipsumus Blah blah blah

我总能指望的一件事是,节标题将是一个新的行,以某种数字xx开头,后跟几个单词,因为这对标题来说非常独特,我想搜索的内容上。

基本上,如果我看到任何新线和形式"第1.2节定义"我知道这是一个新的部分,并希望从那里获取所有文本,直到下一个以"第1.3节示例"开头的新行。或许"第2.1节条款"。章节标题总是以一个新的行开头,其形式为"第1.3节示例","第1.3条示例",或" 1.3示例"。

有时会在一行中间引用标题,我想忽略这些标题。这可以在示例中看到。

有谁知道怎么做?优先在python中,但正则表达式应该足够了。

P.S。是否保留页码是可选的,但正则表达式理想情况下不会根据页码创建新的部分

编辑:到目前为止,这是我运行的MWE。它不在那里。

import re
doc_splitter = re.compile(r"(?<=\n)(?P<secname>[\w]+ )(\d+\.\d+ .*?)(?<=\n)(?P<secname2>[\w]+ )(?=\d+\.\d+|\Z)", re.DOTALL)

text = """

Section 1.1 Lorem Ipsum

Blah blah blah
9
Bleh bleh bleh Section 1.1 hey hey hey

Section 1.2 Lorem Ipsumus 
ref Section 1.3

Blah blah blah

Section 1.3 hey hey

Section 1.4

"""


for match in doc_splitter.finditer(text):
    print([match.group()])

理想情况下它会返回:

['Section 1.1 Lorem Ipsum Blah blah blah 9 Bleh bleh bleh Section 1.1 hey hey hey']
['Section 1.2 Lorem Ipsumus ref Section 1.3 Blah blah blah']
['Section 1.3 hey hey']
['Section 1.4']

但它会返回:

['Section 1.1 Lorem Ipsum\n\nBlah blah blah\n9\nBleh bleh bleh Section 1.1 hey hey hey\n\nSection ']
['Section 1.3 hey hey\n\nSection ']

感谢所有人的帮助!如果有人对如何解决这个问题有任何想法,我将非常感激。

3 个答案:

答案 0 :(得分:2)

只需将我的两分钱 - 你可以使用

^
(?:Section\ )?\d+\.\d+
[\s\S]*?
(?=^(?:Section\ )?\d+\.\d+|\Z)

使用verbosemultiline修饰符,请参阅a demo on regex101.com

<小时/> 在Python

import re

data = """
1.1 Lorem Ipsum

Blah blah blah
9 (page break, never will have a period in it though)
Bleh bleh bleh as referenced in Section 1.3 hey hey hey

1.2 Lorem Ipsumus

Blah blah blah
"""

rx = re.compile(r'''
    ^
    (?:Section\ )?\d+\.\d+
    [\s\S]*?
    (?=^(?:Section\ )?\d+\.\d+|\Z)

    ''', re.VERBOSE | re.MULTILINE)

parts = [match.group(0) for match in rx.finditer(data)]
print(parts)

答案 1 :(得分:1)

您正在寻找的正则表达式可能与此类似:

doc_splitter = re.compile(r"(?<=\n)(\d+\.\d+ .*?)(?<=\n)(?=\d+\.\d+|$)", re.DOTALL)

,给定python代码,可以使用finditer

在整个文档上运行
text = """
1.1 Lorem Ipsum

Blah blah blah
9 (page break, never will have a period in it though)
Bleh bleh bleh

1.2 Lorem Ipsumus

Blah blah blah"""
for match in doc_splitter.finditer(text):
    print([match.group()])  # print in list to suppress \n interpretation 

打印:

['1.1 Lorem Ipsum\n\nBlah blah blah\n9 (page break, never will have a period in it though)\nBleh bleh bleh\n\n']
['1.2 Lorem Ipsumus\n\nBlah blah blah\n']

这似乎是你想要的。

如果以不同方式迭代数据,您可能可以摆脱繁琐的外观断言,这些断言可能无法干净地转换为需要恒定长度外观的其他语言。核心是(\d+\.\d+ .*?),并强制完全匹配。

替代

Jan的答案很好,但我还想添加一个解决问题的解决方案,不需要前瞻条件,因为它们看起来多余:

import re
doc_splitter = re.compile(r"^(?:Section\ )?\d+\.\d+", re.MULTILINE)
text = """

Section 1.1 Lorem Ipsum

Blah blah blah
9
Bleh bleh bleh Section 1.1 hey hey hey

Section 1.2 Lorem Ipsumus 
ref Section 1.3

Blah blah blah

Section 1.3 hey hey

Section 1.4

"""
starts = [match.span()[0] for match in doc_splitter.finditer(text)] + [len(text)]
sections = [text[starts[idx]:starts[idx+1]] for idx in range(len(starts)-1)]
for section in sections:
    print([section])

打印:

['Section 1.1 Lorem Ipsum\n\nBlah blah blah\n9\nBleh bleh bleh Section 1.1 hey hey hey\n\n']
['Section 1.2 Lorem Ipsumus \nref Section 1.3\n\nBlah blah blah\n\n']
['Section 1.3 hey hey\n\n']
['Section 1.4\n\n']

正则表达式只搜索新部分的开头,并且应该足够容易维护和扩展。我们必须完成从每个新开始手动分割text的附加步骤,作为前一部分的结尾。

虽然正则表达式完全能够在一个步骤中处理这种匹配,但我个人更喜欢让它们尽可能短。他们已经很难理解了。

答案 2 :(得分:1)

我建议你试试regex101.com,它会帮助你想象你的正则表达式。此外,documentation for re对于学习(或记住)特殊字符的工作方式非常有用。

使用您的示例我将使用此正则表达式(带有命名组):

(?P<section_number>\d\.\d) (?P<section_title>[\w ]+)\n\n\s*(?P<body>.+?)\s*(?=\d\.\d[\w ]+|$)

打破它:

对于部分编号和标题,我使用了以空格分隔的命名组(?P<section_number>\d\.\d)(?P<section_title>[\w ]+)

身体(?P<body>.+?)跟随正向前瞻(?=\d\.\d[\w ]+|$)。这意味着当另一部分即将开始或文档结束时,它将停止捕获文本。它需要是非常的(+?),或者你只需​​要一个部分,文档的其余部分作为正文。

注意:您需要在编译或搜索匹配项时启用re.DOTALL,否则该点将与新行字符不匹配。

如果您希望章节标题与字符串的开头匹配,您还可以向前瞻添加^,但您需要启用re.MULTILINE。您还必须将$最后更改为\Z,以便它仅匹配文档的末尾而不是每行的结尾。

(?P<section_number>\d\.\d) (?P<section_title>[\w ]+)\n\n\s*(?P<body>.+?)\s*(?=^\d\.\d[\w ]+|\Z)