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

时间:2019-03-01 11:23:55

标签: python parsing

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

一些例子是:

  1. 在设备1上,将单词45和46设置为十六进制331
  2. 在设备1上设置字45和46位3..7至280
  3. 在设备1上将单词45设置为oct 332
  4. 在设备1上将速度设置为60kts 3-4到16进制的单词 (源文本中使用了更多不同的方式)

问题在于它并不总是合乎逻辑且不一致

我看过正则表达式,并匹配了某些单词。可以,但是当我需要知道下一个单词时(例如,在“ 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])

有没有更好的方法来做我想做的事?我稍微看了一下分词,但是不确定该语言是否一开始就没有结构。

2 个答案:

答案 0 :(得分:1)

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

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

(由于大写字母似乎不一致,因此您可以进行不区分大小写的匹配,也可以在开始处理之前将每行转换为小写字母。更普遍的是,您可能会发现其他“规范化”可以简化后续处理。例如,如果人们对于空格不一致,可以将每次运行的空白字符都转换为一个空格。)

(如果您的输入存在印刷错误,例如有人为“ set”写了“ ste”,那么您可以更改正则表达式以允许该(... (set|ste) ...),或转到(的副本)输入文件,然后修正拼写错误。)

然后返回与^(.+) set (.+)匹配的行,仅打印出每组的第一组,并对那些子字符串重复上述过程。 然后在每个“设置”指令中为第二组重复该过程。依此类推,递归地进行。

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

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

答案 1 :(得分:0)

根据这些字符串的实际需求,您可以使用解析器,例如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')]