extract-cite 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 +56 -0
- package/dist/extract-cite.cjs.js +2 -0
- package/dist/extract-cite.cjs.js.map +1 -0
- package/dist/extract-cite.es.js +4 -0
- package/dist/extract-cite.es.js.map +1 -0
- package/dist/html-to-cite/extract-author.d.ts +11 -0
- package/dist/html-to-cite/extract-cite.d.ts +33 -0
- package/dist/html-to-cite/extract-date/date-extractors.d.ts +40 -0
- package/dist/html-to-cite/extract-date/date-validators.d.ts +15 -0
- package/dist/html-to-cite/extract-date/extract-date-quick.d.ts +8 -0
- package/dist/html-to-cite/extract-date/extract-date.d.ts +26 -0
- package/dist/html-to-cite/extract-source.d.ts +11 -0
- package/dist/html-to-cite/extract-title.d.ts +11 -0
- package/dist/html-to-cite/human-names-recognize.d.ts +16 -0
- package/dist/html-to-cite/metadata-to-cite.d.ts +16 -0
- package/dist/html-to-cite/url-to-domain.d.ts +20 -0
- package/dist/index.d.ts +14 -0
- package/dist/llm/call-llm.d.ts +33 -0
- package/dist/llm/extract-cite-llm.d.ts +19 -0
- package/dist/llm/format-citation.d.ts +22 -0
- package/dist/llm/index.d.ts +7 -0
- package/dist/llm/page-text.d.ts +21 -0
- package/dist/llm/parse-reply.d.ts +26 -0
- package/dist/llm/prompt.d.ts +14 -0
- package/dist/llm/types.d.ts +115 -0
- package/package.json +61 -0
- package/src/html-to-cite/extract-author.ts +125 -0
- package/src/html-to-cite/extract-cite.ts +97 -0
- package/src/html-to-cite/extract-date/date-extractors.ts +509 -0
- package/src/html-to-cite/extract-date/date-validators.ts +191 -0
- package/src/html-to-cite/extract-date/extract-date-quick.ts +184 -0
- package/src/html-to-cite/extract-date/extract-date.ts +1051 -0
- package/src/html-to-cite/extract-source.ts +35 -0
- package/src/html-to-cite/extract-title.ts +78 -0
- package/src/html-to-cite/human-names-92k.json +1 -0
- package/src/html-to-cite/human-names-recognize.ts +396 -0
- package/src/html-to-cite/metadata-to-cite.ts +77 -0
- package/src/html-to-cite/url-to-domain.ts +50 -0
- package/src/index.ts +14 -0
- package/src/llm/call-llm.ts +112 -0
- package/src/llm/extract-cite-llm.ts +100 -0
- package/src/llm/format-citation.ts +399 -0
- package/src/llm/index.ts +7 -0
- package/src/llm/page-text.ts +106 -0
- package/src/llm/parse-reply.ts +279 -0
- package/src/llm/prompt.ts +95 -0
- package/src/llm/types.ts +174 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Utility for identifying and extracting author names from HTML metadata and content.
|
|
3
|
+
* Handles individual authors, multiple authors, and organizational authors.
|
|
4
|
+
*/
|
|
5
|
+
import { extractHumanName } from "./human-names-recognize";
|
|
6
|
+
|
|
7
|
+
// https://www.scribbr.com/citation/generator/folders/2rx21jyIjZIKRrcwLk2oXE/lists/4OeJQ4euxzyTk9BiPTRjwn/
|
|
8
|
+
const AUTHOR_META_TAGS = [
|
|
9
|
+
"byl",
|
|
10
|
+
"clmst",
|
|
11
|
+
"dc.author",
|
|
12
|
+
"dcsext.author",
|
|
13
|
+
"dc.creator",
|
|
14
|
+
"rbauthors",
|
|
15
|
+
"authors",
|
|
16
|
+
"sailthru.author",
|
|
17
|
+
"article:author",
|
|
18
|
+
"parsely-author",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const AUTHOR_SELECTORS = [
|
|
22
|
+
".entry .entry-author",
|
|
23
|
+
".author.vcard .fn",
|
|
24
|
+
".author .vcard .fn",
|
|
25
|
+
".byline.vcard .fn",
|
|
26
|
+
".byline .vcard .fn",
|
|
27
|
+
".byline .by .author",
|
|
28
|
+
".byline .by",
|
|
29
|
+
".byline .author",
|
|
30
|
+
".post-author.vcard",
|
|
31
|
+
".post-author .vcard",
|
|
32
|
+
"a[rel=author]",
|
|
33
|
+
"#by_author",
|
|
34
|
+
".by_author",
|
|
35
|
+
"#entryAuthor",
|
|
36
|
+
".entryAuthor",
|
|
37
|
+
".byline a[href*=author]",
|
|
38
|
+
"#author .authorname",
|
|
39
|
+
".author .authorname",
|
|
40
|
+
"#author",
|
|
41
|
+
".author",
|
|
42
|
+
".articleauthor",
|
|
43
|
+
".ArticleAuthor",
|
|
44
|
+
".byline",
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const AUTHOR_MAX_LENGTH = 300;
|
|
48
|
+
const BYLINE_REGEX = /^[\n\s]*By\s*:?\s*/i;
|
|
49
|
+
const CLEAN_AUTHOR_RE = /^\s*(posted |written )?by\s*:?\s*(.*)/i;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Extracts the author from the document and validates it as a human name
|
|
53
|
+
*
|
|
54
|
+
* @param {Document} document
|
|
55
|
+
* @returns {object|null} author_cite, author_short, author_type - or null if no valid author found
|
|
56
|
+
*/
|
|
57
|
+
export function extractAuthor(document) {
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
// 1. Check meta tags
|
|
61
|
+
for (const tag of AUTHOR_META_TAGS) {
|
|
62
|
+
const metaElement = document.querySelector(
|
|
63
|
+
`meta[name="${tag}"], meta[property="${tag}"]`
|
|
64
|
+
);
|
|
65
|
+
if (metaElement) {
|
|
66
|
+
const author = extractAndValidateHumanName(
|
|
67
|
+
metaElement.getAttribute("content")
|
|
68
|
+
);
|
|
69
|
+
if (author) return author;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 2. Check selectors
|
|
74
|
+
for (const selector of AUTHOR_SELECTORS) {
|
|
75
|
+
const element = document.querySelector(selector);
|
|
76
|
+
if (element) {
|
|
77
|
+
const author = extractAndValidateHumanName(element.textContent);
|
|
78
|
+
if (author) return author;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 4. Broader search in divs and spans
|
|
83
|
+
const elements = [
|
|
84
|
+
...document.getElementsByTagName("div"),
|
|
85
|
+
...document.getElementsByTagName("span"),
|
|
86
|
+
];
|
|
87
|
+
for (const element of elements) {
|
|
88
|
+
if (
|
|
89
|
+
element.id.match(/author|byline/i) ||
|
|
90
|
+
element?.className?.match(/author|byline/i)
|
|
91
|
+
) {
|
|
92
|
+
const author = extractAndValidateHumanName(element.innerText);
|
|
93
|
+
if (author) return author;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Helper function to clean and validate author string
|
|
102
|
+
const validateAuthor = (author) => {
|
|
103
|
+
if (!author) return null;
|
|
104
|
+
|
|
105
|
+
if (author.startsWith("@")) author = author.slice(1);
|
|
106
|
+
if (author.startsWith("http")) return null;
|
|
107
|
+
|
|
108
|
+
author = author?.replace(CLEAN_AUTHOR_RE, "$2").trim();
|
|
109
|
+
return author.length > 0 && author.length < AUTHOR_MAX_LENGTH
|
|
110
|
+
? author
|
|
111
|
+
: null;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Function to extract and validate human name
|
|
115
|
+
const extractAndValidateHumanName = (author) => {
|
|
116
|
+
const validatedAuthor = validateAuthor(author);
|
|
117
|
+
if (validatedAuthor) {
|
|
118
|
+
return extractHumanName(validatedAuthor, {
|
|
119
|
+
formatCiteShortenAuthor: false
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Orchestrator for extracting high-quality citations (author, date, title, source) from HTML.
|
|
3
|
+
* Validates names and sources against known patterns and benchmarks.
|
|
4
|
+
*/
|
|
5
|
+
import { parseHTML } from "linkedom";
|
|
6
|
+
import { extractAuthor } from "./extract-author";
|
|
7
|
+
import { extractDateQuick } from "./extract-date/extract-date-quick";
|
|
8
|
+
import { extractDate } from "./extract-date/extract-date";
|
|
9
|
+
import { extractSource } from "./extract-source";
|
|
10
|
+
import { extractTitle } from "./extract-title";
|
|
11
|
+
import { extractCiteFromMetadata } from "./metadata-to-cite";
|
|
12
|
+
import { extractHumanName } from "./human-names-recognize";
|
|
13
|
+
import { parseDate } from "chrono-node";
|
|
14
|
+
|
|
15
|
+
export interface ExtractCiteResult {
|
|
16
|
+
author?: string;
|
|
17
|
+
author_cite?: string;
|
|
18
|
+
date?: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
source?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ExtractCiteOptions {
|
|
24
|
+
url?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* ### \u1f4da\u1f48e Extract Expert Excerpt
|
|
29
|
+
* <img width="350px" src="https://i.imgur.com/4GOOM9s.jpeg" />
|
|
30
|
+
*
|
|
31
|
+
* Extract author, date, source, and title from HTML using meta tags
|
|
32
|
+
* and common class names. Validates human name from author string to check
|
|
33
|
+
* against common list of 90k first names, last names,and organizations to infer
|
|
34
|
+
* if it should be reversed starting by author last name (accounting for affixes/titles),
|
|
35
|
+
* since organizations are not reversed.
|
|
36
|
+
* [Article Extraction Benchmark](https://github.com/scrapinghub/article-extraction-benchmark?tab=readme-ov-file#results)
|
|
37
|
+
* @param {Document | string} document dom object or html string with article content
|
|
38
|
+
* @param {ExtractCiteOptions} [options={}]
|
|
39
|
+
* @returns {ExtractCiteResult | null} An object containing extracted citation information.
|
|
40
|
+
* @category Extract
|
|
41
|
+
* @author [vtempest (2025)](https://github.com/vtempest)
|
|
42
|
+
*/
|
|
43
|
+
export function extractCite(
|
|
44
|
+
document: Document | string,
|
|
45
|
+
options: ExtractCiteOptions = {}
|
|
46
|
+
) {
|
|
47
|
+
const { url = "" } = options;
|
|
48
|
+
|
|
49
|
+
if (!document) return null;
|
|
50
|
+
|
|
51
|
+
//if passing in html string, convert to dom object
|
|
52
|
+
if (typeof document === "string") document = parseHTML(document)?.document;
|
|
53
|
+
|
|
54
|
+
var { author, date, title, source } = extractCiteFromMetadata(document);
|
|
55
|
+
|
|
56
|
+
if (author?.length < 3 || author?.length > 100) author = null;
|
|
57
|
+
|
|
58
|
+
var { author_cite, author_short, author_type } =
|
|
59
|
+
extractAuthor(document) || extractHumanName(author);
|
|
60
|
+
|
|
61
|
+
date = extractDateQuick(document, url) || date;
|
|
62
|
+
|
|
63
|
+
//extract from ways of writing dates in natural language into standard format
|
|
64
|
+
date = parseDate(date)?.toISOString().split("T")[0] || null;
|
|
65
|
+
|
|
66
|
+
if (!date)
|
|
67
|
+
try {
|
|
68
|
+
date = extractDate(
|
|
69
|
+
document,
|
|
70
|
+
true,
|
|
71
|
+
true,
|
|
72
|
+
"%Y-%m-%d",
|
|
73
|
+
url,
|
|
74
|
+
false,
|
|
75
|
+
new Date("2002-01-01")
|
|
76
|
+
);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.log(e);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
date = parseDate(date)?.toISOString().split("T")[0] || null;
|
|
82
|
+
|
|
83
|
+
title = extractTitle(document) || title;
|
|
84
|
+
source = extractSource(document) || source;
|
|
85
|
+
|
|
86
|
+
// URL TO SOURCE
|
|
87
|
+
if (!source && url?.length > 20)
|
|
88
|
+
source = url
|
|
89
|
+
.split("//")[1]
|
|
90
|
+
.split("/")[0]
|
|
91
|
+
?.replace("www.", "")
|
|
92
|
+
.split(" ")
|
|
93
|
+
.map((word) => word[0].toUpperCase() + word.slice(1))
|
|
94
|
+
.join(" ");
|
|
95
|
+
|
|
96
|
+
return { author, author_cite, date, title, source };
|
|
97
|
+
}
|