测试问题修改

This commit is contained in:
chengxl
2025-12-01 09:16:50 +08:00
parent ab0ee4d6b4
commit 5daf18608b

View File

@@ -225,6 +225,10 @@ class ArticleParserService
$aParam['corresponding'] = $oDealFile->getCorrespondingAuthors($aParam); $aParam['corresponding'] = $oDealFile->getCorrespondingAuthors($aParam);
//keywords 和 摘要 //keywords 和 摘要
$aContent = $oDealFile->extractFromWord(); $aContent = $oDealFile->extractFromWord();
if(!mb_check_encoding($sTitle, 'UTF-8')){
$sTitle = mb_convert_encoding($sTitle, 'UTF-8', 'GBK');
}
$aParam['title'] = $oDealFile->fullDecode($aParam['title']);
$aParam += empty($aContent['data']) ? [] : $aContent['data']; $aParam += empty($aContent['data']) ? [] : $aContent['data'];
return json_encode(['status' => 1,'msg' => 'success','data' => $aParam]); return json_encode(['status' => 1,'msg' => 'success','data' => $aParam]);
} }
@@ -240,190 +244,25 @@ class ArticleParserService
foreach ($section->getElements() as $element) { foreach ($section->getElements() as $element) {
$text = $this->getTextFromElement($element); $text = $this->getTextFromElement($element);
$length = mb_strlen(trim($text)); $length = mb_strlen(trim($text));
if ($length > $maxLength && $length > 10) { // 标题通常较长 if ($length > $maxLength && $length > 3) { // 标题通常较长
$title = trim($text); $title = trim($text);
$maxLength = $length; $maxLength = $length;
break 2; // 取第一个最长段落作为标题 break 2; // 取第一个最长段落作为标题
} }
} }
} }
if(!empty($title) && !mb_check_encoding($title, 'UTF-8')){
$title = mb_convert_encoding($title, 'UTF-8', 'GBK');
}
return $title; return $title;
} }
// 提取作者
// private function getAuthors($aParam = []) {
// $title = empty($aParam['title']) ? $this->getTitle() : $aParam['title'];
// $sAuthorContent = $this->getNextParagraphAfterText($title);
// if (empty($sAuthorContent)) {
// return ['author' => [], 'report' => []];
// }
// //编码修复
// $possibleEncodings = [
// 'Windows-1252', 'UTF-8', 'GBK', 'GB2312',
// 'Latin-1', 'ISO-8859-1', 'CP1252'
// ];
// $encodedContent = @mb_convert_encoding($sAuthorContent, 'UTF-8', implode(',', $possibleEncodings));
// $sAuthorContent = $encodedContent ?: $sAuthorContent;
// //清理不可见字符
// $sAuthorContent = preg_replace('/[\x00-\x1F\x7F\x{200B}-\x{200F}]/u', '', $sAuthorContent);
// //修复特殊符号乱码
// $symbolMap = [
// '†' => '†', 'â ' => '†', 'â' => '†', '?†' => '†',
// ':' => ':', ',' => ',', '—' => '-',
// '啊' => '' // 针对性移除异常字符“啊”(若为固定乱码)
// ];
// $sAuthorContent = strtr($sAuthorContent, $symbolMap);
// //格式标准化
// $sAuthorContent = str_replace(['', ';', '', '、'], ',', $sAuthorContent); // 统一分隔符
// $sAuthorContent = preg_replace('/\s+and\s+/i', ', ', $sAuthorContent); // and转逗号
// $sAuthorContent = preg_replace('/\s+/', ' ', $sAuthorContent); // 合并多余空格
// $sAuthorContent = trim($sAuthorContent);
// // 处理作者
// $content = mb_convert_encoding($sAuthorContent, 'UTF-8', 'auto'); // 确保编码正确
// $content = str_replace("\xC2\xA0", ' ', $content); // 替换非-breaking空格为普通空格
// $content = preg_replace('/(\d+)\s*([*#†])/', '$1$2', $content); // 合并"1 *"为"1*"、"1 #"为"1#"
// $content = preg_replace('/,†/', ',†', $content); // 保留"1,†"格式(防止被拆分)
// //标记上标内的逗号+空格(多编号)
// $tempStr = preg_replace('/(\d+)\s*,\s*(\d+)/', '$1<SEP>$2', $content);
// // 原有步骤2正则匹配扩展上标符号支持保持原有逻辑
// $pattern = '/
// ([A-Za-z\s\.\-]+?) # 姓名(支持缩写、空格)
// \s* # 姓名与上标间空格
// ( # 上标组(扩展符号支持)
// \d+ # 起始数字
// (?:[†#*,]|<SEP>\d+)* # 允许:†#*符号、逗号、<SEP>+数字兼容1,†、1,*等)
// )
// \s*,? # 作者间逗号(可选)
// (?=\s|$) # 确保后面是空格或结尾
// /ux';
// preg_match_all($pattern, $tempStr, $matches);
// $authorList = [];
// if(!empty($matches[1])){
// foreach ($matches[1] as $i => $name) {
// $name = trim($name);
// $superscript = trim($matches[2][$i]);
// $superscript = str_replace('<SEP>', ',', $superscript); // 恢复多编号逗号
// $superscript = preg_replace('/,$/', '', $superscript); // 清理末尾逗号
// // 修复符号与数字间的空格(如原始"1 *"被误处理为"1*"的情况,保持原样)
// $superscript = preg_replace('/(\d)([*#†])/', '$1$2', $superscript);
// if (!empty($name)) {
// $authorList[] = [
// 'name' => $name,
// 'superscript' => $superscript
// ];
// }
// }
// }else {
// // 按“两个或多个连续空格”拆分(姓名之间的分隔)
// $authorList = array_filter(
// array_map('trim',
// preg_split('/(,\p{Z}*|\p{Z}{2,})/u', $sAuthorContent)
// )
// );
// }
// // //处理作者
// // $authorList = [];
// // // 新正则:匹配“姓名+上标”整体允许上标含逗号如1,†)
// // // 逻辑:姓名以字母/中文开头,上标以数字开头、以符号/数字结尾
// // // if (preg_match_all('/([A-Za-z\x{4e00}-\x{9fa5}][A-Za-z\s·\-\'\x{4e00}-\x{9fa5}]*)\s*([\d,†#*]+)/u', $sAuthorContent, $matches)) {
// // if(preg_match_all('/([A-Za-z\x{4e00}-\x{9fa5}][A-Za-z\s·\-\'\x{4e00}-\x{9fa5}]*)\s*(\d[\d,†#\s*]*)/u', $sAuthorContent, $matches)){
// // for ($i = 0; $i < count($matches[1]); $i++) {
// // $authorList[] = trim($matches[1][$i] . $matches[2][$i]);
// // }
// // } else {
// // // 按“两个或多个连续空格”拆分(姓名之间的分隔)
// // $authorList = array_filter(
// // array_map('trim',
// // preg_split('/(,\p{Z}*|\p{Z}{2,})/u', $sAuthorContent)
// // )
// // );
// // }
// $aAuthorData = [];
// $aReport = [];
// $namePattern = '/
// (?:[A-Za-z\s·\-\']+| # 英文姓名(支持空格、连字符)
// [\x{4e00}-\x{9fa5}]+| # 中文姓名
// [\x{1800}-\x{18AF}]+| # 蒙古文姓名
// [A-Z]\.) # 单字母缩写(如 J.
// /ux';
// var_dump($authorList);exit;
// foreach ($authorList as $authorStr) {
// if (empty($authorStr)) continue;
// var_dump($authorList);exit;
// //分离姓名与上标支持上标含逗号如1,†)
// $superscript = '';
// // 新正则:匹配以数字开头、含逗号/符号的完整上标如1,†、2*#
// $authorStr = trim(trim($authorStr,','),' ');
// // if (preg_match('/([\d,†#*]+)$/u', $authorStr, $supMatch)) {
// // if(preg_match('/\s*([\d,†#* ]+)$/u', $authorStr, $supMatch)){
// // if (preg_match('/.*?\s*([\d,†#* ]+)$/u', $authorStr, $supMatch)) {
// // if (preg_match('/.*?\s*([\d,\x{2020}#* ]+?)\s*$/u', $authorStr, $supMatch)) {
// // if (preg_match('/^(.+?)\D*?(\d[\d,#*†,\s]*)$/u', $authorStr, $supMatch)) {
// // $superscript = $supMatch[1];
// // // 移除上标,保留纯姓名(避免残留符号)
// // $nameStr = trim(preg_replace('/' . preg_quote($superscript, '/') . '$/', '', $authorStr));
// // } else {
// // $nameStr = $authorStr;
// // }
// $pattern = '/^(.+?)\s*(\d[\d,#*†\s]*?)\s*$/u';
// if (preg_match($pattern, $authorStr, $supMatch)) {
// $nameStr = empty($supMatch[1]) ? '' : trim($supMatch[1]); // 姓名部分:"Liguo Zhang"
// $superscript = empty($supMatch[2]) ? $nameStr : $nameStr.trim($supMatch[2]); // 上标部分:"1
// // echo "姓名: $nameStr, 上标: $superscript\n";
// } else {
// $nameStr = $authorStr;
// }
// //验证姓名合法性(过滤无效内容)
// if (!preg_match($namePattern, $nameStr)) {
// continue;
// }
// //解析上标信息正确识别1,†中的机构编号和符号)
// $companyId = '';
// $isSuper = 0;
// $isReport = 0;
// if (!empty($superscript)) {
// // 提取机构编号忽略上标中的逗号如1,† → 提取1
// if (preg_match('/(\d+)/', $superscript, $numMatch)) {
// $companyId = $numMatch[1];
// }
// // 识别特殊符号(#为超级作者,*†为通讯作者)
// $isSuper = strpos($superscript, '#') !== false ? 1 : 0;
// $isReport = (strpos($superscript, '*') !== false || strpos($superscript, '†') !== false) ? 1 : 0;
// }
// if (preg_match("/^([A-Za-z\s'\.-]+)/u", $nameStr, $match)) {
// $nameStr = trim($match[1]);
// }
// $aAuthorData[] = [
// 'name' => $nameStr,
// 'company_id' => $companyId,
// 'is_super' => $isSuper,
// 'is_report' => $isReport
// ];
// if ($isReport) {
// $aReport[] = $nameStr;
// }
// }
// var_dump($aAuthorData);exit;
// return ['author' => $aAuthorData,'report' => array_unique($aReport)];
// }
// 提取作者 // 提取作者
private function parseAuthorsWithoutRegex($str = '') { private function parseAuthorsWithoutRegex($str = '') {
if (empty($str)) { if (empty($str)) {
return []; return [];
} }
// 清理乱码和特殊字符(扩展全角数字处理) if(!mb_check_encoding($str, 'UTF-8')){
$str = mb_convert_encoding($str, 'UTF-8', 'auto'); $str = mb_convert_encoding($str, 'UTF-8', 'GBK');
}
$str = $this->fullDecode($str);
$str = str_replace(["\xC2\xA0", 'ï¼', '�', '', '', '', '', '', '', '', '', '', '', ''], $str = str_replace(["\xC2\xA0", 'ï¼', '�', '', '', '', '', '', '', '', '', '', '', ''],
[' ', ' ', ' ', ' ', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'], $str); [' ', ' ', ' ', ' ', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'], $str);
$str = trim(str_replace([' and ', ' AND ', ' And '], ', ', $str)); $str = trim(str_replace([' and ', ' AND ', ' And '], ', ', $str));
@@ -584,15 +423,10 @@ class ArticleParserService
if (empty($sAuthorContent)) { if (empty($sAuthorContent)) {
return ['author' => [], 'report' => []]; return ['author' => [], 'report' => []];
} }
if(!mb_check_encoding($sAuthorContent, 'UTF-8')){
//编码修复 $sAuthorContent = mb_convert_encoding($sAuthorContent, 'UTF-8', 'GBK');
$possibleEncodings = [ }
'Windows-1252', 'UTF-8', 'GBK', 'GB2312', $sAuthorContent = $this->fullDecode($sAuthorContent);
'Latin-1', 'ISO-8859-1', 'CP1252'
];
$encodedContent = @mb_convert_encoding($sAuthorContent, 'UTF-8', implode(',', $possibleEncodings));
$sAuthorContent = $encodedContent ?: $sAuthorContent;
//清理不可见字符 //清理不可见字符
$sAuthorContent = preg_replace('/[\x00-\x1F\x7F\x{200B}-\x{200F}]/u', '', $sAuthorContent); $sAuthorContent = preg_replace('/[\x00-\x1F\x7F\x{200B}-\x{200F}]/u', '', $sAuthorContent);
@@ -614,14 +448,10 @@ class ArticleParserService
return ['author' => [],'report' => []]; return ['author' => [],'report' => []];
} }
$aReport = $aAuthorData = []; $aReport = $aAuthorData = [];
foreach ($aAuthor as $key => $value) { foreach ($aAuthor as $key => $value) {
if(empty($value['name']) && empty($value['superscript'])){ if(empty($value['name']) && empty($value['superscript'])){
continue; continue;
} }
if(!mb_check_encoding($value['name'], 'UTF-8')){
$value['name'] = mb_convert_encoding($value['name'], 'UTF-8', 'GBK');
}
if(!empty($value['name']) && !empty($value['is_report']) && $value['is_report'] == 1){ if(!empty($value['name']) && !empty($value['is_report']) && $value['is_report'] == 1){
$aReport[] = $value['name']; $aReport[] = $value['name'];
} }
@@ -629,175 +459,6 @@ class ArticleParserService
} }
return ['author' => $aAuthorData,'report' => array_unique($aReport)]; return ['author' => $aAuthorData,'report' => array_unique($aReport)];
} }
// private function getAuthors($aParam = []) {
// $title = empty($aParam['title']) ? $this->getTitle() : $aParam['title'];
// $sAuthorContent = $this->getNextParagraphAfterText($title);
// if (empty($sAuthorContent)) {
// return ['author' => [], 'report' => []];
// }
// //编码修复
// $possibleEncodings = [
// 'Windows-1252', 'UTF-8', 'GBK', 'GB2312',
// 'Latin-1', 'ISO-8859-1', 'CP1252'
// ];
// $encodedContent = @mb_convert_encoding($sAuthorContent, 'UTF-8', implode(',', $possibleEncodings));
// $sAuthorContent = $encodedContent ?: $sAuthorContent;
// //清理不可见字符
// $sAuthorContent = preg_replace('/[\x00-\x1F\x7F\x{200B}-\x{200F}]/u', '', $sAuthorContent);
// //修复特殊符号乱码
// $symbolMap = [
// '†' => '†', 'â ' => '†', 'â' => '†', '?†' => '†',
// ':' => ':', ',' => ',', '—' => '-',
// '啊' => '' // 针对性移除异常字符“啊”(若为固定乱码)
// ];
// $sAuthorContent = strtr($sAuthorContent, $symbolMap);
// //格式标准化
// $sAuthorContent = str_replace(['', ';', '', '、'], ',', $sAuthorContent); // 统一分隔符
// $sAuthorContent = preg_replace('/\s+and\s+/i', ', ', $sAuthorContent); // and转逗号
// $sAuthorContent = preg_replace('/\s+/', ' ', $sAuthorContent); // 合并多余空格
// $sAuthorContent = trim($sAuthorContent);
// var_dump($this->parseAuthorsWithoutRegex($sAuthorContent));exit;
// // 关键预处理:兼容"and"分隔符、清理乱码、统一空格
// $content = mb_convert_encoding($sAuthorContent, 'UTF-8', 'auto');
// $content = str_replace(["\xC2\xA0", 'ï¼', '�', ''], ' ', $content); // 清理乱码和全角符号
// $content = preg_replace('/\band\b/i', ',', $content); // 将 "and" 转为逗号(统一分隔符)
// $content = preg_replace('/(\d+)\s*([*#†])/', '$1$2', $content); // 合并数字与符号间的空格(如"1 *"→"1*"
// $content = trim(preg_replace('/\s+/', ' ', $content)); // 合并连续空格
// // 标记上标内的逗号(多编号处理)
// $tempStr = preg_replace('/(\d+)\s*,\s*(\d+)/', '$1<SEP>$2', $content);
// // 核心正则(保持原有结构,扩展符号支持)
// $pattern = '/
// ([A-Za-z\s\.\-]+?) # 姓名(支持缩写、空格、连字符)
// \s* # 姓名与上标间的空格允许0或多个
// ( # 上标组(扩展兼容所有符号)
// \d+ # 起始数字至少1个数字
// (?:[†#*,]|<SEP>\d+)* # 允许:符号(†#*)、逗号、<SEP>+数字(多编号)
// )
// \s*,? # 作者间的逗号(可选,允许逗号前有空格)
// (?=\s|$) # 确保后面是空格或字符串结尾(避免跨作者匹配)
// /ux';
// preg_match_all($pattern, $tempStr, $matches);
// // 解析结果并格式化
// $authorList = [];
// if (!empty($matches[1])) {
// foreach ($matches[1] as $i => $name) {
// $name = trim($name);
// $superscript = trim($matches[2][$i]);
// $superscript = str_replace('<SEP>', ',', $superscript); // 恢复多编号逗号
// $superscript = preg_replace('/,$/', '', $superscript); // 清理末尾多余逗号
// if (!empty($name)) {
// $authorList[] = [
// 'name' => $name,
// 'superscript' => $superscript
// ];
// }
// }
// }
// // 输出结果
// echo "<pre>";
// print_r($authorList);
// echo "</pre>";
// exit;
// // 处理作者
// $content = mb_convert_encoding($sAuthorContent, 'UTF-8', 'auto'); // 确保编码正确
// $content = str_replace("\xC2\xA0", ' ', $content); // 替换非-breaking空格为普通空格
// $content = preg_replace('/(\d+)\s*([*#†])/', '$1$2', $content); // 合并"1 *"为"1*"、"1 #"为"1#"
// $content = preg_replace('/,†/', ',†', $content); // 保留"1,†"格式(防止被拆分)
// //标记上标内的逗号+空格(多编号)
// $tempStr = preg_replace('/(\d+)\s*,\s*(\d+)/', '$1<SEP>$2', $content);
// // 原有步骤2正则匹配扩展上标符号支持保持原有逻辑
// $pattern = '/
// ([A-Za-z\s\.\-]+?) # 姓名(支持缩写、空格)
// \s* # 姓名与上标间空格
// ( # 上标组(扩展符号支持)
// \d+ # 起始数字
// (?:[†#*,]|<SEP>\d+)* # 允许:†#*符号、逗号、<SEP>+数字兼容1,†、1,*等)
// )
// \s*,? # 作者间逗号(可选)
// (?=\s|$) # 确保后面是空格或结尾
// /ux';
// preg_match_all($pattern, $tempStr, $matches);
// var_dump($matches);exit;
// $authorList = [];
// if(!empty($matches[1])){
// foreach ($matches[1] as $i => $name) {
// $name = trim($name);
// $superscript = trim($matches[2][$i]);
// $superscript = str_replace('<SEP>', ',', $superscript); // 恢复多编号逗号
// $superscript = preg_replace('/,$/', '', $superscript); // 清理末尾逗号
// // 修复符号与数字间的空格(如原始"1 *"被误处理为"1*"的情况,保持原样)
// $superscript = preg_replace('/(\d)([*#†])/', '$1$2', $superscript);
// if (!empty($name)) {
// $authorList[] = [
// 'name' => $name,
// 'superscript' => $superscript
// ];
// }
// }
// }else {
// // 按“两个或多个连续空格”拆分(姓名之间的分隔)
// $authorList = array_filter(
// array_map('trim',
// preg_split('/(,\p{Z}*|\p{Z}{2,})/u', $sAuthorContent)
// )
// );
// }
// // //处理作者
// $aAuthorData = [];
// $aReport = [];
// $namePattern = '/
// (?:[A-Za-z\s·\-\']+| # 英文姓名(支持空格、连字符)
// [\x{4e00}-\x{9fa5}]+| # 中文姓名
// [\x{1800}-\x{18AF}]+| # 蒙古文姓名
// [A-Z]\.) # 单字母缩写(如 J.
// /ux';
// foreach ($authorList as $authorStr){
// if (empty($authorStr)) continue;
// //获取下标
// $superscript = empty($authorStr['superscript']) ? $authorStr : $authorStr['superscript'];
// $nameStr = empty($authorStr['name']) ? $authorStr : $authorStr['name'];
// $companyId = [];
// $isSuper = 0;
// $isReport = 0;
// if (!empty($superscript)) {
// // 提取机构编号忽略上标中的逗号如1,† → 提取1
// preg_match_all('/\d+/', $superscript, $numMatch);
// // 识别特殊符号(#为超级作者,*†为通讯作者)
// $isSuper = strpos($superscript, '#') !== false ? 1 : 0;
// $isReport = (strpos($superscript, '*') !== false || strpos($superscript, '†') !== false) ? 1 : 0;
// }
// if (preg_match("/^([A-Za-z\s'\.-]+)/u", $nameStr, $match)) {
// $nameStr = trim($match[1]);
// }
// $aAuthorData[] = [
// 'name' => $nameStr,
// 'company_id' => empty($numMatch[0]) ? [] : $numMatch[0],
// 'is_super' => $isSuper,
// 'is_report' => $isReport
// ];
// if ($isReport) {
// $aReport[] = $nameStr;
// }
// }
// return ['author' => $aAuthorData,'report' => array_unique($aReport)];
// }
// 获取机构 // 获取机构
private function getCompany($aParam = []){ private function getCompany($aParam = []){
@@ -815,16 +476,39 @@ class ArticleParserService
$currentNumber = null; // 当前序号 $currentNumber = null; // 当前序号
foreach ($allLines as $line) { foreach ($allLines as $line) {
$line = trim($line); $line = trim($line);
if (empty($line)) continue; if (empty($line)) {
continue;
// 判断是否是新条目的开头:行首为数字(后续可接任意字符或直接接内容) }
if(!mb_check_encoding($line, 'UTF-8')){
$line = mb_convert_encoding($line, 'UTF-8', 'GBK');
}
$line = $this->fullDecode($line);
$number = ''; $number = '';
$i = 0; $i = 0;
$lineLen = strlen($line); $lineLen = strlen($line);
// 提取行首的连续数字(作为序号) // 提取行首的连续数字(作为序号)
while ($i < $lineLen && ctype_digit($line[$i])) { $hasFirstChar = false;
$number .= $line[$i]; while ($i < $lineLen) {
$i++; $currentChar = $line[$i];
// 首字符处理:允许 26个字母大小写或数字
if (!$hasFirstChar) {
if (ctype_digit($currentChar) || ctype_alpha($currentChar)) {
$number .= $currentChar;
$hasFirstChar = true;
$i++;
} else {
// 首字符不符合(非字母/数字),终止循环
break;
}
} else {
// 后续字符必须是数字(保持原逻辑)
if (ctype_digit($currentChar)) {
$number .= $currentChar;
$i++;
} else {
break;
}
}
} }
// 若行首有数字,则视为新条目 // 若行首有数字,则视为新条目
@@ -840,31 +524,33 @@ class ArticleParserService
continue; continue;
} }
// 非新条目,合并到当前序号的内容中 // // 非新条目,合并到当前序号的内容中
if ($currentNumber !== null) { // if ($currentNumber !== null) {
$grouped[$currentNumber] .= ' ' . $line; // $grouped[$currentNumber] .= ' ' . $line;
} // }
} }
//清理结果
$possibleEncodings = [
'Windows-1252', 'UTF-8', 'GBK', 'GB2312',
'Latin-1', 'ISO-8859-1', 'CP1252'
];
$aCompany = []; $aCompany = [];
foreach ($grouped as $number => $institution) { foreach ($grouped as $number => $institution) {
$encodedContent = @mb_convert_encoding($institution, 'UTF-8', implode(',', $possibleEncodings)); $institution = $this->fullDecode($institution);
$sCompany = $encodedContent ?: $sCompany; // 原有基础清理逻辑不变
$institution = preg_replace('/\s+/', ' ', $institution); // 合并多余空格 $institution = preg_replace('/\s+/', ' ', $institution); // 合并多余空格
$institution = rtrim($institution, '.'); $institution = rtrim($institution, '.'); // 去除末尾句号
$institution = preg_replace('/^\d+\s+/', '', $institution); $institution = preg_replace('/^\d+\s+/', '', $institution); // 去除开头数字
$institution = trim($institution); // 清理首尾空格 $institution = trim($institution); // 清理首尾空格
preg_match('/(.*?, [A-Za-z]+ \d+, [A-Za-z]+)/', $institution, $institutionmatches);;
$institution = trim($institutionmatches[1] ?? $institution); // 增强地址提取:匹配"机构名, 城市 邮编, 国家"格式(兼容更多变体)
if (preg_match('/^(.*?)(?=\s*\*Email)/', $institution, $matches)) { // 允许地址中包含多个逗号(如子机构、街道信息),最终以"城市 邮编, 国家"结尾
$institution = trim($matches[1]); // trim() 去除内容前后多余空格 // preg_match('/(.*?, [A-Za-z\s]+ \d+, [A-Za-z\s]+)/', $institution, $institutionmatches);
// $institution = trim($institutionmatches[1] ?? $institution);
// 强化冗余信息过滤:去除"*"及之后的内容(包括通讯作者、邮箱等)
// 新增对"#"、"†"等标记的过滤,兼容更多期刊格式
if (preg_match('/^(.*?)(?=\s*[\*#†]|(?i)\s*Email)/', $institution, $matches)) {
$institution = trim($matches[1]);
} }
if(!empty($institution) && !mb_check_encoding($institution, 'UTF-8')){
// 编码校验不变
if (!empty($institution) && !mb_check_encoding($institution, 'UTF-8')) {
$institution = mb_convert_encoding($institution, 'UTF-8', 'GBK'); $institution = mb_convert_encoding($institution, 'UTF-8', 'GBK');
} }
$aCompany[$number] = $institution; $aCompany[$number] = $institution;
@@ -891,13 +577,10 @@ class ArticleParserService
// 获取机构后的完整内容 // 获取机构后的完整内容
$corrText = $this->getContentAfterText($sCompany); $corrText = $this->getContentAfterText($sCompany);
//编码修复 if(!mb_check_encoding($corrText, 'UTF-8')){
$possibleEncodings = [ $corrText = mb_convert_encoding($corrText, 'UTF-8', 'GBK');
'Windows-1252', 'UTF-8', 'GBK', 'GB2312', }
'Latin-1', 'ISO-8859-1', 'CP1252' $corrText = $this->fullDecode($corrText);
];
$encodedContent = @mb_convert_encoding($corrText, 'UTF-8', implode(',', $possibleEncodings));
$corrText = $encodedContent ?: $corrText;
// // 调试 // // 调试
// file_put_contents(ROOT_PATH . 'runtime/corr_text_raw.log', $corrText); // file_put_contents(ROOT_PATH . 'runtime/corr_text_raw.log', $corrText);
@@ -927,7 +610,8 @@ class ArticleParserService
]; ];
} }
if(empty($aCorresponding)){ if(empty($aCorresponding)){
$pattern = '/Corresponding Authors: (.*?)(?=$|;)/s'; $pattern = '/Corresponding Authors|Correspondence to|Correspondence: (.*?)(?=$|;)/s';
$corrText = trim($corrText,'*');
preg_match($pattern, $corrText, $match); preg_match($pattern, $corrText, $match);
if (!empty($match[1])) { if (!empty($match[1])) {
$corrContent = $match[1]; $corrContent = $match[1];
@@ -942,6 +626,16 @@ class ArticleParserService
]; ];
} }
} }
if(empty($authors[1])){
$authorPattern = '/([A-Za-z0-9\s]+?),\s*([\w@\.\-]+)(?=\.?)/';
preg_match_all($authorPattern, $corrContent, $authors);
for ($i = 0; $i < count($authors[1]); $i++) {
$aCorresponding[] = [
'name' => empty($authors[1][$i]) ? '' : trim($authors[1][$i]),
'email' => empty($authors[2][$i]) ? '' : trim($authors[2][$i])
];
}
}
} }
} }
return $aCorresponding; return $aCorresponding;
@@ -1122,24 +816,88 @@ class ArticleParserService
* 从 Word 文档提取摘要和关键词 * 从 Word 文档提取摘要和关键词
* @return array 提取结果 * @return array 提取结果
*/ */
function extractContentIntervals($str, $markers = []) {
// 1. 初始化标记(支持自定义,默认值兼容原逻辑)
$defaultMarkers = [
'abstract' => 'abstract',
'keywords' => 'keywords',
'end_span' => '===========end-span'
];
$markers = array_merge($defaultMarkers, $markers);
extract($markers); // 解析为变量 $abstract, $keywords, $end_span
// 2. 初始化结果(包含元信息)
$result = [
'abstract_to_keywords' => '',
'keywords_to_end' => '',
'positions' => [ // 标记位置信息(-1 表示未找到)
'abstract' => -1,
'keywords' => -1,
'end_span' => -1
],
'is_valid' => false, // 整体区间是否有效
'error' => '' // 错误信息(如标记顺序异常)
];
// 3. 定位 Abstract不区分大小写
$absPos = stripos($str, $abstract);
if ($absPos === false) {
$result['error'] = "未找到标记: {$abstract}";
return $result;
}
$result['positions']['abstract'] = $absPos;
$absEndPos = $absPos + strlen($abstract);
// 4. 定位 Keywords需在 Abstract 之后,不区分大小写)
$keyPos = stripos($str, $keywords, $absEndPos);
if ($keyPos === false) {
$result['error'] = "未找到 {$keywords} 或在 {$abstract} 之前";
return $result;
}
$result['positions']['keywords'] = $keyPos;
$keyEndPos = $keyPos + strlen($keywords);
// 5. 定位 end-span需在 Keywords 之后,严格匹配)
$endPos = strpos($str, $end_span, $keyEndPos);
if ($endPos === false) {
$result['error'] = "未找到 {$end_span} 或在 {$keywords} 之前";
return $result;
}
$result['positions']['end_span'] = $endPos;
// 6. 截取区间内容(清理标记后的紧邻符号)
// 区间1Abstract 结束 → Keywords 开始(清理标记后的冒号/空格)
$len1 = $keyPos - $absEndPos;
$part1 = substr($str, $absEndPos, $len1);
$part1 = trim($part1);
// 移除 Abstract 后可能的冒号/短横线(如 "Abstract: ..." → 去掉开头的 ":"
$part1 = ltrim($part1, ': -—');
$result['abstract_to_keywords'] = trim($part1);
// 区间2Keywords 结束 → end-span 开始(同理清理)
$len2 = $endPos - $keyEndPos;
$part2 = substr($str, $keyEndPos, $len2);
$part2 = trim($part2);
$part2 = ltrim($part2, ': -—');
$result['keywords_to_end'] = trim($part2);
// 7. 标记为有效
$result['is_valid'] = true;
return $result;
}
public function extractFromWord() { public function extractFromWord() {
$sContent = ''; $sContent = '';
//文本处理 //文本处理
$sFundContent = ''; $sFundContent = '';
$aContent = [];
foreach ($this->sections as $section) { foreach ($this->sections as $section) {
foreach ($section->getElements() as $element) { foreach ($section->getElements() as $element) {
$textContent = $this->getTextFromElement($element); $textContent = $this->getTextFromElement($element);
if(empty($textContent)){ if(empty($textContent)){
continue; continue;
} }
//编码修复 if(!empty($textContent) && !mb_check_encoding($textContent, 'UTF-8')){
$possibleEncodings = [ $textContent = mb_convert_encoding($textContent, 'UTF-8', 'GBK');
'Windows-1252', 'UTF-8', 'GBK', 'GB2312',
'Latin-1', 'ISO-8859-1', 'CP1252'
];
$sContent .= @mb_convert_encoding($textContent, 'UTF-8', implode(',', $possibleEncodings));
if(stripos($textContent, 'Keywords:') !== false){
$sContent .= "Keywords-End-Flag";
} }
if(empty($sFundContent)){ if(empty($sFundContent)){
$aFund = $this->getMatchedFundPhrases($sContent); $aFund = $this->getMatchedFundPhrases($sContent);
@@ -1152,69 +910,194 @@ class ArticleParserService
} }
} }
} }
$sContent .= "\n"; $sContent .= $textContent."===========end-span";
} }
} }
if(!empty($sContent) && !mb_check_encoding($sContent, 'UTF-8')){ if(!empty($sContent) && !mb_check_encoding($sContent, 'UTF-8')){
$sContent = mb_convert_encoding($sContent, 'UTF-8', 'GBK'); $sContent = mb_convert_encoding($sContent, 'UTF-8', 'GBK');
} }
// 2. 基础文本清理(合并多余空格,保留有效换行) $result = $this->extractContentIntervals($sContent);
$textContent = preg_replace('/(\S)\s+/', '$1 ', $sContent);
$textContent = trim($textContent);
// 3. 提取摘要 // 3. 提取摘要
$abstract = ''; $abstract = empty($result['abstract_to_keywords']) ? '' : $result['abstract_to_keywords'];
$abstractPattern = '/Abstract\s*([\s\S]*?)(?=Keywords|$)/i'; if(!empty($abstract) && !mb_check_encoding($abstract, 'UTF-8')){
if (preg_match($abstractPattern, $textContent, $abstractMatches)) { $abstract = mb_convert_encoding($abstract, 'UTF-8', 'GBK');
$abstract = trim($abstractMatches[1]);
$abstract = preg_replace('/\n+/', ' ', $abstract);
} }
// 4. 提取关键词(核心:仅保留两种强制匹配逻辑) $keywords = empty($result['keywords_to_end']) ? '' : $result['keywords_to_end'];
$keywords = []; if(!empty($keywords) && !mb_check_encoding($keywords, 'UTF-8')){
// $keywordPattern = '/Keywords:\s*([\s\S]*?)(?=\s*\d+\.|[;,]\s*[\r\n]+\s*[\r\n]+|(?i)\bintroduction|abbreviations\b|$)/i'; $keywords = mb_convert_encoding($keywords, 'UTF-8', 'GBK');
$keywordPattern = '/Keywords\s*(.*?)\s*Keywords-End-Flag/s';
if (preg_match($keywordPattern, $textContent, $keywordMatches)) {
$keywordStr = trim($keywordMatches[1]);
// 清理关键词列表格式(去除换行、末尾多余符号)
$keywordStr = preg_replace('/\n+/', ' ', $keywordStr);
$keywordStr = rtrim($keywordStr, ';,. '); // 去除末尾分号、逗号等
$keywordStr = trim($keywordStr);
// 分割并过滤有效关键词
$keywords = preg_split('/[,;]\s*/', $keywordStr);
$keywords = array_filter(array_map('trim', $keywords), function($item) {
return !empty($item) && !ctype_space($item);
});
} }
if(empty($keywords)){ if(!empty($sFundContent) && !mb_check_encoding($sFundContent, 'UTF-8')){
$keywordPattern = '/Keywords\s*([\s\S]*?)(?=Introduction|$)/i'; $sFundContent = mb_convert_encoding($sFundContent, 'UTF-8', 'GBK');
if (preg_match($keywordPattern, $textContent, $keywordMatches)) {
$keywordStr = trim($keywordMatches[1]);
// 清理关键词列表格式(去除换行、末尾多余符号)
$keywordStr = preg_replace('/\n+/', ' ', $keywordStr);
$keywordStr = rtrim($keywordStr, ';,. '); // 去除末尾分号、逗号等
$keywordStr = trim($keywordStr);
// 分割并过滤有效关键词
$keywords = preg_split('/[,;]\s*/', $keywordStr);
$keywords = array_filter(array_map('trim', $keywords), function($item) {
return !empty($item) && !ctype_space($item);
});
}
} }
return [ return [
'status' => 1, 'status' => 1,
'msg' => '提取成功', 'msg' => '提取成功',
'data' => [ 'data' => [
'abstrart' => $abstract, 'abstrart' => empty($abstract) ? '' : $this->fullDecode(str_replace('===========end-span', '',$abstract)),
'keywords' => $keywords, 'keywords' => empty($keywords) ? '' : $this->fullDecode(str_replace('===========end-span', '',$keywords)),
'fund' => $sFundContent 'fund' => empty($sFundContent) ? '' : $this->fullDecode(str_replace('===========end-span', '',$sFundContent))
] ]
]; ];
} }
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;
// }
// $original = $str;
// $depth = 0;
// // 循环解码,直到无变化或达到最大次数
// while (true) {
// $depth++;
// if ($depth > $maxDepth) {
// break; // 防止过度解码导致死循环
// }
// // 1. 解码 Unicode 转义(\uXXXX 格式)
// $str = $this->decodeUnicode($str);
// // 2. 解码 HTML 实体(&amp;、&#039;、&lt; 等)
// $str = html_entity_decode($str, ENT_QUOTES | ENT_HTML5, 'UTF-8');
// $str = preg_replace_callback('/\\\\u([0-9a-fA-F]{4})/', function ($m) {
// return mb_chr(hexdec($m[1]), 'UTF-8') ?: $m[0];
// }, $str);
// $str = str_replace([chr(0xC2).chr(0xA0), chr(0xA0)], ' ', $str);
// // 2. 核心:强制匹配所有可能的乱码格式,还原≥
// // 匹配0B?0、0B ?0、0B ?0空格/制表符)→ 0B≥30
// $str = preg_replace('/0B\s*\?0/', '0B≥30', $str);
// // 匹配DL?.18、DL ?.18、DL ?.18 → DL≥0.18
// $str = preg_replace('/DL\s*\?.18/', 'DL≥0.18', $str);
// // 通用匹配:数字前的?(如?30、?0.18)→ ≥30、≥0.18(防止其他变体)
// $str = preg_replace('/\?(\d+)/', '≥$1', $str);
// $str = preg_replace('/\?(\.\d+)/', '≥0$1', $str);
// // 3. 修复前缀的"d with "可能的乱码(若有)
// $str = str_replace('d with ', 'd with ', $str); // 若前缀也乱码,可同步替换
// // 若解码后无变化,退出循环
// if ($str === $original) {
// break;
// }
// $original = $str;
// }
// return trim($str,':');
// }
private function decodeUnicode($str) {
return preg_replace_callback(
'/\\\\u([0-9a-fA-F]{4})/',
function ($matches) {
// 将十六进制 Unicode 码转为 UTF-8 字符
return mb_convert_encoding(pack('H*', $matches[1]), 'UTF-8', 'UCS-2BE');
},
$str
);
}
private function getMatchedFundPhrases($content = '') { private function getMatchedFundPhrases($content = '') {
if (empty($content)) { if (empty($content)) {
return []; return [];
@@ -1223,7 +1106,7 @@ class ArticleParserService
// 基金支持词组列表 // 基金支持词组列表
$fundPhrases = [ $fundPhrases = [
'Supported by', 'Funded by', 'Sponsored by', 'Supported in part by', 'Supported by', 'Funded by', 'Sponsored by', 'Supported in part by',
'Funding was provided by', 'Funded in part by' 'Funding was provided by', 'Funded in part by','FUNDING:'
]; ];
// 1. 转义词组中的特殊字符,使用 # 作为分隔符 // 1. 转义词组中的特殊字符,使用 # 作为分隔符