Split a Scholar author line into coauthor names.
(author_line: str, scholar_name: str = "")
| 657 | |
| 658 | |
| 659 | def _parse_author_names(author_line: str, scholar_name: str = "") -> List[str]: |
| 660 | """Split a Scholar author line into coauthor names.""" |
| 661 | normalized_line = _collapse_whitespace(author_line) |
| 662 | if not normalized_line: |
| 663 | return [] |
| 664 | |
| 665 | splitter = re.sub(r"\s+(?:and|&)\s+", ",", normalized_line, flags=re.IGNORECASE) |
| 666 | candidates = [segment.strip() for segment in re.split(r"[;,]", splitter) if segment.strip()] |
| 667 | def _normalize_person_name(value: str) -> str: |
| 668 | cleaned = _collapse_whitespace(value) |
| 669 | cleaned = re.sub(r"^(?:dr|prof|professor)\.?\s+", "", cleaned, flags=re.IGNORECASE) |
| 670 | cleaned = re.sub(r"[^\w\u4e00-\u9fff]+", " ", cleaned, flags=re.UNICODE) |
| 671 | return _collapse_whitespace(cleaned).casefold() |
| 672 | |
| 673 | normalized_scholar_name = _normalize_person_name(scholar_name) |
| 674 | |
| 675 | names: List[str] = [] |
| 676 | for candidate in candidates: |
| 677 | cleaned = re.sub(r"\b(?:et al\.?|…)\b", "", candidate, flags=re.IGNORECASE).strip() |
| 678 | if not cleaned: |
| 679 | continue |
| 680 | if normalized_scholar_name and _normalize_person_name(cleaned) == normalized_scholar_name: |
| 681 | continue |
| 682 | if re.fullmatch(r"[\d\W_]+", cleaned): |
| 683 | continue |
| 684 | names.append(cleaned) |
| 685 | return names |
| 686 | |
| 687 | |
| 688 | def _build_scholar_network_signals(publications: List[Dict[str, Any]], scholar_name: str = "") -> Dict[str, Any]: |
no test coverage detected