解析Python中的半结构文本字符串



我正在尝试解析伪英语脚本,并希望将其转换为另一台机器可读的语言。但是,剧本过去是许多人写的,每个人都有自己的写作风格。

一些示例是:

  1. 在设备1设置Word 45和46到HEX 331
  2. 在设备上1集45和46位3..7至280
  3. 在设备1设置Word 45到332
  4. 在设备1上将速度设置为60kts单词3-4至十六进制34(源文本中还有许多不同的方式)

问题并不总是逻辑或一致

我看过regexp,并匹配了某些单词。这很好,但是当我需要知道下一个单词时(例如,在'Word 24'中,我会匹配" word",然后尝试弄清楚下一个令牌是否为数字)。在"单词"的情况下,我需要寻找要设置的单词及其值。

在示例1中,它应生成Set word 45 to hex 331Set word 46 to hex 331或者如果可能的话,Set word 45 to hex 331 and word 46 to hex 331

我尝试在re上使用Findall方法 - 只会给我匹配的单词,然后我必须尝试找出下一个单词(即值)手动

另外,我可以使用空间分开字符串,然后手动处理每个单词,然后能够做

之类的事情

假设列表为

['On', 'device1:', 'set', 'Word', '1', '', 'to', '88', 'and', 'word', '2', 'to', '2151']
for i in range (0,sp.__len__()):
    rew = re.search("[Ww]ord", sp[i])
    if rew:
        print ("Found word, next val is ", sp[i+1])

有更好的方法可以做我想做的事吗?我有点仔细研究令牌化,但不确定这是否起作用,因为该语言首先不是构造的。

我建议您开发一个程序,该程序逐渐探讨了人们用来编写脚本的语法。

,例如,您的示例中的每个指令似乎都分解为设备部分和设置部分。因此,您可以尝试将每条线与正则 ^(.+) set (.+)匹配。如果您发现与该模式不匹配的行,请将其打印出来。检查输出,找到与其中一些相匹配的一般模式,在程序中添加相应的正则态度(或修改现有的正则表达式),然后重复。继续进行,直到您(以非常一般的方式)识别输入中的每一行。

(由于大写似乎不一致,您可以进行不敏感的匹配,或者在开始处理之前将每行转换为小写。更一般而言,您可能会发现其他简化后续处理的"正常化"。例如,如果人们对空间不一致,您可以将每个空格字符转换为一个空间。)

(如果您的输入有印刷错误,例如,某人为" SET"编写" Ste",则可以更改正则允许(... (set|ste) ...),或者转到(副本)输入文件,然后修复错字。)

然后返回到与^(.+) set (.+)匹配的行,仅打印出每个组的第一个组,然后重复上述过程中的这些子字符串。然后重复每个"集"指令中第二组的过程。等等,递归。

最终,您的程序实际上将是脚本语言的解析器。那时,您可以开始添加代码以将每个识别的构造转换为输出语言。

根据您在Python的经验,您可以找到使代码简洁的方法。

根据您从这些字符串中真正想要的东西,您可以使用解析器,例如parsimonious

from parsimonious.nodes import NodeVisitor
from parsimonious.grammar import Grammar
grammar = Grammar(
    r"""
    command     = set operand to? number (operator number)* middle? to? numsys? number
    operand     = (~r"words?" / "speed") ws
    middle      = (~r"[Ww]ords" / "bits")+ ws number
    to          = ws "to" ws
    number      = ws ~r"[-d.]+" "kts"? ws
    numsys      = ws ("oct" / "hex") ws
    operator    = ws "and" ws
    set         = ~"[Ss]et" ws
    ws          = ~r"s*"
    """
)
class HorribleStuff(NodeVisitor):
    def __init__(self):
        self.cmds = []
    def generic_visit(self, node, visited_children):
        pass
    def visit_operand(self, node, visited_children):
        self.cmds.append(('operand', node.text))
    def visit_number(self, node, visited_children):
        self.cmds.append(('number', node.text))

examples = ['Set word 45 and 46 to hex 331',
            'set words 45 and 46 bits 3..7 to 280',
            'set word 45 to oct 332',
            'set speed to 60kts Words 3-4 to hex 34']

for example in examples:
    tree = grammar.parse(example)
    hs = HorribleStuff()
    hs.visit(tree)
    print(hs.cmds)

这将产生

[('operand', 'word '), ('number', '45 '), ('number', '46 '), ('number', '331')]
[('operand', 'words '), ('number', '45 '), ('number', '46 '), ('number', '3..7 '), ('number', '280')]
[('operand', 'word '), ('number', '45 '), ('number', '332')]
[('operand', 'speed '), ('number', '60kts '), ('number', '3-4 '), ('number', '34')]

最新更新