Python 中字符串的复杂解析

3

我想解析一个类似于这样格式的字符串:

[{text1}]{quantity}[{text2}]

这个规则意味着在开头可能有一些可以选择存在或不存在的文本,接下来是{quantity},它的语法我将在下面进行描述,然后是更多的可选文本。
{quantity} 可以采取各种形式,其中{n} 是任何正整数。
{n}
{n}PCS
{n}PC
{n}PCS.
{n}PC.
Lot of {n}

此外,它应该接受这个额外的规则:
{n} {text2} 

在此规则中,{n} 后跟一个空格再跟 {text2}
在 PC 或 PCS 出现的情况下:
- 可能会出现句点也可能没有 - 不区分大小写 - {n} 和 PCS 之间可以选择性地出现一个空格 - PC 或 PCS、可选的句点和可选的空格都被删除
期望输出规范化为两个变量:
- {n} 作为整数 - [{text1}] [{text2}],即,先是 {text1}(如果存在),然后是一个空格,最后是 {text2}(如果存在),连接成为一个字符串。只有当有两个文本片段时,才使用空格来分隔它们。
如果 {quantity} 中除了正整数以外还包含其他内容,则 {n} 仅由整数组成,将从 {n} 和生成的文本字符串中剥离掉其余部分(例如“PCS.”)。
在文本部分中,可能会出现更多的整数。任何除 {quantity} 外的其他整数都应视为纯文本,而非另一个数量。
我曾经是 C/C++ 程序员。如果我必须用这些语言解决这个问题,我可能会使用 lex 和 yacc 中的规则,否则我就必须编写大量恶心的代码来手动解析它。
我想学习一种清晰的方法,在 Python 中高效地编写代码,可能使用某种形式的规则轻松支持更多情况。我认为我可以在 Python 中使用 lex 和 yacc,但我想知道是否有更简单的方法。我是 Python 新手;我甚至不知道该如何开始。
我并不要求任何人为解决方案编写完整的代码,而是需要一种方法或两种方法,并且可能还需要一些示例代码,展示如何处理其中的一部分。

3
需要翻译的内容:The first question to figure out is if your language is context free or not. That will determine if you can use a regex or similar tool. If you can't then honestly yacc is the default tools for the job. There may be a python specific package for yacc, but the original works just as well :-)你需要解决的第一个问题是确定你的语言是否是上下文无关的。这将决定你是否可以使用正则表达式或类似的工具。如果不能,那么yacc是这项工作的默认工具。可能会有针对Python的yacc特定软件包,但原始版本同样适用 :-) - AnilRedshift
2
PLY是Python的词法分析器和语法分析器,但pyparsing可能更容易上手。 - PaulMcG
我认为它是上下文无关的,也就是说会有被单独分析的孤立的一行数据。所以你建议使用正则表达式来处理上下文无关的数据行? - Mark Colan
3个回答

3

Pyparsing让您通过使用“+”和“|”运算符(以及其他运算符)将较小的解析器组合在一起来构建解析器。您还可以为解析器中的各个元素附加名称,以便更轻松地获取值。

from pyparsing import (pyparsing_common, CaselessKeyword, Optional, ungroup, restOfLine, 
    oneOf, SkipTo)

int_qty = pyparsing_common.integer

# compose an expression for the quantity, in its various forms
"""
{n}
{n}PCS
{n}PC
{n}PCS.
{n}PC.
Lot of {n}
"""
LOT = CaselessKeyword("lot")
OF = CaselessKeyword("of")
pieces = oneOf("PC PCS PC. PCS.", caseless=True)
qty_expr = Optional(LOT + OF).suppress() + int_qty("qty") + Optional(pieces).suppress()

# compose expression for entire line
line_expr = SkipTo(qty_expr)("text1") + qty_expr + restOfLine("text2")

tests = """
    Send me 1000 widgets pronto!
    Deliver a Lot of 50 barrels of maple syrup by Monday, June 10.
    My shipment was short by 25 pcs.
    """

line_expr.runTests(tests)

输出:

Send me 1000 widgets pronto!
['Send me', 1000, ' widgets pronto!']
- qty: 1000
- text1: ['Send me']
- text2:  widgets pronto!


Deliver a Lot of 50 barrels of maple syrup by Monday, June 10.
['Deliver a ', 50, ' barrels of maple syrup by Monday, June 10.']
- qty: 50
- text1: ['Deliver a ']
- text2:  barrels of maple syrup by Monday, June 10.


My shipment was short by 25 pcs.
['My shipment was short by', 25, '']
- qty: 25
- text1: ['My shipment was short by']
- text2: 

编辑: Pyparsing支持两种匹配方法: MatchFirst,它会在第一个匹配的选择处停止(使用“|”运算符定义),以及Or,它会评估所有选择并选择最长的匹配项(使用“^”运算符定义)。因此,如果您需要提高数量表达式的优先级,则需要明确定义:

