首页 > 解决方案 > PHP regexp 解析避免子字符串

问题描述

我正在编写一个简单的 Markdown 解析器来为也使用一些 LaTeX 方程的页面输出 HTML。例如,对于斜体:

  // italic
  $content = preg_replace_callback(
    '/(\*|_)(.+)\1/',
    function ($m) {
      return "<i>" . $m[2] . "</i>";
    },
    $content
  );

不幸的是,很多 Markdown 格式与 LaTeX 符号(以及代码块)发生冲突,所以我需要先转义 LaTeX 部分,并仅在这些部分之外解析 Markdown。LaTeX 位由$and分隔$$,因此很容易发现它们:

preg_match("/\$+(.*?)\$+/", $content)

例如,这是一个此类页面的示例:


## Section title

Lorem ipsum *dolores* sic amet. $E = mc^2$, and since :

$$
\cos(3*\pi*\sqrt{2}) = \delta
$$

所以……斜体和乘法之间的冲突。

我的第一个猜测是我应该将内容分成 2 个数组:一个包含 LaTeX 位及其索引,一个包含位于 LaTeX 位之间的非 LaTeX 位,处理第二个数组,然后将它们合并在一起。

preg_split()中断所述模式并返回中间子字符串,但丢弃与模式匹配的子字符串。似乎可以用一个PREG_SPLIT_DELIM_CAPTURE标志来调整它以返回所有子字符串,包括与正则表达式匹配的断点,但是当使用这个标志时,文档没有显示输出数据结构,所以我不知道如何迭代输出数组,并且仅适用于与模式不匹配的部分。

此函数输出什么和/或是否有更好/更快的方法在与其他模式匹配的区域之外执行模式检测?

标签: phpregex

解决方案


一种选择可能是$使用 SKIP FAIL 使仅在同一行上开始和结束的部分不属于匹配项。

然后在捕获组中捕获*_捕获,并使用反向引用\1来匹配相同的字符,而不匹配其间的相同字符。

^\$+(?:\R(?!\$+$).*)*\R\$+$(*SKIP)(*FAIL)|([*_])((?:(?!\1).)+)\1

模式匹配:

  • ^字符串的开始
  • \$+匹配 1+ 次$
  • (?:\R(?!\$+$).*)*匹配所有不只有的行$
  • \R\$+$只匹配一行$
  • (*SKIP)(*FAIL)|跳过当前匹配的内容
  • ([*_])第 1 组中捕获*或捕获_
  • ((?:(?!\1).)+)重复匹配除捕获的字符之外的所有字符
  • \1对第 1 组的反向引用,匹配与捕获的相同字符

正则表达式演示| php演示

例子

$content= <<<'DATA'
## Section title

Lorem ipsum *dolores* sic amet. $E = mc^2$, and since :

$$
\cos(3*\pi*\sqrt{2}) = \delta
$$
DATA;

$content = preg_replace_callback(
    '/^\$+(?:\R(?!\$+$).*)*\R\$+$(*SKIP)(*FAIL)|([*_])((?:(?!\1).)+)\1/m',
    function ($m) {
        return "<i>" . $m[2] . "</i>";
    },
    $content
);

echo $content;

输出

## Section title

Lorem ipsum <i>dolores</i> sic amet. $E = mc^2$, and since :

$$
\cos(3*\pi*\sqrt{2}) = \delta
$$

我必须注意,使用正则表达式获取标记可能很脆弱并且有边缘情况。

您可以通过例如断言空白边界来使模式更加具体。

^\$+(?:\R(?!\$+$).*)*\R\$+$(*SKIP)(*FAIL)|(?<!\S)([*_])((?:(?!\1).)+)\1(?!\S)

正则表达式演示


推荐阅读