【发布时间】:2009-12-03 20:17:01
【问题描述】:
我正在使用 Python 编写一个正则表达式,用于将部分字符串替换为 XML 节点。
源字符串如下所示:
你好 REPLACE(str1) 这是替换 REPLACE(str2) 这是替换结果字符串应该是这样的:
你好谁能帮帮我?
【问题讨论】:
我正在使用 Python 编写一个正则表达式,用于将部分字符串替换为 XML 节点。
源字符串如下所示:
你好 REPLACE(str1) 这是替换 REPLACE(str2) 这是替换结果字符串应该是这样的:
你好谁能帮帮我?
【问题讨论】:
让你的问题有点棘手的是你想在多行字符串中进行匹配。您需要使用 re.MULTILINE 标志来完成这项工作。
然后,您需要匹配源字符串中的一些组,并在最终输出中使用这些组。以下代码可以解决您的问题:
import re
s_pat = "^\s*REPLACE\(([^)]+)\)(.*)$"
pat = re.compile(s_pat, re.MULTILINE)
s_input = """\
Hello
REPLACE(str1) this is to replace
REPLACE(str2) this is to replace"""
def mksub(m):
return '<replace name="%s">%s</replace>' % m.groups()
s_output = re.sub(pat, mksub, s_input)
唯一棘手的部分是正则表达式模式。让我们详细看一下。
^ 匹配字符串的开头。使用re.MULTILINE,它匹配多行字符串中的行首;换句话说,它在字符串中的换行符之后匹配。
\s* 匹配可选空格。
REPLACE 匹配文字字符串“REPLACE”。
\( 匹配文字字符串“(”。
( 开始一个“匹配组”。
[^)] 表示“匹配除 ") 以外的任何字符。
+ 表示“匹配一个或多个前面的模式。
) 关闭“匹配组”。
\) 匹配字符串“)”
(.*) 是另一个包含“.*”的匹配组。
$ 匹配字符串的结尾。使用re.MULTILINE,它匹配多行字符串中的行尾;换句话说,它匹配字符串中的换行符。
. 匹配任何字符,* 表示匹配零个或多个前面的模式。因此.* 匹配任何内容,直到行尾。
所以,我们的模式有两个“匹配组”。当您运行re.sub() 时,它将生成一个“匹配对象”,该对象将传递给mksub()。匹配对象有一个方法.groups(),它将匹配的子字符串作为一个元组返回,并被替换为替换文本。
编辑:您实际上不需要使用替换功能。您可以将特殊字符串\1放在替换文本中,它将被匹配组1的内容替换。(匹配组从1开始计数;特殊匹配组0对应模式匹配的整个字符串。) \1 字符串中唯一棘手的部分是 \ 在字符串中是特殊的。在普通字符串中,要获得\,您需要将两个反斜杠放在一行中,例如:"\\1" 但是您可以使用 Python 的“原始字符串”来方便地编写替换模式。这样做你会得到这个:
重新导入
s_pat = "^\s*REPLACE\(([^)]+)\)(.*)$"
pat = re.compile(s_pat, re.MULTILINE)
s_repl = r'<replace name="\1">\2</replace>'
s_input = """\
Hello
REPLACE(str1) this is to replace
REPLACE(str2) this is to replace"""
s_output = re.sub(pat, s_repl, s_input)
【讨论】:
这里是excellent tutorial,介绍如何在 Python 中编写正则表达式。
【讨论】:
这是一个使用 pyparsing 的解决方案。我知道您特别询问了正则表达式解决方案,但如果您的需求发生变化,您可能会发现扩展 pyparsing 解析器更容易。或者,pyparsing 原型解决方案可能会让您更深入地了解导致正则表达式或其他最终实现的问题。
src = """\
Hello
REPLACE(str1) this is to replace
REPLACE(str2) this is to replace
"""
from pyparsing import Suppress, Word, alphas, alphanums, restOfLine
LPAR,RPAR = map(Suppress,"()")
ident = Word(alphas, alphanums)
replExpr = "REPLACE" + LPAR + ident("name") + RPAR + restOfLine("body")
replExpr.setParseAction(
lambda toks : '<replace name="%(name)s">%(body)s </replace>' % toks
)
print replExpr.transformString(src)
在这种情况下,您创建要与 pyparsing 匹配的表达式,定义一个解析操作来进行文本转换,然后调用 transformString 扫描输入源以查找所有匹配项,将解析操作应用于每个匹配项,并返回结果输出。 parse 操作的功能与@steveha 解决方案中的 mksub 类似。
除了解析动作,pyparsing 还支持命名表达式的各个元素——我用“name”和“body”来标记感兴趣的两个部分,它们在 re 解决方案中表示为组 1 和组 2。您可以在 re 中命名组,对应的 re 如下所示:
s_pat = "^\s*REPLACE\((?P<name>[^)]+)\)(?P<body>.*)$"
不幸的是,要按名称访问这些组,您必须在重新匹配对象上调用 group() 方法,您不能像在我的 lambda 解析操作中那样直接执行命名字符串插值。但这是 Python,对吧?我们可以用一个类包装该可调用对象,该类将使我们能够按名称访问组:
class CallableDict(object):
def __init__(self,fn):
self.fn = fn
def __getitem__(self,name):
return self.fn(name)
def mksub(m):
return '<replace name="%(name)s">%(body)s</replace>' % CallableDict(m.group)
s_output = re.sub(pat, mksub, s_input)
使用CallableDict,mksub 中的字符串插值现在可以为每个字段调用 m.group,看起来我们正在检索字典的 ['name'] 和 ['body'] 元素。
【讨论】:
可能是这样的?
import re
mystr = """Hello
REPLACE(str1) this is to replace
REPLACE(str2) this is to replace"""
prog = re.compile(r'REPLACE\((.*?)\)\s(.*)')
for line in mystr.split("\n"):
print prog.sub(r'< replace name="\1" > \2',line)
【讨论】:
这样的事情应该可以工作:
import re,sys
f = open( sys.argv[1], 'r' )
for i in f:
g = re.match( r'REPLACE\((.*)\)(.*)', i )
if g is None:
print i
else:
print '<replace name=\"%s\">%s</replace>' % (g.group(1),g.group(2))
f.close()
【讨论】:
import re
a="""Hello
REPLACE(str1) this is to replace
REPLACE(str2) this is to replace"""
regex = re.compile(r"^REPLACE\(([^)]+)\)\s+(.*)$", re.MULTILINE)
b=re.sub(regex, r'< replace name="\1" > \2 < /replace >', a)
print b
将在一行中进行替换。
【讨论】: