@tkeron/html-parser 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/npm_deploy.yml +24 -0
- package/LICENSE +21 -0
- package/README.md +120 -0
- package/bun.lock +29 -0
- package/index.ts +18 -0
- package/package.json +25 -0
- package/src/css-selector.ts +172 -0
- package/src/dom-simulator.ts +592 -0
- package/src/dom-types.ts +78 -0
- package/src/parser.ts +355 -0
- package/src/tokenizer.ts +413 -0
- package/tests/advanced.test.ts +487 -0
- package/tests/api-integration.test.ts +114 -0
- package/tests/dom-extended.test.ts +173 -0
- package/tests/dom.test.ts +482 -0
- package/tests/google-dom.test.ts +118 -0
- package/tests/google-homepage.txt +13 -0
- package/tests/official/README.md +87 -0
- package/tests/official/acid/acid-tests.test.ts +309 -0
- package/tests/official/final-output/final-output.test.ts +361 -0
- package/tests/official/html5lib/tokenizer-utils.ts +204 -0
- package/tests/official/html5lib/tokenizer.test.ts +184 -0
- package/tests/official/html5lib/tree-construction-utils.ts +208 -0
- package/tests/official/html5lib/tree-construction.test.ts +250 -0
- package/tests/official/validator/validator-tests.test.ts +237 -0
- package/tests/official/validator-nu/validator-nu.test.ts +335 -0
- package/tests/official/whatwg/whatwg-tests.test.ts +205 -0
- package/tests/official/wpt/wpt-tests.test.ts +409 -0
- package/tests/parser.test.ts +642 -0
- package/tests/selectors.test.ts +65 -0
- package/tests/test-page-0.txt +362 -0
- package/tests/tokenizer.test.ts +666 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, it, expect } from 'bun:test';
|
|
2
|
+
import { querySelector, querySelectorAll } from '../src/css-selector';
|
|
3
|
+
import { parseHTML } from '../index';
|
|
4
|
+
import type { Element, Document } from '../src/dom-simulator';
|
|
5
|
+
|
|
6
|
+
describe('CSS Selectors', () => {
|
|
7
|
+
const htmlContent = `
|
|
8
|
+
<html>
|
|
9
|
+
<body>
|
|
10
|
+
<p id="intro" class="first">
|
|
11
|
+
<span class="highlight">Hello</span>
|
|
12
|
+
</p>
|
|
13
|
+
<p class="second">World</p>
|
|
14
|
+
<div>
|
|
15
|
+
<p class="note">Note</p>
|
|
16
|
+
</div>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const doc: Document = parseHTML(htmlContent);
|
|
22
|
+
|
|
23
|
+
describe('querySelectorAll', () => {
|
|
24
|
+
it('should be a function', () => {
|
|
25
|
+
expect(typeof querySelectorAll).toBe('function');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should find all elements by tag name', () => {
|
|
29
|
+
const paragraphs = querySelectorAll(doc, 'p');
|
|
30
|
+
expect(paragraphs.length).toBe(3);
|
|
31
|
+
expect(paragraphs[0]!.attributes.class).toBe('first');
|
|
32
|
+
expect(paragraphs[1]!.attributes.class).toBe('second');
|
|
33
|
+
expect(paragraphs[2]!.attributes.class).toBe('note');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should find all elements by class name', () => {
|
|
37
|
+
const second = querySelectorAll(doc, '.second');
|
|
38
|
+
expect(second.length).toBe(1);
|
|
39
|
+
expect(second[0]!.tagName).toBe('P');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('querySelector', () => {
|
|
44
|
+
it('should be a function', () => {
|
|
45
|
+
expect(typeof querySelector).toBe('function');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should find the first element by tag name', () => {
|
|
49
|
+
const firstParagraph = querySelector(doc, 'p');
|
|
50
|
+
expect(firstParagraph).not.toBeNull();
|
|
51
|
+
expect(firstParagraph?.attributes.id).toBe('intro');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should find an element by ID', () => {
|
|
55
|
+
const intro = querySelector(doc, '#intro');
|
|
56
|
+
expect(intro).not.toBeNull();
|
|
57
|
+
expect(intro?.tagName).toBe('P');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should return null if no element is found', () => {
|
|
61
|
+
const nonExistent = querySelector(doc, '#nonexistent');
|
|
62
|
+
expect(nonExistent).toBeNull();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
<!DOCTYPE html>
|
|
4
|
+
<html lang="es"><head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
7
|
+
<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>
|
|
358
|
+
|
|
359
|
+
<body>
|
|
360
|
+
|
|
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("/./bg-ES2RJEGU.png");"><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>
|