【问题标题】:Regex to match top level delimiters in a multi dimensional string正则表达式匹配多维字符串中的顶级分隔符
【发布时间】:2012-07-13 03:21:43
【问题描述】:

我有一个大型多维结构的文件,类似于 json,但不够接近,无法使用 json 库。

数据看起来像这样:

alpha {
    beta {
        charlie;
    }
    delta;
}

echo;
foxtrot {
    golf;
    hotel;
}

我正在尝试构建的正则表达式(对于 preg_match_all)应该匹配每个顶级父级(由 {} 大括号分隔),以便我可以递归匹配,构建一个表示数据的多维 php 数组。

我尝试的第一个正则表达式是/(?<=\{).*(?=\})/s,它贪婪地匹配大括号内的内容,但这并不完全正确,因为当顶层有多个同级时,匹配过于贪婪。下面的例子:

使用正则表达式/(?<=\{).*(?=\})/s 匹配为:

第 1 场比赛:

    beta {
        charlie;
    }
    delta;
}

echo;
foxtrot {
    golf;
    hotel;

结果应该是: 第一场比赛:

    beta {
        charlie;
    }
    delta;

第 2 场比赛:

    golf;
    hotel;

那么正则表达式向导,我在这里缺少什么功能,或者我需要以某种方式用 php 解决这个问题?非常欢迎任何提示:)

【问题讨论】:

  • 这个字符串是从哪里来的?它的创作是由你控制的吗?如果是这样,你为什么不使用 json? IMO 这比使用正则表达式解析 HTML 更糟糕,如果值包含 {or},它必然会破坏发生的情况
  • @LawrenceCherone 是的,我有控制权,正如下面评论中所解释的,字符串是一种具有相当严格语法的模板标记语言。我只提到了 json,因为它具有类似的带有大括号的层次嵌套样式,但是它的功能非常不同。如果一个值包含一个杂散的大括号,它将中断,但这是设计使然。语言语法中不允许使用大括号,除非它位于 ("") 块中。

标签: php regex pcre multidimensional-array


【解决方案1】:

你不能1用正则表达式来做到这一点。

或者,如果要匹配从深到浅的块,可以使用\{[^\{\}]*?\}preg_replace_callback() 来存储值,并返回null 以将其从字符串中删除。回调需要相应地处理嵌套值。

$heirarchalStorage = ...;
do {
    $string = \preg_replace_callback('#\{[^\{\}]*?\}#', function($block)
    use(&$heirarchalStorage) {
        // do your magic with $heirarchalStorage
        // in here
        return null;
    }, $string);
} while (!empty($string));

不完整、未经测试且无保修。

这种方法要求字符串也包含在{} 中,否则最终匹配不会发生,您将永远循环。

这是一项糟糕低效)工作,而这些工作可以通过 JSON 等众所周知的交换/存储格式轻松解决。 p>

1我本来打算说“你可以,但是...”,但我还是再说一遍,“ 你不能" 2

2不要

【讨论】:

  • 谢谢,preg_replace 的从深到浅的方法很有帮助。我已经在脚本的其他地方使用了几乎相同的代码,但用于不同的任务。我意识到解析这些数据的效率低下,但在我的示例中,我省略了语言中存在的大量额外代码。这是我正在制作的“编译”标记语言,而不是数据传输语言,因此我可以避免解析效率低下。
【解决方案2】:

当然你可以用正则表达式来做到这一点。

preg_match_all(
    '/([^\s]+)\s*{((?:[^{}]*|(?R))*)}/',
    $yourStuff,
    $matches,
    PREG_SET_ORDER
);

这在比赛中给了我以下信息:

[1]=>
string(5) "alpha"
[2]=>
string(46) "
beta {
    charlie;
}
delta;
"

[1]=>
string(7) "foxtrot"
[2]=>
string(22) "
golf;
hotel;
"

稍微分解一下。

([^\s]+)                # non-whitespace (block name)
\s*                     # whitespace (between name and block)
{                       # literal brace
    (                   # begin capture
        (?:             # don't create another capture set
            [^{}]*      # everything not a brace
            |(?R)       # OR recurse
        )*              # none or more times
    )                   # end capture
}                       # literal brace

仅供参考,这适用于 n-deep 级别的大括号​​。

【讨论】:

  • 谢谢,您(或其他人)能否评论一下这种技术的效率/可读性/最佳实践与@Bracketworks 给出的递归PHP 风格?
  • 好吧,我想从深到浅的方法会增加复杂性,因为您必须向后构建数组,您不知道从多深开始,并且您不知道您正在处理的块的“名称”。不确定您需要做多少额外的数组摆弄才能满足这一点。此外,当您进入一个新分支时(即您刚刚完成 alpha,然后突然间您有了高尔夫+酒店),您怎么知道您在一个新分支中?没有给出深度信息。他的替换工作方式也会在$block 中为您提供奇怪的值,需要更多处理...
【解决方案3】:

我想你可能会通过匹配[a-zA-Z0-9][:blank]+{} 来使用preg_split 得到一些东西。您将能够通过查看结果来构建您的数组。使用递归函数,当你匹配一个开始标签时,它会更深入,并且在结束标签上。

否则,最干净的解决方案是实现 ANTLR 语法!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-29
    • 1970-01-01
    相关资源
    最近更新 更多