@cocreate/sitemap 1.4.0 → 1.4.2
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/CHANGELOG.md +15 -0
- package/docs/index.html +216 -216
- package/package.json +1 -9
- package/src/index.js +527 -451
package/src/index.js
CHANGED
|
@@ -23,457 +23,533 @@
|
|
|
23
23
|
const { parse } = require("node-html-parser");
|
|
24
24
|
|
|
25
25
|
class CoCreateSitemap {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
26
|
+
constructor(crud) {
|
|
27
|
+
this.crud = crud;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async check(file, host) {
|
|
31
|
+
// Ensure the file is HTML and does not have a sitemap object yet
|
|
32
|
+
if (!file.sitemap && file["content-type"] !== "text/html") return;
|
|
33
|
+
|
|
34
|
+
// Ensure the file is public and is not sitemap false
|
|
35
|
+
if (
|
|
36
|
+
file.sitemap === false ||
|
|
37
|
+
file.sitemap === "false" ||
|
|
38
|
+
!file.public ||
|
|
39
|
+
file.public === "false"
|
|
40
|
+
)
|
|
41
|
+
return;
|
|
42
|
+
|
|
43
|
+
// Check if the file is HTML and contains a noindex meta or title tag
|
|
44
|
+
if (file["content-type"] === "text/html") {
|
|
45
|
+
if (
|
|
46
|
+
/<meta\s+name=["']robots["']\s+content=["'][^"']*noindex[^"']*["']/i.test(
|
|
47
|
+
file.src
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
return;
|
|
51
|
+
if (!/<title[^>]*>[\s\S]*?<\/title>/i.test(file.src)) return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Compare the lastmod date in the sitemap with the modified.on date
|
|
55
|
+
if (file.sitemap && file.sitemap.lastmod && file.modified.on) {
|
|
56
|
+
if (new Date(file.sitemap.lastmod) >= new Date(file.modified.on))
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Logic to update the sitemap
|
|
61
|
+
this.updateSitemap(file, host);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ToDo: check for <link rel="alternate" hreflang="x" /> and create entries
|
|
65
|
+
async updateSitemap(file, host) {
|
|
66
|
+
try {
|
|
67
|
+
if (!file.sitemap) file.sitemap = {};
|
|
68
|
+
|
|
69
|
+
const entry = this.createEntry(file, host);
|
|
70
|
+
|
|
71
|
+
let { mainSitemap, sitemap } = await this.getSitemap(file, host);
|
|
72
|
+
|
|
73
|
+
if (file.pathname) {
|
|
74
|
+
// Perform regex search starting at the pathname
|
|
75
|
+
const regexPattern = `<url>\\s*<loc>.*?${file.pathname.replace(
|
|
76
|
+
/[.*+?^${}()|[\]\\]/g,
|
|
77
|
+
"\\$&"
|
|
78
|
+
)}.*?</loc>[\\s\\S]*?</url>`;
|
|
79
|
+
const match = sitemap.src.match(new RegExp(regexPattern));
|
|
80
|
+
|
|
81
|
+
if (match) {
|
|
82
|
+
const position = match.index; // Start position of the <url> block
|
|
83
|
+
const endPosition = match.index + match[0].length; // End position of the <url> block
|
|
84
|
+
|
|
85
|
+
// Replace the original <url> block with the modified one
|
|
86
|
+
sitemap.src =
|
|
87
|
+
sitemap.src.slice(0, position) +
|
|
88
|
+
entry +
|
|
89
|
+
sitemap.src.slice(endPosition);
|
|
90
|
+
} else {
|
|
91
|
+
sitemap.src = sitemap.src.replace(
|
|
92
|
+
"</urlset>",
|
|
93
|
+
`${entry}</urlset>`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
file.sitemap.pathname = sitemap.pathname;
|
|
98
|
+
sitemap.src = sitemap.src.replace(
|
|
99
|
+
"</urlset>",
|
|
100
|
+
`${entry}</urlset>`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.saveSitemap(mainSitemap, host);
|
|
105
|
+
this.saveSitemap(sitemap, host);
|
|
106
|
+
this.saveSitemap(file, host);
|
|
107
|
+
|
|
108
|
+
// console.log('Sitemap updated successfully.');
|
|
109
|
+
} catch (err) {
|
|
110
|
+
console.error("Error updating sitemap:", err);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
createEntry(file) {
|
|
115
|
+
file.sitemap.loc = file.pathname;
|
|
116
|
+
file.sitemap.lastmod = file.modified.on;
|
|
117
|
+
|
|
118
|
+
if (file["content-type"] === "text/html") {
|
|
119
|
+
this.parseHtml(file);
|
|
120
|
+
|
|
121
|
+
if (
|
|
122
|
+
file.sitemap.type !== "news" &&
|
|
123
|
+
file.sitemap.type !== "image" &&
|
|
124
|
+
file.sitemap.type !== "video"
|
|
125
|
+
) {
|
|
126
|
+
if (!file.sitemap.changefreq)
|
|
127
|
+
file.sitemap.changefreq = "monthly";
|
|
128
|
+
|
|
129
|
+
if (!file.sitemap.priority) {
|
|
130
|
+
const depth = (file.pathname.match(/\//g) || []).length;
|
|
131
|
+
file.sitemap.priority = Math.max(
|
|
132
|
+
0.1,
|
|
133
|
+
1.0 - (depth - 1) * 0.1
|
|
134
|
+
).toFixed(1);
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
delete file.sitemap.changefreq;
|
|
138
|
+
delete file.sitemap.priority;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let entry = `\t<url>\n`;
|
|
143
|
+
|
|
144
|
+
for (const key of Object.keys(file.sitemap)) {
|
|
145
|
+
if (key === "pathname" || key === "type") continue;
|
|
146
|
+
|
|
147
|
+
let value = file.sitemap[key];
|
|
148
|
+
|
|
149
|
+
if (
|
|
150
|
+
typeof value === "object" &&
|
|
151
|
+
value !== null &&
|
|
152
|
+
!(value instanceof Date)
|
|
153
|
+
) {
|
|
154
|
+
if (!Array.isArray(value)) value = [value];
|
|
155
|
+
|
|
156
|
+
for (let i = 0; i < value.length; i++) {
|
|
157
|
+
entry += `\t\t<${key}:${key}>\n`;
|
|
158
|
+
|
|
159
|
+
for (const nestedKey of Object.keys(value[i])) {
|
|
160
|
+
let nestedValue = value[i][nestedKey];
|
|
161
|
+
// Handle nested objects
|
|
162
|
+
if (
|
|
163
|
+
typeof nestedValue === "object" &&
|
|
164
|
+
nestedValue !== null &&
|
|
165
|
+
!(nestedValue instanceof Date)
|
|
166
|
+
) {
|
|
167
|
+
entry += `\t\t\t<${key}:${nestedKey}>\n`;
|
|
168
|
+
for (const subKey of Object.keys(nestedValue)) {
|
|
169
|
+
const subValue = nestedValue[subKey];
|
|
170
|
+
entry += `\t\t\t\t<${key}:${subKey}>${subValue}</${key}:${subKey}>\n`;
|
|
171
|
+
}
|
|
172
|
+
entry += `\t\t\t</${key}:${nestedKey}>\n`;
|
|
173
|
+
} else {
|
|
174
|
+
if (nestedKey === "loc") {
|
|
175
|
+
if (
|
|
176
|
+
!nestedValue.startsWith("https://") &&
|
|
177
|
+
!nestedValue.startsWith("http://") &&
|
|
178
|
+
!nestedValue.startsWith("{{$host}}")
|
|
179
|
+
) {
|
|
180
|
+
nestedValue = `{{$host}}${nestedValue}`;
|
|
181
|
+
}
|
|
182
|
+
} else if (nestedKey === "publication_date") {
|
|
183
|
+
nestedValue =
|
|
184
|
+
new Date(nestedValue)
|
|
185
|
+
.toISOString()
|
|
186
|
+
.split(".")[0] + "Z";
|
|
187
|
+
} else {
|
|
188
|
+
nestedValue = this.encodeXML(nestedValue);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
entry += `\t\t\t<${key}:${nestedKey}>${nestedValue}</${key}:${nestedKey}>\n`;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
entry += `\t\t</${key}:${key}>\n`;
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
if (key === "loc") {
|
|
199
|
+
if (
|
|
200
|
+
!value.startsWith("https://") &&
|
|
201
|
+
!value.startsWith("http://")
|
|
202
|
+
) {
|
|
203
|
+
value = `{{$host}}${value}`;
|
|
204
|
+
}
|
|
205
|
+
} else if (key === "lastmod") {
|
|
206
|
+
value =
|
|
207
|
+
new Date(file.modified.on).toISOString().split(".")[0] +
|
|
208
|
+
"Z";
|
|
209
|
+
} else {
|
|
210
|
+
value = this.encodeXML(value);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
entry += `\t\t<${key}>${value}</${key}>\n`;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
entry += `\t</url>\n`;
|
|
218
|
+
|
|
219
|
+
return entry;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async getSitemap(file, host) {
|
|
223
|
+
let mainSitemap = {
|
|
224
|
+
host: file.host,
|
|
225
|
+
name: "sitemap.xml",
|
|
226
|
+
path: "/",
|
|
227
|
+
pathname: "/sitemap.xml",
|
|
228
|
+
directory: "/",
|
|
229
|
+
"content-type": "application/xml",
|
|
230
|
+
public: true,
|
|
231
|
+
organization_id: file.organization_id
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
mainSitemap = await this.readSitemap(mainSitemap, host);
|
|
235
|
+
if (!mainSitemap.src) mainSitemap.src = this.createSitemap("main");
|
|
236
|
+
|
|
237
|
+
let sitemap = {
|
|
238
|
+
host: file.host,
|
|
239
|
+
path: "/",
|
|
240
|
+
pathname: file.sitemap.pathname,
|
|
241
|
+
directory: "/",
|
|
242
|
+
"content-type": "application/xml",
|
|
243
|
+
public: true,
|
|
244
|
+
organization_id: file.organization_id
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Update loc using pathname
|
|
248
|
+
file.sitemap.loc = `${file.pathname}`;
|
|
249
|
+
|
|
250
|
+
// Query the database for the correct sitemap based on the loc and type
|
|
251
|
+
if (file.sitemap.pathname) {
|
|
252
|
+
sitemap = await this.readSitemap(sitemap, host);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!sitemap.src) {
|
|
256
|
+
let type = "sitemap";
|
|
257
|
+
|
|
258
|
+
// Identify content type to determine sitemap type
|
|
259
|
+
if (file["content-type"].startsWith("image/")) {
|
|
260
|
+
type = "image";
|
|
261
|
+
} else if (file["content-type"].startsWith("video/")) {
|
|
262
|
+
type = "video";
|
|
263
|
+
} else if (file.sitemap.type === "news") {
|
|
264
|
+
type = "news";
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let name = `sitemap`;
|
|
268
|
+
if (type === "image" || type === "video" || type === "news")
|
|
269
|
+
name = `sitemap-${type}`;
|
|
270
|
+
|
|
271
|
+
// If no existing sitemap found check last index sitemap
|
|
272
|
+
let index = await this.getLastSitemapIndex(mainSitemap, name);
|
|
273
|
+
if (index) {
|
|
274
|
+
sitemap.pathname = `/${name}${index}.xml`;
|
|
275
|
+
sitemap = await this.readSitemap(sitemap, host);
|
|
276
|
+
} else {
|
|
277
|
+
index = 1;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Check if there's room in the last index sitemap
|
|
281
|
+
if (!this.checkSitemap(sitemap.src)) {
|
|
282
|
+
if (sitemap.src) index += 1;
|
|
283
|
+
else sitemap.src = this.createSitemap(type);
|
|
284
|
+
|
|
285
|
+
sitemap.name = `${name}${index}.xml`;
|
|
286
|
+
sitemap.pathname = `/${name}${index}.xml`;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Create the regex pattern to match the <sitemap> block containing the specific <loc> for the pathname
|
|
291
|
+
const regexPattern = `<sitemap>\\s*<loc>[^<]*${sitemap.pathname}[^<]*</loc>[\\s\\S]*?</sitemap>`;
|
|
292
|
+
|
|
293
|
+
// Execute the regex match against the sitemap index source
|
|
294
|
+
const match = mainSitemap.src.match(new RegExp(regexPattern));
|
|
295
|
+
|
|
296
|
+
// Check if a match is found
|
|
297
|
+
if (!match) {
|
|
298
|
+
//TODO: if sitemap found but not in index should we add to sitemap pathname to index or should we check the sitmap for the next index available see if room add or create new index.
|
|
299
|
+
const indexEntry = `\t<sitemap>\n\t\t<loc>{{$host}}${sitemap.pathname}</loc>\n</sitemap>`;
|
|
300
|
+
mainSitemap.src = mainSitemap.src.replace(
|
|
301
|
+
"</sitemapindex>",
|
|
302
|
+
`${indexEntry}\n</sitemapindex>`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return { mainSitemap, sitemap };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async readSitemap(file, host) {
|
|
310
|
+
let data = {
|
|
311
|
+
method: "object.read",
|
|
312
|
+
host: host,
|
|
313
|
+
array: "files",
|
|
314
|
+
$filter: {
|
|
315
|
+
query: {
|
|
316
|
+
host: { $in: [host, "*"] },
|
|
317
|
+
pathname: file.pathname
|
|
318
|
+
},
|
|
319
|
+
limit: 1
|
|
320
|
+
},
|
|
321
|
+
organization_id: file.organization_id
|
|
322
|
+
};
|
|
323
|
+
data = await this.crud.send(data);
|
|
324
|
+
if (data.object && data.object.length) return data.object[0];
|
|
325
|
+
else return file;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
createSitemap(type) {
|
|
329
|
+
const xmlDeclaration = `<?xml version="1.0" encoding="UTF-8"?>\n`;
|
|
330
|
+
const sitemapNamespace =
|
|
331
|
+
'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"';
|
|
332
|
+
|
|
333
|
+
if (type === "main") {
|
|
334
|
+
return `${xmlDeclaration}<sitemapindex ${sitemapNamespace}>\n</sitemapindex>`;
|
|
335
|
+
} else {
|
|
336
|
+
const imageNamespace =
|
|
337
|
+
'xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"';
|
|
338
|
+
const videoNamespace =
|
|
339
|
+
'xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"';
|
|
340
|
+
const newsNamespace =
|
|
341
|
+
'xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"';
|
|
342
|
+
|
|
343
|
+
if (type === "image") {
|
|
344
|
+
return `${xmlDeclaration}<urlset ${sitemapNamespace} ${imageNamespace}>\n</urlset>`;
|
|
345
|
+
} else if (type === "video") {
|
|
346
|
+
return `${xmlDeclaration}<urlset ${sitemapNamespace} ${videoNamespace}>\n</urlset>`;
|
|
347
|
+
} else {
|
|
348
|
+
// For 'news' type or any other types
|
|
349
|
+
return `${xmlDeclaration}<urlset ${sitemapNamespace} ${newsNamespace} ${imageNamespace} ${videoNamespace}>\n</urlset>`;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async saveSitemap(file, host) {
|
|
355
|
+
let data = {
|
|
356
|
+
method: "object.update",
|
|
357
|
+
host: host,
|
|
358
|
+
array: "files",
|
|
359
|
+
object: file,
|
|
360
|
+
upsert: true,
|
|
361
|
+
organization_id: file.organization_id
|
|
362
|
+
};
|
|
363
|
+
if (!file._id)
|
|
364
|
+
data.$filter = {
|
|
365
|
+
query: {
|
|
366
|
+
host: { $in: [host, "*"] },
|
|
367
|
+
pathname: file.pathname
|
|
368
|
+
},
|
|
369
|
+
limit: 1
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
data = await this.crud.send(data);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async getLastSitemapIndex(mainSitemap, filename) {
|
|
376
|
+
try {
|
|
377
|
+
// Use regex to match all sitemap entries for the given type
|
|
378
|
+
const regex = new RegExp(`\\/${filename}(\\d*)\\.xml<\\/loc>`, "g");
|
|
379
|
+
const matches = mainSitemap.src.match(regex);
|
|
380
|
+
|
|
381
|
+
return matches ? matches.length : null;
|
|
382
|
+
} catch (err) {
|
|
383
|
+
console.error(
|
|
384
|
+
`Error determining next sitemap index for ${filename}:`,
|
|
385
|
+
err
|
|
386
|
+
);
|
|
387
|
+
return null; // Or some default value or throw an error
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
checkSitemap(sitemap) {
|
|
392
|
+
try {
|
|
393
|
+
if (!sitemap) return false;
|
|
394
|
+
|
|
395
|
+
// Count the number of <url> entries
|
|
396
|
+
const urlCount = (sitemap.match(/<url>/g) || []).length;
|
|
397
|
+
if (urlCount >= 50000) return false;
|
|
398
|
+
|
|
399
|
+
// Get the size of the sitemap string in bytes
|
|
400
|
+
const fileSizeInBytes = Buffer.byteLength(sitemap, "utf8");
|
|
401
|
+
const fileSizeInMB = fileSizeInBytes / (1024 * 1024);
|
|
402
|
+
|
|
403
|
+
// console.log(`Sitemap has ${urlCount} entries and is ${fileSizeInMB.toFixed(2)} MB.`);
|
|
404
|
+
|
|
405
|
+
// Check if the file size exceeds either the 50MB limit or the MongoDB 16MB limit
|
|
406
|
+
if (fileSizeInMB >= 50 || fileSizeInMB >= 15) return false;
|
|
407
|
+
|
|
408
|
+
return true;
|
|
409
|
+
} catch (err) {
|
|
410
|
+
console.error("Error checking sitemap file:", err);
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
parseHtml(file) {
|
|
416
|
+
const dom = parse(file.src);
|
|
417
|
+
const entries = dom.querySelectorAll('[sitemap="true"]');
|
|
418
|
+
|
|
419
|
+
let types = ["image", "video", "news"];
|
|
420
|
+
|
|
421
|
+
const previousEntries = {};
|
|
422
|
+
for (let i = 0; i < types.length; i++) {
|
|
423
|
+
if (!file.sitemap[types[i]]) continue;
|
|
424
|
+
if (Array.isArray(file.sitemap[types[i]]))
|
|
425
|
+
previousEntries[types[i]] = file.sitemap[types[i]];
|
|
426
|
+
else previousEntries[types[i]] = [file.sitemap[types[i]]];
|
|
427
|
+
|
|
428
|
+
delete file.sitemap[types[i]];
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
for (let i = 0; i < entries.length; i++) {
|
|
432
|
+
let type = "",
|
|
433
|
+
query = "";
|
|
434
|
+
let existingObject;
|
|
435
|
+
let entryObject = {};
|
|
436
|
+
|
|
437
|
+
if (entries[i].tagName === "IMG") {
|
|
438
|
+
// Corrected to 'IMG' for images
|
|
439
|
+
type = "image";
|
|
440
|
+
query = "loc";
|
|
441
|
+
entryObject.loc = entries[i].getAttribute("src");
|
|
442
|
+
entryObject.title =
|
|
443
|
+
entries[i].getAttribute("sitemap-title") ||
|
|
444
|
+
entries[i].getAttribute("title") ||
|
|
445
|
+
entries[i].getAttribute("alt");
|
|
446
|
+
entryObject.caption =
|
|
447
|
+
entries[i].getAttribute("sitemap-caption") ||
|
|
448
|
+
entries[i].getAttribute("alt") ||
|
|
449
|
+
entryObject.title;
|
|
450
|
+
entryObject.geo_location = entries[i].getAttribute(
|
|
451
|
+
"sitemap-geo-location"
|
|
452
|
+
);
|
|
453
|
+
} else if (entries[i].tagName === "VIDEO") {
|
|
454
|
+
type = "video";
|
|
455
|
+
query = "content_loc";
|
|
456
|
+
entryObject.content_loc = entries[i].src;
|
|
457
|
+
entryObject.title =
|
|
458
|
+
entries[i].getAttribute("sitemap-title") ||
|
|
459
|
+
entries[i].getAttribute("title");
|
|
460
|
+
entryObject.description =
|
|
461
|
+
entries[i].getAttribute("description"); // 'description' if available
|
|
462
|
+
entryObject.thumbnail_loc =
|
|
463
|
+
entries[i].getAttribute("sitemap-thumbnail") ||
|
|
464
|
+
entries[i].getAttribute("poster");
|
|
465
|
+
entryObject.duration =
|
|
466
|
+
entries[i].getAttribute("sitemap-duration");
|
|
467
|
+
} else {
|
|
468
|
+
type = "news";
|
|
469
|
+
file.sitemap.type = "news";
|
|
470
|
+
query = "title";
|
|
471
|
+
entryObject.title = entries[i].getAttribute("sitemap-title");
|
|
472
|
+
if (!entryObject.title) {
|
|
473
|
+
const title = dom.querySelector("title");
|
|
474
|
+
entryObject.title = title ? title.text : "";
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
entryObject.publication = {
|
|
478
|
+
name: entries[i].getAttribute("sitemap-publication-name"), // Use proper attribute
|
|
479
|
+
language: entries[i].getAttribute(
|
|
480
|
+
"sitemap-publication-language"
|
|
481
|
+
) // Use proper attribute
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
if (!entryObject.publication.language) {
|
|
485
|
+
// Fallback to HTML lang attribute
|
|
486
|
+
const htmlElement = dom.querySelector("html");
|
|
487
|
+
entryObject.publication.language = htmlElement
|
|
488
|
+
? htmlElement.getAttribute("lang")
|
|
489
|
+
: null;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
entryObject.publication_date =
|
|
493
|
+
entries[i].getAttribute("sitemap-publication-date") ||
|
|
494
|
+
file.modified.on;
|
|
495
|
+
|
|
496
|
+
entryObject.keywords =
|
|
497
|
+
entries[i].getAttribute("sitemap-keywords");
|
|
498
|
+
if (!entryObject.keywords) {
|
|
499
|
+
const keywords = dom.querySelector('meta[name="keywords"]');
|
|
500
|
+
entryObject.keywords = keywords
|
|
501
|
+
? keywords.getAttribute("content")
|
|
502
|
+
: "";
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
entryObject.genres = entries[i].getAttribute("sitemap-genres");
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (previousEntries[type]) {
|
|
509
|
+
existingObject = previousEntries[type].find(
|
|
510
|
+
(item) => item[query] === entryObject[query]
|
|
511
|
+
);
|
|
512
|
+
entryObject = { ...existingObject, ...entryObject };
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
Object.keys(entryObject).forEach((key) => {
|
|
516
|
+
if (!entryObject[key]) delete entryObject[key];
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
if (!file.sitemap[type]) file.sitemap[type] = [];
|
|
520
|
+
|
|
521
|
+
file.sitemap[type].push(entryObject);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (
|
|
525
|
+
file.sitemap.type !== "news" &&
|
|
526
|
+
file.sitemap.type !== "image" &&
|
|
527
|
+
file.sitemap.type !== "video"
|
|
528
|
+
) {
|
|
529
|
+
const priorityMeta = dom.querySelector(
|
|
530
|
+
'meta[name="sitemap-priority"]'
|
|
531
|
+
);
|
|
532
|
+
const changefreqMeta = dom.querySelector(
|
|
533
|
+
'meta[name="sitemap-changefreq"]'
|
|
534
|
+
);
|
|
535
|
+
file.sitemap.priority = priorityMeta
|
|
536
|
+
? priorityMeta.getAttribute("content")
|
|
537
|
+
: file.sitemap.priority; // Default priority if not specified
|
|
538
|
+
file.sitemap.changefreq = changefreqMeta
|
|
539
|
+
? changefreqMeta.getAttribute("content")
|
|
540
|
+
: file.sitemap.changefreq || "monthly"; // Default changefreq if not specified
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
encodeXML(str) {
|
|
545
|
+
if (str)
|
|
546
|
+
return str
|
|
547
|
+
.replace(/&/g, "&")
|
|
548
|
+
.replace(/</g, "<")
|
|
549
|
+
.replace(/>/g, ">")
|
|
550
|
+
.replace(/"/g, """)
|
|
551
|
+
.replace(/'/g, "'");
|
|
552
|
+
}
|
|
477
553
|
}
|
|
478
554
|
|
|
479
555
|
module.exports = CoCreateSitemap;
|