测试问题修改

This commit is contained in:
chengxl
2025-12-01 11:53:41 +08:00
parent c15b784cf8
commit 93f9e705cb

View File

@@ -14,7 +14,7 @@ class ArticleParserService
{
private $phpWord;
private $sections;
private $iNum = 0;
public function __construct($filePath = '')
{
if (!file_exists($filePath)) {
@@ -736,6 +736,10 @@ class ArticleParserService
// 统一提取元素文本
private function getTextFromElement($element,$lineNumber = 0){
$text = '';
if ($element instanceof \PhpOffice\PhpWord\Element\ListItemRun) {
$this->iNum++;
$text .= $this->iNum;
}
// 处理PreserveText元素
if ($element instanceof \PhpOffice\PhpWord\Element\PreserveText) {
// 通过反射获取私有属性 text
@@ -940,106 +944,329 @@ class ArticleParserService
]
];
}
private function fullDecode($str, $maxDepth = 5) {
/**
* 核心解码方法(无静态缓存,高性能版)
* @param string $str 待解码字符串
* @param int $maxDepth 最大解析深度
* @return string
*/
private function fullDecode($str, $maxDepth = 2)
{
// 空值/深度为0直接返回提前终止避免无效操作
if (empty($str) || $maxDepth <= 0) {
return $str;
}
// 【性能优化1预编译所有正则表达式】避免每次循环重新解析正则
// 预编译:≥专属场景正则
$regOb0 = '/0B\s*\?0/';
$regDl18 = '/DL\s*\?.18/';
// 预编译:≥通用场景正则
$regQMarkNum = '/\?(\d+)/';
$regQMarkDotNum = '/\?(\.\d+)/';
// 预编译:≤、≠空格修复正则
$regNeNum = '/≠\s*(\d+)/';
$regLeNum = '/\s*(\d+)/';
// 预编译:混合符号乱码正则(中文顿号/英文逗号)
$regMixCn = '/(\?)\s*、\s*(\?)\s*、\s*(\?)(\d+)/';
$regMixEn = '/(\?)\s*,\s*(\?)\s*,\s*(\?)(\d+)/';
// 预编译:≤、≠专属标识正则
$regLeMark = '/LE\s*\?(\d+)/';
$regNeMark = '/NE\s*\?(\d+)/';
// 预编译Unicode转义正则提取到外部避免闭包重复创建
$regUnicode = '/\\\\u([0-9a-fA-F]{4})/';
// 【性能优化2预定义常量/映射】避免循环内重复创建数组/字符串
// HTML实体映射一次性定义避免循环内重复赋值
$htmlEntityMap = [
'&le;' => '≤', '&#8804;' => '≤', '&#x2264;' => '≤',
'&ge;' => '≥', '&#8805;' => '≥', '&#x2265;' => '≥',
'&ne;' => '≠', '&#8800;' => '≠', '&#x2260;' => '≠',
$str = $this->decodeUnicode($str);
// ========== 预编译所有正则(合并同类型,避免循环内重复解析) ==========
$regexps = [
// 原有专属场景正则
'ob0' => '/0B\s*\?0/',
'dl18' => '/DL\s*\?.18/',
// 原有通用场景正则
'qMarkNum' => '/\?(\d+)/',
'qMarkDotNum' => '/\?(\.\d+)/',
// ≤、≠空格修复正则
'neNum' => '/\s*(\d+)/',
'leNum' => '/≤\s*(\d+)/',
// 混合符号乱码正则(合并中英文顿号/逗号)
'mixSymbol' => '/(\?)\s*(、|,)\s*(\?)\s*(、|,)\s*(\?)(\d+)/',
// ≤、≠专属标识正则合并LE/NE
'leNeMark' => '/(LE|NE)\s*\?(\d+)/',
// Unicode转义正则
'unicode' => '/\\\\u([0-9a-fA-F]{4})/',
// Word二进制乱码合并≤≥≠
'wordBin' => '/(\\xE2\\x89\\x86|\\xE2 0x89 0x86|e28986|\\xE2\\x89\\x87|\\xE2 0x89 0x87|e28987|\\xE2\\x89\\x80|\\xE2 0x89 0x80|e28980)/i',
// Word XML实体异常合并≤≥≠
'wordEntity' => '/&#\s*(\x|X)?\s*(2264|2265|2260)\s*;?/i',
// 不可见控制字符
'controlChar' => '/[\x00-\x1F\x7F]/',
// 重复符号去重(合并≤≥≠)
'repeatSymbol' => '/(≤{2,}|≥{2,}|≠{2,})/',
// GBK编码乱码合并≤≥≠
'gbkSymbol' => '/(\xA1\xF2|\xA1\xF3|\xA1\xF0)/'
];
// 不间断空格替换数组
$nbspReplace = [chr(0xC2) . chr(0xA0), chr(0xA0)];
// Unicode回调函数预定义避免循环内重复创建闭包
// ========== 预定义所有替换映射(避免循环内重复创建) ==========
$maps = [
// HTML实体映射扩展Word实体
'htmlEntity' => [
'&le;' => '≤', '&#8804;' => '≤', '&#x2264;' => '≤',
'&ge;' => '≥', '&#8805;' => '≥', '&#x2265;' => '≥',
'&ne;' => '≠', '&#8800;' => '≠', '&#x2260;' => '≠',
'&le' => '≤', '&ge' => '≥', '&ne' => '≠',
'&#2264' => '≤', '&#2265' => '≥', '&#2260' => '≠',
'&#x2264' => '≤', '&#x2265' => '≥', '&#x2260' => '≠',
'&#X2264' => '≤', '&#X2265' => '≥', '&#X2260' => '≠',
'&#60;' => '≤', '&#62;' => '≥'
],
// 空格替换数组扩展Word中的各种空格
'nbsp' => [
chr(0xC2) . chr(0xA0), // UTF-8不间断空格
chr(0xA0), // 拉丁1不间断空格
' ', // 全角空格
chr(0x2002), // 方头空格
chr(0x2003), // 全角空格
chr(0x2004) // 三分之一全角空格
],
// 二进制乱码映射
'wordBin' => [
'e28986' => '≤', '\\xe2\\x89\\x86' => '≤', '\\xe2 0x89 0x86' => '≤',
'e28987' => '≥', '\\xe2\\x89\\x87' => '≥', '\\xe2 0x89 0x87' => '≥',
'e28980' => '≠', '\\xe2\\x89\\x80' => '≠', '\\xe2 0x89 0x80' => '≠'
],
// XML实体编码映射
'wordEntity' => [
'2264' => '≤',
'2265' => '≥',
'2260' => '≠'
],
// GBK编码映射
'gbkSymbol' => [
'\xA1\xF2' => '≤',
'\xA1\xF3' => '≥',
'\xA1\xF0' => '≠'
]
];
// 预定义回调函数(仅创建一次,避免循环内重复实例化)
$unicodeCallback = function ($m) {
return mb_chr(hexdec($m[1]), 'UTF-8') ?: $m[0];
};
$original = $str;
$depth = 0;
$hasChange = false; // 标记是否有变化,提前终止循环
$hasChange = false;
$original = $str;
// 循环解码:仅在有变化且未达最大深度时执行
do {
$depth++;
$hasChange = false;
$prevStr = $str; // 保存当前状态,用于判断变化
$prevStr = $str;
// 1. 解码Unicode转义\uXXXX格式
$str = $this->decodeUnicode($str);
// ========== 前置处理(惰性执行,避免无意义操作) ==========
$countCtrl = 0;
// 1. 过滤不可见控制字符(仅当包含时执行)
if (preg_match($regexps['controlChar'], $str)) {
$str = preg_replace($regexps['controlChar'], '', $str, -1, $countCtrl);
}
// 2. 解码HTML实体先替换专属实体再执行通用解码
$str = strtr($str, $htmlEntityMap); // 高性能替换strtr比str_replace快
$str = html_entity_decode($str, ENT_QUOTES | ENT_HTML5, 'UTF-8');
// 2. GBK/GB2312编码转UTF-8仅当非UTF-8时执行
if (!mb_check_encoding($str, 'UTF-8')) {
$str = mb_convert_encoding($str, 'UTF-8', 'GBK,GB2312,ISO-8859-1');
}
// 3. 再次处理遗漏的Unicode转义使用预编译正则+预定义回调)
$str = preg_replace_callback($regUnicode, $unicodeCallback, $str);
// ========== 核心解码逻辑 ==========
// 1. 解码Unicode转义
$str = preg_replace_callback($regexps['unicode'], $unicodeCallback, $str);
// 4. 替换不间断空格为普通空格strtr比str_replace更高效
$str = str_replace($nbspReplace, ' ', $str);
// 2. 解码HTML实体高性能strtr替换
$str = strtr($str, $maps['htmlEntity']);
$str = html_entity_decode($str, ENT_QUOTES | ENT_HTML5 | ENT_SUBSTITUTE, 'UTF-8');
// 5. 核心替换逻辑(优化执行顺序,避免覆盖)
// 5.1 原有≥专属场景(保留)
$str = preg_replace($regOb0, '0B≥30', $str, -1, $count1);
$str = preg_replace($regDl18, 'DL≥0.18', $str, -1, $count2);
// 5.2 ≤、≠空格修复(保留)
$str = preg_replace($regNeNum, '≠$1', $str, -1, $count3);
$str = preg_replace($regLeNum, '≤$1', $str, -1, $count4);
// 5.3 原有≥通用场景(保留)
$str = preg_replace($regQMarkNum, '≥$1', $str, -1, $count5);
$str = preg_replace($regQMarkDotNum, '≥0$1', $str, -1, $count6);
// 5.4 混合符号乱码还原(保留)
$str = preg_replace($regMixCn, '≤、≥、≠$4', $str, -1, $count7);
$str = preg_replace($regMixEn, '≤、≥、≠$4', $str, -1, $count8);
// 5.5 ≤、≠专属标识还原(保留)
$str = preg_replace($regLeMark, '≤$1', $str, -1, $count9);
$str = preg_replace($regNeMark, '≠$1', $str, -1, $count10);
// 3. 替换各种空格为普通空格
$str = str_replace($maps['nbsp'], ' ', $str);
// 5.6 修复前缀"d with "乱码(保留)
$str = str_replace('d with ', 'd with ', $str, $count11);
// ========== Word特殊符号乱码修复合并+惰性) ==========
$countBin = $countEnt = $countGbk = $countRepeat = 0;
// 1. 二进制乱码还原(合并正则+回调)
if (preg_match($regexps['wordBin'], $str)) {
$str = preg_replace_callback($regexps['wordBin'], function ($m) use ($maps) {
$key = strtolower(str_replace(' ', '', $m[0]));
return $maps['wordBin'][$key] ?? $m[0];
}, $str, -1, $countBin);
}
// 2. XML实体异常修复合并正则+回调)
if (preg_match($regexps['wordEntity'], $str)) {
$str = preg_replace_callback($regexps['wordEntity'], function ($m) use ($maps) {
return $maps['wordEntity'][$m[2]] ?? $m[0];
}, $str, -1, $countEnt);
}
// 3. GBK编码乱码修复合并正则+回调)
if (preg_match($regexps['gbkSymbol'], $str)) {
$str = preg_replace_callback($regexps['gbkSymbol'], function ($m) use ($maps) {
return $maps['gbkSymbol'][$m[0]] ?? $m[0];
}, $str, -1, $countGbk);
}
// 4. 重复符号去重(合并正则+极简回调)
if (preg_match($regexps['repeatSymbol'], $str)) {
$str = preg_replace_callback($regexps['repeatSymbol'], function ($m) {
return $m[0][0]; // 取第一个字符实现去重
}, $str, -1, $countRepeat);
}
// ========== 原有核心替换逻辑(合并+惰性) ==========
$count1 = $count2 = $count3 = $count4 = $count5 = $count6 = 0;
$count7 = $count8 = $count9 = 0;
// 1. 专属场景替换(惰性执行)
if (strpos($str, '0B?0') !== false) {
$str = preg_replace($regexps['ob0'], '0B≥30', $str, -1, $count1);
}
if (strpos($str, 'DL?.18') !== false) {
$str = preg_replace($regexps['dl18'], 'DL≥0.18', $str, -1, $count2);
}
// 2. ≤、≠空格修复(惰性执行)
if (preg_match($regexps['neNum'], $str)) {
$str = preg_replace($regexps['neNum'], '≠$1', $str, -1, $count3);
}
if (preg_match($regexps['leNum'], $str)) {
$str = preg_replace($regexps['leNum'], '≤$1', $str, -1, $count4);
}
// 3. 通用场景替换(惰性执行)
if (preg_match($regexps['qMarkNum'], $str)) {
$str = preg_replace($regexps['qMarkNum'], '≥$1', $str, -1, $count5);
}
if (preg_match($regexps['qMarkDotNum'], $str)) {
$str = preg_replace($regexps['qMarkDotNum'], '≥0$1', $str, -1, $count6);
}
// 4. 混合符号乱码还原(合并中英文,惰性执行)
if (preg_match($regexps['mixSymbol'], $str)) {
$str = preg_replace($regexps['mixSymbol'], '≤$2≥$4≠$6', $str, -1, $count7);
}
// 5. ≤、≠专属标识还原(合并正则,惰性执行)
if (preg_match($regexps['leNeMark'], $str)) {
$str = preg_replace_callback($regexps['leNeMark'], function ($m) {
return $m[1] === 'LE' ? '≤' . $m[2] : '≠' . $m[2];
}, $str, -1, $count8);
}
// 6. 修复前缀"d with "乱码(惰性执行)
if (strpos($str, 'd with ') !== false) {
$str = str_replace('d with ', 'd with ', $str, $count9);
}
// ========== 变化判断(合并计数,减少运算) ==========
$totalCount = $countCtrl + $countBin + $countEnt + $countGbk + $countRepeat +
$count1 + $count2 + $count3 + $count4 + $count5 + $count6 +
$count7 + $count8 + $count9;
// 【性能优化3统计所有替换次数判断是否有变化】
$totalCount = $count1 + $count2 + $count3 + $count4 + $count5 + $count6 +
$count7 + $count8 + $count9 + $count10 + $count11;
if ($totalCount > 0 || $str !== $prevStr) {
$hasChange = true;
$original = $str;
}
// 【性能优化4提前终止】单次循环无变化直接退出
// 提前终止:无变化则退出循环
if (!$hasChange) {
break;
}
} while ($depth < $maxDepth); // 改用do-while减少循环判断次数
} while ($depth < $maxDepth);
// 最终清理仅执行一次trim
return trim($str, ':');
// 最终清理+兜底替换
$str = trim($str, ':');
$str = strtr($str, $maps['htmlEntity']);
return $str;
}
// private function fullDecode($str, $maxDepth = 5) {
// // 空值/深度为0直接返回提前终止避免无效操作
// if (empty($str) || $maxDepth <= 0) {
// return $str;
// }
// // 【性能优化1预编译所有正则表达式】避免每次循环重新解析正则
// // 预编译:≥专属场景正则
// $regOb0 = '/0B\s*\?0/';
// $regDl18 = '/DL\s*\?.18/';
// // 预编译:≥通用场景正则
// $regQMarkNum = '/\?(\d+)/';
// $regQMarkDotNum = '/\?(\.\d+)/';
// // 预编译:≤、≠空格修复正则
// $regNeNum = '/≠\s*(\d+)/';
// $regLeNum = '/≤\s*(\d+)/';
// // 预编译:混合符号乱码正则(中文顿号/英文逗号)
// $regMixCn = '/(\?)\s*、\s*(\?)\s*、\s*(\?)(\d+)/';
// $regMixEn = '/(\?)\s*,\s*(\?)\s*,\s*(\?)(\d+)/';
// // 预编译:≤、≠专属标识正则
// $regLeMark = '/LE\s*\?(\d+)/';
// $regNeMark = '/NE\s*\?(\d+)/';
// // 预编译Unicode转义正则提取到外部避免闭包重复创建
// $regUnicode = '/\\\\u([0-9a-fA-F]{4})/';
// // 【性能优化2预定义常量/映射】避免循环内重复创建数组/字符串
// // HTML实体映射一次性定义避免循环内重复赋值
// $htmlEntityMap = [
// '&le;' => '≤', '&#8804;' => '≤', '&#x2264;' => '≤',
// '&ge;' => '≥', '&#8805;' => '≥', '&#x2265;' => '≥',
// '&ne;' => '≠', '&#8800;' => '≠', '&#x2260;' => '≠',
// ];
// // 不间断空格替换数组
// $nbspReplace = [chr(0xC2) . chr(0xA0), chr(0xA0)];
// // Unicode回调函数预定义避免循环内重复创建闭包
// $unicodeCallback = function ($m) {
// return mb_chr(hexdec($m[1]), 'UTF-8') ?: $m[0];
// };
// $original = $str;
// $depth = 0;
// $hasChange = false; // 标记是否有变化,提前终止循环
// // 循环解码:仅在有变化且未达最大深度时执行
// do {
// $depth++;
// $hasChange = false;
// $prevStr = $str; // 保存当前状态,用于判断变化
// // 1. 解码Unicode转义\uXXXX格式
// $str = $this->decodeUnicode($str);
// // 2. 解码HTML实体先替换专属实体再执行通用解码
// $str = strtr($str, $htmlEntityMap); // 高性能替换strtr比str_replace快
// $str = html_entity_decode($str, ENT_QUOTES | ENT_HTML5, 'UTF-8');
// // 3. 再次处理遗漏的Unicode转义使用预编译正则+预定义回调)
// $str = preg_replace_callback($regUnicode, $unicodeCallback, $str);
// // 4. 替换不间断空格为普通空格strtr比str_replace更高效
// $str = str_replace($nbspReplace, ' ', $str);
// // 5. 核心替换逻辑(优化执行顺序,避免覆盖)
// // 5.1 原有≥专属场景(保留)
// $str = preg_replace($regOb0, '0B≥30', $str, -1, $count1);
// $str = preg_replace($regDl18, 'DL≥0.18', $str, -1, $count2);
// // 5.2 ≤、≠空格修复(保留)
// $str = preg_replace($regNeNum, '≠$1', $str, -1, $count3);
// $str = preg_replace($regLeNum, '≤$1', $str, -1, $count4);
// // 5.3 原有≥通用场景(保留)
// $str = preg_replace($regQMarkNum, '≥$1', $str, -1, $count5);
// $str = preg_replace($regQMarkDotNum, '≥0$1', $str, -1, $count6);
// // 5.4 混合符号乱码还原(保留)
// $str = preg_replace($regMixCn, '≤、≥、≠$4', $str, -1, $count7);
// $str = preg_replace($regMixEn, '≤、≥、≠$4', $str, -1, $count8);
// // 5.5 ≤、≠专属标识还原(保留)
// $str = preg_replace($regLeMark, '≤$1', $str, -1, $count9);
// $str = preg_replace($regNeMark, '≠$1', $str, -1, $count10);
// // 5.6 修复前缀"d with "乱码(保留)
// $str = str_replace('d with ', 'd with ', $str, $count11);
// // 【性能优化3统计所有替换次数判断是否有变化】
// $totalCount = $count1 + $count2 + $count3 + $count4 + $count5 + $count6 +
// $count7 + $count8 + $count9 + $count10 + $count11;
// if ($totalCount > 0 || $str !== $prevStr) {
// $hasChange = true;
// $original = $str;
// }
// // 【性能优化4提前终止】单次循环无变化直接退出
// if (!$hasChange) {
// break;
// }
// } while ($depth < $maxDepth); // 改用do-while减少循环判断次数
// // 最终清理仅执行一次trim
// return trim($str, ':');
// }
// private function fullDecode($str, $maxDepth = 5) {
// if (empty($str) || $maxDepth <= 0) {
// return $str;