extract-webpage 1.2.5

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.
Files changed (117) hide show
  1. package/README.md +212 -0
  2. package/dist/config/env.d.ts +6 -0
  3. package/dist/config/index.d.ts +23 -0
  4. package/dist/config/serverRegistry.d.ts +7 -0
  5. package/dist/config/types.d.ts +4 -0
  6. package/dist/extract-webpage.cjs.js +2 -0
  7. package/dist/extract-webpage.cjs.js.map +1 -0
  8. package/dist/extract-webpage.es.js +5 -0
  9. package/dist/extract-webpage.es.js.map +1 -0
  10. package/dist/html-to-cite/extract-author.d.ts +11 -0
  11. package/dist/html-to-cite/extract-cite.d.ts +33 -0
  12. package/dist/html-to-cite/extract-date/date-extractors.d.ts +40 -0
  13. package/dist/html-to-cite/extract-date/date-validators.d.ts +15 -0
  14. package/dist/html-to-cite/extract-date/extract-date-quick.d.ts +8 -0
  15. package/dist/html-to-cite/extract-date/extract-date.d.ts +26 -0
  16. package/dist/html-to-cite/extract-source.d.ts +7 -0
  17. package/dist/html-to-cite/extract-title.d.ts +11 -0
  18. package/dist/html-to-cite/human-names-recognize.d.ts +16 -0
  19. package/dist/html-to-cite/metadata-to-cite.d.ts +12 -0
  20. package/dist/html-to-cite/url-to-domain.d.ts +20 -0
  21. package/dist/html-to-content/extract-content/extract-content-mercury-utils.d.ts +27 -0
  22. package/dist/html-to-content/extract-content/extract-content-mercury.d.ts +61 -0
  23. package/dist/html-to-content/extract-content/extract-content-readability.d.ts +101 -0
  24. package/dist/html-to-content/html-to-basic-html.d.ts +36 -0
  25. package/dist/html-to-content/html-to-content.d.ts +51 -0
  26. package/dist/html-to-content/html-utils.d.ts +76 -0
  27. package/dist/index.d.ts +26 -0
  28. package/dist/search/index.d.ts +14 -0
  29. package/dist/search/meta-search-agent-reexport.d.ts +8 -0
  30. package/dist/search/public-searxng.d.ts +47 -0
  31. package/dist/search/search-web.d.ts +33 -0
  32. package/dist/search/tavily.d.ts +20 -0
  33. package/dist/search/url-to-html.d.ts +62 -0
  34. package/dist/seektopic/fold-keyphrases.d.ts +28 -0
  35. package/dist/seektopic/ngrams.d.ts +27 -0
  36. package/dist/seektopic/rank-sentences-keyphrases.d.ts +28 -0
  37. package/dist/seektopic/seektopic-keyphrases.d.ts +53 -0
  38. package/dist/seektopic/types.d.ts +86 -0
  39. package/dist/seektopic/vector-search.d.ts +89 -0
  40. package/dist/seektopic/weight-keyphrases.d.ts +22 -0
  41. package/dist/suggest-next-words/autocomplete-ai.d.ts +0 -0
  42. package/dist/suggest-next-words/autocomplete-search-engines.d.ts +64 -0
  43. package/dist/tokenize/suggest-complete-word.d.ts +48 -0
  44. package/dist/tokenize/text-to-chunks.d.ts +48 -0
  45. package/dist/tokenize/text-to-sentences.d.ts +35 -0
  46. package/dist/tokenize/text-to-topic-tokens.d.ts +51 -0
  47. package/dist/tokenize/word-is-ignored.d.ts +12 -0
  48. package/dist/tokenize/word-to-root-stem.d.ts +16 -0
  49. package/dist/url-to-content/docx-to-content.d.ts +22 -0
  50. package/dist/url-to-content/is-url-adult.d.ts +26 -0
  51. package/dist/url-to-content/url-to-content.d.ts +127 -0
  52. package/dist/url-to-content/url-to-html.d.ts +60 -0
  53. package/dist/url-to-content/youtube-helpers.d.ts +23 -0
  54. package/dist/url-to-content/youtube-to-text.d.ts +70 -0
  55. package/dist/utils/documents.d.ts +4 -0
  56. package/dist/utils/grab.d.ts +18 -0
  57. package/package.json +109 -0
  58. package/src/config/env.ts +8 -0
  59. package/src/config/index.ts +233 -0
  60. package/src/config/serverRegistry.ts +24 -0
  61. package/src/config/types.ts +17 -0
  62. package/src/fs-mock.js +22 -0
  63. package/src/global.d.ts +8 -0
  64. package/src/html-to-cite/extract-author.ts +125 -0
  65. package/src/html-to-cite/extract-cite.ts +97 -0
  66. package/src/html-to-cite/extract-date/date-extractors.ts +484 -0
  67. package/src/html-to-cite/extract-date/date-validators.ts +191 -0
  68. package/src/html-to-cite/extract-date/extract-date-quick.ts +184 -0
  69. package/src/html-to-cite/extract-date/extract-date.ts +1049 -0
  70. package/src/html-to-cite/extract-source.ts +30 -0
  71. package/src/html-to-cite/extract-title.ts +78 -0
  72. package/src/html-to-cite/human-names-92k.json +1 -0
  73. package/src/html-to-cite/human-names-recognize.ts +396 -0
  74. package/src/html-to-cite/metadata-to-cite.ts +73 -0
  75. package/src/html-to-cite/url-to-domain.ts +50 -0
  76. package/src/html-to-content/extract-content/extract-content-mercury-utils.ts +696 -0
  77. package/src/html-to-content/extract-content/extract-content-mercury.ts +830 -0
  78. package/src/html-to-content/extract-content/extract-content-readability.ts +432 -0
  79. package/src/html-to-content/extract-content/extract-selectors-per-domain.json +3453 -0
  80. package/src/html-to-content/html-to-basic-html.ts +282 -0
  81. package/src/html-to-content/html-to-content.ts +97 -0
  82. package/src/html-to-content/html-utils.ts +398 -0
  83. package/src/index.ts +29 -0
  84. package/src/search/__tests__/public-searxng.test.ts +529 -0
  85. package/src/search/index.ts +43 -0
  86. package/src/search/meta-search-agent-reexport.ts +38 -0
  87. package/src/search/public-searxng.ts +470 -0
  88. package/src/search/search-web.ts +668 -0
  89. package/src/search/tavily.ts +106 -0
  90. package/src/search/url-to-html.ts +278 -0
  91. package/src/seektopic/fold-keyphrases.ts +87 -0
  92. package/src/seektopic/ngrams.ts +64 -0
  93. package/src/seektopic/rank-sentences-keyphrases.ts +132 -0
  94. package/src/seektopic/seektopic-keyphrases.ts +279 -0
  95. package/src/seektopic/types.ts +92 -0
  96. package/src/seektopic/vector-search.ts +232 -0
  97. package/src/seektopic/weight-keyphrases.ts +59 -0
  98. package/src/suggest-next-words/autocomplete-ai.ts +38 -0
  99. package/src/suggest-next-words/autocomplete-search-engines.ts +435 -0
  100. package/src/tokenize/suggest-complete-word.ts +137 -0
  101. package/src/tokenize/text-to-chunks.ts +150 -0
  102. package/src/tokenize/text-to-sentences.ts +614 -0
  103. package/src/tokenize/text-to-topic-tokens.ts +175 -0
  104. package/src/tokenize/word-is-ignored.ts +53 -0
  105. package/src/tokenize/word-to-root-stem.ts +151 -0
  106. package/src/types.d.ts +130 -0
  107. package/src/url-to-content/.fuse_hidden003bd28a0000000d +332 -0
  108. package/src/url-to-content/__tests__/url-to-content.test.ts +368 -0
  109. package/src/url-to-content/__tests__/url-to-html.test.ts +301 -0
  110. package/src/url-to-content/docx-to-content.ts +702 -0
  111. package/src/url-to-content/is-url-adult.ts +318 -0
  112. package/src/url-to-content/url-to-content.ts +367 -0
  113. package/src/url-to-content/url-to-html.ts +436 -0
  114. package/src/url-to-content/youtube-helpers.ts +64 -0
  115. package/src/url-to-content/youtube-to-text.ts +468 -0
  116. package/src/utils/documents.ts +71 -0
  117. package/src/utils/grab.ts +51 -0
@@ -0,0 +1,484 @@
1
+ /**
2
+ * @fileoverview Lower-level date extraction primitives and regex patterns.
3
+ * Provides specialized extractors for URLs, JSON-LD, and idiosyncratic text patterns.
4
+ */
5
+ import { convert_date, is_valid_date } from './date-validators';
6
+
7
+
8
+ var DEBUG = 0;
9
+
10
+ const LOGGER = DEBUG ? console : { debug: () => { }, error: () => { } };
11
+
12
+
13
+ const TEI_VALID_TAGS = new Set(['ab', 'body', 'cell', 'code', 'del', 'div', 'graphic', 'head', 'hi',
14
+ 'item', 'lb', 'list', 'p', 'quote', 'ref', 'row', 'table']);
15
+ const TEI_VALID_ATTRS = new Set(['rend', 'rendition', 'role', 'target', 'type']);
16
+ const TEI_REMOVE_TAIL = new Set(["ab", "p"]);
17
+ const TEI_DIV_SIBLINGS = new Set(["p", "list", "table", "quote", "ab"]);
18
+
19
+ const NEWLINE_ELEMS = new Set(['code', 'graphic', 'head', 'lb', 'list', 'p', 'quote', 'row', 'table']);
20
+ const SPECIAL_FORMATTING = new Set(['del', 'head', 'hi', 'ref']);
21
+ const WITH_ATTRIBUTES = new Set(['cell', 'row', 'del', 'graphic', 'head', 'hi', 'item', 'list', 'ref']);
22
+ const NESTING_WHITELIST = new Set(["cell", "figure", "item", "note", "quote"]);
23
+
24
+ const META_ATTRIBUTES = [
25
+ 'sitename', 'title', 'author', 'date', 'url', 'hostname',
26
+ 'description', 'categories', 'tags', 'license', 'id',
27
+ 'fingerprint', 'language'
28
+ ];
29
+
30
+ const HI_FORMATTING = { '#b': '**', '#i': '*', '#u': '__', '#t': '`' };
31
+
32
+ const MAX_TABLE_WIDTH = 1000;
33
+ // Note: FAST_PREPEND would need to be defined separately
34
+ const FAST_PREPEND = ''; // Define your FAST_PREPEND value here
35
+
36
+ // Segment length constraints
37
+ const MIN_SEGMENT_LEN = 6;
38
+ const MAX_SEGMENT_LEN = 52;
39
+
40
+ const DISCARD_EXPRESSIONS = './/div[@id="wm-ipp-base" or @id="wm-ipp"]';
41
+
42
+
43
+ // Alternative JavaScript-based selector approach (faster in some browsers)
44
+ const DATE_EXPRESSIONS = [
45
+ '[id*="date" i]',
46
+ '[class*="date" i]',
47
+ '[itemprop*="date" i]',
48
+ '[id*="datum" i]',
49
+ '[class*="datum" i]',
50
+ '[itemprop*="datum" i]',
51
+ '[id*="meta" i]',
52
+ '[class*="meta" i]',
53
+ '[id*="time" i]',
54
+ '[class*="time" i]',
55
+ '[id*="publish" i]',
56
+ '[class*="publish" i]',
57
+ '[id*="footer" i]',
58
+ '[class*="footer" i]',
59
+ '[class*="info" i]',
60
+ '[class*="post_detail" i]',
61
+ '[class*="block-content" i]',
62
+ '[class*="byline" i]',
63
+ '[class*="subline" i]',
64
+ '[class*="posted" i]',
65
+ '[class*="submitted" i]',
66
+ '[class*="created-post" i]',
67
+ '[class*="publication" i]',
68
+ '[class*="author" i]',
69
+ '[class*="autor" i]',
70
+ '[class*="field-content" i]',
71
+ '[class*="fa-clock-o" i]',
72
+ '[class*="fa-calendar" i]',
73
+ '[class*="fecha" i]',
74
+ '[class*="parution" i]'
75
+ ].join(',');
76
+
77
+ // XPath-style expressions
78
+ const SLOW_PREPEND = "";
79
+
80
+ const DAY_RE = "[0-3]?[0-9]";
81
+ const MONTH_RE = "[0-1]?[0-9]";
82
+ const YEAR_RE = "199[0-9]|20[0-3][0-9]";
83
+
84
+ const FREE_TEXT_EXPRESSIONS = ".//*[self::div or self::h2 or self::h3 or self::h4 or self::li or self::p or self::span or self::time or self::ul]/text()";
85
+
86
+ // Core regex patterns
87
+ const THREE_COMP_REGEX_A = new RegExp(`(${DAY_RE})[/.-](${MONTH_RE})[/.-](${YEAR_RE})`);
88
+ const THREE_COMP_REGEX_B = new RegExp(`(${DAY_RE})/(${MONTH_RE})/([0-9]{2})|(${DAY_RE})[.-](${MONTH_RE})[.-]([0-9]{2})`);
89
+ const TWO_COMP_REGEX = new RegExp(`(${MONTH_RE})[/.-](${YEAR_RE})`);
90
+
91
+ // Search patterns
92
+ const YEAR_PATTERN = new RegExp(`^\\D?(${YEAR_RE})`);
93
+ const COPYRIGHT_PATTERN = new RegExp(`(?:\u00a9|\\©|Copyright|\\(c\\))\\D*(?:${YEAR_RE})?-?(${YEAR_RE})\\D`);
94
+ const THREE_PATTERN = new RegExp("/([0-9]{4}/[0-9]{2}/[0-9]{2})[01/]");
95
+ const THREE_CATCH = new RegExp("([0-9]{4})/([0-9]{2})/([0-9]{2})");
96
+ const THREE_LOOSE_PATTERN = new RegExp("\\D([0-9]{4}[/.-][0-9]{2}[/.-][0-9]{2})\\D");
97
+ const THREE_LOOSE_CATCH = new RegExp("([0-9]{4})[/.-]([0-9]{2})[/.-]([0-9]{2})");
98
+ const SELECT_YMD_PATTERN = new RegExp("\\D([0-3]?[0-9][/.-][01]?[0-9][/.-][0-9]{4})\\D");
99
+ const SELECT_YMD_YEAR = new RegExp(`(${YEAR_RE})\\D?$`);
100
+ const YMD_YEAR = new RegExp(`^(${YEAR_RE})`);
101
+ const DATESTRINGS_PATTERN = new RegExp("\\D(?:19[0-9]{2}|20[0-9]{2})[01][0-9][0-3][0-9]\\D");
102
+ const DATESTRINGS_CATCH = new RegExp(`(${YEAR_RE})([01][0-9])([0-3][0-9])`);
103
+ const SLASHES_PATTERN = new RegExp("\\D([0-3]?[0-9]/[01]?[0-9]/[0129][0-9]|[0-3][0-9]\\.[01][0-9]\\.[0129][0-9])\\D");
104
+ const SLASHES_YEAR = new RegExp("([0-9]{2})$");
105
+ const YYYYMM_PATTERN = new RegExp("\\D([12][0-9]{3}[/.-](?:1[0-2]|0[1-9]))\\D");
106
+ const YYYYMM_CATCH = new RegExp(`(${YEAR_RE})[/.-](1[0-2]|0[1-9]|)`);
107
+ const MMYYYY_PATTERN = new RegExp("\\D([01]?[0-9][/.-][12][0-9]{3})\\D");
108
+ const MMYYYY_YEAR = new RegExp(`(${YEAR_RE})\\D?$`);
109
+ const SIMPLE_PATTERN = new RegExp(`(?<!w3.org)\\D(${YEAR_RE})\\D`);
110
+
111
+
112
+ const YMD_NO_SEP_PATTERN = /\b(\d{8})\b/;
113
+ const YMD_PATTERN = new RegExp(
114
+ `(?:\\D|^)(?:(?<year>${YEAR_RE})[\-/.](?<month>${MONTH_RE})[\-/.](?<day>${DAY_RE})|` +
115
+ `(?<day2>${DAY_RE})[\-/.](?<month2>${MONTH_RE})[\-/.](?<year2>\\d{2,4}))(?:\\D|$)`
116
+ );
117
+ const YM_PATTERN = new RegExp(
118
+ `(?:\\D|^)(?:(?<year>${YEAR_RE})[\-/.](?<month>${MONTH_RE})|` +
119
+ `(?<month2>${MONTH_RE})[\-/.](?<year2>${YEAR_RE}))(?:\\D|$)`
120
+ );
121
+
122
+ const REGEX_MONTHS = `
123
+ January?|February?|March|A[pv]ril|Ma[iy]|Jun[ei]|Jul[iy]|August|September|O[ck]tober|November|De[csz]ember|
124
+ Jan|Feb|M[a\u00e4]r|Apr|Jun|Jul|Aug|Sep|O[ck]t|Nov|De[cz]|
125
+ Januari|Februari|Maret|Mei|Agustus|
126
+ J\u00e4nner|Feber|M\u00e4rz|
127
+ janvier|f\u00e9vrier|mars|juin|juillet|aout|septembre|octobre|novembre|d\u00e9cembre|
128
+ Ocak|\u015eubat|Mart|Nisan|May\u0131s|Haziran|Temmuz|A\u011fustos|Eyl\u00fcl|Ekim|Kas\u0131m|Aral\u0131k|
129
+ Oca|\u015eub|Mar|Nis|Haz|Tem|A\u011fu|Eyl|Eki|Kas|Ara
130
+ `.replace(/\n/g, '');
131
+
132
+ const LONG_TEXT_PATTERN = new RegExp(
133
+ `(?<month>${REGEX_MONTHS})\\s` +
134
+ `(?<day>${DAY_RE})(?:st|nd|rd|th)?,? (?<year>${YEAR_RE})|` +
135
+ `(?<day2>${DAY_RE})(?:st|nd|rd|th|\\.)? (?:of )?` +
136
+ `(?<month2>${REGEX_MONTHS})[,.]? (?<year2>${YEAR_RE})`,
137
+ 'i'
138
+ );
139
+
140
+ const COMPLETE_URL = new RegExp(`\\D(${YEAR_RE})[/_-](${MONTH_RE})[/_-](${DAY_RE})(?:\\D|$)`);
141
+
142
+ const JSON_MODIFIED = new RegExp(`"dateModified": ?"(${YEAR_RE}-${MONTH_RE}-${DAY_RE})"`, 'i');
143
+ const JSON_PUBLISHED = new RegExp(`"datePublished": ?"(${YEAR_RE}-${MONTH_RE}-${DAY_RE})"`, 'i');
144
+ const TIMESTAMP_PATTERN = new RegExp(`(${YEAR_RE}-${MONTH_RE}-${DAY_RE}).[0-9]{2}:[0-9]{2}:[0-9]{2}`);
145
+
146
+ const MONTHS = [
147
+ ["jan", "januar", "j\u00e4nner", "january", "januari", "janvier", "ocak", "oca"],
148
+ ["feb", "februar", "feber", "february", "februari", "f\u00e9vrier", "\u015fubat", "\u015fub"],
149
+ ["mar", "m\u00e4r", "m\u00e4rz", "march", "maret", "mart", "mars"],
150
+ ["apr", "april", "avril", "nisan", "nis"],
151
+ ["may", "mai", "mei", "may\u0131s"],
152
+ ["jun", "juni", "june", "juin", "haziran", "haz"],
153
+ ["jul", "juli", "july", "juillet", "temmuz", "tem"],
154
+ ["aug", "august", "agustus", "a\u011fustos", "a\u011fu", "aout"],
155
+ ["sep", "september", "septembre", "eyl\u00fcl", "eyl"],
156
+ ["oct", "oktober", "october", "octobre", "okt", "ekim", "eki"],
157
+ ["nov", "november", "kas\u0131m", "kas", "novembre"],
158
+ ["dec", "dez", "dezember", "december", "desember", "d\u00e9cembre", "aral\u0131k", "ara"],
159
+ ];
160
+
161
+ const TEXT_MONTHS = {};
162
+ MONTHS.forEach((monthList, index) => {
163
+ monthList.forEach(month => {
164
+ TEXT_MONTHS[month] = index + 1;
165
+ });
166
+ });
167
+
168
+ const TEXT_DATE_PATTERN = /[.:,_/ -]|^\d+$/;
169
+
170
+ const DISCARD_PATTERNS = new RegExp(
171
+ "^\\d{2}:\\d{2}(?: |:|$)|" +
172
+ "^\\D*\\d{4}\\D*$|" +
173
+ "[$\u20ac\u00a5\u04b0\u00a3\u00a2\u20bd\u20b1\u0e3f#\u20b9]|" +
174
+ "[A-Z]{3}[^A-Z]|" +
175
+ "(?:^|\\D)(?:\\+\\d{2}|\\d{3}|\\d{5})\\D|" +
176
+ "ftps?|https?|sftp|" +
177
+ "\\.(?:com|net|org|info|gov|edu|de|fr|io)\\b|" +
178
+ "IBAN|[A-Z]{2}[0-9]{2}|" +
179
+ "\u00ae"
180
+ );
181
+
182
+ const TEXT_PATTERNS = new RegExp(
183
+ '(?:date[^0-9"]{,20}|updated|published|on)(?:[ :])*?([0-9]{1,4})[./]([0-9]{1,2})[./]([0-9]{2,4})|' +
184
+ "(?:Datum|Stand|Ver\u00f6ffentlicht am):? ?([0-9]{1,2})\\.([0-9]{1,2})\\.([0-9]{2,4})|" +
185
+ "(?:g\u00fcncellen?me|yay\u0131(?:m|n)lan?ma) *?(?:tarihi)? *?:? *?([0-9]{1,2})[./]([0-9]{1,2})[./]([0-9]{2,4})|" +
186
+ "([0-9]{1,2})[./]([0-9]{1,2})[./]([0-9]{2,4}) *?(?:'de|'da|'te|'ta|'de|'da|'te|'ta|tarihinde) *(?:g\u00fcncellendi|yay\u0131(?:m|n)land\u0131)",
187
+ "i"
188
+ );
189
+
190
+ function discard_unwanted(tree) {
191
+ const myDiscarded = [];
192
+ const discardElements = tree.querySelectorAll('#wm-ipp-base, #wm-ipp');
193
+ discardElements.forEach(element => {
194
+ myDiscarded.push(element);
195
+ element.parentNode.removeChild(element);
196
+ });
197
+ return [tree, myDiscarded];
198
+ }
199
+
200
+ function extract_url_date(testurl, options) {
201
+ if (testurl) {
202
+ const match = COMPLETE_URL.exec(testurl);
203
+ if (match) {
204
+ LOGGER.debug(`found date in URL: ${match[0]}`);
205
+ try {
206
+ const dateObject = new Date(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]));
207
+ if (is_valid_date(dateObject, options.format, options.min, options.max)) {
208
+ return dateObject.toISOString().slice(0, 10);
209
+ }
210
+ } catch (err) {
211
+ LOGGER.debug(`conversion error: ${match[0]} ${err}`);
212
+ }
213
+ }
214
+ }
215
+ return null;
216
+ }
217
+
218
+ function correct_year(year) {
219
+ if (year < 100) {
220
+ return year + (year >= 90 ? 1900 : 2000);
221
+ }
222
+ return year;
223
+ }
224
+
225
+ function try_swap_values(day, month) {
226
+ return month > 12 && day <= 12 ? [month, day] : [day, month];
227
+ }
228
+
229
+ function regex_parse(string) {
230
+ const match = LONG_TEXT_PATTERN.exec(string);
231
+ if (!match) {
232
+ return null;
233
+ }
234
+ const groups = match.groups.year ? ['day', 'month', 'year'] : ['day2', 'month2', 'year2'];
235
+ try {
236
+ let [day, month, year] = [
237
+ parseInt(match.groups[groups[0]]),
238
+ TEXT_MONTHS[match.groups[groups[1]]?.toLowerCase().replace(/\.$/, '')],
239
+ parseInt(match.groups[groups[2]])
240
+ ];
241
+ year = correct_year(year);
242
+ [day, month] = try_swap_values(day, month);
243
+ const dateObject = new Date(year, month - 1, day);
244
+ LOGGER.debug(`multilingual text found: ${dateObject}`);
245
+ return dateObject;
246
+ } catch (err) {
247
+ return null;
248
+ }
249
+ }
250
+
251
+ function custom_parse(string, outputformat, min_date, max_date) {
252
+ LOGGER.debug(`custom parse test: ${string}`);
253
+
254
+ if (string.slice(0, 4).match(/^\d{4}$/)) {
255
+ let candidate = null;
256
+ if (string.slice(4, 8).match(/^\d{4}$/)) {
257
+ try {
258
+ candidate = new Date(parseInt(string.slice(0, 4)), parseInt(string.slice(4, 6)) - 1, parseInt(string.slice(6, 8)));
259
+ } catch (err) {
260
+ LOGGER.debug(`8-digit error: ${string.slice(0, 8)}`);
261
+ }
262
+ } else {
263
+ try {
264
+ candidate = new Date(string);
265
+ } catch (err) {
266
+ LOGGER.debug(`not an ISO date string: ${string}`);
267
+ try {
268
+ candidate = new Date(Date.parse(string));
269
+ } catch (err) {
270
+ LOGGER.debug(`dateutil parsing error: ${string}`);
271
+ }
272
+ }
273
+ }
274
+ if (candidate && is_valid_date(candidate, outputformat, min_date, max_date)) {
275
+ LOGGER.debug(`parsing result: ${candidate}`);
276
+ return candidate.toISOString().slice(0, 10);
277
+ }
278
+ }
279
+
280
+ const match = YMD_NO_SEP_PATTERN.exec(string);
281
+ if (match) {
282
+ try {
283
+ const [year, month, day] = [parseInt(match[1].slice(0, 4)), parseInt(match[1].slice(4, 6)), parseInt(match[1].slice(6, 8))];
284
+ const candidate = new Date(year, month - 1, day);
285
+ if (is_valid_date(candidate, '%Y-%m-%d', min_date, max_date)) {
286
+ LOGGER.debug(`YYYYMMDD match: ${candidate}`);
287
+ return candidate.toISOString().slice(0, 10);
288
+ }
289
+ } catch (err) {
290
+ LOGGER.debug(`YYYYMMDD value error: ${match[0]}`);
291
+ }
292
+ }
293
+
294
+ const ymdMatch = YMD_PATTERN.exec(string);
295
+ if (ymdMatch) {
296
+ try {
297
+ let year, month, day;
298
+ if (ymdMatch.groups.day) {
299
+ [year, month, day] = [parseInt(ymdMatch.groups.year), parseInt(ymdMatch.groups.month), parseInt(ymdMatch.groups.day)];
300
+ } else {
301
+ [day, month, year] = [parseInt(ymdMatch.groups.day2), parseInt(ymdMatch.groups.month2), parseInt(ymdMatch.groups.year2)];
302
+ year = correct_year(year);
303
+ [day, month] = try_swap_values(day, month);
304
+ }
305
+ const candidate = new Date(year, month - 1, day);
306
+ if (is_valid_date(candidate, '%Y-%m-%d', min_date, max_date)) {
307
+ LOGGER.debug(`regex match: ${candidate}`);
308
+ return candidate.toISOString().slice(0, 10);
309
+ }
310
+ } catch (err) {
311
+ LOGGER.debug(`regex value error: ${ymdMatch[0]}`);
312
+ }
313
+ }
314
+
315
+ const ymMatch = YM_PATTERN.exec(string);
316
+ if (ymMatch) {
317
+ try {
318
+ let year, month;
319
+ if (ymMatch.groups.month) {
320
+ [year, month] = [parseInt(ymMatch.groups.year), parseInt(ymMatch.groups.month)];
321
+ } else {
322
+ [year, month] = [parseInt(ymMatch.groups.year2), parseInt(ymMatch.groups.month2)];
323
+ }
324
+ const candidate = new Date(year, month - 1, 1);
325
+ if (is_valid_date(candidate, '%Y-%m-%d', min_date, max_date)) {
326
+ LOGGER.debug(`Y-M match: ${candidate}`);
327
+ return candidate.toISOString().slice(0, 10);
328
+ }
329
+ } catch (err) {
330
+ LOGGER.debug(`Y-M value error: ${ymMatch[0]}`);
331
+ }
332
+ }
333
+
334
+ const dateObject = regex_parse(string);
335
+ if (is_valid_date(dateObject, outputformat, min_date, max_date)) {
336
+ try {
337
+ LOGGER.debug(`custom parse result: ${dateObject}`);
338
+ return dateObject.toISOString().slice(0, 10);
339
+ } catch (err) {
340
+ LOGGER.error(`value error during conversion: ${string} ${err}`);
341
+ }
342
+ }
343
+
344
+ return null;
345
+ }
346
+ function external_date_parser(string, outputformat) {
347
+ LOGGER.debug(`send to external parser: ${string}`);
348
+ try {
349
+ const target = new Date(string);
350
+ if (isNaN(target.getTime())) {
351
+ return null;
352
+ }
353
+ return target.toISOString().slice(0, 10);
354
+ } catch (err) {
355
+ LOGGER.error(`external parser error: ${string} ${err}`);
356
+ return null;
357
+ }
358
+ }
359
+
360
+ function try_date_expr(string, outputformat, extensive_search, min_date, max_date) {
361
+ if (!string) {
362
+ return null;
363
+ }
364
+
365
+ string = string.trim().slice(0, MAX_SEGMENT_LEN);
366
+
367
+ const digitCount = string.split('').filter(char => /\d/.test(char)).length;
368
+ if (!string || !(4 <= digitCount && digitCount <= 18)) {
369
+ return null;
370
+ }
371
+
372
+ if (DISCARD_PATTERNS.test(string)) {
373
+ return null;
374
+ }
375
+
376
+ const customresult = custom_parse(string, outputformat, min_date, max_date);
377
+ if (customresult !== null) {
378
+ return customresult;
379
+ }
380
+
381
+ if (extensive_search && TEXT_DATE_PATTERN.test(string)) {
382
+ const dateparserResult = external_date_parser(string, outputformat);
383
+ if (is_valid_date(dateparserResult, outputformat, min_date, max_date)) {
384
+ return dateparserResult;
385
+ }
386
+ }
387
+
388
+ return null;
389
+ }
390
+
391
+ function img_search(tree, options) {
392
+ const element = tree.querySelector('meta[property="og:image"][content]');
393
+ if (element !== null) {
394
+ return extract_url_date(element.getAttribute("content"), options);
395
+ }
396
+ return null;
397
+ }
398
+
399
+ function pattern_search(text, date_pattern, options) {
400
+ const match = date_pattern.exec(text);
401
+ if (match && is_valid_date(match[1], "%Y-%m-%d", options.min, options.max)) {
402
+ LOGGER.debug(`regex found: ${date_pattern} ${match[0]}`);
403
+ return convert_date(match[1], "%Y-%m-%d", options.format);
404
+ }
405
+ return null;
406
+ }
407
+
408
+ function json_search(tree, options) {
409
+ // const json_pattern = options.original ? JSON_PUBLISHED : JSON_MODIFIED;
410
+ const json_pattern = JSON_MODIFIED;
411
+ const elements = tree.querySelectorAll('script[type="application/ld+json"], script[type="application/settings+json"]');
412
+ for (const elem of elements) {
413
+ if (!elem.textContent || !elem.textContent.includes('"date')) {
414
+ continue;
415
+ }
416
+ return pattern_search(elem.textContent, json_pattern, options);
417
+ }
418
+ return null;
419
+ }
420
+
421
+ function idiosyncrasies_search(htmlstring, options) {
422
+ const match = TEXT_PATTERNS.exec(htmlstring);
423
+ if (match) {
424
+ const parts = match.slice(1).filter(Boolean);
425
+ try {
426
+ let candidate;
427
+ if (parts[0].length === 4) {
428
+ candidate = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));
429
+ } else {
430
+ let [day, month] = try_swap_values(parseInt(parts[0]), parseInt(parts[1]));
431
+ let year = correct_year(parseInt(parts[2]));
432
+ candidate = new Date(year, month - 1, day);
433
+ }
434
+ if (is_valid_date(candidate, "%Y-%m-%d", options.min, options.max)) {
435
+ return candidate.toISOString().slice(0, 10);
436
+ }
437
+ } catch (err) {
438
+ LOGGER.debug(`cannot process idiosyncrasies: ${match[0]}`);
439
+ }
440
+ }
441
+ return null;
442
+ }
443
+
444
+ export {
445
+ discard_unwanted,
446
+ extract_url_date,
447
+ regex_parse,
448
+ custom_parse,
449
+ external_date_parser,
450
+ try_date_expr,
451
+ img_search,
452
+ pattern_search,
453
+ json_search,
454
+ idiosyncrasies_search,
455
+ DATE_EXPRESSIONS,
456
+ FAST_PREPEND,
457
+ SLOW_PREPEND,
458
+ FREE_TEXT_EXPRESSIONS,
459
+ MAX_SEGMENT_LEN,
460
+ MIN_SEGMENT_LEN,
461
+ YEAR_PATTERN,
462
+ YMD_PATTERN,
463
+ COPYRIGHT_PATTERN,
464
+ TIMESTAMP_PATTERN,
465
+ THREE_PATTERN,
466
+ THREE_CATCH,
467
+ THREE_LOOSE_PATTERN,
468
+ THREE_LOOSE_CATCH,
469
+ SELECT_YMD_PATTERN,
470
+ SELECT_YMD_YEAR,
471
+ YMD_YEAR,
472
+ DATESTRINGS_PATTERN,
473
+ DATESTRINGS_CATCH,
474
+ SLASHES_PATTERN,
475
+ SLASHES_YEAR,
476
+ YYYYMM_PATTERN,
477
+ YYYYMM_CATCH,
478
+ MMYYYY_PATTERN,
479
+ MMYYYY_YEAR,
480
+ SIMPLE_PATTERN,
481
+ THREE_COMP_REGEX_A,
482
+ THREE_COMP_REGEX_B,
483
+ TWO_COMP_REGEX,
484
+ };
@@ -0,0 +1,191 @@
1
+ /**
2
+ * @fileoverview Validation and filtering logic for extracted date candidates.
3
+ * Ensures dates fall within plausible ranges and meet format requirements.
4
+ */
5
+
6
+ // Function cache
7
+ var CACHE_SIZE = 8192;
8
+ // earliest possible date to take into account (inclusive)
9
+ var MIN_DATE = new Date(2002, 0, 0, 1); // Note: month is 0-indexed in JavaScript
10
+
11
+ var DEBUG = 0;
12
+
13
+ const LOGGER = DEBUG ? console : { debug: () => { }, log: () => { }, error: () => { }, warn: () => { } };
14
+
15
+ function is_valid_date(date_input, outputformat, earliest, latest) {
16
+ // safety check
17
+ if (date_input === null) {
18
+ return false;
19
+ }
20
+ if (!earliest || !earliest.getFullYear)
21
+ earliest = new Date(2002, 0, 0, 1);
22
+ if (!latest)
23
+ latest = new Date();
24
+
25
+ let dateobject;
26
+ if (date_input instanceof Date) {
27
+ dateobject = date_input;
28
+ } else {
29
+ try {
30
+ if (outputformat === 'YYYY-MM-DD') {
31
+ dateobject = new Date(date_input.slice(0, 4), date_input.slice(5, 7) - 1, date_input.slice(8, 10));
32
+ } else {
33
+ dateobject = new Date(date_input);
34
+ }
35
+ } catch (error) {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ if (
41
+ earliest.getFullYear() <= dateobject.getFullYear() &&
42
+ dateobject.getFullYear() <= latest.getFullYear() &&
43
+ earliest.getTime() <= dateobject.getTime() &&
44
+ dateobject.getTime() <= latest.getTime()
45
+ ) {
46
+ return true;
47
+ }
48
+ LOGGER.debug(`date not valid: ${date_input}`);
49
+ return false;
50
+ }
51
+
52
+ function is_valid_format(outputformat) {
53
+ // test with date object
54
+ var dateobject = new Date(2017, 8, 1, 0, 0);
55
+ try {
56
+ dateobject.toLocaleDateString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit' });
57
+ } catch (err) {
58
+ LOGGER.error(`wrong output format or type: ${outputformat} ${err}`);
59
+ return false;
60
+ }
61
+ // test in abstracto (could be the only test)
62
+ if (typeof outputformat !== 'string' || !outputformat.includes('%')) {
63
+ LOGGER.error(`malformed output format: ${outputformat}`);
64
+ return false;
65
+ }
66
+ return true;
67
+ }
68
+
69
+ function plausible_year_filter(htmlstring, pattern, yearpat, earliest, latest, incomplete = false) {
70
+ var occurrences = new Map();
71
+
72
+
73
+ if (!htmlstring)
74
+ return occurrences;
75
+
76
+ var matches = htmlstring.match(new RegExp(pattern, 'g')) || [];
77
+
78
+ matches.forEach(item => {
79
+ var count = occurrences.get(item) || 0;
80
+ occurrences.set(item, count + 1);
81
+ });
82
+
83
+ for (var [item, count] of occurrences) {
84
+ var year_match = item.match(yearpat);
85
+ if (!year_match) {
86
+ LOGGER.debug(`not a year pattern: ${item}`);
87
+ occurrences.delete(item);
88
+ continue;
89
+ }
90
+
91
+ var lastdigits = year_match[1];
92
+ let potential_year;
93
+ if (!incomplete) {
94
+ potential_year = parseInt(lastdigits);
95
+ } else {
96
+ var century = lastdigits[0] === '9' ? '19' : '20';
97
+ potential_year = parseInt(century + lastdigits);
98
+ }
99
+
100
+ if (!(earliest.getFullYear() <= potential_year && potential_year <= latest.getFullYear())) {
101
+ LOGGER.debug(`no potential year: ${item}`);
102
+ occurrences.delete(item);
103
+ }
104
+ }
105
+
106
+ return occurrences;
107
+ }
108
+
109
+ function compare_values(reference, attempt, options) {
110
+ try {
111
+ var timestamp = new Date(attempt).getTime() / 1000;
112
+ if (options.original) {
113
+ reference = reference ? Math.min(reference, timestamp) : timestamp;
114
+ } else {
115
+ reference = Math.max(reference, timestamp);
116
+ }
117
+ } catch (err) {
118
+ LOGGER.debug(`Date parsing exception: ${err} for string ${attempt}`);
119
+ }
120
+ return reference;
121
+ }
122
+
123
+ function filter_ymd_candidate(bestmatch, pattern, original_date, copyear, outputformat, min_date, max_date) {
124
+ if (bestmatch) {
125
+ var pagedate = `${bestmatch[1]}-${bestmatch[2]}-${bestmatch[3]}`;
126
+ if (is_valid_date(pagedate, 'YYYY-MM-DD', min_date, max_date) &&
127
+ (copyear === 0 || parseInt(bestmatch[1]) >= copyear)) {
128
+ LOGGER.debug(`date found for pattern "${pattern}": ${pagedate}`);
129
+ return convert_date(pagedate, 'YYYY-MM-DD', outputformat);
130
+ }
131
+ }
132
+ return null;
133
+ }
134
+
135
+ function convert_date(datestring, inputformat, outputformat) {
136
+ if (inputformat === outputformat) {
137
+ return datestring;
138
+ }
139
+ if (datestring instanceof Date) {
140
+ return datestring.toLocaleDateString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit' });
141
+ }
142
+ var dateobject = new Date(datestring);
143
+ return dateobject.toLocaleDateString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit' });
144
+ }
145
+
146
+ function check_extracted_reference(reference, options) {
147
+ if (reference > 0) {
148
+ var dateobject = new Date(reference * 1000);
149
+ var converted = dateobject.toLocaleDateString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit' });
150
+ if (is_valid_date(converted, options.format, options.min, options.max)) {
151
+ return converted;
152
+ }
153
+ }
154
+ return null;
155
+ }
156
+
157
+ function check_date_input(date_object, default_date) {
158
+ if (date_object instanceof Date) {
159
+ return date_object;
160
+ }
161
+ if (typeof date_object === 'string') {
162
+ try {
163
+ return new Date(date_object);
164
+ } catch (error) {
165
+ LOGGER.warn(`invalid datetime string: ${date_object}`);
166
+ }
167
+ }
168
+ return default_date;
169
+ }
170
+
171
+ function get_min_date(min_date) {
172
+ return check_date_input(min_date, MIN_DATE);
173
+ }
174
+
175
+ function get_max_date(max_date) {
176
+ return check_date_input(max_date, new Date());
177
+ }
178
+
179
+
180
+ export {
181
+ is_valid_date,
182
+ is_valid_format,
183
+ plausible_year_filter,
184
+ compare_values,
185
+ filter_ymd_candidate,
186
+ convert_date,
187
+ check_extracted_reference,
188
+ check_date_input,
189
+ get_min_date,
190
+ get_max_date
191
+ };