@tkeron/html-parser 0.1.3 → 0.1.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.
@@ -0,0 +1,208 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { parseHTML } from "../index";
3
+
4
+ describe("outerHTML replacement - Browser behavior", () => {
5
+ it("should replace element with its innerHTML when setting outerHTML = innerHTML", () => {
6
+
7
+
8
+
9
+
10
+ const doc = parseHTML(`
11
+ <html>
12
+ <body>
13
+ <div id="mi-prueba" style="border: 2px solid red; padding: 10px;">
14
+ <strong>Lorem ipsum!</strong> Dolor sit amet consectetur.
15
+ </div>
16
+ </body>
17
+ </html>
18
+ `);
19
+
20
+ const elem = doc.querySelector("#mi-prueba");
21
+ expect(elem).not.toBeNull();
22
+
23
+
24
+ const innerHTML = elem!.innerHTML;
25
+ expect(innerHTML).toContain("<strong>Lorem ipsum!</strong>");
26
+ expect(innerHTML).toContain("Dolor sit amet consectetur.");
27
+
28
+
29
+ const parent = elem!.parentNode;
30
+ expect(parent).not.toBeNull();
31
+ expect(parent!.childNodes).toContain(elem);
32
+
33
+
34
+ elem!.outerHTML = innerHTML;
35
+
36
+
37
+ const elemAfter = doc.querySelector("#mi-prueba");
38
+ expect(elemAfter).toBeNull();
39
+
40
+
41
+ const body = doc.querySelector("body");
42
+ expect(body!.innerHTML).toContain("<strong>Lorem ipsum!</strong>");
43
+ expect(body!.innerHTML).toContain("Dolor sit amet consectetur.");
44
+
45
+
46
+ expect(body!.innerHTML).not.toContain('id="mi-prueba"');
47
+ expect(body!.innerHTML).not.toContain('style=');
48
+ });
49
+
50
+ it("should replace element with simple text content", () => {
51
+ const doc = parseHTML(`
52
+ <div>
53
+ <p id="paragraph" class="styled">Simple text</p>
54
+ </div>
55
+ `);
56
+
57
+ const paragraph = doc.querySelector("#paragraph");
58
+ expect(paragraph).not.toBeNull();
59
+
60
+ const parent = paragraph!.parentNode;
61
+ const innerHTML = paragraph!.innerHTML;
62
+
63
+
64
+ paragraph!.outerHTML = innerHTML;
65
+
66
+
67
+ expect(doc.querySelector("#paragraph")).toBeNull();
68
+
69
+
70
+ expect(parent!.textContent).toContain("Simple text");
71
+ });
72
+
73
+ it("should replace element with multiple child nodes", () => {
74
+ const doc = parseHTML(`
75
+ <ul>
76
+ <li id="item-container">
77
+ <span>Item 1</span>
78
+ <span>Item 2</span>
79
+ </li>
80
+ </ul>
81
+ `);
82
+
83
+ const container = doc.querySelector("#item-container");
84
+ expect(container).not.toBeNull();
85
+
86
+ const ul = doc.querySelector("ul");
87
+ const innerHTML = container!.innerHTML;
88
+
89
+
90
+ container!.outerHTML = innerHTML;
91
+
92
+
93
+ expect(doc.querySelector("#item-container")).toBeNull();
94
+
95
+
96
+ const spans = ul!.querySelectorAll("span");
97
+ expect(spans.length).toBe(2);
98
+ expect(spans[0]?.textContent).toBe("Item 1");
99
+ expect(spans[1]?.textContent).toBe("Item 2");
100
+ });
101
+
102
+ it("should replace element with empty string", () => {
103
+ const doc = parseHTML(`
104
+ <div>
105
+ <span id="to-remove"></span>
106
+ </div>
107
+ `);
108
+
109
+ const span = doc.querySelector("#to-remove");
110
+ expect(span).not.toBeNull();
111
+
112
+ const parent = span!.parentNode;
113
+ const childCountBefore = parent!.childNodes.length;
114
+
115
+
116
+ span!.outerHTML = "";
117
+
118
+
119
+ expect(doc.querySelector("#to-remove")).toBeNull();
120
+
121
+
122
+ expect(parent!.childNodes.length).toBe(childCountBefore - 1);
123
+ });
124
+
125
+ it("should replace element with new HTML structure", () => {
126
+ const doc = parseHTML(`
127
+ <div>
128
+ <p id="old">Old content</p>
129
+ </div>
130
+ `);
131
+
132
+ const oldParagraph = doc.querySelector("#old");
133
+ expect(oldParagraph).not.toBeNull();
134
+
135
+ const parent = oldParagraph!.parentNode;
136
+
137
+
138
+ oldParagraph!.outerHTML = '<div id="new">New content</div>';
139
+
140
+
141
+ expect(doc.querySelector("#old")).toBeNull();
142
+
143
+
144
+ const newDiv = doc.querySelector("#new");
145
+ expect(newDiv).not.toBeNull();
146
+ expect(newDiv!.textContent).toBe("New content");
147
+ expect(newDiv!.parentNode).toBe(parent);
148
+ });
149
+
150
+ it("should maintain sibling relationships after outerHTML replacement", () => {
151
+ const doc = parseHTML(`
152
+ <div>
153
+ <span>First</span>
154
+ <p id="middle">Middle</p>
155
+ <span>Last</span>
156
+ </div>
157
+ `);
158
+
159
+ const middle = doc.querySelector("#middle");
160
+ const firstSpan = doc.querySelectorAll("span")[0];
161
+ const lastSpan = doc.querySelectorAll("span")[1];
162
+
163
+
164
+ middle!.outerHTML = middle!.innerHTML;
165
+
166
+
167
+ expect(firstSpan!.nextSibling).not.toBe(middle);
168
+ expect(lastSpan!.previousSibling).not.toBe(middle);
169
+
170
+
171
+ const parent = firstSpan!.parentNode;
172
+ expect(parent!.textContent).toContain("Middle");
173
+ });
174
+
175
+ it("should handle complex nested HTML replacement", () => {
176
+ const doc = parseHTML(`
177
+ <article>
178
+ <section id="wrapper" class="container" data-id="123">
179
+ <h2>Title</h2>
180
+ <p>Paragraph <strong>bold</strong> text</p>
181
+ <ul>
182
+ <li>Item 1</li>
183
+ <li>Item 2</li>
184
+ </ul>
185
+ </section>
186
+ </article>
187
+ `);
188
+
189
+ const wrapper = doc.querySelector("#wrapper");
190
+ expect(wrapper).not.toBeNull();
191
+
192
+ const article = doc.querySelector("article");
193
+ const innerHTML = wrapper!.innerHTML;
194
+
195
+
196
+ wrapper!.outerHTML = innerHTML;
197
+
198
+
199
+ expect(doc.querySelector("#wrapper")).toBeNull();
200
+ expect(doc.querySelector("section")).toBeNull();
201
+
202
+
203
+ expect(article!.querySelector("h2")).not.toBeNull();
204
+ expect(article!.querySelector("h2")!.textContent).toBe("Title");
205
+ expect(article!.querySelector("strong")).not.toBeNull();
206
+ expect(article!.querySelectorAll("li").length).toBe(2);
207
+ });
208
+ });
@@ -326,7 +326,7 @@ describe('HTML Parser', () => {
326
326
  )!;
327
327
  expect(htmlElement.type).toBe(ASTNodeType.ELEMENT);
328
328
  expect(htmlElement.tagName).toBe('html');
329
- expect(htmlElement.attributes!.lang).toBe('es');
329
+ expect(htmlElement.attributes!.lang).toBe('en');
330
330
  const headElement = htmlElement.children!.find(
331
331
  child => child.type === ASTNodeType.ELEMENT && child.tagName === 'head'
332
332
  )!;
@@ -1,362 +1,19 @@
1
1
 
2
-
3
2
  <!DOCTYPE html>
4
- <html lang="es"><head>
3
+ <html lang="en"><head>
5
4
  <meta charset="UTF-8">
6
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
- <title>LEMAGUILERA TECH SOLUTIONS - Servicios</title>
9
- <style>.container {
10
- width: 100%;
11
- display: flex;
12
- flex-direction: row;
13
- align-items: center;
14
- justify-content: center;
15
-
16
- background-position: center;
17
- background-repeat: no-repeat;
18
- background-size: cover;
19
-
20
- position: relative;
21
- }
22
-
23
- .container .content {
24
- max-width: 1280px;
25
- width: 100%;
26
- z-index: 1;
27
- padding: 0px 15px;
28
- }
29
-
30
- .container .bg_color {
31
- position: absolute;
32
-
33
- top: 0px;
34
- left: 0px;
35
-
36
- height: 100%;
37
- width: 100%;
38
-
39
- z-index: 0;
40
- }
41
- </style><style>a.a {
42
- text-decoration: none;
43
- }
44
- </style><style>.logo {
45
- height: 62%;
46
- }
47
-
48
- a.logo {
49
- height: 100%;
50
- }
51
- </style><style>.footer {
52
- padding-top: 80px;
53
- width: 100%;
54
- background: #060303;
55
- color: #eed;
56
- }
57
-
58
- .footer a {
59
- text-decoration: none;
60
- color: #eed;
61
- }
62
-
63
- .footer a:visited {
64
- color: inherit;
65
- }
66
-
67
- .footer a {
68
- grid-area: a;
69
- }
70
-
71
- .footer .copyright {
72
- grid-area: c;
73
- justify-self: center;
74
- margin-top: 40px;
75
- font-size: 12px;
76
- }
77
-
78
- .footer .content {
79
- display: grid;
80
- grid-template:
81
- "l a1 a4"
82
- "l a2 a5"
83
- "l a3 a6"
84
- "l a7 a8"
85
- "c c c";
86
- justify-content: space-between;
87
- }
88
-
89
- .footer .logo {
90
- max-width: 250px;
91
- height: auto;
92
- }
93
-
94
- .footer a.logo {
95
- grid-area: l;
96
- }
97
-
98
- @media screen and (max-width: 800px) {
99
- .footer .content {
100
- display: grid;
101
- grid-template:
102
- "a1 a4"
103
- "a2 a5"
104
- "a3 a6"
105
- "a7 a8"
106
- "l l"
107
- "c c";
108
- justify-content: space-evenly;
109
- }
110
- .footer .logo {
111
- margin-top: 20px;
112
- justify-self: center;
113
- }
114
- }
115
- </style><style>.top {
116
- background: linear-gradient(0deg, #060303, #6c6868);
117
- }
118
-
119
- .container.top .content {
120
- display: flex;
121
- flex-direction: row;
122
- justify-content: space-between;
123
- align-items: center;
124
-
125
- height: 90px;
126
- }
127
-
128
- .container.top .content a {
129
- display: flex;
130
-
131
- align-items: center;
132
- }
133
-
134
- @media screen and (max-width: 600px) {
135
- .container.top .content {
136
- height: 60px;
137
- }
138
- }
139
- </style><style>
140
- .sectionMenu .content {
141
- width: 100%;
142
- display: flex;
143
- justify-content: space-between;
144
-
145
-
146
- padding-top: 10px;
147
- padding-bottom: 10px;
148
-
149
-
150
- font-weight: bold;
151
- font-size: 20px;
152
- color: #fff;
153
- }
154
-
155
- .sectionMenu {
156
- background: linear-gradient(0deg, #f2be11, #87722d);
157
- }
158
-
159
- .sectionMenu a.a {
160
- color: white;
161
- }
162
- </style><style></style><style>.button,
163
- button {
164
- border: none;
165
- padding: 20px 35px;
166
- font-size: 30px;
167
- font-weight: bold;
168
- border-radius: 15px;
169
- cursor: pointer;
170
-
171
- color: #1c1c1c;
172
- background: #f2be11;
173
- }
174
-
175
- a.button {
176
- display: block;
177
- width: fit-content;
178
- margin: 20px;
179
- text-align: center;
180
- }
181
- </style><style>.container.header .content {
182
- display: grid;
183
- grid-template-areas:
184
- "h1 h1"
185
- "im h2";
186
- grid-template-columns: 1fr 720px;
187
- gap: 30px;
188
- margin: 50px auto;
189
- color: #eed;
190
- }
191
- .service .content {
192
- display: grid;
193
- grid-template-areas:
194
- "h3 h3"
195
- "h4 h4"
196
- "a p";
197
- grid-template-columns: 1fr 720px;
198
- gap: 30px;
199
- margin: 100px auto;
200
- color: #4c4c4c;
201
- }
202
-
203
- .header .content h1 {
204
- grid-area: h1;
205
- }
206
- .header .content h2 {
207
- grid-area: h2;
208
- }
209
- .header .content img {
210
- grid-area: im;
211
- width: 100%;
212
- }
213
-
214
- .service .content a {
215
- display: flex;
216
- grid-area: a;
217
- justify-content: center;
218
- align-items: center;
219
- margin-left: 0;
220
- }
221
-
222
- .service .content h3 {
223
- grid-area: h3;
224
- max-width: 720px;
225
- }
226
- .service .content h4 {
227
- grid-area: h4;
228
- max-width: 720px;
229
- }
230
- .service .content p {
231
- grid-area: p;
232
- max-width: 720px;
233
- }
234
-
235
- .service.service4,
236
- .service.service2 {
237
- background: #4c4c4c;
238
- }
239
-
240
- .service.service4 .content,
241
- .service.service2 .content {
242
- color: #eed;
243
- }
244
-
245
- .container.cta .content {
246
- display: flex;
247
- justify-content: center;
248
- align-items: center;
249
- flex-direction: column;
250
- margin: 100px auto;
251
- }
252
-
253
- @media screen and (max-width: 1280px) {
254
- .service .content {
255
- grid-template-areas:
256
- "h3 h3"
257
- "h4 h4"
258
- ". p"
259
- "a a";
260
- grid-template-columns: 1fr;
261
- gap: 30px;
262
- }
263
- .service .content a {
264
- justify-content: center;
265
- align-items: center;
266
- margin-left: 0;
267
- justify-self: end;
268
- margin-right: 0;
269
- }
270
- .container.header .content {
271
- display: grid;
272
- grid-template-areas:
273
- "h1 h1"
274
- "im h2";
275
- grid-template-columns: 1fr 2fr;
276
- gap: 30px;
277
- margin: 50px auto;
278
- color: #eed;
279
- }
280
- }
281
- @media screen and (max-width: 700px) {
282
- .container.header .content {
283
- grid-template-areas:
284
- "h1"
285
- "h2";
286
- grid-template-columns: 1fr;
287
- }
288
- }
289
- </style><style>* {
290
- margin: 0;
291
- padding: 0;
292
- box-sizing: border-box;
293
- transition: all 0.3s ease;
294
- font-family: inherit;
295
- }
296
-
297
- body,
298
- button {
299
- font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
300
- Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
301
-
302
- line-height: 1.6;
303
- }
304
-
305
- h1 {
306
- font-size: 60px;
307
- }
308
- h2 {
309
- font-size: 42px;
310
- }
311
- h3 {
312
- font-size: 32px;
313
- }
314
- h4 {
315
- font-size: 20px;
316
- }
317
- p {
318
- font-size: 18px;
319
- }
320
-
321
- @media screen and (max-width: 1200px) {
322
- h1 {
323
- font-size: 42px;
324
- }
325
- h2 {
326
- font-size: 32px;
327
- }
328
- h3 {
329
- font-size: 20px;
330
- }
331
- h4 {
332
- font-size: 18px;
333
- }
334
- p {
335
- font-size: 16px;
336
- }
337
- }
338
-
339
- @media screen and (max-width: 800px) {
340
- h1 {
341
- font-size: 32px;
342
- }
343
- h2 {
344
- font-size: 20px;
345
- }
346
- h3 {
347
- font-size: 18px;
348
- }
349
- h4{
350
- font-size: 16px;
351
- }
352
- p {
353
- font-size: 14px;
354
- }
355
- }
356
- </style><script id='tkeron_page_js'>(()=>{})();
357
- </script></head>
6
+ <title>Lorem Ipsum Services</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body { font-family: Arial, sans-serif; line-height: 1.6; }
10
+ .container { max-width: 1200px; margin: 0 auto; padding: 20px; }
11
+ .header { background: #333; color: #fff; text-align: center; padding: 60px 20px; }
12
+ .service { margin: 40px 0; padding: 20px; border: 1px solid #ddd; }
13
+ .footer { background: #f4f4f4; padding: 20px; text-align: center; margin-top: 40px; }
14
+ </style>
15
+ </head>
358
16
 
359
17
  <body>
360
18
 
361
- <div class="container top"><div class="content"><a class="a logo" href="/"><img class="logo" src="/./LTS_LOGO-D7NU7N7F.png"></a></div></div><div class="container sectionMenu"><div class="content"><a class="a" href="/">INICIO</a><a class="a" href="/servicios">SERVICIOS</a><a class="a" href="/contacto">CONTACTO</a></div></div><div class="container header" style="background-image: url(&quot;/./bg-ES2RJEGU.png&quot;);"><div class="bg_color" style="background: rgba(0, 0, 0, 0.4);"></div><div class="content"><h1>Nuestros Servicios</h1><h2>Soluciones a medida para optimizar y automatizar tus procesos empresariales con Google Workspace.</h2></div></div><div class="container service service1"><div class="content"><h3>Apps Personalizadas para Google Workspace</h3><h4>Creamos aplicaciones a medida que se integran perfectamente con Google Docs, Sheets, Gmail y más.</h4><p>Diseñamos e implementamos aplicaciones personalizadas que amplían la funcionalidad de Google Workspace según las necesidades únicas de tu negocio.</p><a class="a button" href="/contacto/apps-personalizadas-para-google-workspace">Solicita una app a medida</a></div></div><div class="container service service2"><div class="content"><h3>Automatización de Procesos</h3><h4>Automatizamos tareas repetitivas y flujos de trabajo con Google Apps Script.</h4><p>Ahorra tiempo y reduce errores humanos automatizando flujos de trabajo con potentes soluciones basadas en Google Apps Script.</p><a class="a button" href="/contacto/automatizacion-de-procesos">Automatiza tus procesos</a></div></div><div class="container service service3"><div class="content"><h3>Widgets y Extensiones Personalizadas</h3><h4>Desarrollamos barras laterales, cuadros de diálogo y herramientas personalizadas para Docs, Sheets y Gmail.</h4><p>Creamos interfaces y herramientas dentro de Google Workspace que mejoran la productividad y experiencia de tu equipo.</p><a class="a button" href="/contacto/widgets-y-extensiones-personalizadas">Agrega un widget personalizado</a></div></div><div class="container service service4"><div class="content"><h3>Integraciones con APIs de Google Workspace</h3><h4>Conectamos tus sistemas internos con los servicios de Google para un flujo de datos sin interrupciones.</h4><p>Desarrollamos conexiones seguras entre tus sistemas y Google Workspace para unificar operaciones y datos.</p><a class="a button" href="/contacto/integraciones-con-apis-de-google-workspace">Integra con APIs</a></div></div><div class="container service service5"><div class="content"><h3>Otras Necesidades Personalizadas</h3><h4>Abiertos a conversar sobre requerimientos específicos no listados anteriormente.</h4><p>Nos adaptamos a las necesidades de tu negocio. Si tienes un desafío o una idea particular relacionada con Google Workspace, estamos listos para ayudarte a diseñar y construir la solución adecuada.</p><a class="a button" href="/contacto/otras-necesidades-personalizadas">Propón tu idea</a></div></div><div class="container footer"><div class="content"><a class="a logo" href="/"><img class="logo" src="/./LTS_LOGO-D7NU7N7F.png"></a><div class="copyright">© 2025 LEMAGUILERA TECH SOLUTIONS. Todos los derechos reservados.</div><a class="a" href="/" style="grid-area: a1;">Inicio</a><a class="a" href="/servicios" style="grid-area: a2;">Servicios</a><a class="a" href="/contacto" style="grid-area: a3;">Contacto</a><a class="a" href="/politica-de-privacidad" style="grid-area: a4;">Política de privacidad</a><a class="a" href="/politica-de-contratacion" style="grid-area: a5;">Política de contratación</a><a class="a" href="/politica-de-cookies" style="grid-area: a6;">Política de cookies</a></div></div><script defer src="https://static.cloudflareinsights.com/beacon.min.js/vcd15cbe7772f49c399c6a5babf22c1241717689176015" integrity="sha512-ZpsOmlRQV6y907TI0dKBHq9Md29nnaEIPlkf84rnaERnq6zvWvPUqr2ft8M1aS28oN72PdrCzSjY4U6VaAw1EQ==" data-cf-beacon='{"rayId":"955eb8668eaacdc9","version":"2025.6.2","r":1,"token":"09161630e3ae4c94992e51777c351c88","serverTiming":{"name":{"cfExtPri":true,"cfEdge":true,"cfOrigin":true,"cfL4":true,"cfSpeedBrain":true,"cfCacheStatus":true}}}' crossorigin="anonymous"></script>
362
- </body></html>
19
+ <div class="container top"><div class="content"><a class="a logo" href="/"><img class="logo" src="/./logo.png"></a></div></div><div class="container sectionMenu"><div class="content"><a class="a" href="/">HOME</a><a class="a" href="/services">SERVICES</a><a class="a" href="/contact">CONTACT</a></div></div><div class="container header" style="background-image: url(&quot;/./bg.png&quot;);"><div class="bg_color" style="background: rgba(0, 0, 0, 0.4);"></div><div class="content"><h1>Our Services</h1><h2>Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor.</h2></div></div><div class="container service service1"><div class="content"><h3>Custom Lorem Applications</h3><h4>Lorem ipsum dolor sit amet consectetur adipiscing elit.</h4><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p><a class="a button" href="/contact/custom-apps">Request custom app</a></div></div><div class="container service service2"><div class="content"><h3>Process Automation</h3><h4>Ut enim ad minim veniam quis nostrud exercitation ullamco.</h4><p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p><a class="a button" href="/contact/automation">Automate processes</a></div></div><div class="container service service3"><div class="content"><h3>Custom Widgets</h3><h4>Excepteur sint occaecat cupidatat non proident sunt in culpa.</h4><p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium.</p><a class="a button" href="/contact/widgets">Add custom widget</a></div></div><div class="container service service4"><div class="content"><h3>API Integrations</h3><h4>Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut.</h4><p>At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium.</p><a class="a button" href="/contact/api">Integrate APIs</a></div></div><div class="container service service5"><div class="content"><h3>Other Custom Needs</h3><h4>Quis autem vel eum iure reprehenderit qui in ea voluptate.</h4><p>Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates.</p><a class="a button" href="/contact/other">Propose your idea</a></div></div><div class="container footer"><div class="content"><a class="a logo" href="/"><img class="logo" src="/./logo.png"></a><div class="copyright">© 2025 LOREM IPSUM TECH. All rights reserved.</div><a class="a" href="/" style="grid-area: a1;">Home</a><a class="a" href="/services" style="grid-area: a2;">Services</a><a class="a" href="/contact" style="grid-area: a3;">Contact</a><a class="a" href="/privacy-policy" style="grid-area: a4;">Privacy Policy</a><a class="a" href="/terms" style="grid-area: a5;">Terms</a><a class="a" href="/cookies" style="grid-area: a6;">Cookies Policy</a></div></div><script defer src="https://static.cloudflareinsights.com/beacon.min.js/vcd15cbe7772f49c399c6a5babf22c1241717689176015" integrity="sha512-ZpsOmlRQV6y907TI0dKBHq9Md29nnaEIPlkf84rnaERnq6zvWvPUqr2ft8M1aS28oN72PdrCzSjY4U6VaAw1EQ==" data-cf-beacon='{"rayId":"955eb8668eaacdc9","version":"2025.6.2","r":1,"token":"09161630e3ae4c94992e51777c351c88","serverTiming":{"name":{"cfExtPri":true,"cfEdge":true,"cfOrigin":true,"cfL4":true,"cfSpeedBrain":true,"cfCacheStatus":true}}}' crossorigin="anonymous"></script>