$max) { return implode(', ', array_slice($units, 0, $keep)) . ', et al'; } return implode(', ', $units); } public static function countAuthors($author) { return count(self::split($author)); } public static function isTruncated($author) { return (bool)preg_match('/\bet\s+al\.?\s*$/iu', (string)$author); } /** * 拆成单个作者 * * @return string[] */ public static function split($author) { $author = self::normalize($author); $author = preg_replace('/[,;]?\s*\bet\s+al\.?\s*$/iu', '', $author); $author = trim($author, " ,;."); if ($author === '') { return []; } if (strpos($author, ';') !== false) { // 分号是无歧义的作者分隔符 $units = preg_split('/\s*;\s*/u', $author); } else { $text = $author; // "A, B, and C" 里的 and/& 才是分隔符;没有逗号时它多半是机构名的一部分 // (如 "National Institute for Health and Care Excellence"),不能切 if (strpos($text, ',') !== false) { $text = preg_replace('/\s*,?\s+(?:and|&)\s+/iu', ', ', $text); } $units = preg_split('/\s*,\s*/u', $text); } $units = array_values(array_filter(array_map(function ($u) { return trim($u, " ,;"); }, (array)$units), 'strlen')); $units = self::mergeGivenNames($units); // 整串没有一个单元像人名,多半是带逗号的机构名,算一位作者 $hasPerson = false; foreach ($units as $unit) { if (self::looksLikePerson($unit)) { $hasPerson = true; break; } } return $hasPerson ? $units : [$author]; } /** * 合并 APA 的 "姓, 名" —— "Smith", "A. B." → "Smith, A. B." * * @param string[] $units * @return string[] */ private static function mergeGivenNames(array $units) { $merged = []; foreach ($units as $unit) { // 上一位作者已经带了名缩写,说明这一段是新作者的姓,不能再往上并 $last = empty($merged) ? '' : $merged[count($merged) - 1]; if ($last !== '' && !self::hasInitials($last) && self::looksLikeGivenName($unit)) { $merged[count($merged) - 1] .= ', ' . $unit; continue; } $merged[] = $unit; } return $merged; } private static function hasInitials($unit) { return (bool)preg_match('/\p{Lu}\./u', $unit) || (bool)preg_match('/\s\p{Lu}{1,4}$/u', $unit); } /** * 是否只是名/缩写(属于上一个姓):每个词都是首字母大写,且至少有一个是"单字母 + 点" * "A. B." / "H. Karl." → 是;"Jones AC" / "Bruce Alberts" → 否 */ private static function looksLikeGivenName($unit) { $unit = trim((string)$unit); if ($unit === '') { return false; } // 末位作者的名常被上游去掉尾点,"K." 会变成 "K" if (preg_match('/^\p{Lu}\.?$/u', $unit)) { return true; } if (!preg_match('/\b\p{Lu}\./u', $unit)) { return false; } foreach (preg_split('/\s+/u', $unit) as $word) { $word = trim($word, " ."); if ($word === '') { continue; } if (!preg_match('/^\p{Lu}[\p{L}]*$/u', $word)) { return false; } } return true; } /** * 判定要偏严:机构名里的逗号片段("Department of Health" / "Education")不能算人名, * 否则 "Department of Health, Education, and Welfare" 会被当成三位作者 */ private static function looksLikePerson($unit) { $unit = trim(preg_replace('/\s+/u', ' ', str_replace(',', ' ', (string)$unit))); if ($unit === '') { return false; } // 中文姓名 if (preg_match('/^[\x{4e00}-\x{9fff}·]{2,10}$/u', $unit)) { return true; } // 以缩写结尾:"Smith AB" / "van den Berg AB" if (preg_match('/^\p{Lu}.*\s\p{Lu}{1,4}$/u', $unit)) { return true; } // 带点的名缩写:"Smith A. B." / "Butcher H. Karl." if (preg_match('/\b\p{Lu}\./u', $unit)) { return true; } // 两个词的全名:"Bruce Alberts" return (bool)preg_match('/^\p{Lu}[\p{L}\'\x{2019}\-]+\s+\p{Lu}[\p{L}\'\x{2019}\-]+$/u', $unit); } private static function normalize($author) { $author = trim((string)$author); $author = str_replace([',', ';', '.', ' '], [',', ';', '.', ' '], $author); $author = preg_replace('/\s+/u', ' ', $author); return trim($author, " ,;"); } }