preg_replace 多行匹配但保留新行

preg_replace multiline match but preserve new lines

我需要一个从 HTML 文件中修剪 PHP 的衬纸。诀窍是我还需要它来保留之前由 PHP 行占用的换行符。

php -r "echo preg_replace('/<\\?.*(\\?>|$)/Us','', file_get_contents($argv[1]));" -- "./index.php"

这个"works"但是不保留新行,例如:

<html><?php test(); ?>
  <head>
    <?php test();

    ?>
  </head>
  <body>
  </body>
<html>

解析为:

<html>
  <head>

  </head>
  <body>
  </body>
<html>

但我需要它来解决:

<html>
  <head>



  </head>
  <body>
  </body>
<html>

也许我正在使用锤子来拧螺丝,但我想做的是删除 PHP 代码,运行 通过 htmlhint 得到结果,并使报告的行号与文件中的行。

如果有更好的解决方案,我很想听听。最终目标是用它们各自的 linters 对混合了 PHP、Javascript 和 HTML 的文件进行 lint。

简介

正则表达式绝对不是这个问题的最佳答案,但由于您正在寻找正则表达式形式的答案,这里有!

注意:如果注释或字符串包含 <?.

,这将中断

代码

See this regex in use here

(?:\G(?!\A)|\h*(?=<\?))(.*(?=(?:(?!<\?)[\s\S])*?(?<=\?>)))

结果

输入

<html><?php test(); ?>
  <head>
    <?php test();

    ?>
  </head>
  <body>
  </body>
<html>

输出

<html>
  <head>



  </head>
  <body>
  </body>
<html>

说明

  • (?:\G(?!\A)|\h*(?=<\?)) 匹配以下任一选项
    • \G(?!\A)
      • \G 断言位置在上一个匹配的末尾或第一个匹配的字符串的开头
      • (?!\A) 否定前瞻断言后面的内容不是字符串的开头(这基本上使 \G 仅匹配上一个匹配项的结尾)
    • \h*(?=<\?)匹配以下
      • \h*匹配任意数量的水平空格(用于清理<?
      • 之前的空格
      • (?=<\?) 正面前瞻确保以下匹配
        • <按字面意思匹配小于号<
        • \?按字面意思匹配问号字符?
  • (.*(?=(?:(?!<\?)[\s\S])*?(?<=\?>))) 将以下内容捕获到捕获组 1
    • .* 匹配任意字符(行终止符除外)任意次数
    • (?=(?:(?!<\?)[\s\S])*?(?<=\?>)) 正向前瞻确保后续匹配
      • (?:(?!<\?)[\s\S])*? 匹配以下任意次数,但越少越好
        • (?!<\?) 负前瞻确保后面的内容不匹配
          • <按字面意思匹配小于号<
          • \?按字面意思匹配问号字符?
        • [\s\S]匹配任意字符
      • (?<=\?>) 负向回顾确保前面的内容与以下内容匹配
        • \?按字面意思匹配问号字符?
        • >按字面意思匹配大于号>

Ok 一行使用分词器(里面丑陋的东西):

php -r 'echo array_reduce(token_get_all(file_get_contents($argv[1])),function($c,$i){return $i[0]==321?$c.$i[1]:$c.str_repeat("\n",@count_chars($i.$i[1])[10]);});'

demo

tokenizer 的优势:即使像 "abc <?php echo '?>'; ?> def" 这样的字符串也能被正确解析。

321 是常量 T_INLINE_HTML 的值(所有不在 php 标记之间的值)。

10 是换行符 (LF) 的 ASCII 码。 (默认情况下,count_chars returns 以ASCII码为键,出现次数为值的关联数组).

丑陋的是 $i.$i[1] 将数组与字符串或字符串与未定义的内容连接起来。 @ 避免警告和通知。不管怎样,这个技巧避免了测试并且换行符的数量被保留了下来。 (看什么returnstoken_get_all就明白问题了)


DOMDocument:

php -r '$d=DOMDocument::loadHTMLFile($argv[1],8196);foreach((new DOMXPath($d))->query("//processing-instruction()")as$p)$p->parentNode->replaceChild($d->createTextNode(preg_replace("~\S+~","",$p->nodeValue)),$p);echo$d->saveHTML();'