@nuxtjs/sitemap 8.3.0 → 8.3.1

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/dist/utils.mjs CHANGED
@@ -1,1024 +1,6 @@
1
1
  export { p as parseHtmlExtractSitemapMeta } from './shared/sitemap.BoMnWHOt.mjs';
2
- import { XMLParser } from 'fast-xml-parser';
2
+ export { createSitemapReader } from 'sitemapd';
3
+ export { createFetchDocumentLoader } from 'sitemapd/fetch';
4
+ export { collectSitemap, parseSitemap } from 'sitemapd/parse';
3
5
  import 'ufo';
4
6
  import 'ultrahtml';
5
-
6
- function isValidUrl(value) {
7
- return URL.canParse(value);
8
- }
9
- async function parseSitemapIndex(xml) {
10
- if (!xml)
11
- throw new Error("Empty XML input provided");
12
- const parser = new XMLParser({
13
- isArray: (tagName) => tagName === "sitemap",
14
- removeNSPrefix: true,
15
- trimValues: true
16
- });
17
- const parsed = parser.parse(xml);
18
- if (parsed?.sitemapindex === void 0)
19
- throw new Error("XML does not contain a valid sitemapindex element");
20
- if (!parsed.sitemapindex || !parsed.sitemapindex.sitemap)
21
- return { entries: [], warnings: [] };
22
- const sitemaps = Array.isArray(parsed.sitemapindex.sitemap) ? parsed.sitemapindex.sitemap : [parsed.sitemapindex.sitemap];
23
- const warnings = [];
24
- const entries = [];
25
- for (const s of sitemaps) {
26
- if (typeof s.loc !== "string" || !s.loc.trim().length) {
27
- warnings.push({
28
- type: "validation",
29
- message: "Sitemap entry missing required loc element"
30
- });
31
- continue;
32
- }
33
- const loc = s.loc.trim();
34
- if (!isValidUrl(loc)) {
35
- warnings.push({
36
- type: "validation",
37
- message: "Sitemap entry has invalid URL",
38
- context: { url: loc }
39
- });
40
- continue;
41
- }
42
- entries.push({
43
- loc,
44
- ...s.lastmod && { lastmod: s.lastmod.trim() }
45
- });
46
- }
47
- return { entries, warnings };
48
- }
49
- function isSitemapIndex(xml) {
50
- return xml.includes("<sitemapindex") || xml.includes("sitemapindex>");
51
- }
52
-
53
- const DEFAULT_MAX_ENTRY_BYTES = 1024 * 1024;
54
- const ARRAY_TAGS = /* @__PURE__ */ new Set(["url", "image", "video", "link", "tag", "price"]);
55
- const CHANGE_FREQUENCIES = /* @__PURE__ */ new Set(["always", "hourly", "daily", "weekly", "monthly", "yearly", "never"]);
56
- const SIMPLE_URL_FIELDS = /* @__PURE__ */ new Set(["loc", "lastmod", "changefreq", "priority"]);
57
- function createUrlParser() {
58
- return new XMLParser({
59
- isArray: (tagName) => ARRAY_TAGS.has(tagName),
60
- removeNSPrefix: true,
61
- parseAttributeValue: false,
62
- ignoreAttributes: false,
63
- attributeNamePrefix: "",
64
- trimValues: true
65
- });
66
- }
67
- function isValidString(value) {
68
- return typeof value === "string" && value.trim().length > 0;
69
- }
70
- function parseNumber(value) {
71
- if (typeof value === "number")
72
- return value;
73
- if (typeof value === "string" && value.trim()) {
74
- const num = Number.parseFloat(value.trim());
75
- return Number.isNaN(num) ? void 0 : num;
76
- }
77
- return void 0;
78
- }
79
- function parseInteger(value) {
80
- if (typeof value === "number")
81
- return Math.floor(value);
82
- if (typeof value === "string" && value.trim()) {
83
- const num = Number.parseInt(value.trim(), 10);
84
- return Number.isNaN(num) ? void 0 : num;
85
- }
86
- return void 0;
87
- }
88
- function extractUrlFromParsedElement(urlElement, warnings) {
89
- if (!isValidString(urlElement.loc)) {
90
- warnings.push({
91
- type: "validation",
92
- message: "URL entry missing required loc element",
93
- context: { url: String(urlElement.loc || "undefined") }
94
- });
95
- return null;
96
- }
97
- const urlObj = { loc: urlElement.loc };
98
- if (isValidString(urlElement.lastmod)) {
99
- urlObj.lastmod = urlElement.lastmod;
100
- }
101
- if (isValidString(urlElement.changefreq)) {
102
- if (CHANGE_FREQUENCIES.has(urlElement.changefreq)) {
103
- urlObj.changefreq = urlElement.changefreq;
104
- } else {
105
- warnings.push({
106
- type: "validation",
107
- message: "Invalid changefreq value",
108
- context: { url: urlElement.loc, field: "changefreq", value: urlElement.changefreq }
109
- });
110
- }
111
- }
112
- const priority = parseNumber(urlElement.priority);
113
- if (priority !== void 0 && !Number.isNaN(priority)) {
114
- if (priority < 0 || priority > 1) {
115
- warnings.push({
116
- type: "validation",
117
- message: "Priority value should be between 0.0 and 1.0, clamping to valid range",
118
- context: { url: urlElement.loc, field: "priority", value: priority }
119
- });
120
- }
121
- urlObj.priority = Math.max(0, Math.min(1, priority));
122
- } else if (urlElement.priority !== void 0) {
123
- warnings.push({
124
- type: "validation",
125
- message: "Invalid priority value",
126
- context: { url: urlElement.loc, field: "priority", value: urlElement.priority }
127
- });
128
- }
129
- if (urlElement.image) {
130
- const images = Array.isArray(urlElement.image) ? urlElement.image : [urlElement.image];
131
- const validImages = [];
132
- for (const image of images) {
133
- if (isValidString(image.loc)) {
134
- validImages.push({ loc: image.loc });
135
- } else {
136
- warnings.push({
137
- type: "validation",
138
- message: "Image missing required loc element",
139
- context: { url: urlElement.loc, field: "image.loc" }
140
- });
141
- }
142
- }
143
- if (validImages.length > 0)
144
- urlObj.images = validImages;
145
- }
146
- if (urlElement.video) {
147
- const videos = Array.isArray(urlElement.video) ? urlElement.video : [urlElement.video];
148
- const validVideos = videos.map((video) => {
149
- const missingFields = [];
150
- if (!isValidString(video.title))
151
- missingFields.push("title");
152
- if (!isValidString(video.thumbnail_loc))
153
- missingFields.push("thumbnail_loc");
154
- if (!isValidString(video.description))
155
- missingFields.push("description");
156
- if (!isValidString(video.content_loc))
157
- missingFields.push("content_loc");
158
- if (missingFields.length > 0) {
159
- warnings.push({
160
- type: "validation",
161
- message: `Video missing required fields: ${missingFields.join(", ")}`,
162
- context: { url: urlElement.loc, field: "video" }
163
- });
164
- return null;
165
- }
166
- const videoObj = {
167
- title: video.title,
168
- thumbnail_loc: video.thumbnail_loc,
169
- description: video.description,
170
- content_loc: video.content_loc
171
- };
172
- if (isValidString(video.player_loc)) {
173
- videoObj.player_loc = video.player_loc;
174
- }
175
- const duration = parseInteger(video.duration);
176
- if (duration !== void 0) {
177
- videoObj.duration = duration;
178
- } else if (video.duration !== void 0) {
179
- warnings.push({
180
- type: "validation",
181
- message: "Invalid video duration value",
182
- context: { url: urlElement.loc, field: "video.duration", value: video.duration }
183
- });
184
- }
185
- if (isValidString(video.expiration_date)) {
186
- videoObj.expiration_date = video.expiration_date;
187
- }
188
- const rating = parseNumber(video.rating);
189
- if (rating !== void 0) {
190
- if (rating < 0 || rating > 5) {
191
- warnings.push({
192
- type: "validation",
193
- message: "Video rating should be between 0.0 and 5.0",
194
- context: { url: urlElement.loc, field: "video.rating", value: rating }
195
- });
196
- }
197
- videoObj.rating = rating;
198
- } else if (video.rating !== void 0) {
199
- warnings.push({
200
- type: "validation",
201
- message: "Invalid video rating value",
202
- context: { url: urlElement.loc, field: "video.rating", value: video.rating }
203
- });
204
- }
205
- const viewCount = parseInteger(video.view_count);
206
- if (viewCount !== void 0) {
207
- videoObj.view_count = viewCount;
208
- } else if (video.view_count !== void 0) {
209
- warnings.push({
210
- type: "validation",
211
- message: "Invalid video view_count value",
212
- context: { url: urlElement.loc, field: "video.view_count", value: video.view_count }
213
- });
214
- }
215
- if (isValidString(video.publication_date)) {
216
- videoObj.publication_date = video.publication_date;
217
- }
218
- if (isValidString(video.family_friendly)) {
219
- const validValues = ["yes", "no"];
220
- if (validValues.includes(video.family_friendly)) {
221
- videoObj.family_friendly = video.family_friendly;
222
- } else {
223
- warnings.push({
224
- type: "validation",
225
- message: 'Invalid video family_friendly value, should be "yes" or "no"',
226
- context: { url: urlElement.loc, field: "video.family_friendly", value: video.family_friendly }
227
- });
228
- }
229
- }
230
- if (isValidString(video.requires_subscription)) {
231
- const validValues = ["yes", "no"];
232
- if (validValues.includes(video.requires_subscription)) {
233
- videoObj.requires_subscription = video.requires_subscription;
234
- } else {
235
- warnings.push({
236
- type: "validation",
237
- message: 'Invalid video requires_subscription value, should be "yes" or "no"',
238
- context: { url: urlElement.loc, field: "video.requires_subscription", value: video.requires_subscription }
239
- });
240
- }
241
- }
242
- if (isValidString(video.live)) {
243
- const validValues = ["yes", "no"];
244
- if (validValues.includes(video.live)) {
245
- videoObj.live = video.live;
246
- } else {
247
- warnings.push({
248
- type: "validation",
249
- message: 'Invalid video live value, should be "yes" or "no"',
250
- context: { url: urlElement.loc, field: "video.live", value: video.live }
251
- });
252
- }
253
- }
254
- if (video.restriction && typeof video.restriction === "object") {
255
- const restriction = video.restriction;
256
- if (isValidString(restriction.relationship) && isValidString(restriction["#text"])) {
257
- const validRelationships = ["allow", "deny"];
258
- if (validRelationships.includes(restriction.relationship)) {
259
- videoObj.restriction = {
260
- relationship: restriction.relationship,
261
- restriction: restriction["#text"]
262
- };
263
- } else {
264
- warnings.push({
265
- type: "validation",
266
- message: 'Invalid video restriction relationship, should be "allow" or "deny"',
267
- context: { url: urlElement.loc, field: "video.restriction.relationship", value: restriction.relationship }
268
- });
269
- }
270
- }
271
- }
272
- if (video.platform && typeof video.platform === "object") {
273
- const platform = video.platform;
274
- if (isValidString(platform.relationship) && isValidString(platform["#text"])) {
275
- const validRelationships = ["allow", "deny"];
276
- if (validRelationships.includes(platform.relationship)) {
277
- videoObj.platform = {
278
- relationship: platform.relationship,
279
- platform: platform["#text"]
280
- };
281
- } else {
282
- warnings.push({
283
- type: "validation",
284
- message: 'Invalid video platform relationship, should be "allow" or "deny"',
285
- context: { url: urlElement.loc, field: "video.platform.relationship", value: platform.relationship }
286
- });
287
- }
288
- }
289
- }
290
- if (video.price) {
291
- const prices = Array.isArray(video.price) ? video.price : [video.price];
292
- const validPrices = prices.map((price) => {
293
- const priceValue = price["#text"];
294
- if (priceValue == null || typeof priceValue !== "string" && typeof priceValue !== "number") {
295
- warnings.push({
296
- type: "validation",
297
- message: "Video price missing value",
298
- context: { url: urlElement.loc, field: "video.price" }
299
- });
300
- return null;
301
- }
302
- const validTypes = ["rent", "purchase", "package", "subscription"];
303
- if (price.type && !validTypes.includes(price.type)) {
304
- warnings.push({
305
- type: "validation",
306
- message: `Invalid video price type "${price.type}", should be one of: ${validTypes.join(", ")}`,
307
- context: { url: urlElement.loc, field: "video.price.type", value: price.type }
308
- });
309
- }
310
- return {
311
- price: String(priceValue),
312
- currency: price.currency,
313
- type: price.type
314
- };
315
- }).filter((p) => p !== null);
316
- if (validPrices.length > 0) {
317
- videoObj.price = validPrices;
318
- }
319
- }
320
- if (video.uploader && typeof video.uploader === "object") {
321
- const uploader = video.uploader;
322
- if (isValidString(uploader.info) && isValidString(uploader["#text"])) {
323
- videoObj.uploader = {
324
- uploader: uploader["#text"],
325
- info: uploader.info
326
- };
327
- } else {
328
- warnings.push({
329
- type: "validation",
330
- message: "Video uploader missing required info or name",
331
- context: { url: urlElement.loc, field: "video.uploader" }
332
- });
333
- }
334
- }
335
- if (video.tag) {
336
- const tags = Array.isArray(video.tag) ? video.tag : [video.tag];
337
- const validTags = tags.filter(isValidString);
338
- if (validTags.length > 0) {
339
- videoObj.tag = validTags;
340
- }
341
- }
342
- return videoObj;
343
- }).filter((video) => video !== null);
344
- if (validVideos.length > 0) {
345
- urlObj.videos = validVideos;
346
- }
347
- }
348
- if (urlElement.link) {
349
- const links = Array.isArray(urlElement.link) ? urlElement.link : [urlElement.link];
350
- const alternatives = links.map((link) => {
351
- if (link.rel === "alternate" && isValidString(link.hreflang) && isValidString(link.href)) {
352
- return {
353
- hreflang: link.hreflang,
354
- href: link.href
355
- };
356
- } else {
357
- warnings.push({
358
- type: "validation",
359
- message: 'Alternative link missing required rel="alternate", hreflang, or href',
360
- context: { url: urlElement.loc, field: "link" }
361
- });
362
- return null;
363
- }
364
- }).filter((alt) => alt !== null);
365
- if (alternatives.length > 0) {
366
- urlObj.alternatives = alternatives;
367
- }
368
- }
369
- if (urlElement.news && typeof urlElement.news === "object") {
370
- const news = urlElement.news;
371
- if (isValidString(news.title) && isValidString(news.publication_date) && news.publication && isValidString(news.publication.name) && isValidString(news.publication.language)) {
372
- urlObj.news = {
373
- title: news.title,
374
- publication_date: news.publication_date,
375
- publication: {
376
- name: news.publication.name,
377
- language: news.publication.language
378
- }
379
- };
380
- } else {
381
- warnings.push({
382
- type: "validation",
383
- message: "News entry missing required fields (title, publication_date, publication.name, publication.language)",
384
- context: { url: urlElement.loc, field: "news" }
385
- });
386
- }
387
- }
388
- return urlObj;
389
- }
390
- function parseTag(xml, start, end) {
391
- let cursor = start + 1;
392
- while (cursor < end && isXmlWhitespace(xml.charCodeAt(cursor)))
393
- cursor++;
394
- const closing = xml.charCodeAt(cursor) === 47;
395
- if (closing)
396
- cursor++;
397
- if (cursor >= end || xml.charCodeAt(cursor) === 33 || xml.charCodeAt(cursor) === 63)
398
- return null;
399
- const nameStart = cursor;
400
- while (cursor < end) {
401
- const code = xml.charCodeAt(cursor);
402
- if (isXmlWhitespace(code) || code === 47)
403
- break;
404
- cursor++;
405
- }
406
- if (cursor === nameStart)
407
- return null;
408
- const qualifiedName = xml.slice(nameStart, cursor);
409
- const colon = qualifiedName.lastIndexOf(":");
410
- let tail = end - 1;
411
- while (tail > start && isXmlWhitespace(xml.charCodeAt(tail)))
412
- tail--;
413
- return {
414
- name: colon === -1 ? qualifiedName : qualifiedName.slice(colon + 1),
415
- closing,
416
- selfClosing: !closing && xml.charCodeAt(tail) === 47
417
- };
418
- }
419
- const URLSET_OPEN = 1;
420
- const URLSET_CLOSE = 2;
421
- const URLSET_SELF_CLOSING = 3;
422
- const URL_OPEN = 4;
423
- const URL_SELF_CLOSING = 5;
424
- const URL_CLOSE = 6;
425
- const SITEMAP_INDEX_OPEN = 7;
426
- const SITEMAP_INDEX_CLOSE = 8;
427
- const SITEMAP_INDEX_SELF_CLOSING = 9;
428
- const SITEMAP_OPEN = 10;
429
- const SITEMAP_SELF_CLOSING = 11;
430
- const SITEMAP_CLOSE = 12;
431
- function parseBoundaryTag(xml, start, end) {
432
- let cursor = start + 1;
433
- while (cursor < end && isXmlWhitespace(xml.charCodeAt(cursor)))
434
- cursor++;
435
- const closing = xml.charCodeAt(cursor) === 47;
436
- if (closing)
437
- cursor++;
438
- if (cursor >= end || xml.charCodeAt(cursor) === 33 || xml.charCodeAt(cursor) === 63)
439
- return 0;
440
- let localNameStart = cursor;
441
- while (cursor < end) {
442
- const code = xml.charCodeAt(cursor);
443
- if (isXmlWhitespace(code) || code === 47)
444
- break;
445
- if (code === 58)
446
- localNameStart = cursor + 1;
447
- cursor++;
448
- }
449
- const localNameLength = cursor - localNameStart;
450
- let tail = end - 1;
451
- while (tail > start && isXmlWhitespace(xml.charCodeAt(tail)))
452
- tail--;
453
- const selfClosing = !closing && xml.charCodeAt(tail) === 47;
454
- if (localNameLength === 3 && xml.startsWith("url", localNameStart)) {
455
- if (closing)
456
- return URL_CLOSE;
457
- return selfClosing ? URL_SELF_CLOSING : URL_OPEN;
458
- }
459
- if (localNameLength === 6 && xml.startsWith("urlset", localNameStart)) {
460
- if (closing)
461
- return URLSET_CLOSE;
462
- return selfClosing ? URLSET_SELF_CLOSING : URLSET_OPEN;
463
- }
464
- if (localNameLength === 12 && xml.startsWith("sitemapindex", localNameStart)) {
465
- if (closing)
466
- return SITEMAP_INDEX_CLOSE;
467
- return selfClosing ? SITEMAP_INDEX_SELF_CLOSING : SITEMAP_INDEX_OPEN;
468
- }
469
- if (localNameLength === 7 && xml.startsWith("sitemap", localNameStart)) {
470
- if (closing)
471
- return SITEMAP_CLOSE;
472
- return selfClosing ? SITEMAP_SELF_CLOSING : SITEMAP_OPEN;
473
- }
474
- return 0;
475
- }
476
- function isXmlWhitespace(code) {
477
- return code === 32 || code === 9 || code === 10 || code === 13;
478
- }
479
- function findMarkupEnd(xml, start) {
480
- if (xml.startsWith("<!--", start)) {
481
- const end = xml.indexOf("-->", start + 4);
482
- return end === -1 ? -1 : end + 2;
483
- }
484
- if (xml.startsWith("<![CDATA[", start)) {
485
- const end = xml.indexOf("]]>", start + 9);
486
- return end === -1 ? -1 : end + 2;
487
- }
488
- if (xml.startsWith("<?", start)) {
489
- const end = xml.indexOf("?>", start + 2);
490
- return end === -1 ? -1 : end + 1;
491
- }
492
- let quote = 0;
493
- for (let cursor = start + 1; cursor < xml.length; cursor++) {
494
- const code = xml.charCodeAt(cursor);
495
- if (quote) {
496
- if (code === quote)
497
- quote = 0;
498
- } else if (code === 34 || code === 39) {
499
- quote = code;
500
- } else if (code === 62) {
501
- return cursor;
502
- }
503
- }
504
- return -1;
505
- }
506
- function isAsyncIterable(input) {
507
- return typeof input === "object" && input !== null && Symbol.asyncIterator in input;
508
- }
509
- function isIterable(input) {
510
- return typeof input === "object" && input !== null && Symbol.iterator in input;
511
- }
512
- function isReadableStream(input) {
513
- return typeof input === "object" && input !== null && "getReader" in input;
514
- }
515
- async function* iterateInput(input) {
516
- if (typeof input === "string" || input instanceof Uint8Array) {
517
- yield input;
518
- return;
519
- }
520
- if (isReadableStream(input)) {
521
- const reader = input.getReader();
522
- let completed = false;
523
- try {
524
- while (true) {
525
- const result = await reader.read();
526
- if (result.done) {
527
- completed = true;
528
- return;
529
- }
530
- yield result.value;
531
- }
532
- } finally {
533
- if (!completed)
534
- await reader.cancel();
535
- reader.releaseLock();
536
- }
537
- }
538
- if (isAsyncIterable(input)) {
539
- yield* input;
540
- return;
541
- }
542
- if (isIterable(input)) {
543
- yield* input;
544
- return;
545
- }
546
- throw new TypeError("Sitemap XML input must be a string, Uint8Array, iterable, or ReadableStream");
547
- }
548
- async function* decodeInput(input) {
549
- let decoder = new TextDecoder();
550
- let decodingBytes = false;
551
- for await (const chunk of iterateInput(input)) {
552
- if (typeof chunk === "string") {
553
- if (decodingBytes) {
554
- const tail = decoder.decode();
555
- if (tail)
556
- yield tail;
557
- decoder = new TextDecoder();
558
- decodingBytes = false;
559
- }
560
- if (chunk)
561
- yield chunk;
562
- } else if (chunk instanceof Uint8Array) {
563
- decodingBytes = true;
564
- const text = decoder.decode(chunk, { stream: true });
565
- if (text)
566
- yield text;
567
- } else {
568
- throw new TypeError("Sitemap XML chunks must be strings or Uint8Array values");
569
- }
570
- }
571
- if (decodingBytes) {
572
- const tail = decoder.decode();
573
- if (tail)
574
- yield tail;
575
- }
576
- }
577
- function utf8ByteLength(value) {
578
- let bytes = 0;
579
- for (let index = 0; index < value.length; index++) {
580
- const code = value.charCodeAt(index);
581
- if (code < 128) {
582
- bytes++;
583
- } else if (code < 2048) {
584
- bytes += 2;
585
- } else if (code >= 55296 && code <= 56319 && index + 1 < value.length) {
586
- const next = value.charCodeAt(index + 1);
587
- if (next >= 56320 && next <= 57343) {
588
- bytes += 4;
589
- index++;
590
- } else {
591
- bytes += 3;
592
- }
593
- } else {
594
- bytes += 3;
595
- }
596
- }
597
- return bytes;
598
- }
599
- function exceedsUtf8ByteLimit(value, limit) {
600
- if (value.length > limit)
601
- return true;
602
- return value.length > Math.floor(limit / 3) && utf8ByteLength(value) > limit;
603
- }
604
- function resolveMaxEntryBytes(options) {
605
- const value = options.maxEntryBytes ?? DEFAULT_MAX_ENTRY_BYTES;
606
- if (!Number.isSafeInteger(value) || value < 1)
607
- throw new TypeError("maxEntryBytes must be a positive safe integer");
608
- return value;
609
- }
610
- function resolveMaxBufferBytes(options) {
611
- const value = options.maxBufferBytes ?? DEFAULT_MAX_ENTRY_BYTES;
612
- if (!Number.isSafeInteger(value) || value < 1)
613
- throw new TypeError("maxBufferBytes must be a positive safe integer");
614
- return value;
615
- }
616
- function decodeXmlEntities(value) {
617
- const firstEntity = value.indexOf("&");
618
- if (firstEntity === -1)
619
- return value;
620
- let decoded = value.slice(0, firstEntity);
621
- let cursor = firstEntity;
622
- while (cursor < value.length) {
623
- if (value.charCodeAt(cursor) !== 38) {
624
- decoded += value[cursor];
625
- cursor++;
626
- continue;
627
- }
628
- const end = value.indexOf(";", cursor + 1);
629
- if (end === -1) {
630
- decoded += value.slice(cursor);
631
- break;
632
- }
633
- const entity = value.slice(cursor + 1, end);
634
- if (entity === "amp") {
635
- decoded += "&";
636
- } else if (entity === "lt") {
637
- decoded += "<";
638
- } else if (entity === "gt") {
639
- decoded += ">";
640
- } else if (entity === "quot") {
641
- decoded += '"';
642
- } else if (entity === "apos") {
643
- decoded += "'";
644
- } else if (entity.charCodeAt(0) === 35) {
645
- const hex = entity.charCodeAt(1) === 120 || entity.charCodeAt(1) === 88;
646
- const codePoint = Number.parseInt(entity.slice(hex ? 2 : 1), hex ? 16 : 10);
647
- decoded += Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 1114111 && !(codePoint >= 55296 && codePoint <= 57343) ? String.fromCodePoint(codePoint) : value.slice(cursor, end + 1);
648
- } else {
649
- decoded += value.slice(cursor, end + 1);
650
- }
651
- cursor = end + 1;
652
- }
653
- return decoded;
654
- }
655
- function decodeElementContent(value) {
656
- const firstMarkup = value.indexOf("<");
657
- if (firstMarkup === -1)
658
- return decodeXmlEntities(value.trim());
659
- let decoded = "";
660
- let cursor = 0;
661
- while (cursor < value.length) {
662
- const markupStart = value.indexOf("<", cursor);
663
- if (markupStart === -1) {
664
- decoded += decodeXmlEntities(value.slice(cursor));
665
- break;
666
- }
667
- decoded += decodeXmlEntities(value.slice(cursor, markupStart));
668
- if (value.startsWith("<![CDATA[", markupStart)) {
669
- const end = value.indexOf("]]>", markupStart + 9);
670
- if (end === -1)
671
- return null;
672
- decoded += value.slice(markupStart + 9, end);
673
- cursor = end + 3;
674
- } else if (value.startsWith("<!--", markupStart)) {
675
- const end = value.indexOf("-->", markupStart + 4);
676
- if (end === -1)
677
- return null;
678
- cursor = end + 3;
679
- } else {
680
- return null;
681
- }
682
- }
683
- return decoded.trim();
684
- }
685
- function parseCommonUrlEntry(xml) {
686
- if (!xml.startsWith("<url>"))
687
- return null;
688
- const parsed = {};
689
- let seenFields = 0;
690
- let cursor = 5;
691
- while (cursor < xml.length) {
692
- while (cursor < xml.length && isXmlWhitespace(xml.charCodeAt(cursor)))
693
- cursor++;
694
- if (xml.startsWith("</url>", cursor)) {
695
- cursor += 6;
696
- while (cursor < xml.length && isXmlWhitespace(xml.charCodeAt(cursor)))
697
- cursor++;
698
- return cursor === xml.length ? parsed : null;
699
- }
700
- let name;
701
- let fieldBit;
702
- if (xml.startsWith("<loc>", cursor)) {
703
- name = "loc";
704
- fieldBit = 1;
705
- } else if (xml.startsWith("<lastmod>", cursor)) {
706
- name = "lastmod";
707
- fieldBit = 2;
708
- } else if (xml.startsWith("<changefreq>", cursor)) {
709
- name = "changefreq";
710
- fieldBit = 4;
711
- } else if (xml.startsWith("<priority>", cursor)) {
712
- name = "priority";
713
- fieldBit = 8;
714
- } else {
715
- return null;
716
- }
717
- if (seenFields & fieldBit)
718
- return null;
719
- seenFields |= fieldBit;
720
- const contentStart = cursor + name.length + 2;
721
- const closingTag = `</${name}>`;
722
- const contentEnd = xml.indexOf(closingTag, contentStart);
723
- if (contentEnd === -1 || xml.indexOf("<", contentStart) !== contentEnd)
724
- return null;
725
- parsed[name] = decodeXmlEntities(xml.slice(contentStart, contentEnd).trim());
726
- cursor = contentEnd + closingTag.length;
727
- }
728
- return null;
729
- }
730
- function parseSimpleUrlEntry(xml) {
731
- const parsed = {};
732
- const seenFields = /* @__PURE__ */ new Set();
733
- let scanIndex = 0;
734
- let depth = 0;
735
- let field;
736
- let contentStart = 0;
737
- let closedUrl = false;
738
- while (true) {
739
- const markupStart = xml.indexOf("<", scanIndex);
740
- if (markupStart === -1)
741
- break;
742
- const markupEnd = findMarkupEnd(xml, markupStart);
743
- if (markupEnd === -1)
744
- return null;
745
- const tag = parseTag(xml, markupStart, markupEnd);
746
- scanIndex = markupEnd + 1;
747
- if (!tag)
748
- continue;
749
- if (!tag.closing) {
750
- if (depth === 0 && tag.name === "url") {
751
- if (tag.selfClosing)
752
- return {};
753
- depth = 1;
754
- continue;
755
- }
756
- if (depth !== 1 || !SIMPLE_URL_FIELDS.has(tag.name) || seenFields.has(tag.name))
757
- return null;
758
- seenFields.add(tag.name);
759
- if (tag.selfClosing) {
760
- parsed[tag.name] = "";
761
- continue;
762
- }
763
- field = tag.name;
764
- contentStart = markupEnd + 1;
765
- depth = 2;
766
- continue;
767
- }
768
- if (depth === 2 && field === tag.name) {
769
- const value = decodeElementContent(xml.slice(contentStart, markupStart));
770
- if (value === null)
771
- return null;
772
- parsed[field] = value;
773
- field = void 0;
774
- depth = 1;
775
- } else if (depth === 1 && tag.name === "url") {
776
- closedUrl = true;
777
- depth = 0;
778
- } else {
779
- return null;
780
- }
781
- }
782
- return closedUrl && depth === 0 ? parsed : null;
783
- }
784
- function parseUrlEntry(parser, xml) {
785
- const simple = parseCommonUrlEntry(xml) ?? parseSimpleUrlEntry(xml);
786
- if (simple)
787
- return simple;
788
- try {
789
- const parsed = parser.parse(xml);
790
- const value = Array.isArray(parsed?.url) ? parsed.url[0] : parsed?.url;
791
- return value && typeof value === "object" ? value : {};
792
- } catch (error) {
793
- throw new Error(`Failed to parse XML: ${error instanceof Error ? error.message : String(error)}`);
794
- }
795
- }
796
- function parseSitemapEntry(parser, xml, warnings) {
797
- let parsed;
798
- try {
799
- parsed = parser.parse(xml);
800
- } catch (error) {
801
- throw new Error(`Failed to parse XML: ${error instanceof Error ? error.message : String(error)}`);
802
- }
803
- const loc = typeof parsed.sitemap?.loc === "string" ? parsed.sitemap.loc.trim() : "";
804
- if (!loc) {
805
- warnings.push({
806
- type: "validation",
807
- message: "Sitemap entry missing required loc element"
808
- });
809
- return null;
810
- }
811
- if (!URL.canParse(loc)) {
812
- warnings.push({
813
- type: "validation",
814
- message: "Sitemap entry has invalid URL",
815
- context: { url: loc }
816
- });
817
- return null;
818
- }
819
- const entry = { loc };
820
- if (typeof parsed.sitemap?.lastmod === "string" && parsed.sitemap.lastmod.trim())
821
- entry.lastmod = parsed.sitemap.lastmod.trim();
822
- return entry;
823
- }
824
- async function* parseSitemapStream(input, options = {}) {
825
- const maxEntryBytes = resolveMaxEntryBytes(options);
826
- const maxBufferBytes = resolveMaxBufferBytes(options);
827
- const parser = createUrlParser();
828
- let buffer = "";
829
- let scanIndex = 0;
830
- let entryStart = -1;
831
- let sawInput = false;
832
- let kind;
833
- let insideRoot = false;
834
- let closedRoot = false;
835
- let entryCount = 0;
836
- let validUrlCount = 0;
837
- for await (const chunk of decodeInput(input)) {
838
- sawInput = true;
839
- buffer += chunk;
840
- while (true) {
841
- const markupStart = buffer.indexOf("<", scanIndex);
842
- if (markupStart === -1) {
843
- scanIndex = buffer.length;
844
- break;
845
- }
846
- const markupEnd = findMarkupEnd(buffer, markupStart);
847
- if (markupEnd === -1) {
848
- scanIndex = markupStart;
849
- break;
850
- }
851
- const tag = parseBoundaryTag(buffer, markupStart, markupEnd);
852
- if (!kind && tag === 0) {
853
- const root = parseTag(buffer, markupStart, markupEnd);
854
- if (root && !root.closing)
855
- throw new Error("XML does not contain a valid sitemap element");
856
- }
857
- let record;
858
- if (!kind && (tag === URLSET_OPEN || tag === URLSET_SELF_CLOSING)) {
859
- kind = "urlset";
860
- insideRoot = tag === URLSET_OPEN;
861
- closedRoot = tag === URLSET_SELF_CLOSING;
862
- yield { _tag: "kind", kind };
863
- } else if (!kind && (tag === SITEMAP_INDEX_OPEN || tag === SITEMAP_INDEX_SELF_CLOSING)) {
864
- kind = "index";
865
- insideRoot = tag === SITEMAP_INDEX_OPEN;
866
- closedRoot = tag === SITEMAP_INDEX_SELF_CLOSING;
867
- yield { _tag: "kind", kind };
868
- } else if (entryStart === -1 && (kind === "urlset" && tag === URLSET_CLOSE || kind === "index" && tag === SITEMAP_INDEX_CLOSE)) {
869
- insideRoot = false;
870
- closedRoot = true;
871
- }
872
- const openingEntry = kind === "urlset" ? tag === URL_OPEN || tag === URL_SELF_CLOSING : tag === SITEMAP_OPEN || tag === SITEMAP_SELF_CLOSING;
873
- const closingEntry = kind === "urlset" ? tag === URL_CLOSE : tag === SITEMAP_CLOSE;
874
- const selfClosingEntry = tag === URL_SELF_CLOSING || tag === SITEMAP_SELF_CLOSING;
875
- if (insideRoot && openingEntry && entryStart === -1) {
876
- entryStart = markupStart;
877
- if (selfClosingEntry)
878
- record = buffer.slice(entryStart, markupEnd + 1);
879
- } else if (closingEntry && entryStart !== -1) {
880
- record = buffer.slice(entryStart, markupEnd + 1);
881
- }
882
- if (!record) {
883
- scanIndex = markupEnd + 1;
884
- continue;
885
- }
886
- if (exceedsUtf8ByteLimit(record, maxEntryBytes))
887
- throw new Error(`Sitemap entry exceeds maxEntryBytes of ${maxEntryBytes}`);
888
- entryCount++;
889
- const warnings = [];
890
- scanIndex = markupEnd + 1;
891
- entryStart = -1;
892
- const entry = kind === "urlset" ? extractUrlFromParsedElement(parseUrlEntry(parser, record), warnings) : parseSitemapEntry(parser, record, warnings);
893
- for (const warning of warnings)
894
- yield { _tag: "warning", warning };
895
- if (kind === "urlset" && entry) {
896
- validUrlCount++;
897
- yield { _tag: "url", url: entry };
898
- } else if (kind === "index" && entry) {
899
- yield { _tag: "sitemap", sitemap: entry };
900
- }
901
- }
902
- if (entryStart !== -1) {
903
- if (buffer.length - entryStart > maxEntryBytes)
904
- throw new Error(`Sitemap entry exceeds maxEntryBytes of ${maxEntryBytes}`);
905
- if (entryStart > 0) {
906
- buffer = buffer.slice(entryStart);
907
- scanIndex -= entryStart;
908
- entryStart = 0;
909
- }
910
- } else if (scanIndex > 0) {
911
- buffer = buffer.slice(scanIndex);
912
- scanIndex = 0;
913
- }
914
- if (entryStart === -1 && exceedsUtf8ByteLimit(buffer, maxBufferBytes))
915
- throw new Error(`Sitemap XML buffer exceeds maxBufferBytes of ${maxBufferBytes}`);
916
- }
917
- if (!sawInput)
918
- throw new Error("Empty XML input provided");
919
- if (entryStart !== -1)
920
- throw new Error(`Failed to parse XML: Unclosed ${kind === "index" ? "sitemap" : "url"} element`);
921
- if (!kind)
922
- throw new Error("XML does not contain a valid sitemap element");
923
- if (!closedRoot)
924
- throw new Error(`Failed to parse XML: Unclosed ${kind === "index" ? "sitemapindex" : "urlset"} element`);
925
- if (kind === "urlset" && entryCount > 0 && validUrlCount === 0) {
926
- yield {
927
- _tag: "warning",
928
- warning: {
929
- type: "validation",
930
- message: "No valid URLs found in sitemap after validation"
931
- }
932
- };
933
- }
934
- }
935
- async function* parseSitemapXmlStream(input, options = {}) {
936
- for await (const event of parseSitemapStream(input, options)) {
937
- if (event._tag === "kind") {
938
- if (event.kind !== "urlset")
939
- throw new Error("XML does not contain a valid urlset element");
940
- continue;
941
- }
942
- if (event._tag === "sitemap")
943
- continue;
944
- yield event;
945
- }
946
- }
947
- async function* parseSitemapIndexStream(input, options = {}) {
948
- for await (const event of parseSitemapStream(input, options)) {
949
- if (event._tag === "kind") {
950
- if (event.kind !== "index")
951
- throw new Error("XML does not contain a valid sitemapindex element");
952
- continue;
953
- }
954
- if (event._tag === "url")
955
- continue;
956
- yield event;
957
- }
958
- }
959
- async function parseSitemapXml(xml) {
960
- if (!xml)
961
- throw new Error("Empty XML input provided");
962
- const urls = [];
963
- const warnings = [];
964
- const parser = createUrlParser();
965
- let scanIndex = 0;
966
- let entryStart = -1;
967
- let sawUrlset = false;
968
- let insideUrlset = false;
969
- let closedUrlset = false;
970
- let entryCount = 0;
971
- while (true) {
972
- const markupStart = xml.indexOf("<", scanIndex);
973
- if (markupStart === -1)
974
- break;
975
- const markupEnd = findMarkupEnd(xml, markupStart);
976
- if (markupEnd === -1)
977
- break;
978
- const tag = parseBoundaryTag(xml, markupStart, markupEnd);
979
- let record;
980
- if (tag === URLSET_OPEN) {
981
- sawUrlset = true;
982
- insideUrlset = true;
983
- } else if (tag === URLSET_SELF_CLOSING) {
984
- sawUrlset = true;
985
- insideUrlset = false;
986
- closedUrlset = true;
987
- } else if (tag === URLSET_CLOSE && entryStart === -1) {
988
- insideUrlset = false;
989
- closedUrlset = true;
990
- }
991
- if (insideUrlset && (tag === URL_OPEN || tag === URL_SELF_CLOSING) && entryStart === -1) {
992
- entryStart = markupStart;
993
- if (tag === URL_SELF_CLOSING)
994
- record = xml.slice(entryStart, markupEnd + 1);
995
- } else if (tag === URL_CLOSE && entryStart !== -1) {
996
- record = xml.slice(entryStart, markupEnd + 1);
997
- }
998
- scanIndex = markupEnd + 1;
999
- if (!record)
1000
- continue;
1001
- if (exceedsUtf8ByteLimit(record, DEFAULT_MAX_ENTRY_BYTES))
1002
- throw new Error(`Sitemap URL entry exceeds maxEntryBytes of ${DEFAULT_MAX_ENTRY_BYTES}`);
1003
- entryCount++;
1004
- entryStart = -1;
1005
- const url = extractUrlFromParsedElement(parseUrlEntry(parser, record), warnings);
1006
- if (url)
1007
- urls.push(url);
1008
- }
1009
- if (entryStart !== -1)
1010
- throw new Error("Failed to parse XML: Unclosed url element");
1011
- if (!sawUrlset)
1012
- throw new Error("XML does not contain a valid urlset element");
1013
- if (!closedUrlset)
1014
- throw new Error("Failed to parse XML: Unclosed urlset element");
1015
- if (entryCount > 0 && urls.length === 0) {
1016
- warnings.push({
1017
- type: "validation",
1018
- message: "No valid URLs found in sitemap after validation"
1019
- });
1020
- }
1021
- return { urls, warnings };
1022
- }
1023
-
1024
- export { isSitemapIndex, parseSitemapIndex, parseSitemapIndexStream, parseSitemapStream, parseSitemapXml, parseSitemapXmlStream };