【问题标题】:Modify regex pattern to capture nested tags into an array of objects修改正则表达式模式以将嵌套标签捕获到对象数组中
【发布时间】:2019-03-19 18:41:28
【问题描述】:

我正在尝试创建一个正则表达式模式来匹配我正在构建的一个小型应用程序的“人造”html 标签。

我创建了正则表达式来捕获在{tag}brackets{/tag} 中找到的匹配项,并将它们输出到一个对象数组中,如下所示:

{
  {key : value}, 
  {key : value}
}

当前模式的代码:

let str = "{p}This is a paragraph{/p} {img}(path/to/image) {ul}{li}This is a list item{/li}{li}Another list item{/li}{/ul}";

let regex = /\{(\w+)}(?:\()?([^\{\)]+)(?:\{\/1})?/g;
let match;
let matches = [];

while (match = regex.exec(str)) {
    matches.push({ [match[1]]: match[2]})
}

console.log(matches)

Link to JSbin

我意识到我也需要该模式来捕获嵌套组,并将它们放入一个数组中——因此上述string 的结果将是:

[
  {p : "This is a paragraph"},
  {img : "path/to/image"},
  {ul : ["This is a list item", "Another List item"]}
]

这里的想法是按顺序匹配每个标签,以便数组的索引与找到它们的顺序匹配(即上面字符串中的第一段是array[0]等等)。

如果有人对我如何构建模式有一点意见,我将不胜感激。 我不会有超过 1 级的深度嵌套,如果这有什么不同的话。

如果这会有所帮助,我可以灵活地为ul 使用不同的标记,但是我不能使用方括号[text],因为与生成我在此步骤中尝试提取的文本的另一个函数发生冲突。

编辑:一个让我印象深刻的想法是让第三个捕获组捕获并推送到列表数组,但我不确定这在现实中是否可行?到目前为止我还没有让它工作

【问题讨论】:

  • {img} 没有结束标签?还是括号是分隔符?
  • 很好——我的错字了,我用更新的模式更新了线程。专利权是可选的捕获,如果它们存在于标签之后,那么它会捕获里面的文本

标签: javascript arrays json regex


【解决方案1】:

JavaScript 不支持正则表达式中的递归,否则这是一个潜在的解决方案。

但我会选择不同的解决方案:

您可以依赖 DOMParser -- 在浏览器中可用,或者如果您在 Node 上,在几个模块中也有类似的功能。

要使用它,你需要有一个 XML 格式的字符串,所以除非你想使用 <p> 风格的标签,你首先必须将你的字符串转换成那个,确保带有 < 的内容会需要得到<

{img} 标签也需要一个结束标签而不是括号。因此,对于这种特殊情况,需要更换。

一旦不碍事,从 XML 中获取 DOM 就非常简单了,这对您来说可能已经足够好使用了,但是可以通过一个简单的递归函数将其简化为您想要的结构:

const str = "{p}This is a paragraph{/p} {img}(path/to/image) {ul}{li}This is a list item{/li}{li}Another list item{/li}{/ul}";

const xml = str.replace(/\{img\}\((.*?)\)/g, "{img}$1{/img}") 
               .replace(/</g, "&lt;")
               .replace(/\{/g, "<").replace(/\}/g, ">");
const parser = new DOMParser();
const dom = parser.parseFromString("<root>" + xml + "</root>", "application/xml").firstChild;
const parse = dom => dom.nodeType === 3 ? dom.nodeValue.trim() : {
    [dom.nodeName]: dom.children.length
                ? Array.from(dom.childNodes, parse).filter(Boolean)
                : dom.firstChild.nodeValue
};
const result = parse(dom).root;
console.log(result);

输出几乎是您想要的,除了 li 元素也表示为 { li: "...." } 对象。

【讨论】:

  • 感谢您的广泛回复。是否可以在另一种语言中使用递归,例如Java?你的方法是怎样的?
  • 递归在 Java 中具有相同的方法。这个想法是检查节点是否有子节点,如果有,则迭代它们并递归调用函数传递每个子节点。如果没有孩子,则返回字符串值。