qty_pcs_expr = int_qty("qty") + White().suppress() + pieces.suppress()
qty_expr = Optional(LOT + OF).suppress() + int_qty("qty") + FollowedBy(White())

# compose expression for entire line
line_expr = (SkipTo(qty_pcs_expr)("text1") + qty_pcs_expr + restOfLine("text2") |
             SkipTo(qty_expr)("text1") + qty_expr + restOfLine("text2"))

这里是新的测试:
tests = """
    Send me 1000 widgets pronto!
    Deliver a Lot of 50 barrels of maple syrup by Monday, June 10.
    My shipment was short by 25 pcs.
    2. I expect 22 pcs delivered in the morning
    On May 15 please deliver 1000 PCS.
    """

提供:

2. I expect 22 pcs delivered in the morning
['2. I expect ', 22, ' delivered in the morning']
- qty: 22
- text1: ['2. I expect ']
- text2:  delivered in the morning


On May 15 please deliver 1000 PCS.
['On May 15 please deliver ', 1000, '']
- qty: 1000
- text1: ['On May 15 please deliver ']
- text2: 

想知道:如果一个字符串不符合我的规则会发生什么?是否有一种方法可以抛出异常? - Mark Colan
1
这个声明式API真是太棒了。比我开发的正则表达式解决方案更易读。 - mtadd
@MarkColan - 是的,如果输入字符串没有匹配,pyparsing会引发ParseException异常。 - PaulMcG
我有一个问题需要手动编辑一些记录。只有在整数后面跟着一个空格时,才应将其视为数量。在您的示例中,即使没有空格,单个整数(即不符合PCS规则)也被视为数量。如何解决这个问题?此外,是否有一种方法可以优先考虑{quantity}规则,例如始终选择“100个”或“100个”形式(即使它在末尾)? - Mark Colan

1
我不知道你是否想使用re,但是这里有一个正则表达式,我认为它可以工作。您可以更改str的值进行测试。匹配返回一个元组,其中包含三个值[{text1}]{quantity}[{text2}]。如果text1和text2不存在,则元组中的第一个和最后一个项目将为空。
import re

str = "aSOETIHSIBSROG1PCS.ecsrGIR"

matchObj = re.search(r'([a-zA-Z]+|)(\dPCS?\.?|Lot of \d)([a-zA-Z]+|)',str).groups()
print matchObj.groups()

#Output
('aSOETIHSIBSROG', '1PCS.', 'ecsrGIR')

0

这里有一个使用正则表达式匹配两种情况的规则处理器。我创建了一个自定义的匹配结果类,用于保存输入字符串中提取的相关值。规则处理器按照以下顺序尝试以下规则:

  • 规则1 - 尝试匹配{n}后面跟着pc、pc.、pcs或pcs。
  • 规则2 - 尝试匹配"lot of"前面的{n}
  • 规则3 - 尝试匹配{n}后面跟着{text2}

运行时会得到以下结果:

abc 23 PCS. def
amount=23 qtype=PCS. text1="abc" text2="def" rule=1
abc 23pc def
amount=23 qtype=pc text1="abc" text2="def" rule=1
abc 24pc.def
amount=24 qtype=pc. text1="abc" text2="def" rule=1
abc 24 pcs def
amount=24 qtype=pcs text1="abc" text2="def" rule=1
abc lot of 24 def
amount=24 qtype=lot of text1="abc" text2="def" rule=2
3 abcs
amount=3 qtype=None text1="" text2="abcs" rule=3
import re

class Match:
    def __init__(self, amount, qtype, text1, text2, rule):
        self.amount = int(amount)
        self.qtype = qtype
        self.text1 = text1
        self.text2 = text2
        self.rule = rule

    def __str__(self):
        return 'amount={} qtype={} text1="{}" text2="{}" rule={}'.format(
            self.amount, self.qtype, self.text1, self.text2, self.rule)
#{n} pc pc. pcs pcs. def rule1(s): m = re.search("\s*(?P\d+)\s*(?PPCS?\.?)\s*", s, re.IGNORECASE) if m: return Match(m.group('amount'), m.group('qtype'), text1=s[:m.start()], text2=s[m.end():], rule=1) return None #lot of {n} def rule2(s): m = re.search("\s*lot of\s*(?P\d+)\s*", s, re.IGNORECASE) if m: return Match(m.group('amount'), 'lot of', text1=s[:m.start()], text2=s[m.end():], rule=2) return None #{n} {text2} def rule3(s): m = re.search("\s*(?P\d+)\s*",s) if m: return Match(m.group('amount'), None, text1=s[:m.start()], text2=s[m.end():], rule=3) return None RULES = [rule1, rule2, rule3]
def process(s): for rule in RULES: m = rule(s) if m: return m return None
tests = [ "abc 23 PCS. def", "abc 23pc def", "abc 24pc.def", "abc 24 pcs def", "abc lot of 24 def", "3 abcs" ]
for t in tests: m = process(t) print(t) print(m)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接