如何用正则捕获XML同标签内可变数量组且不越界?
问题描述
我有如下格式的XML文件:
<Table> <Persons> <Person> <ID>71</ID> <FullNameLikeX>"sentence expected"</FullNameLikeX> <Age>49</Age> <FavoriteFood>Banana</FavoriteFood> <NameParts> <word>Jhon</word> <word>Henry</word> <word>Abbot</word> </NameParts> </Person> <Person> <ID>72</ID> <FullNameLikeX>"sentence expected"</FullNameLikeX> <Age>26</Age> <FavoriteFood>Cake</FavoriteFood> <NameParts> <word>Cecilia</word> <word>Elisabeth</word> <word>Maria</word> <word>Smith</word> </NameParts> </Person> <Person> <ID>73</ID> <FullNameLikeX>"sentence expected"</FullNameLikeX> <Age>17</Age> <FavoriteFood>Lasagna</FavoriteFood> <NameParts> <word>Luc</word> <word>Hernandez</word> </NameParts> </Person> </Persons> </Table>
需要在Notepad++中用正则表达式将<FullNameLikeX>标签内的"sentence expected"替换为对应<NameParts>下所有<word>标签内容拼接后加" like "和<FavoriteFood>标签内容(比如第一个Person应替换为"Jhon Henry Abbot like Banana")。
但处理<NameParts>内数量可变(1-5个)的<word>标签时,尝试多种正则均出现问题:
我使用的正则结构为:
(<FullNameLikeX>")[\s\S]*?("<\/FullNameLikeX>)([\s\S]*?<FavoriteFood>([\s\S]*?)<\/FavoriteFood>[\s\S]*?<NameParts>###[\s\S]*?<\/NameParts>)
其中###位置尝试了以下写法:
(?:[\s\S]*?<word>([\s\S]*?)<\/word>)?:当<word>数量少于分组数时,组会越界到下一个<Person>;(?:[\s\S]*?<word>([\s\S]*?)<\/word>)??:不会越界,但无法捕获分组;(?:[\s\S]*?<word>([\s\S]*?)<\/word>)+?:当<word>数量少于分组数时,组会越界到下一个<Person>;(?:[\s\S]*?<word>([\s\S]*?)<\/word>(?![\s\S]*?<\/Person>[\s\S]*?))?:不会越界,但捕获组为空。
现询问:仅用正则表达式是否可实现捕获可变数量XML标签内容且不越界的需求?
回答
单步正则替换很难完美实现这个需求——因为正则的捕获组数量是固定的,没法动态匹配并拼接可变数量的<word>内容。不过在Notepad++里可以分两步操作,简单高效:
第一步:合并<NameParts>内的<word>内容
先把每个<NameParts>下的所有<word>标签去掉,将内容用空格拼接:
- 打开Notepad++的替换窗口(Ctrl+H);
- 选择“正则表达式”模式,勾选“匹配换行符”;
- 查找内容:
(<NameParts>[\s\S]*?)<word>([^<]+)<\/word>([\s\S]*?<\/NameParts>)
- 替换为:
$1$2 $3
- 重复点击“替换全部”,直到提示“0处替换完成”。这时候
<NameParts>里就只剩下用空格分隔的名字内容了。
第二步:替换<FullNameLikeX>的内容
现在可以直接提取拼接好的名字和<FavoriteFood>内容来替换目标标签:
- 同样在替换窗口,保持正则模式和匹配换行符的勾选;
- 查找内容:
(<Person>[\s\S]*?<FullNameLikeX>)"sentence expected"(<\/FullNameLikeX>[\s\S]*?<FavoriteFood>)([^<]+)(<\/FavoriteFood>[\s\S]*?<NameParts>)([^<]+?)(<\/NameParts>[\s\S]*?<\/Person>)
- 替换为:
$1"$5 like $3"$2$3$4$5$6
点击“替换全部”,就能得到你想要的结果。
如果非要用单步正则,也可以写一个覆盖1-5个<word>的冗长表达式,但扩展性极差(超过5个<word>就失效),还会出现多余空格需要处理,完全没必要。分两步操作是最实用的方案。
内容的提问来源于stack exchange,提问作者Nidar
相关产品推荐
相关产品推荐

