尝试解析时失控回溯 XML

Runaway Backtracking when trying to parse XML

我在尝试解析某些 OpenXML 标准 (docx) 时遇到问题。我们使用像 {Contact.MailAddress} 这样的表达式,并在第二步中从数据中填充它。然而,Word(和 LibreOffice)的方式是,它们有时会像这样拆分这些标签:

<w:r w:rsidRPr="00E22BCD">
    <w:rPr>
        <w:rFonts w:eastAsia="Times New Roman"/>
        <w:lang w:val="fr-CH"/>
    </w:rPr>
    <w:t>{</w:t>
</w:r>
<w:proofErr w:type="spellStart"/>
<w:r w:rsidRPr="00E22BCD">
    <w:rPr>
        <w:rFonts w:eastAsia="Times New Roman"/>
        <w:lang w:val="fr-CH"/>
    </w:rPr>
    <w:t>Contakt.MailAddress</w:t>
</w:r>
<w:proofErr w:type="spellEnd"/>
<w:r w:rsidRPr="00E22BCD">
    <w:rPr>
        <w:rFonts w:eastAsia="Times New Roman"/>
        <w:lang w:val="fr-CH"/>
    </w:rPr>
    <w:t>}</w:t>
</w:r>

所以我制作了以下正则表达式:

(?<expr>{)((?<tag><[^>]+>)|(?<expr>[\w\s.]+))+(?<expr>})

expr 组中的所有内容都是 {Contact.MailAddress} 表达式的一部分并合并在一起。 tag 组中的所有内容都合并到标签中,以便稍后将 xml 一起修复。

现在,这很好用。但是,当我们使用 {foreach} 语法时,xml 可能会变得很大,然后我们就会失控。

谁能想出一个正则表达式,它能更好地捕捉到这一点而不会导致失控?

编辑 1:该程序是用 C#/.NET 编写的。对于正则表达式风格。

编辑 2:我采用了另一种方法:我列出了所有匹配 {[^}]} 的匹配项,并且在其中我将所有标签和空格替换为空:

var matches = Regex.Matches(xml, @"{[^}]+}")
    .Cast<Match>()
    .OrderByDescending(x => x.Index)
    .ToList();

foreach (var match in matches)
{
    var replacement = Regex.Replace(match.Value, @"<[^>]+>", "");
    replacement = Regex.Replace(replacement, @"\s+", "");
    xml = xml.Substring(0, match.Index) + replacement + xml.Substring(match.Index + match.Length);
}

诀窍是按索引降序排列匹配项,以便 Substring 中的数学运算有效。

您似乎想删除 {} 之间的所有标记和空格。如果您不担心其他不应该匹配的括号,这应该有效:

s = Regex.Replace(s, 
    @"(?<brace>{)\s*(?:<[^<>]+>\s*)*|\s*(?:<[^<>]+>\s*)*(?<brace>})", 
    @"${brace}");

为了安全起见,您可能想要添加最近的实际标签(假设它们始终相同):

@"(?<brace>{)</w:t>\s*(?:<[^<>]+>\s*)*|\s*(?:<[^<>]+>\s*)*<w:t>(?<brace>})"

使用任一正则表达式,我得到以下结果:

<w:r w:rsidRPr="00E22BCD">
    <w:rPr>
        <w:rFonts w:eastAsia="Times New Roman"/>
        <w:lang w:val="fr-CH"/>
    </w:rPr>
    <w:t>{Contakt.MailAddress}</w:t>
</w:r>

...而且根本没有回溯。

编辑:

事实证明,标签也被插入到大括号内的点之前和之后。我原来的解决方案对此不起作用,所以这里有一个 two-stage 方法,它可以找到 brace-enclosed 文本并将其替换为相同的文本并删除标签和空格:

s = Regex.Replace(s, 
    @"{[^{}]*}", 
    m => Regex.Replace(m.Value, @"\s*(?:<[^<>]+>\s*)*", ""));