python中的嵌套替换

时间:2019-05-02 19:54:20

标签: python string replace nested

我需要过滤一个(长)文本文件替换模式,可能是嵌套的。

不幸的是,模式有些模棱两可(以下不是完整列表):

  • \textit{whatever}-> @e{whatever}e@
  • \textbf{whatever}-> @b{whatever}b@
  • \tqt{whatever}-> @q{whatever}q@

嵌套模式时会出现问题,例如:

\tqt{Da oggi sarai conosciuto anche come \textbf{"guds morder"}: uccisore di Dei}, furono le ultime parole che sentì.

一个简单的实现:

import re
line = 'tqt{Da oggi sarai conosciuto anche come \textbf{"guds morder"}: uccisore di Dei}, furono le ultime parole che sentì.'
line = re.sub(r'\\textbf{([^}]+)}', r'@b{\1}b@', line)
line = re.sub(r'\\tqt{([^}]+)}', r'@q{\1}q@', line)

产生错误的答案(@q{Da oggi sarai conosciuto anche come @b{"guds morder"}q@b@: uccisore di Dei}, furono le ultime parole che sentì.),因为中间形式(\\tgt{Da oggi sarai conosciuto anche come @b{``guds morder''}b@: uccisore di Dei}, furono le ultime parole che sentì.)有点模棱两可,并且以下模式与“错误的”右括号匹配(“正确的”字符串应为:{{ 1}})。

我想分两步执行此操作,但要采用一些中间(明确)的形式,但这过于复杂,如果模式顺序颠倒(文件较长且存在多个嵌套条件),将无济于事。

注意:嵌套总是完整的;即;模式永远不会跨越彼此的边界,否则问题将无法解决。

进行此类替换的pythonic方法是什么?

1 个答案:

答案 0 :(得分:2)

Pyparsing应该胜任这份工作。您可以使用Forward为“一切”进行递归定义。

这是一个示例,其中包含一些调试信息以了解发生了什么情况

import pyparsing as pp
pp.ParserElement.setDefaultWhitespaceChars('') #want to preserve whitespace as is

#a placeholder, to be filled in later
whatever = pp.Forward()

textit = "\\textit{" + whatever + "}"
def textit_action(inputs):
    print('textit')
    outputs = ["@e{"+''.join(inputs[1:-1])+"}e@"]
    return outputs
textit.setParseAction(textit_action)

textbf = "\\textbf{" + whatever + "}"
def textbf_action(inputs):
    print('textbf')
    outputs = ["@b{"+''.join(inputs[1:-1])+"}b@"]
    return outputs
textbf.setParseAction(textbf_action)

tqt = "\\tqt{" + whatever + "}"
def tqt_action(inputs):
    print('tqt')
    print(inputs)
    outputs = ["@q{"+''.join(inputs[1:-1])+"}q@"]
    return outputs
tqt.setParseAction(tqt_action)

anything = pp.Regex('[^\}\{]') 
#(there is probably a more pyparsing-y way to do this)
#Matching only a single character to make this not greedy.
#Else it matches e.g. 'test \textbf', swallowing the textbf.
#This is prevented now, as or_ takes the first that matches.
def anything_action(inputs):
    print('anything')
    print(inputs)
    return inputs
anything.setParseAction(anything_action)

other_brackets = '{' + whatever + '}'
def other_brackets_action(inputs):
    print('other brackets')
    print(inputs)
    return inputs
other_brackets.setParseAction(other_brackets_action)

or_ = pp.MatchFirst([textit, textbf, tqt, other_brackets, anything] )
whatever << pp.ZeroOrMore(or_)

def whatever_action(inputs):
    print('final')
    print(inputs)
    outputs = [''.join(inputs)]
    print(outputs)
    return outputs
whatever.setParseAction(whatever_action)

whatever.parseString(r'\tqt{Da oggi sarai conosciuto anche come \textbf{"guds morder"}: uccisore di Dei}, furono le ultime parole che sentì.')
(['@q{Da oggi sarai conosciuto anche come @b{"guds morder"}b@: uccisore di Dei}q@, furono le ultime parole che sentì.'], {})