正则表达式 - 将负面预测与非陈述

时间:2018-05-07 13:45:47

标签: r regex

亲爱的Stack Overflow人,

虽然我会说我大部分时间都用正则表达式完成工作,现在我遇到了一个我似乎无法掌握的问题:

我有需要解析的文本文件(语言为R,但这似乎并不重要)。基本上这些文件是发言者的协议,我想提取一些信息。发言者通常遵循这种模式:

  

先生。保罗(以英语发言):文本。

     

先生。 Hernandez Gabriel(用西班牙语发言):正文。

     

先生。 Jenchewkow(俄语发言,翻译提供):正文。

我用于这些发言者的正则表达式是:^(Mr\.)\s*([^\(]*?)\s*(|\(speaks.*?\)):\s*(.*)$

当这些发言者引用其他人或引用类似的内容时会出现问题:

  

先生。 Puk曾经说过:“你好,我想要排在第二位。

在这里,有时会出现不匹配,因为正则表达式捕获“先生”和冒号之间的所有内容,将第二个捕获组解析为:“Puk曾说过”并弄乱解析的文档。因此,我试图用负面的前瞻来排除这些匹配,猜测先生和结肠之间可能出现的词语,如“说”,“表达”等。

但是,a)我似乎无法将负向前瞻与第二个捕获组([^\(]*?)结合起来,而b)这种方法似乎并不普遍,因为还存在其他不匹配: / p>

  

先生。彼得认为可以接受:有些文字。

所以我的问题有两个:我如何排除名称后面带有“说”,“表达”等的匹配?其次:是否有更好,更普遍的方法来实现这一目标?我想限制“先生”和冒号之间的单词数量,但这似乎并没有解决问题。

提前致谢!

编辑:

作为对这一点非常有用的答案的反应,我应该强调

a)确实有人在数据中有多个名字

b)有些发言者没有跟着“说话......”。因此,Mr\.\s*([^\(]*)\s\(speaks in [^\)]*\):与它们不匹配。一个例子是:

  

先生。保罗:你好!

在给出最初的例子时,最后一个是我的疏忽。遗憾!

2 个答案:

答案 0 :(得分:1)

这个更通用的正则表达式会捕获每种情况下的名称,然后是冒号后面的任何文本:

^Mr\.?\s*([^\s]*)[^:]*:\s*(.+)$

注意我在第一段时间之后加了一个问号,以防你偶尔遇到没有a的先生。如果您始终希望匹配期间,请删除问号。此外,您可能会考虑再次设置不区分大小写,以防您偶尔有先生。是否有女性可能会说话?

忘了说:这个正则表达式假设只有一个姓氏。如果你有像“加西亚·埃尔南德斯先生所说的那样”,那么正则表达式需要更加复杂才能找到这个名字。在这种情况下,这只会匹配加西亚。

编辑:为了回应更多信息,我现在写这样的正则表达式(在R语法中):

grepl("Mr\\.?\\s*([A-Z](?:[^\\s:]|\\s(?=[A-Z]))+)[^:]*:\\s*(.+)", subject, perl=TRUE);

这项工作的条件是Mr总是带有大写字母,并且名称总是以ASCII范围[A-Z]中的大写字母开头(否则Regex如何知道它的名字?)。作为一个普通的正则表达式,它看起来像这样(没有R语法):

Mr\.?\s*([A-Z](?:[^\s:]|\s(?=[A-Z]))+)[^:]*:\s*(.+)

请注意,我已删除了字符串的开头^和字符串结尾$因为它似乎匹配^并且在R(3.1-3.4)中不支持长字符串中行的结尾处的$。如果您正在处理单个字符串,请更改似乎点在R中不能多线工作,所以最后一个(。+)匹配到行的末尾。如果有一位演讲者讲述“正如Hernández先生所说的那样......”,你可能会得到一些误报,但如果在那之后没有冒号到达终点,那么它应该仍然有效。这是开始时$可以提供帮助的地方,如有必要,请将其添加回来。

只要它们以[A-Z]开头,这将匹配冒号前的任意数量的姓氏。这也必须在区分大小写模式下运行。如果你想要解释它是如何工作的,那就问一下,但也许你还是会关注它。

通过编号的捕获组输出上述正则表达式:

Mr. Paul (speaks in English): Text. -> 1. Paul -> 2. Text.
Mr. Hernandez Gabriel Theodor (speaks in Spanish): Text. -> 1. Hernandez Gabriel Theodor -> 2. Text.
Mr. Jenchewkow (speaks in Russian, translation provided): Text. -> 1. Jenchewkow -> 2. Text.
Mr. Puk once said: ‚Hello‘ and I want to second that. -> 1. Puk -> 2. ‚Hello‘ and I want to second that.
Mr. Peter thought it acceptable that: Some text. -> 1. Peter -> 2. Some text.
Mr Paul: Hello! -> 1. Paul -> 2. Hello!

进一步编辑:

好的,所以要在冒号之前排除除括号内的文字之外的任何内容,你可以这样做:

Mr\.?\s*([A-Z](?:[^\s:]|\s(?=[A-Z]))+)(?=[\s]*[(:])[^:]*:\s*(.+)

您可以尝试一下并在此处更改选项:https://regex101.com/r/YzHPa0/1 - 查看该屏幕右侧的匹配信息,以查看捕获组匹配的内容。

请注意,这需要区分大小写。如果要指定括号中的文本以获得更高的选择性,则必须将[^:]*更改为(?:\s\(speaks\sin[^:]+)?

答案 1 :(得分:1)

我建议以下更灵活但仍然固定的模式:

Mr\.\s*([^\(]*)\s\(speaks in [^\)]*\):

Demo

Mr.充当起始锚点,\s\(speaks in ... ):用作第二部分。单\s不是绝对必需的,但输出会变得更好。

您的更新要求使得很难提出一个防水解决方案。如果松散类型的扬声器数量有限,您可以将它们添加为单独的案例,例如:Mr. (Paul|Peter|Matt)(?=:)),然后将所有内容包装起来:

(?|Mr\.\s*([^\(]*)\s\(speaks in [^\)]*\):|Mr. (Paul|Peter|Mary)(?=:))

如果这还不够,您可以为只有名称(包括第二个名字)的情况添加替换:

(?|Mr\.\s*([^\(]*)\s\(speaks in [^\)]*\):|Mr. ([A-Z]\w+)(?=:)|Mr. ([A-Z]\w+ [A-Z]\w+)(?=:))

Demo2