PHP使用preg_split解析文本时如何保留最后一列消息完整内容
固定列宽结构化TXT的表格渲染实现方案
问题根因
原有逐行拆分逻辑存在两个本质缺陷,直接导致渲染错位:
- 无差别按空白字符分割所有内容,没有识别到前6列是独立固定字段、第7列Message是允许包含任意空格的长文本字段,把Message内部的正常空格也当成了字段分割符
- 未做行类型区分:固定排版下,仅当行开头对应ADDRESS字段的位置有有效非空值时才是新记录行;开头对应前6个字段位置全为空白的行,本质是上一条Message的跨行续行,应该归属于上一条记录的Message内容,而非独立记录
核心实现逻辑
针对固定宽度对齐的TXT排版特性,调整处理规则:
- 放弃全局空白拆分逻辑,改为按固定字符偏移位置截取前6个固定字段,截取完成后,该行从Message列起始位置到行尾的所有内容(包含所有内部空格)直接作为该行的Message片段,不做二次拆分
- 增加行类型判断:
- 若截取得到的ADDRESS字段为非空有效值,判定为新记录:先将上一条缓存的完整记录渲染为表格行,再初始化新的记录缓存
- 若截取得到的ADDRESS字段为空,判定为上一条记录的Message续行:直接将当前行的Message片段追加到上一条缓存的Message字段末尾,不生成独立表格行
- 循环读取文件结束后,统一输出最后一条缓存的未渲染记录,避免漏数据
注意:所有固定字段的截取偏移长度,需要根据自身实际TXT文件的排版宽度调整,可通过打印行字符串的字符位置校验匹配
适配后的可直接运行代码
// 初始化表格结构,带表头 echo "<table border='1' cellpadding='6' cellspacing='0'>"; echo "<thead><tr> <th>ADDRESS</th><th>TIME</th><th>DATE</th><th>MODE</th><th>TYPE</th><th>BITRATE</th><th>MESSAGE</th> </tr></thead><tbody>"; $file_handle = fopen("lib/today.txt", "r"); $current_record = null; // 缓存当前正在拼接的记录 // 列截取规则:根据实际文件排版调整每个字段的结束位置(字符索引,从0开始) // 参考示例排版的偏移配置: $col_end_pos = [ 'ADDRESS' => 6, // 第0-6位为ADDRESS字段 'TIME' => 15, // 第7-15位为TIME字段 'DATE' => 23, // 第16-23位为DATE字段 'MODE' => 29, // 第24-29位为MODE字段 'TYPE' => 38, // 第30-38位为TYPE字段 'BITRATE' => 45 // 第39-45位为BITRATE字段,第46位开始为MESSAGE区域 ]; while (!feof($file_handle)) { $line_of_text = rtrim(utf8_encode(fgets($file_handle)), "\r\n"); // 去掉行尾换行符,避免拼接时多余换行 // 跳过空行 if (empty(trim($line_of_text))) continue; // 按固定位置截取前6个字段 $address = trim(substr($line_of_text, 0, $col_end_pos['ADDRESS'])); $time = trim(substr($line_of_text, $col_end_pos['ADDRESS'], $col_end_pos['TIME'] - $col_end_pos['ADDRESS'])); $date = trim(substr($line_of_text, $col_end_pos['TIME'], $col_end_pos['DATE'] - $col_end_pos['TIME'])); $mode = trim(substr($line_of_text, $col_end_pos['DATE'], $col_end_pos['MODE'] - $col_end_pos['DATE'])); $type = trim(substr($line_of_text, $col_end_pos['MODE'], $col_end_pos['TYPE'] - $col_end_pos['MODE'])); $bitrate = trim(substr($line_of_text, $col_end_pos['TYPE'], $col_end_pos['BITRATE'] - $col_end_pos['TYPE'])); // 从BITRATE结束位置到行尾的所有内容,就是当前行的Message片段 $msg_part = substr($line_of_text, $col_end_pos['BITRATE']); // 跳过表头行 if (trim($address) === 'ADDRESS') continue; if (!empty($address)) { // 遇到新记录,先把上一条缓存的记录渲染出来 if ($current_record !== null) { echo "<tr> <td>{$current_record['address']}</td> <td>{$current_record['time']}</td> <td>{$current_record['date']}</td> <td>{$current_record['mode']}</td> <td>{$current_record['type']}</td> <td>{$current_record['bitrate']}</td> <td style='white-space: pre-wrap;'>{$current_record['message']}</td> </tr>"; } // 初始化新的记录缓存 $current_record = [ 'address' => $address, 'time' => $time, 'date' => $date, 'mode' => $mode, 'type' => $type, 'bitrate' => $bitrate, 'message' => trim($msg_part) ]; } else { // 是续行,直接把Message片段拼到上一条的Message后面 if ($current_record !== null) { $current_record['message'] .= "\n" . trim($msg_part); } } } // 循环结束后,输出最后一条未渲染的缓存记录 if ($current_record !== null) { echo "<tr> <td>{$current_record['address']}</td> <td>{$current_record['time']}</td> <td>{$current_record['date']}</td> <td>{$current_record['mode']}</td> <td>{$current_record['type']}</td> <td>{$current_record['bitrate']}</td> <td style='white-space: pre-wrap;'>{$current_record['message']}</td> </tr>"; } echo "</tbody></table>"; fclose($file_handle);
实现效果说明
- Message列保留了所有原始内容,包括内部空格、跨行的续行内容,通过CSS的
white-space: pre-wrap可以让单元格内的换行正确展示,不会出现内容被拆分的问题 - 开头为空白的续行不会再生成独立的错位表格行,所有续行内容都会归属到上一条对应记录的Message单元格中
内容的提问来源于stack exchange,提问作者relova
相关产品推荐
相关产品推荐

