Javascript 正则表达式:在不使用额外括号的情况下获取括号内的特定部分

Javascript Regex: get specific parts inside brackets without taking extra brackets

提问人:Yk Music 提问时间:5/18/2023 最后编辑:Yk Music 更新时间:5/18/2023 访问量:49

问:

我正在构建一个程序,可以简化编码不和谐机器人。我想添加内联函数,但似乎我的正则表达式选择器不是完美的匹配。

我的程序遍历每个内联函数,并测试它是否存在于字符串中。并将内联函数字符串替换为内联函数返回。

例如,{ping} 被转换为 91。或者,将 {id author} 转换为消息作者的 id。

问题在于,如果内联函数中有内联函数。我设法每 1 个解决 1 个内联函数,但随着数量的增长,我的正则表达式选择器失败了,只是匹配了错误的东西。

我希望它匹配什么:

{avatarurl {id {message}}}
           ^^^^^^^^^^^^^^

甚至可以匹配这一点:

{avatarurl {id {message}}}
               ^^^^^^^^^

这也是:

{avatarurl {id {message}}}
^^^^^^^^^^^^^^^^^^^^^^^^^^

等等。

它只需要从 {(function name) 开始并以 } 结尾。

我的正则表达式选择器无法匹配这部分,它只是匹配额外的括号。我需要一个完美的正则表达式选择器,它将匹配 {blabla},但在放入另一个 {blabla} 时仍然可以工作。

这是没有奏效的模式:new RegExp(`\{${func.name}([^\}]+[^])*\}`)

func.name 是函数名称。 我还需要“{funcname”之后和“}”之前的部分。

JavaScript 正则表达式 匹配 选择器 括号

评论

1赞 Barmar 5/18/2023
正则表达式不擅长处理平衡括号。另辟蹊径。

答:

2赞 blakkwater 5/18/2023 #1

这是括号的通用非正则表达式解决方案(支持非平衡输入)。

然后,您只需遍历树并搜索匹配项即可。

编辑:转换为类

class BracketTree {

    constructor (brackets, string) {

        if (typeof brackets != 'string' || brackets.length != 2 || brackets[0] == brackets[1]) {
            return null;
        }

        let opening = brackets[0];
        let closing = brackets[1];

        function parse (start) {

            let children = [];
            let pos = start;

            loop: while (pos < string.length) {

                switch (string[pos]) {

                case opening:
                    let child = parse(pos + 1);
                    children.push(child);
                    if (child.end == string.length) {
                        break loop;
                    }
                    pos = child.end;
                    break;

                case closing:
                    if (start == 0) {
                        children = [{
                            children, start, end: pos, opened: false, closed: true,
                            contents: string.slice(0, pos)
                        }];
                    }
                    else {
                        return {
                            children, start, end: pos, opened: true, closed: true,
                            contents: string.slice(start, pos)
                        };
                    }
                }

                pos++;
            }

            return (start == 0)? {
                children, start, end: string.length, opened: false, closed: false,
                contents: string
            }: {
                children, start, end: string.length, opened: true, closed: false,
                contents: string.slice(start)
            };
        }

        this.root = parse(0);
    }

    traverse (callback) {

        if (typeof callback != 'function') {
            return false;
        }

        let root = this.root;
        let input = root.contents;
        let nodeId = 0;

        function recurse (parent, level) {

            function callbackLeaf (start, end) {
                callback({
                    root, parent, level,
                    nodeId: nodeId++, childId: childId++,
                    start, end, contents: input.slice(start, end)
                });
            }

            function callbackBranch (branch) {
                return callback({
                    root, parent, branch, level,
                    nodeId: nodeId++, childId: childId++
                });
            }

            let children = parent.children;
            let childId = 0;
            if (children.length == 0) {
                callbackLeaf(parent.start, parent.end);
                return;
            }

            callbackLeaf(parent.start, children[0].start - children[0].opened);
            if (callbackBranch(children[0])) {
                recurse(children[0], level+1);
            }

            for (var i = 0; i < children.length-1; i++) {
                callbackLeaf(children[i].end + children[i].closed, children[i+1].start - children[i+1].opened);
                if (callbackBranch(children[i+1])) {
                    recurse(children[i+1], level+1);
                }
            }

            callbackLeaf(children[i].end + children[i].closed, parent.end);
        }

        recurse(root, 0);
        return true;
    }
}

let input = 'NOT OPENED {3}2}1}***{avatarurl {id {message}}} blah blah blah {1{2{3} NOT CLOSED';
let tree = new BracketTree('{}', input);

function filteredTraverse (caption, leafFilter, branchFilter) {
    console.log(`${'-'.repeat(29 - caption.length/2)} ${caption} `.padEnd(60, '-'));
    leafFilter ??= () => true;
    branchFilter ??= () => true;
    tree.traverse((args) => {
        if (args.branch) {
            return branchFilter(args);
        }
        if (leafFilter(args)) {
            console.log(`${'  '.repeat(args.level)}<${args.contents}>`);
        }
    });
}

filteredTraverse(
    'Ignore unbalanced and all their descendants',
    null,
    ({branch}) => branch.opened && branch.closed
);

filteredTraverse(
    'Ignore unbalanced but include their descendants',
    ({parent}) => parent.opened == parent.closed
);

filteredTraverse(
    'Ignore empty',
    ({start, end}) => start != end
);

filteredTraverse(
    'Show non-empty first children only',
    ({childId, start, end}) => childId == 0 && start != end
);