正则表达式替换为XML节点

时间:2009-12-03 20:17:01

标签: python regex

我正在使用Python编写一个正则表达式,用XML节点替换部分字符串。

源字符串如下所示:

Hello
REPLACE(str1) this is to replace
REPLACE(str2) this is to replace

结果字符串应该是:

Hello
<replace name="str1"> this is to replace </replace>
<replace name="str2"> this is to replace </replace>

任何人都可以帮助我吗?

6 个答案:

答案 0 :(得分:5)

是什么让你的问题有点棘手,你想要在多线字符串内匹配。您需要使用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)

答案 1 :(得分:4)

这是关于如何在Python中编写正则表达式的excellent tutorial

答案 2 :(得分:1)

这是使用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以扫描输入源以查找所有匹配项,将解析操作应用于每个匹配项,并返回结果输出。解析操作在@ steveha的解决方案中提供与mksub类似的功能。

除了解析动作之外,pyparsing还支持命名表达式的各个元素 - 我使用“name”和“body”来标记感兴趣的两个部分,它们在re解决方案中表示为组1和2。您可以 在re中命名组,相应的re将如下所示:

s_pat = "^\s*REPLACE\((?P<name>[^)]+)\)(?P<body>.*)$"

不幸的是,要按名称访问这些组,您必须在重新匹配对象上调用group()方法,您不能像我的lambda解析操作那样直接执行命名字符串插值。但这是Python,对吗?我们可以用一个类来包装那个callable,这个类将通过名称给我们类似dict的访问权限:

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']元素。

答案 3 :(得分:0)

也许是这样的?

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)

答案 4 :(得分:0)

这样的事情应该有效:

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()

答案 5 :(得分:0)

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

将在一行中进行替换。