如何用pyparsing写语法:匹配一组单词但不包含特定模式

2 投票
2 回答
1282 浏览
提问于 2025-04-15 16:25

我刚接触Python和pyparsing,想要完成一些事情。

我有一行这样的文本:

12 items - Ironing Service    11 Mar 2009 to 10 Apr 2009
Washing service (3 Shirt)  23 Mar 2009

我需要提取出物品的描述和时间段。

tok_date_in_ddmmmyyyy = Combine(Word(nums,min=1,max=2)+ " " + Word(alphas, exact=3) + " " + Word(nums,exact=4))
tok_period = Combine((tok_date_in_ddmmmyyyy + " to " + tok_date_in_ddmmmyyyy)|tok_date_in_ddmmmyyyy)

tok_desc =  Word(alphanums+"-()") but stop before tok_period

该怎么做呢?

2 个回答

3

M K Saravanan,这个解析问题其实用老牌的正则表达式(re)来解决并不难:

import re
import string

text='''
12 items - Ironing Service    11 Mar 2009 to 10 Apr 2009
Washing service (3 Shirt)  23 Mar 2009
This line does not match
'''

date_pat=re.compile(
    r'(\d{1,2}\s+[a-zA-Z]{3}\s+\d{4}(?:\s+to\s+\d{1,2}\s+[a-zA-Z]{3}\s+\d{4})?)')
for line in text.splitlines():
    if line:
        try:
            description,period=map(string.strip,date_pat.split(line)[:2])
            print((description,period))
        except ValueError:
            # The line does not match
            pass

结果是

# ('12 items - Ironing Service', '11 Mar 2009 to 10 Apr 2009')
# ('Washing service (3 Shirt)', '23 Mar 2009')

这里的主要工作就是正则表达式的模式。我们来逐步解析一下:

\d{1,2}\s+[a-zA-Z]{3}\s+\d{4} 是用来匹配日期的正则表达式,相当于 tok_date_in_ddmmmyyyy\d{1,2} 匹配一位或两位数字,\s+ 匹配一个或多个空格,[a-zA-Z]{3} 匹配三个字母,等等。

(?:\s+to\s+\d{1,2}\s+[a-zA-Z]{3}\s+\d{4})? 是一个被 (?:...) 包围的正则表达式。这表示这是一个非分组的正则表达式。使用这个方式,就不会给这个正则表达式分配一个组(比如 match.group(2))。这很重要,因为 date_pat.split() 返回的列表中每个组都是列表的一个成员。通过不分组,我们可以把整个时间段 11 Mar 2009 to 10 Apr 2009 保持在一起。最后的问号表示这个模式可以出现零次或一次。这让正则表达式可以同时匹配 23 Mar 200911 Mar 2009 to 10 Apr 2009

text.splitlines() 是用来根据 \n 来分割文本的。

date_pat.split('12 items - Ironing Service 11 Mar 2009 to 10 Apr 2009')

是根据 date_pat 的正则表达式来分割字符串。匹配的部分会包含在返回的列表中。这样我们得到:

['12 items - Ironing Service ', '11 Mar 2009 to 10 Apr 2009', '']

map(string.strip,date_pat.split(line)[:2]) 是用来美化结果的。

如果 line 不匹配 date_pat,那么 date_pat.split(line) 会返回 [line,],所以

description,period=map(string.strip,date_pat.split(line)[:2])

会抛出一个 ValueError,因为我们不能把只有一个元素的列表解包成两个值。我们会捕获这个异常,但只是简单地继续执行下一行。

5

我建议你看看 SkipTo 这个类,它是 pyparsing 中最合适的一个,因为你已经很好地定义了 不想要 的文本,但在那之前的内容几乎都可以接受。下面是几种使用 SkipTo 的方法:

text = """\
12 items - Ironing Service    11 Mar 2009 to 10 Apr 2009
Washing service (3 Shirt)  23 Mar 2009"""

# using tok_period as defined in the OP

# parse each line separately
for tx in text.splitlines():
    print SkipTo(tok_period).parseString(tx)[0]

# or have pyparsing search through the whole input string using searchString
for [[td,_]] in SkipTo(tok_period,include=True).searchString(text):
    print td

这两个 for 循环都会输出以下内容:

12 items - Ironing Service    
Washing service (3 Shirt) 

撰写回答