$authorsParsed * @return array{name:string,email:?string,affiliation:?string,university_name:?string}|null */ public static function leadAuthor(?string $authorsString, array $authorsParsed = []): ?array { if ($authorsParsed !== []) { $first = $authorsParsed[0]; $name = trim((string) ($first['name'] ?? '')); if ($name !== '') { return [ 'name' => $name, 'email' => self::normalizeEmail($first['email'] ?? null), 'affiliation' => self::cleanText($first['affiliation'] ?? null), 'university_name' => self::universityFromAffiliation($first['affiliation'] ?? null) ?? self::cleanText($first['university_name'] ?? null), ]; } } if (! $authorsString) { return null; } $parts = preg_split('/[;;,,]/u', $authorsString) ?: []; $name = trim((string) ($parts[0] ?? '')); if ($name === '') { return null; } return [ 'name' => $name, 'email' => null, 'affiliation' => null, 'university_name' => null, ]; } /** * @return list */ public static function splitAuthorsString(?string $authors): array { if (! $authors) { return []; } $rows = []; foreach (preg_split('/[;;]/u', $authors) ?: [] as $chunk) { $name = trim($chunk); if ($name !== '') { $rows[] = ['name' => $name, 'email' => null, 'affiliation' => null, 'university_name' => null]; } } return $rows; } public static function normalizeEmail(?string $email): ?string { $email = trim((string) $email); if ($email === '' || ! filter_var($email, FILTER_VALIDATE_EMAIL)) { return null; } return strtolower($email); } public static function cleanText(?string $text): ?string { $text = self::ensureUtf8((string) $text); if ($text === '') { return null; } $text = trim(preg_replace('/\s+/u', ' ', $text) ?? ''); return $text === '' ? null : $text; } public static function ensureUtf8(string $text): string { if ($text === '') { return ''; } if (! mb_check_encoding($text, 'UTF-8')) { $converted = @mb_convert_encoding($text, 'UTF-8', 'UTF-8,GBK,GB2312,BIG5,ISO-8859-1'); $text = is_string($converted) ? $converted : $text; } if (function_exists('mb_scrub')) { $text = mb_scrub($text, 'UTF-8'); } else { $text = iconv('UTF-8', 'UTF-8//IGNORE', $text) ?: ''; } return $text; } /** * 判断文本是否像真实职称(过滤导航文案如「电气首页」)。 */ public static function looksLikeAcademicTitle(?string $title): bool { $title = trim((string) $title); if ($title === '' || mb_strlen($title) > 30) { return false; } if (preg_match('/首页|导航|菜单|返回|更多|搜索|登录|关于/u', $title)) { return false; } return (bool) preg_match( '/教授|副教授|讲师|助教|研究员|副研究员|助理研究员|实验师|工程师|院士|博导|导师|专家|学者|长聘|准聘|特聘|特任|教轨|兼职|访问|青年|副高|正高|中级|初级/u', $title, ); } /** * 递归清洗数组/字符串,确保可安全 json_encode。 * * @param mixed $value * @return mixed */ public static function sanitizeUtf8Deep(mixed $value): mixed { if (is_string($value)) { return self::ensureUtf8($value); } if (is_array($value)) { $out = []; foreach ($value as $key => $item) { $safeKey = is_string($key) ? self::ensureUtf8($key) : $key; $out[$safeKey] = self::sanitizeUtf8Deep($item); } return $out; } return $value; } public static function universityFromAffiliation(?string $affiliation): ?string { $affiliation = self::cleanText($affiliation); if (! $affiliation) { return null; } if (preg_match('/^([^,,;;]+(?:大学|学院|研究院|研究所|University|College)[^,,;;]*)/iu', $affiliation, $m)) { return trim($m[1]); } return null; } }