使用Python在节标题上使用Regex匹配将文本文档拆分为节

2024-07-05 14:07:22 发布

您现在位置:Python中文网/ 问答频道 /正文

我的文档中有一些用标题很好地表示的部分。我想用这些标题把文件分成几个部分。示例:

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

我想要一个正则表达式,可以采取标题和文本,直到下一个标题出现。所以这个例子的期望结果是

^{pr2}$

以及

1.2 Lorem Ipsumus Blah blah blah

有一件事我总是可以指望的是,部分标题将是一个新的行,以某种数字x.x开头,后面跟着几个单词,因为这是标题的独特之处,所以我想搜索它。在

基本上,如果我看到一个新的行,形式是“1.2节定义”,我知道这是一个新的部分,我想从那里抓取所有的文本,直到下一行以“1.3节示例”或“2.1节术语”开头。章节标题总是以新行开头,格式为“第1.3节示例”、“第1.3条示例”或“1.3示例”。在

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

有人知道怎么做吗?最好是在python中,但是如果regex不足够的话,那么regex就足够了。在

p.s.是否保留页码是可选的,但是regex最好不会基于页码创建新的部分


编辑:到目前为止,这是我运行的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 ']

谢谢大家的帮助!如果有人对如何解决最后一个问题有任何想法,我们将不胜感激。在


Tags: in文本re标题示例sectionregexblah
3条回答

您要查找的正则表达式可能类似于:

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

,在给定python代码的情况下,可以使用finditer在整个文档上运行:

^{pr2}$

印刷品:

['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']

这似乎是你想要的。在

如果您以不同的方式迭代数据,您可能能够摆脱繁琐的lookaround断言,这些断言可能无法清晰地转换为其他需要恒定长度lookaround的语言。核心使用(\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']

regex只搜索新部分的开始部分,并且应该易于维护和扩展。我们必须通过另外一个步骤,从每个新的开始手工拆分text,这是前一部分的结尾。在

虽然regex完全可以在一个步骤中处理这种匹配,但我个人更希望它们尽可能短。它们已经够难理解了。在

你可以用我的两分钱

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

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


Python中: ^{pr2}$

我建议您尝试regex101.com,它将帮助您可视化您的正则表达式。另外,documentation for re对于学习(或记住)特殊字符是如何工作的非常有用。在

在您的示例中,我将使用以下regex(带命名组):

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

分解:

对于节号和标题,我使用了以空格分隔的命名组(?P<section_number>\d\.\d)和{}。在

主体(?P<body>.+?)后面是正展望(?=\d\.\d[\w ]+|$)。这意味着当另一节即将开始或文档结束时,它将停止捕获文本。它必须是nongreedy(+?),否则您只需要打开一个部分,将文档的其余部分作为主体。在

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

如果希望节标题与字符串的begging匹配,也可以在lookahead中添加一个^,但是需要启用re.MULTILINE。您还必须将末尾的$改为\Z,这样它只匹配文档的结尾,而不是每行的结尾。在

^{pr2}$

相关问题 更多 >