ether-code 0.7.8 → 0.7.9

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,588 @@
1
+ const { EtherParserBase, TokenType } = require('./ether-parser-base')
2
+
3
+ class EtherParserHTML extends EtherParserBase {
4
+ constructor(options = {}) {
5
+ super(options)
6
+ this.loadI18n(['i18n-html.json'])
7
+ }
8
+
9
+ parse(source) {
10
+ this.tokenize(source)
11
+ return this.parseHTML()
12
+ }
13
+
14
+ parseHTML() {
15
+ const document = {
16
+ type: 'Document',
17
+ children: []
18
+ }
19
+
20
+ this.skipNewlines()
21
+
22
+ while (!this.isAtEnd()) {
23
+ const node = this.parseHTMLNode()
24
+ if (node) {
25
+ document.children.push(node)
26
+ }
27
+ this.skipNewlines()
28
+ }
29
+
30
+ return document
31
+ }
32
+
33
+ parseHTMLNode() {
34
+ this.skipNewlines()
35
+ const token = this.current()
36
+
37
+ if (!token || token.type === TokenType.EOF) return null
38
+
39
+ if (token.type === TokenType.IDENTIFIER) {
40
+ return this.parseHTMLElement()
41
+ }
42
+
43
+ if (token.type === TokenType.STRING) {
44
+ this.advance()
45
+ return {
46
+ type: 'Text',
47
+ content: token.value.replace(/^["']|["']$/g, '')
48
+ }
49
+ }
50
+
51
+ this.advance()
52
+ return null
53
+ }
54
+
55
+ parseHTMLElement() {
56
+ const tagToken = this.current()
57
+ let tagName = this.normalizeAccents(tagToken.value)
58
+ const originalTagName = tagName
59
+ this.advance()
60
+
61
+ const includeKeywords = ['inclure', 'requiert', 'include', 'require', 'importer']
62
+ if (includeKeywords.includes(tagName)) {
63
+ let includePath = null
64
+
65
+ if (this.current() && this.current().type === TokenType.COLON) {
66
+ this.advance()
67
+ }
68
+
69
+ const pathToken = this.current()
70
+ if (pathToken && pathToken.type === TokenType.STRING) {
71
+ includePath = pathToken.value.replace(/^["']|["']$/g, '')
72
+ this.advance()
73
+ } else if (pathToken && pathToken.type === TokenType.IDENTIFIER) {
74
+ let pathParts = [pathToken.value]
75
+ this.advance()
76
+ while (this.current() && (this.current().type === TokenType.SLASH || this.current().type === TokenType.DOT || this.current().type === TokenType.IDENTIFIER)) {
77
+ if (this.current().type === TokenType.SLASH) {
78
+ pathParts.push('/')
79
+ } else if (this.current().type === TokenType.DOT) {
80
+ pathParts.push('.')
81
+ } else {
82
+ pathParts.push(this.current().value)
83
+ }
84
+ this.advance()
85
+ }
86
+ includePath = pathParts.join('')
87
+ }
88
+
89
+ this.skipNewlines()
90
+
91
+ return {
92
+ type: 'Include',
93
+ path: includePath,
94
+ resolved: false
95
+ }
96
+ }
97
+
98
+ tagName = this.processCompoundTags(tagName)
99
+
100
+ const tag = this.translateHTMLTag(tagName)
101
+
102
+ const attributes = {}
103
+ let inlineText = null
104
+
105
+ const compoundAttrs = {
106
+ 'texte': ['alternatif'],
107
+ 'longueur': ['max', 'min'],
108
+ 'valeur': ['max', 'min'],
109
+ 'zone': ['depot'],
110
+ 'mode': ['saisie'],
111
+ 'verification': ['orthographe'],
112
+ 'ordre': ['tabulation'],
113
+ 'action': ['formulaire'],
114
+ 'methode': ['formulaire'],
115
+ 'encodage': ['formulaire'],
116
+ 'validation': ['formulaire', 'auto'],
117
+ 'cible': ['formulaire', 'popover'],
118
+ 'lecture': ['seule', 'auto'],
119
+ 'nouvelle': ['fenetre'],
120
+ 'a': ['popup'],
121
+ 'jeu': ['caracteres', 'caractères'],
122
+ 'equivalent': ['http'],
123
+ 'équivalent': ['http'],
124
+ 'fusion': ['colonnes', 'lignes'],
125
+ 'plein': ['ecran', 'écran'],
126
+ 'bac': ['sable'],
127
+ 'origine': ['croisee', 'croisée'],
128
+ 'sources': ['reactives', 'réactives']
129
+ }
130
+
131
+ while (!this.isAtEnd()) {
132
+ const current = this.current()
133
+
134
+ if (!current || current.type === TokenType.NEWLINE ||
135
+ current.type === TokenType.INDENT || current.type === TokenType.EOF) {
136
+ break
137
+ }
138
+
139
+ if (current.type === TokenType.STRING) {
140
+ inlineText = current.value.replace(/^["']|["']$/g, '')
141
+ this.advance()
142
+ break
143
+ }
144
+
145
+ if (current.type === TokenType.IDENTIFIER) {
146
+ let attrName = current.value.toLowerCase()
147
+ this.advance()
148
+
149
+ if (compoundAttrs[attrName]) {
150
+ const nextTok = this.current()
151
+ if (nextTok && nextTok.type === TokenType.IDENTIFIER) {
152
+ const nextVal = nextTok.value.toLowerCase()
153
+ if (compoundAttrs[attrName].includes(nextVal)) {
154
+ attrName = attrName + ' ' + nextVal
155
+ this.advance()
156
+ }
157
+ }
158
+ }
159
+
160
+ const nextToken = this.current()
161
+
162
+ if (nextToken && (nextToken.type === TokenType.EQUALS || nextToken.type === TokenType.COLON)) {
163
+ this.advance()
164
+ const valueToken = this.current()
165
+ if (valueToken) {
166
+ if (valueToken.type === TokenType.STRING) {
167
+ attributes[attrName] = valueToken.value.replace(/^["']|["']$/g, '')
168
+ } else if (valueToken.type === TokenType.IDENTIFIER ||
169
+ valueToken.type === TokenType.NUMBER || valueToken.type === TokenType.INTEGER) {
170
+ attributes[attrName] = valueToken.value
171
+ }
172
+ this.advance()
173
+ }
174
+
175
+ if (this.current() && this.current().type === TokenType.COMMA) {
176
+ this.advance()
177
+ }
178
+ } else {
179
+ attributes[attrName] = true
180
+
181
+ if (this.current() && this.current().type === TokenType.COMMA) {
182
+ this.advance()
183
+ }
184
+ }
185
+ } else if (current.type === TokenType.COMMA) {
186
+ this.advance()
187
+ } else {
188
+ break
189
+ }
190
+ }
191
+
192
+ this.skipNewlines()
193
+
194
+ const children = []
195
+
196
+ if (inlineText) {
197
+ children.push({
198
+ type: 'Text',
199
+ content: inlineText
200
+ })
201
+ }
202
+
203
+ const rawContentTags = ['script', 'style']
204
+ const isRawContent = rawContentTags.includes(tag)
205
+ const isScript = tag === 'script'
206
+
207
+ if (this.match(TokenType.INDENT)) {
208
+ if (isRawContent) {
209
+ let rawText = []
210
+ while (!this.isAtEnd()) {
211
+ const current = this.current()
212
+
213
+ if (current && current.type === TokenType.DEDENT) {
214
+ this.advance()
215
+ break
216
+ }
217
+
218
+ if (current && current.type === TokenType.NEWLINE) {
219
+ rawText.push('\n')
220
+ this.advance()
221
+ continue
222
+ }
223
+
224
+ if (current && current.type === TokenType.INDENT) {
225
+ this.advance()
226
+ continue
227
+ }
228
+
229
+ if (current && current.type === TokenType.STRING) {
230
+ if (isScript) {
231
+ rawText.push("'" + current.value.replace(/'/g, "\\'") + "'")
232
+ } else {
233
+ rawText.push(current.value)
234
+ }
235
+ this.advance()
236
+ continue
237
+ }
238
+
239
+ rawText.push(this.tokenToString(current))
240
+ this.advance()
241
+ }
242
+
243
+ const textContent = rawText.join('').trim()
244
+ if (textContent) {
245
+ children.push({
246
+ type: 'Text',
247
+ content: textContent
248
+ })
249
+ }
250
+ } else {
251
+ while (!this.isAtEnd()) {
252
+ const current = this.current()
253
+
254
+ if (current && current.type === TokenType.DEDENT) {
255
+ this.advance()
256
+ break
257
+ }
258
+
259
+ const child = this.parseHTMLNode()
260
+ if (child) {
261
+ children.push(child)
262
+ }
263
+
264
+ this.skipNewlines()
265
+ }
266
+ }
267
+ }
268
+
269
+ if (originalTagName === 'document') {
270
+ return {
271
+ type: 'Doctype',
272
+ children: children
273
+ }
274
+ }
275
+
276
+ return {
277
+ type: 'Element',
278
+ tag: tag,
279
+ attributes: attributes,
280
+ children: children
281
+ }
282
+ }
283
+
284
+ tokenToString(token) {
285
+ if (!token) return ''
286
+
287
+ const tokenMap = {
288
+ [TokenType.LPAREN]: '(',
289
+ [TokenType.RPAREN]: ')',
290
+ [TokenType.LBRACKET]: '[',
291
+ [TokenType.RBRACKET]: ']',
292
+ [TokenType.LBRACE]: '{',
293
+ [TokenType.RBRACE]: '}',
294
+ [TokenType.DOT]: '.',
295
+ [TokenType.COMMA]: ',',
296
+ [TokenType.COLON]: ':',
297
+ [TokenType.EQUALS]: '=',
298
+ [TokenType.DOUBLE_EQUALS]: '==',
299
+ [TokenType.NOT_EQUALS]: '!=',
300
+ [TokenType.BANG]: '!',
301
+ [TokenType.SLASH]: '/',
302
+ [TokenType.PERCENT]: '%',
303
+ [TokenType.AMPERSAND]: '&',
304
+ [TokenType.DOUBLE_AMPERSAND]: '&&',
305
+ [TokenType.PIPE]: '|',
306
+ [TokenType.DOUBLE_PIPE]: '||',
307
+ [TokenType.LT]: '<',
308
+ [TokenType.GT]: '>',
309
+ [TokenType.LTE]: '<=',
310
+ [TokenType.GTE]: '>=',
311
+ [TokenType.QUESTION]: '?',
312
+ [TokenType.FAT_ARROW]: '=>',
313
+ [TokenType.ARROW]: '->',
314
+ [TokenType.SEMICOLON]: ';'
315
+ }
316
+
317
+ if (tokenMap[token.type]) {
318
+ return tokenMap[token.type]
319
+ }
320
+
321
+ return String(token.value)
322
+ }
323
+
324
+ processCompoundTags(tagName) {
325
+ const nextToken = this.current()
326
+ if (!nextToken || nextToken.type !== TokenType.IDENTIFIER) {
327
+ return tagName
328
+ }
329
+
330
+ const compoundMap = {
331
+ 'titre': { check: () => nextToken.type === TokenType.NUMBER || nextToken.type === TokenType.INTEGER, process: () => `titre${nextToken.value}` },
332
+ 'liste ordonnee': { keywords: ['ordonnee', 'ordered'] },
333
+ 'element liste': { keywords: ['liste', 'list'] },
334
+ 'zone texte': { keywords: ['texte', 'text'] },
335
+ 'retour ligne': { keywords: ['ligne', 'line'] },
336
+ 'ligne horizontale': { keywords: ['horizontale'] },
337
+ 'entete cellule': { keywords: ['entete', 'header'] },
338
+ 'groupe titres': { keywords: ['titres'] },
339
+ 'groupe options': { keywords: ['options'] },
340
+ 'groupe colonnes': { keywords: ['colonnes'] },
341
+ 'entete tableau': { keywords: ['tableau', 'table'] },
342
+ 'corps tableau': { keywords: ['tableau', 'table'] },
343
+ 'pied tableau': { keywords: ['tableau', 'table'] },
344
+ 'legende tableau': { keywords: ['tableau', 'table'] },
345
+ 'legende figure': { keywords: ['figure'] },
346
+ 'ensemble champs': { keywords: ['champs', 'fields'] },
347
+ 'liste donnees': { keywords: ['donnees', 'data'] },
348
+ 'liste description': { keywords: ['description'] },
349
+ 'image reactive': { keywords: ['reactive', 'responsive'] },
350
+ 'cadre en ligne': { keywords: ['en'], nextKeyword: 'ligne' },
351
+ 'citation bloc': { keywords: ['bloc', 'block'] },
352
+ 'reference citation': { keywords: ['citation'] },
353
+ 'sans script': { keywords: ['script'] },
354
+ 'isolation bidi': { keywords: ['bidi'] },
355
+ 'remplacement bidi': { keywords: ['bidi'] },
356
+ 'annotation ruby': { keywords: ['ruby', 'rubis'] },
357
+ 'parentheses ruby': { keywords: ['ruby', 'rubis'] },
358
+ 'carte image': { keywords: ['image'] },
359
+ 'texte alternatif': { keywords: ['alternatif', 'alt'] },
360
+ 'entree clavier': { keywords: ['clavier'] },
361
+ 'sortie exemple': { keywords: ['exemple'] },
362
+ 'definition description': { keywords: ['description'] }
363
+ }
364
+
365
+ const nextVal = this.normalizeAccents(nextToken.value)
366
+
367
+ if (tagName === 'titre' && (nextToken.type === TokenType.NUMBER || nextToken.type === TokenType.INTEGER)) {
368
+ const num = parseInt(nextToken.value)
369
+ if (num >= 1 && num <= 6) {
370
+ this.advance()
371
+ return `titre${num}`
372
+ }
373
+ }
374
+
375
+ const compounds = [
376
+ { base: 'liste', next: ['ordonnee', 'ordered'], result: 'liste ordonnee' },
377
+ { base: 'element', next: ['liste', 'list'], result: 'element liste' },
378
+ { base: 'zone', next: ['texte', 'text'], result: 'zone texte' },
379
+ { base: 'retour', next: ['ligne', 'line'], result: 'retour ligne' },
380
+ { base: 'saut', next: ['ligne', 'line'], result: 'retour ligne' },
381
+ { base: 'ligne', next: ['horizontale'], result: 'ligne horizontale' },
382
+ { base: 'cellule', next: ['entete', 'header'], result: 'entete cellule' },
383
+ { base: 'groupe', next: ['titres'], result: 'groupe titres' },
384
+ { base: 'groupe', next: ['options'], result: 'groupe options' },
385
+ { base: 'groupe', next: ['colonnes'], result: 'groupe colonnes' },
386
+ { base: 'entete', next: ['tableau', 'table'], result: 'entete tableau' },
387
+ { base: 'corps', next: ['tableau', 'table'], result: 'corps tableau' },
388
+ { base: 'pied', next: ['tableau', 'table'], result: 'pied tableau' },
389
+ { base: 'legende', next: ['tableau', 'table'], result: 'legende tableau' },
390
+ { base: 'legende', next: ['figure'], result: 'legende figure' },
391
+ { base: 'ensemble', next: ['champs', 'fields'], result: 'ensemble champs' },
392
+ { base: 'liste', next: ['donnees', 'data'], result: 'liste donnees' },
393
+ { base: 'liste', next: ['description'], result: 'liste description' },
394
+ { base: 'image', next: ['reactive', 'responsive'], result: 'image reactive' },
395
+ { base: 'citation', next: ['bloc', 'block'], result: 'citation bloc' },
396
+ { base: 'reference', next: ['citation'], result: 'reference citation' },
397
+ { base: 'sans', next: ['script'], result: 'sans script' },
398
+ { base: 'isolation', next: ['bidi'], result: 'isolation bidi' },
399
+ { base: 'remplacement', next: ['bidi'], result: 'remplacement bidi' },
400
+ { base: 'annotation', next: ['ruby', 'rubis'], result: 'annotation ruby' },
401
+ { base: 'parentheses', next: ['ruby', 'rubis'], result: 'parentheses ruby' },
402
+ { base: 'carte', next: ['image'], result: 'carte image' },
403
+ { base: 'texte', next: ['alternatif', 'alt'], result: 'texte alternatif' },
404
+ { base: 'entree', next: ['clavier'], result: 'entree clavier' },
405
+ { base: 'sortie', next: ['exemple'], result: 'sortie exemple' },
406
+ { base: 'definition', next: ['description'], result: 'definition description' }
407
+ ]
408
+
409
+ for (const compound of compounds) {
410
+ if (tagName === compound.base && compound.next.includes(nextVal)) {
411
+ this.advance()
412
+ return compound.result
413
+ }
414
+ }
415
+
416
+ return tagName
417
+ }
418
+
419
+ translateHTMLTag(tag) {
420
+ const map = this.reverseMaps.html || {}
421
+ const lower = this.normalizeAccents(tag)
422
+ const withSpaces = lower.replace(/-/g, ' ')
423
+
424
+ const translations = {
425
+ 'document': 'html',
426
+ 'page': 'html',
427
+ 'tete': 'head',
428
+ 'corps': 'body',
429
+ 'entete': 'header',
430
+ 'piedpage': 'footer',
431
+ 'pied': 'footer',
432
+ 'navigation': 'nav',
433
+ 'nav': 'nav',
434
+ 'principal': 'main',
435
+ 'section': 'section',
436
+ 'article': 'article',
437
+ 'cote': 'aside',
438
+ 'aside': 'aside',
439
+ 'titre': 'title',
440
+ 'titre1': 'h1',
441
+ 'titre2': 'h2',
442
+ 'titre3': 'h3',
443
+ 'titre4': 'h4',
444
+ 'titre5': 'h5',
445
+ 'titre6': 'h6',
446
+ 'titre 1': 'h1',
447
+ 'titre 2': 'h2',
448
+ 'titre 3': 'h3',
449
+ 'titre 4': 'h4',
450
+ 'titre 5': 'h5',
451
+ 'titre 6': 'h6',
452
+ 'groupe titres': 'hgroup',
453
+ 'paragraphe': 'p',
454
+ 'texte': 'span',
455
+ 'division': 'div',
456
+ 'div': 'div',
457
+ 'bloc': 'div',
458
+ 'span': 'span',
459
+ 'lien': 'a',
460
+ 'ressource': 'link',
461
+ 'lien externe': 'link',
462
+ 'image': 'img',
463
+ 'liste': 'ul',
464
+ 'liste ordonnee': 'ol',
465
+ 'liste non ordonnee': 'ul',
466
+ 'element': 'li',
467
+ 'element liste': 'li',
468
+ 'tableau': 'table',
469
+ 'ligne': 'tr',
470
+ 'cellule': 'td',
471
+ 'entete cellule': 'th',
472
+ 'formulaire': 'form',
473
+ 'champ': 'input',
474
+ 'entree': 'input',
475
+ 'input': 'input',
476
+ 'zone texte': 'textarea',
477
+ 'bouton': 'button',
478
+ 'selection': 'select',
479
+ 'option': 'option',
480
+ 'etiquette': 'label',
481
+ 'gras': 'strong',
482
+ 'fort': 'strong',
483
+ 'italique': 'em',
484
+ 'emphase': 'em',
485
+ 'souligne': 'u',
486
+ 'barre': 's',
487
+ 'code': 'code',
488
+ 'pre': 'pre',
489
+ 'preformate': 'pre',
490
+ 'citation': 'blockquote',
491
+ 'retour ligne': 'br',
492
+ 'saut ligne': 'br',
493
+ 'br': 'br',
494
+ 'ligne horizontale': 'hr',
495
+ 'hr': 'hr',
496
+ 'video': 'video',
497
+ 'audio': 'audio',
498
+ 'source': 'source',
499
+ 'cadre': 'iframe',
500
+ 'canvas': 'canvas',
501
+ 'toile': 'canvas',
502
+ 'figure': 'figure',
503
+ 'legende': 'legend',
504
+ 'legende figure': 'figcaption',
505
+ 'legende tableau': 'caption',
506
+ 'legende ensemble': 'legend',
507
+ 'entete tableau': 'thead',
508
+ 'corps tableau': 'tbody',
509
+ 'pied tableau': 'tfoot',
510
+ 'ensemble champs': 'fieldset',
511
+ 'liste donnees': 'datalist',
512
+ 'liste description': 'dl',
513
+ 'definition description': 'dd',
514
+ 'image reactive': 'picture',
515
+ 'cadre en ligne': 'iframe',
516
+ 'citation bloc': 'blockquote',
517
+ 'reference citation': 'cite',
518
+ 'groupe options': 'optgroup',
519
+ 'groupe colonnes': 'colgroup',
520
+ 'colonne': 'col',
521
+ 'entree clavier': 'kbd',
522
+ 'sortie exemple': 'samp',
523
+ 'texte alternatif': 'alt',
524
+ 'resultat': 'output',
525
+ 'piste': 'track',
526
+ 'insere': 'ins',
527
+ 'supprime': 'del',
528
+ 'donnee': 'data',
529
+ 'retour ligne possible': 'wbr',
530
+ 'carte image': 'map',
531
+ 'script': 'script',
532
+ 'details': 'details',
533
+ 'resume': 'summary',
534
+ 'dialogue': 'dialog',
535
+ 'modele': 'template',
536
+ 'emplacement': 'slot',
537
+ 'temps': 'time',
538
+ 'marque': 'mark',
539
+ 'metre': 'meter',
540
+ 'progression': 'progress',
541
+ 'sortie': 'output',
542
+ 'donnees': 'data',
543
+ 'clavier': 'kbd',
544
+ 'exemple': 'samp',
545
+ 'variable': 'var',
546
+ 'abreviation': 'abbr',
547
+ 'indice': 'sub',
548
+ 'exposant': 'sup',
549
+ 'petit': 'small',
550
+ 'cite': 'cite',
551
+ 'definition': 'dfn',
552
+ 'adresse': 'address',
553
+ 'rubis': 'ruby',
554
+ 'texte rubis': 'rt',
555
+ 'parenthese rubis': 'rp',
556
+ 'bidirectionnel': 'bdi',
557
+ 'direction': 'bdo',
558
+ 'cesure': 'wbr',
559
+ 'noscript': 'noscript',
560
+ 'sans script': 'noscript',
561
+ 'isolation bidi': 'bdi',
562
+ 'remplacement bidi': 'bdo',
563
+ 'annotation ruby': 'rt',
564
+ 'parentheses ruby': 'rp',
565
+ 'terme': 'dt',
566
+ 'menu': 'menu',
567
+ 'recherche': 'search',
568
+ 'objet': 'object',
569
+ 'integrer': 'embed',
570
+ 'svg': 'svg',
571
+ 'math': 'math',
572
+ 'zone': 'area'
573
+ }
574
+
575
+ const result = translations[lower] || translations[withSpaces] || map[lower] || map[withSpaces] || tag
576
+ return result.replace(/^<!DOCTYPE\s+/i, '').replace(/^<|>$/g, '').trim() || 'div'
577
+ }
578
+
579
+ parseStatement(lang) {
580
+ return null
581
+ }
582
+
583
+ parseExpression(lang) {
584
+ return null
585
+ }
586
+ }
587
+
588
+ module.exports = { EtherParserHTML }