将一大 html table 拆分为 5 行的多个 table 的正则表达式

Regular expression to split one big html table to several tables of 5 rows

我正在尝试使用正则表达式来解决问题,所以我遇到了一个问题: 问题是我有一些随机 HTML 纯文本文件,只有一个 table。文本可以在table前后,table不包含<thead><tbody><tfoot> rowspan等。所以我需要将这个 table 分成几个 tables,每行 5 行,最后一行不超过 5 行,并在每个 table 中重复原始 table 的第一个字符串.例如:

<table>
  <tr>
   <td>A</td><td>B</td>
  </tr>
  <tr>
   <td>A1</td><td>B1</td>
  </tr>
  <tr>
   <td>C</td><td>D</td>
  </tr>
  <tr>
   <td>E</td><td>F</td>
  </tr>
  <tr>
   <td>E1</td><td>F1</td>
  </tr>
  <tr>
   <td>E2</td><td>F2</td>
  </tr>
  <tr>
   <td>E3</td><td>F3</td>
  </tr>
  <tr>
   <td>E4</td><td>F4</td>
  </tr>
</table>

应该变成:

<table>
  <tr>
   <td>A</td><td>B</td><--!!!(not needed to be in code)-->
  </tr>
  <tr>
   <td>A1</td><td>B1</td>
  </tr>
  <tr>
   <td>C</td><td>D</td>
  </tr>
  <tr>
   <td>E</td><td>F</td>
  </tr>
  <tr>
   <td>E1</td><td>F1</td>
  </tr>
</table>
<table>
  <tr>
   <td>A</td><td>B</td><--!!!(not needed to be in code)-->
  </tr>
  <tr>
   <td>E2</td><td>F2</td>
  </tr>
  <tr>
   <td>E3</td><td>F3</td>
  </tr>
  <tr>
   <td>E4</td><td>F4</td>
  </tr>
</table>

我需要在 PHP 中使用 PCRE 完成这些工作,包括大量模板和更改。所以我在实现上有问题。现在我可以找到这样的第一行 <table>\s*?(<tr>(?:\s|.)*?<\/tr>) 和 4 一行一行 (<tr>(?:\s|.)*?<\/tr>\s*){1,4} 但我不知道我应该如何找到第二个模板的所有出现以便我以后可以使用它们如果有 </table> table 结束标记,如何停止搜索。所以请帮忙

编辑

问题已得到解答,因此下一级将添加原始 table 标签 <thead><tbody><tfoot>。在输出 tables 中,原始 table 的结构应该被重建,所以我的意思是如果原始 table 的第一行是 <thead> 标签的一部分,它应该在 <thead>全部输出tables.

您可以通过执行循环来实现此目的,其中每次迭代都会添加下一个 "table break" 和 preg_replace(但请参阅末尾的免责声明)。建议的正则表达式将找到以下组:

  • 最后一次出现的 <table> 标签及其后的第一行,或者,如果有 thead and/or tbody 标签,直到结束 </thead> 标签,包括开始 <tbody> 标签(如果有的话)。
  • 接下来的 4 行。必须有4个。

然后它还会向前看以确保至少还有一行。

利用该信息,可以将单个 "table break" 注入到 HTML 字符串中。

如果 table 有一个 tfooter 部分(然后也应该在 table 的每个分区中重复),我们还没有那个信息,因为它发生了在输入的最后。因此,在循环开始之前,需要进行单独的解析以提取页脚。

这是假设输入在变量中的代码 $html:

// Extract the footer part (if there is one) and closing table tag
preg_match("#(\s*(</tbody|<tfooter).*?)?</table>#s", $html, $tableEnd);
$tableEnd = $tableEnd[0];

// Add a table break in each iteration as long as the last partition has more than 4 rows:
while (true) {
    $res = preg_replace("#(<table(?!.*<table).*?/tr>(?:.*?/thead>)?(?:.*?<tbody>)?)((?:.*?/tr>(?=\s*<tr)){4})#s", 
                        "$tableEnd\n", $html);
    if (strlen($res) === strlen($html)) break;
    $html = $res;
}

echo $res;

eval.in 上查看 运行。

主要正则表达式的解释

以下是主要正则表达式中的一些要点:

  • #:我使用它作为正则表达式分隔符而不是 / 以避免需要在正则表达式本身内部转义 /。如果您需要使用 / 作为分隔符,则将每个 / 转义为 \/:一个反斜杠用于正则表达式,另一个反斜杠用于在字符串文字的上下文中转义该反斜杠。

  • (?!.*<table):确保在我们要匹配的标签后面没有其他 <table> 标签。这是一个消极的展望。

  • ((?:.*?/tr>(?=\s*<tr)){4}):抓取 4 行,正向展望 ((?= )) 要求每一行紧接着另一行。 (?: ) 模式不会创建捕获组,但外括号会创建一个。

替换

如果替换只是再次注入 2 个捕获的组(即 </code>),那么什么都不会改变。额外的 <code>$tableEnd\n 将关闭 table (带有页脚)并通过重用第一个捕获组开始下一个。这将包含第一行 and/or table header 的起始 <table> 标签。

免责声明

虽然上面的方法在很多情况下都可行,但很可能会破坏它,因为正则表达式不是 parse/interpret HTML 的理想方式。你真的应该为此使用 DOM api,PHP 有一个:DOMDocument.