extract-human-name 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -2
- package/author-splitter.ts +45 -0
- package/common-names.ts +768 -0
- package/constants.ts +201 -0
- package/dist/author-splitter.d.ts +8 -0
- package/dist/common-names.d.ts +10 -0
- package/dist/constants.d.ts +29 -0
- package/dist/extract-human-name-full.cjs.js +2 -0
- package/dist/extract-human-name-full.cjs.js.map +1 -0
- package/dist/extract-human-name-full.d.ts +2 -0
- package/dist/extract-human-name-full.es.js +2 -0
- package/dist/extract-human-name-full.es.js.map +1 -0
- package/dist/extract-human-name.cjs.js +2 -0
- package/dist/extract-human-name.cjs.js.map +1 -0
- package/dist/extract-human-name.d.ts +2 -0
- package/dist/extract-human-name.es.js +2 -0
- package/dist/extract-human-name.es.js.map +1 -0
- package/dist/full.d.ts +1 -0
- package/dist/human-name-recognizer.d.ts +18 -0
- package/dist/human-names-db.d.ts +41 -0
- package/dist/index.d.ts +20 -0
- package/dist/is-organization.d.ts +8 -0
- package/dist/name-parser.d.ts +32 -0
- package/dist/types/types.d.ts +39 -0
- package/dist/vitest.config.d.ts +2 -0
- package/full.ts +14 -0
- package/human-name-recognizer.ts +160 -0
- package/human-names-92k.json +1 -0
- package/human-names-db.ts +82 -0
- package/index.ts +20 -0
- package/is-organization.ts +68 -0
- package/name-parser.ts +155 -0
- package/package.json +59 -3
- package/types/types.ts +42 -0
- package/vitest.config.ts +22 -0
package/README.md
CHANGED
|
@@ -1,3 +1,96 @@
|
|
|
1
|
-
#
|
|
1
|
+
# extract-human-name
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Extracts, characterizes, and structures human author names from arbitrary text. Tells a person ("Dr. John Doe") from an organization ("The New York Times"), parses a name into its parts, splits multi-author bylines, and formats APA-style citations — no network, no model, no browser.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bun add extract-human-name
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { extractHumanName } from "extract-human-name";
|
|
11
|
+
|
|
12
|
+
extractHumanName("by: Dr. John Q. Public Jr.");
|
|
13
|
+
// {
|
|
14
|
+
// author_cite: "Public, John Q., Jr.",
|
|
15
|
+
// author_short: "Public",
|
|
16
|
+
// author_type: 1
|
|
17
|
+
// }
|
|
18
|
+
|
|
19
|
+
extractHumanName("John Doe and Jane Smith");
|
|
20
|
+
// { author_cite: "Doe, John & Smith, Jane", author_short: "Doe & Smith", author_type: 2 }
|
|
21
|
+
|
|
22
|
+
extractHumanName("The New York Times");
|
|
23
|
+
// { author_cite: "The New York Times", author_short: "The New York Times", author_type: 4 }
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## API
|
|
27
|
+
|
|
28
|
+
### `extractHumanName(author, options?)`
|
|
29
|
+
|
|
30
|
+
The main entry. Sanitizes the input (strips `by:` prefixes, collapses whitespace), splits a byline into individual authors, classifies each one as a person or an organization, and compiles citation forms.
|
|
31
|
+
|
|
32
|
+
| Option | Default | |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| `formatCiteShortenAuthor` | `false` | Abbreviate given names to initials: `"John Quincy Doe"` → `"Doe, J. Q."` |
|
|
35
|
+
| `maxAuthorsBeforeEtAl` | `2` | List this many authors in full before collapsing to `"First et al."` |
|
|
36
|
+
|
|
37
|
+
`author_type` values:
|
|
38
|
+
|
|
39
|
+
| Value | Meaning |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| `0` | Empty or unparseable input |
|
|
42
|
+
| `1` | A single person |
|
|
43
|
+
| `2` | Exactly two authors |
|
|
44
|
+
| `3` | Three or more authors |
|
|
45
|
+
| `4` | A single organization |
|
|
46
|
+
|
|
47
|
+
### `extractHumanNameParts(input)`
|
|
48
|
+
|
|
49
|
+
Parses one name into `{ title, firstname, middle, lastname, honorific }`. Handles `"Last, First"` inversion, multi-word family prefixes (`Ludwig van Beethoven` → lastname `"van Beethoven"`), quoted aliases (`John "The Rocket" Doe`), and fixes the case of all-upper/all-lower input.
|
|
50
|
+
|
|
51
|
+
### `cleanProfessionalQualifications(authorName)`
|
|
52
|
+
|
|
53
|
+
Strips honorifics, titles, and professional qualifications (`"Dr. John Smith, Professor"` → `"John Smith"`).
|
|
54
|
+
|
|
55
|
+
### `splitMultipleAuthors(authorString)`
|
|
56
|
+
|
|
57
|
+
Partitions a byline into individual names. Handles `"Last, First & Last, First"`, comma series with a trailing `and`, `&`/`and`/semicolon separators, and drops `et al.`.
|
|
58
|
+
|
|
59
|
+
### `isOrganization(nameString)`
|
|
60
|
+
|
|
61
|
+
The person-vs-organization heuristic: rigid acronyms (`WHO`), an organization dictionary (decisive terms, or ambiguous terms in pairs like `Morgan Stanley`), qualification counter-terms (`Professor …`), `"Last, First"` inversion, organizational phrases, word count, and the names database.
|
|
62
|
+
|
|
63
|
+
## The names database: slim default vs. `/full`
|
|
64
|
+
|
|
65
|
+
The default entry is slim: it does **not** bundle the 92k-entry names JSON (`human-names-92k.json`, ~1.1 MB). Person-vs-organization detection runs on a compact built-in list of ~5,000 given names and 5,000 common surnames plus word heuristics. The full database is loaded **only when needed**, one of two ways:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
// Lazy-load it from the jsDelivr CDN (one shared request, cached;
|
|
69
|
+
// resolves null on failure, so a CDN outage never breaks extraction)
|
|
70
|
+
import { extractHumanName, loadHumanNamesDB } from "extract-human-name";
|
|
71
|
+
await loadHumanNamesDB(); // or { url, fetch } to self-host
|
|
72
|
+
|
|
73
|
+
// Or bundle it: same API, names database registered on import
|
|
74
|
+
import { extractHumanName } from "extract-human-name/full";
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`setHumanNamesDB(db)` registers a copy you already hold, and `getHumanNamesDB()` reads the current one. Both entries share one database, so importing `extract-human-name/full` once enables it for the slim import too.
|
|
78
|
+
|
|
79
|
+
With the database loaded, a name whose words are outside the compact list is still recognized as a person:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
await loadHumanNamesDB();
|
|
83
|
+
extractHumanName("Zephyr Quill Xylo").author_type; // 1 (person), not 4 (organization)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Demo
|
|
87
|
+
|
|
88
|
+
The Citation tab of the [extract-pdf site](../extract-pdf/site) (`/demo#cite`) runs name recognition against news, journal, organization and blog URLs.
|
|
89
|
+
|
|
90
|
+
## Tests
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
cd packages/extract-human-name && bun run test
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Covers name parsing, byline splitting, organization detection (with and without the database), the `extractHumanName` citation forms, and the lazy loader (single shared request, failure fallback, retry, `/full` registration).
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safely partitions a comprehensive author byline into discrete individual names.
|
|
3
|
+
* Applies sophisticated token replacement and pattern recognition to discern standard splits.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} authorString - The source byline string (e.g., "John Doe, Jane Smith, and Alex Jones").
|
|
6
|
+
* @returns {string[]} An array containing individual un-parsed author name segments.
|
|
7
|
+
*/
|
|
8
|
+
export function splitMultipleAuthors(authorString: string): string[] {
|
|
9
|
+
if (!authorString) return [];
|
|
10
|
+
|
|
11
|
+
let normalized = authorString.replace(/\s+et\s+al\.?/gi, "");
|
|
12
|
+
|
|
13
|
+
// Pattern A: Last, First & Last, First Formats
|
|
14
|
+
if (/\w+,\s*\w+\s*&\s*\w+,\s*\w+/.test(normalized)) {
|
|
15
|
+
return normalized.split(/\s*&\s*/).filter(Boolean);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Pattern B: Mixed comma + "and" series
|
|
19
|
+
if (
|
|
20
|
+
/\w+,\s*\w+,\s*\w+,\s*\w+/.test(normalized) ||
|
|
21
|
+
/\w+\s\w+,\s\w+\s\w+/.test(normalized)
|
|
22
|
+
) {
|
|
23
|
+
normalized = normalized.replace(/,\s*(and|&)\s*(?=[^,]*$)/i, " & ");
|
|
24
|
+
return normalized
|
|
25
|
+
.split(/\s*(?:,|&)\s*(?=[^,]*(?:,|$))/)
|
|
26
|
+
.map((s) => s.trim())
|
|
27
|
+
.filter(Boolean);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Pattern C: Standard "and" / "&" conjunction block
|
|
31
|
+
if (/\w+\s\w+\s+(and|&)\s+\w+\s\w+/i.test(normalized)) {
|
|
32
|
+
return normalized
|
|
33
|
+
.split(/\s+(?:and|&)\s+/i)
|
|
34
|
+
.map((s) => s.trim())
|
|
35
|
+
.filter(Boolean);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Fallback broad delimiter standardisation
|
|
39
|
+
normalized = normalized
|
|
40
|
+
.replace(/\s+and\s+/gi, " & ")
|
|
41
|
+
// Break on semi-colons and commas not enclosed by parenthesis blocks (e.g., degrees parsing fallback).
|
|
42
|
+
.replace(/\s*[,;]\s*(?![^(]*\))/g, " & ");
|
|
43
|
+
|
|
44
|
+
return normalized.split(/\s*&\s*/).filter((a) => a.trim().length > 0);
|
|
45
|
+
}
|