参考文献的作者数变成6
This commit is contained in:
201
application/common/AuthorListFormatter.php
Normal file
201
application/common/AuthorListFormatter.php
Normal file
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
namespace app\common;
|
||||
|
||||
/**
|
||||
* 作者列表著录规则:作者数不超过 MAX_LISTED 时全部列出,超过时只列前 KEEP 位再加 et al。
|
||||
*
|
||||
* 单独抽出来是因为"数作者个数"并不能简单按逗号切:
|
||||
* - 温哥华式 "Smith AB, Jones AC" 逗号 = 作者分隔
|
||||
* - APA 式 "Smith, A. B., Jones, A. C." 逗号既分隔作者,也分隔姓与名
|
||||
* - 机构作者 "Department of Health, Education, and Welfare" 逗号只是机构名的一部分
|
||||
* 按逗号数会把 APA 的 3 位作者数成 6 位,凭空触发截断。
|
||||
*
|
||||
* 拿不准的一律少切(宁可多列几位,也不要把不该截的截掉)。
|
||||
*/
|
||||
class AuthorListFormatter
|
||||
{
|
||||
/** 全部列出的上限 */
|
||||
const MAX_LISTED = 6;
|
||||
|
||||
/** 超过上限时保留的作者数 */
|
||||
const KEEP = 3;
|
||||
|
||||
/**
|
||||
* 按著录规则输出作者串
|
||||
*/
|
||||
public static function format($author, $max = self::MAX_LISTED, $keep = self::KEEP)
|
||||
{
|
||||
$author = self::normalize($author);
|
||||
if ($author === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// 已经是 "…, et al" 形态的(历史数据或上游截断过),保持原样,不再叠加
|
||||
if (self::isTruncated($author)) {
|
||||
return $author;
|
||||
}
|
||||
|
||||
$units = self::split($author);
|
||||
if (empty($units)) {
|
||||
return $author;
|
||||
}
|
||||
|
||||
$max = max(1, (int)$max);
|
||||
$keep = min(max(1, (int)$keep), $max);
|
||||
if (count($units) > $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, " ,;");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user