@longform/longform 0.0.6 → 0.0.8

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/lib/longform.ts CHANGED
@@ -1,17 +1,40 @@
1
- import type { FragmentType, ParsedResult, WorkingElement, WorkingFragment, Fragment } from "./types.ts";
2
-
3
- const sniffTestRe = /^(?:(?:(--).*)|(?: *(@|#).*)|(?: *[\w\-]+(?::[\w\-]+)?(?:[#.[][^\n]+)?(::).*)|(?: +([\["]).*)|(\ \ .*))$/gmi
4
- , element1 = /((?:\ \ )+)? ?([\w\-]+(?::[\w\-]+)?)([#\.\[][^\n]*)?::(?: ({{?|[^\n]+))?/gmi
5
- , directive1 = /((?:\ \ )+)? ?@([\w][\w\-]+)(?::: ?([^\n]+)?)?/gmi
1
+ import type { AppliedDirective, DirectiveDef, FragmentType, LongformArgs, ParsedResult, SerializationElement, SerializerConfig, SimplifiedElement, WorkingElement, WorkingFragment } from "./types.ts";
2
+
3
+ const LINE = 0
4
+ , INDENT = 1
5
+ , DIRECTIVE_KEY = 2
6
+ , DIRECTIVE = 3
7
+ , DIRECTIVE_INLINE_ARGS = 4
8
+ , ID_TYPE = 5
9
+ , ID = 6
10
+ , FRAGMENT_TYPE = 7
11
+ , ELEMENT = 8
12
+ , ATTR = 9
13
+ ;
14
+
15
+ const sniffTestRe = /^((?: )*)(?:(@)([a-z][a-z\-]*(?::[a-z][a-z\-]*)?)(?:(?::: (.*))| *)?|(##?)([a-z][a-z\-]*)(?: ?(?: +([\["]))? *|(?: *))?|(?:[a-z][a-z\-]*(?::[a-z][a-z\-])?.*(::).*)|(?:(\[)[a-z][a-z\-]?.*(?:=.+)?\]\w*)|(.+))$/gmi
16
+
17
+ // captures a single element definition which could be in a chain.
18
+ // id, class and attributes are matched as a single block for a later loop
19
+ // if in chained situation the regexp will need to be looped over for each element
20
+ // we know the element is the last if the final capture group has a positive match
21
+ // or if the line is consumed by the regexp.
22
+ //, outer = /([a-z][\w\-]*(?::[a-z][\w\-]*)?)([^:]+)*::(?: (?:({{?)|(.*)))?/gi
23
+ , outer = /([a-z][\w\-]*(?::[a-z][\w\-]*)?)((?:(?:[^:])|(?::(?!:)))*)::(?: (?:({{?)|(.*)))?/gi
24
+ //, outer = /([a-z][\w\-]*(?::[a-z][\w\-]*)?)(.+?(?=::))?(?: (?:({{?)|(.*)))?/gi
25
+
26
+ // captures each id, class and attribute declaration in an element
27
+ , inner = /(?:\.([a-z][\w\-]+)|#([a-z][\w\-]+)|\[([a-z][a-z\-]+(?::[a-z][a-z|\-]*)?)(?:=(?:"([^"]+)"|'([^']+)'|([^\]]+)))?\])/gi
6
28
  , attribute1 = /((?:\ \ )+)\[(\w[\w-]*(?::\w[\w-]*)?)(?:=([^\n]+))?\]/
7
29
  , preformattedClose = /[ \t]*}}?[ \t]*/
8
30
  , id1 = /((?:\ \ )+)?#(#)?([\w\-]+)(?: ([\["]))?/gmi
9
- , idnt1 = /^(\ \ )+/
31
+ , identRe = /^(\ \ )+/
10
32
  , text1 = /^((?:\ \ )+)([^ \n][^\n]*)$/i
11
- , paramsRe = /(?:(#|\.)([^#.\[\n]+)|(?:\[(\w[\w\-]*(?::\w[\w\-]*)?)(?:=([^\n\]]+))?\]))/g
12
33
  , refRe = /#\[([\w\-]+)\]/g
13
34
  , escapeRe = /([&<>"'#\[\]{}])/g
14
35
  , templateLinesRe = /^(\ \ )?([^\n]+)$/gmi
36
+
37
+ // TODO: Benchmark v Array.includes()
15
38
  , voids = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wrb']);
16
39
 
17
40
  let m1: RegExpExecArray | null
@@ -40,8 +63,17 @@ function escape(value: string): string {
40
63
  function makeElement(indent: number = 0): WorkingElement {
41
64
  return {
42
65
  indent,
43
- html: '',
66
+ key: undefined,
67
+ id: undefined,
68
+ tag: undefined,
69
+ class: undefined,
70
+ text: undefined,
44
71
  attrs: {},
72
+ html: '',
73
+ mount: undefined,
74
+ serializerConfig: undefined,
75
+ chain: undefined,
76
+ beforeRender: undefined,
45
77
  };
46
78
  }
47
79
 
@@ -57,14 +89,70 @@ function makeFragment(type: FragmentType = 'bare'): WorkingFragment {
57
89
  };
58
90
  }
59
91
 
92
+ class Doc {
93
+ id?: string = undefined;
94
+ lang?: string = undefined;
95
+ meta: Record<string, unknown> = {};
96
+ #serializerConfig: SerializerConfig;
97
+
98
+ constructor(
99
+ id?: string | undefined,
100
+ lang?: string | undefined,
101
+ allowAll?: boolean | undefined,
102
+ allowedAttributes?: string[] | undefined,
103
+ allowedElements?: Array<string | SerializationElement>,
104
+ ) {
105
+ this.id = id;
106
+ this.lang = lang;
107
+ this.#serializerConfig = {
108
+ allowAll: allowAll ?? false,
109
+ allowedAttributes: allowedAttributes ?? [],
110
+ allowedElements: {},
111
+ };
112
+
113
+ if (allowedElements != null) {
114
+ for (let i = 0, l = allowedElements.length, el = allowedElements[i]; i < l; i++) {
115
+ if (typeof el === 'string') {
116
+ this.#serializerConfig.allowedElements[el] = {
117
+ tag: el,
118
+ attrs: [],
119
+ };
120
+ } else {
121
+ this.#serializerConfig.allowedElements[el.tag] = el;
122
+ }
123
+ }
124
+ }
125
+
126
+ Object.freeze(this);
127
+ }
128
+
129
+ allowAll() {
130
+
131
+ }
132
+
133
+ allowAttributes() {
134
+
135
+ }
136
+
137
+ allowElements() {
138
+
139
+ }
140
+ }
141
+
142
+ const directiveValidator = /^[a-z][a-z\-]*\:[a-z][a-z\-]*$/gi;
143
+
144
+
60
145
  /**
61
146
  * Parses a longform document into a object containing the root and fragments
62
147
  * in the output format.
63
148
  *
64
- * @param {string} doc - The longform document to parse.
65
- * @returns {ParsedResult}
149
+ * @param input - The Longform document to parse.
150
+ * @param args - Arguments for the Longform parser.
66
151
  */
67
- export function longform(doc: string, debug: (...d: unknown[]) => void = () => {}): ParsedResult {
152
+ export function longform(
153
+ input: string,
154
+ args?: LongformArgs,
155
+ ): ParsedResult {
68
156
  let skipping: boolean = false
69
157
  , textIndent: number | null = null
70
158
  , verbatimSerialize: boolean = true
@@ -74,21 +162,68 @@ export function longform(doc: string, debug: (...d: unknown[]) => void = () => {
74
162
  , fragment: WorkingFragment = makeFragment()
75
163
  // the root fragment
76
164
  , root: WorkingFragment | null = null
165
+ , id: string | undefined
166
+ , lang: string | undefined
167
+ , doc: Doc | undefined
77
168
  // ids of claimed fragments
78
169
  const claimed: Set<string> = new Set()
79
170
  // parsed fragments
80
171
  , parsed: Map<string, WorkingFragment> = new Map()
81
- , output: ParsedResult = Object.create(null);
172
+ , output: ParsedResult = Object.create(null)
173
+ , directives: Record<string, DirectiveDef> = {}
174
+
82
175
 
83
176
  output.fragments = Object.create(null);
84
177
  output.templates = Object.create(null);
178
+
179
+ if (args?.directives != null) {
180
+ const entries = Object.entries(args.directives);
181
+
182
+ for (let i = 0, l = entries.length; i < l; i++) {
183
+ if (!directiveValidator.test(entries[i][0])) {
184
+ console.warn(`Invalid custom directive name '${entries[i][0]}'`);
185
+
186
+ continue;
187
+ }
85
188
 
86
-
189
+ directives[entries[i][0]] = entries[i][1];
190
+ }
191
+ }
192
+
87
193
  /**
88
194
  * Closes any current in progress element definition
89
195
  * and creates a new working element.
90
196
  */
91
197
  function applyIndent(targetIndent: number) {
198
+ if (Array.isArray(element.beforeRender)) {
199
+ const el: SimplifiedElement = {
200
+ id: element.id,
201
+ tag: element.tag,
202
+ class: element.class,
203
+ attrs: element.attrs,
204
+ };
205
+ const chain: SimplifiedElement[] = [];
206
+
207
+ for (let i = 0, l = element.chain.length, el = element.chain[i]; i < l; i++) {
208
+ chain.push({
209
+ id: el.id,
210
+ tag: el.tag,
211
+ class: el.class,
212
+ attrs: el.attrs,
213
+ });
214
+ }
215
+
216
+ for (let i = 0, l = element.beforeRender.length, def = element.beforeRender[i]; i < l; i++) {
217
+ def.beforeRender({
218
+ el,
219
+ chain,
220
+ doc,
221
+ inlineArg: def.inlineArg,
222
+ blockArg: def.blockArg,
223
+ });
224
+ }
225
+ }
226
+
92
227
  if (element.tag != null) {
93
228
  const root = fragment.type === 'range'
94
229
  ? targetIndent < 2
@@ -105,15 +240,15 @@ export function longform(doc: string, debug: (...d: unknown[]) => void = () => {
105
240
  }
106
241
  }
107
242
 
108
- if (element.mount != null) {
243
+ if (element.mount !== undefined) {
109
244
  fragment.html += ` data-lf-mount="${element.mount}"`;
110
245
  }
111
246
 
112
- if (element.id != null) {
247
+ if (element.id !== undefined) {
113
248
  fragment.html += ' id="' + element.id + '"';
114
249
  }
115
250
 
116
- if (element.class != null) {
251
+ if (element.class !== undefined) {
117
252
  fragment.html += ' class="' + element.class + '"';
118
253
  }
119
254
 
@@ -127,6 +262,34 @@ export function longform(doc: string, debug: (...d: unknown[]) => void = () => {
127
262
 
128
263
  fragment.html += '>';
129
264
 
265
+ if (Array.isArray(element.chain)) {
266
+ let chained: WorkingElement;
267
+
268
+ for (let i = 0, l = element.chain.length; i < l; i++) {
269
+ chained = element.chain[i];
270
+
271
+ fragment.html += '<' + chained.tag;
272
+
273
+ if (chained.id !== undefined) {
274
+ fragment.html += ' id="' + chained.id + '"';
275
+ }
276
+
277
+ if (chained.class != undefined) {
278
+ fragment.html += ' class="' + chained.class + '"';
279
+ }
280
+
281
+ for (const attr of Object.entries(chained.attrs)) {
282
+ if (attr[1] === undefined) {
283
+ fragment.html += ' ' + attr[0]
284
+ } else {
285
+ fragment.html += ` ${attr[0]}="${attr[1]}"`;
286
+ }
287
+ }
288
+
289
+ fragment.html += '>';
290
+ }
291
+ }
292
+
130
293
  if (!voids.has(element.tag as string) && element.text != null) {
131
294
  fragment.html += element.text;
132
295
  }
@@ -149,11 +312,16 @@ export function longform(doc: string, debug: (...d: unknown[]) => void = () => {
149
312
  ) {
150
313
  const element = fragment.els.pop();
151
314
 
315
+ if (Array.isArray(element.chain)) {
316
+ for (let i = 0, l = element.chain.length; i < l; i++) {
317
+ fragment.html += `</${element.chain[i].tag}>`;
318
+ }
319
+ }
320
+
152
321
  fragment.html += `</${element?.tag}>`;
153
322
  }
154
323
 
155
324
  if (targetIndent === 0) {
156
- debug(0, '<', fragment.type, fragment.id);
157
325
  if (fragment.template) {
158
326
  output.templates[fragment.id] = fragment.html;
159
327
  } else if (fragment.type === 'root') {
@@ -169,7 +337,7 @@ export function longform(doc: string, debug: (...d: unknown[]) => void = () => {
169
337
  }
170
338
  }
171
339
 
172
- while ((m1 = sniffTestRe.exec(doc))) {
340
+ main: while ((m1 = sniffTestRe.exec(input))) {
173
341
  if (m1[1] === '--') {
174
342
  continue;
175
343
  } else if (fragment.template) {
@@ -182,15 +350,14 @@ export function longform(doc: string, debug: (...d: unknown[]) => void = () => {
182
350
  // by ending the declaration with `:: {`.
183
351
  if (verbatimIndent != null) {
184
352
  // inside a script or preformatted block
185
- idnt1.lastIndex = 0;
186
- m2 = idnt1.exec(m1[0]);
353
+ identRe.lastIndex = 0;
354
+ m2 = identRe.exec(m1[0]);
187
355
  const indent = m2 == null
188
356
  ? null
189
357
  : m2[0].length / 2;
190
358
 
191
359
  if (m2 == null || indent as number <= verbatimIndent) {
192
360
  fragment.html += '\n';
193
- debug(indent, '}', m2?.[0]);
194
361
 
195
362
  applyIndent(indent);
196
363
  verbatimIndent = null;
@@ -202,7 +369,6 @@ export function longform(doc: string, debug: (...d: unknown[]) => void = () => {
202
369
  }
203
370
  } else {
204
371
  const line = m1[0].replace(' '.repeat(verbatimIndent + 1), '');
205
- debug(indent, '{', line);
206
372
 
207
373
  if (element.tag != null) {
208
374
  applyIndent(indent as number);
@@ -215,88 +381,171 @@ export function longform(doc: string, debug: (...d: unknown[]) => void = () => {
215
381
  }
216
382
 
217
383
  if (verbatimSerialize) {
218
- fragment.html += escape(line);
219
- } else {
220
384
  fragment.html += line;
385
+ } else {
386
+ fragment.html += escape(line);
221
387
  }
222
388
 
223
389
  continue;
224
390
  }
225
391
  }
226
392
 
227
- if (m1[0].trim() === '') {
393
+ if (m1[LINE].trim() === '') {
228
394
  continue;
229
395
  }
230
396
 
231
- switch (m1[2] ?? m1[3] ?? m1[4]) {
232
- // deno-lint-ignore no-fallthrough
233
- case '#': {
397
+ // The id and lang directives should proceed all other directives and
398
+ // fragment declarations.
399
+ if (doc === undefined) {
400
+ const inlineArgs = m1[DIRECTIVE_INLINE_ARGS] ?? '';
401
+
402
+ switch (m1[DIRECTIVE]) {
403
+ case 'id': {
404
+ // TODO: Add id validation
405
+ id = inlineArgs.trim();
406
+ continue main;
407
+ }
408
+ case 'lang': {
409
+ lang = inlineArgs.trim();
410
+ continue main;
411
+ }
412
+ }
413
+
414
+ doc = new Doc(id, lang, args?.allowAll, args?.allowedAttributes, args?.allowedElements);
415
+ }
416
+
417
+ switch (m1[DIRECTIVE_KEY] ?? m1[ID_TYPE] ?? m1[ELEMENT] ?? m1[ATTR]) {
418
+ case '#':
419
+ case '##': {
234
420
  id1.lastIndex = 0;
235
- m2 = id1.exec(m1[0]);
236
421
 
237
- if (m2 != null) {
238
- const indent = (m2[1]?.length ?? 0) / 2;
422
+ const indent = (m1[INDENT].length ?? 0) / 2;
239
423
 
240
- if (element.tag != null || textIndent != null) {
241
- applyIndent(indent);
242
- textIndent = null;
424
+ if (element.tag != null || textIndent != null) {
425
+ applyIndent(indent);
426
+ textIndent = null;
427
+ }
428
+
429
+ fragment.id = m1[ID];
430
+
431
+ if (indent === 0) {
432
+ if (m1[FRAGMENT_TYPE] == '[') {
433
+ fragment.type = 'range';
434
+ } else if (m1[FRAGMENT_TYPE] === '"') {
435
+ fragment.type = 'text';
436
+ } else if (m1[ID_TYPE] === '##') {
437
+ fragment.type = 'bare';
438
+ } else {
439
+ fragment.type = 'embed';
440
+ element.id = fragment.id;
243
441
  }
442
+ } else {
443
+ element.id = fragment.id;
444
+ }
244
445
 
245
- debug(indent, 'id', m2[2], m2[3], m2[4]);
446
+ break;
447
+ }
448
+ case '@': {
449
+ const indent = m1[INDENT].length / 2;
246
450
 
247
- fragment.id = m2[3];
451
+ if (element.tag != null || textIndent != null) {
452
+ applyIndent(indent);
453
+ }
248
454
 
249
- if (indent === 0) {
250
- if (m2[4] == '[') {
251
- fragment.type = 'range';
252
- } else if (m2[4] === '"') {
253
- fragment.type = 'text';
254
- } else if (m2[2] != null) {
255
- fragment.type = 'bare';
256
- } else {
257
- fragment.type = 'embed';
258
- element.id = fragment.id;
455
+ switch (m1[DIRECTIVE]) {
456
+ case 'doctype': {
457
+ const args = m1[DIRECTIVE_INLINE_ARGS] ?? 'html';
458
+ fragment.html += `<!doctype ${args.trim()}>`;
459
+ break;
460
+ }
461
+ case 'xml': {
462
+ const args = m1[DIRECTIVE_INLINE_ARGS] ?? 'version="1.0" encoding="UTF-8"';
463
+ fragment.html += `<?xml ${args.trim()}?>`;
464
+ break;
465
+ }
466
+ case 'template': {
467
+ let indented = false;
468
+ fragment.template = indent === 0;
469
+
470
+ templateLinesRe.lastIndex = sniffTestRe.lastIndex;
471
+ while ((m2 = templateLinesRe.exec(input))) {
472
+ if (m2[1] == null && !indented && fragment.id == null) {
473
+ id1.lastIndex = 0;
474
+ m3 = id1.exec(m2[0]);
475
+
476
+ if (m3 != null) fragment.id = m3[3];
477
+
478
+ fragment.html += m2[0];
479
+ } else if (m2[1] == null && indented) {
480
+ sniffTestRe.lastIndex = templateLinesRe.lastIndex - m2[0].length;
481
+ break;
482
+ } else {
483
+ fragment.html += '\n' + m2[0];
484
+ if (m2[1] != null) indented = true;
485
+ }
259
486
  }
487
+
488
+ applyIndent(0);
489
+ break;
260
490
  }
491
+ case 'mount': {
492
+ if (m1[DIRECTIVE_INLINE_ARGS] == null) {
493
+ throw new Error('Mount points must have a name');
494
+ } else if (fragment.type !== 'root') {
495
+ throw new Error('Mounting is only allowed on a root element');
496
+ }
261
497
 
262
- break;
498
+ fragment.mountable = true;
499
+ element.mount = m1[DIRECTIVE_INLINE_ARGS].trim();
500
+ break;
501
+ }
502
+ default: {
503
+ const def = directives[m1[DIRECTIVE]];
504
+
505
+ if (def == null) break;
506
+
507
+ if (typeof def.beforeRender === 'function') {
508
+ if (element.id != null) {
509
+ applyIndent(indent);
510
+ }
511
+
512
+ if (element.beforeRender == null) element.beforeRender = [];
513
+
514
+ // TODO: Parse block args
515
+ const applied: AppliedDirective = {
516
+ ...def,
517
+ inlineArg: m1[DIRECTIVE_INLINE_ARGS],
518
+ };
519
+
520
+ element.beforeRender.push(applied);
521
+ }
522
+ }
263
523
  }
524
+
525
+ break;
264
526
  }
265
- case '@':
266
527
  case '[':
267
- // deno-lint-ignore no-fallthrough
268
528
  case '::': {
269
- element1.lastIndex = 0;
270
- // fall through if m1[3] is a # or @
271
- m2 = m1[2] ?? m1[4] != null
272
- ? null
273
- : element1.exec(m1[0]);
274
-
275
- // if null then invalid element selector
276
- // allow the default text case to handle
277
- if (m2 != null) {
278
- const indent = (m2[1]?.length ?? 0) / 2
279
- , tg = m2[2]
280
- , ar = m2[3]
281
- , pr = m2[4] === '{' || m2[4] === '{{'
282
- const tx = pr ? null : m2[4]
283
-
284
- debug(indent, 'e', tg, pr, tx);
529
+ if (m1[ELEMENT] !== undefined) {
530
+ const indent = (m1[INDENT]?.length ?? 0) / 2;
531
+ let preformattedType: string | undefined;
532
+ let inlineText: string | undefined;
285
533
 
286
534
  if (
287
- element.tag != null ||
535
+ element.tag !== undefined ||
288
536
  element.indent > indent
289
537
  ) {
290
538
  applyIndent(indent);
291
539
  }
292
540
 
293
541
  element.indent = indent;
294
- element.tag = tg;
295
542
 
296
543
  textIndent = null;
297
544
 
298
545
  if (indent === 0 && fragment.id == null) {
299
546
  if (root != null) {
547
+ // skip if root is found and this fragment
548
+ // has no id
300
549
  skipping = true;
301
550
  } else {
302
551
  fragment.type = 'root';
@@ -304,30 +553,58 @@ export function longform(doc: string, debug: (...d: unknown[]) => void = () => {
304
553
  }
305
554
  }
306
555
 
307
- if (ar != null) {
308
- debug(indent, 'a', ar);
309
- while ((m2 = paramsRe.exec(ar))) {
310
- if (m2[1] === '#') {
311
- element.id = m2[2];
312
- } else if (m2[1] === '.') {
313
- if (element.class == null) {
314
- element.class = m2[2];
556
+ const parent = element;
557
+ outer.lastIndex = 0;
558
+
559
+
560
+ // Looping through chained element declarations
561
+ // foo#x.y::bar::free::
562
+ while ((m2 = outer.exec(m1[LINE]))) {
563
+ let working: WorkingElement;
564
+
565
+ preformattedType = m2[3];
566
+ inlineText = m2[4];
567
+
568
+ if (element.tag === undefined) {
569
+ element.tag = m2[1];
570
+ working = element;
571
+ } else {
572
+ if (parent.chain === undefined) parent.chain = [];
573
+ working = makeElement(indent);
574
+ working.tag = m2[1];
575
+ parent.chain.push(working);
576
+ }
577
+
578
+ inner.lastIndex = 0;
579
+ // Looping through ids, classes and attrs
580
+ while((m3 = inner.exec(m2[2]))) {
581
+ if (m3[2] !== undefined) {
582
+ working.id = m3[2];
583
+ } else if (m3[1] !== undefined) {
584
+ if (working.class == null) {
585
+ working.class = m3[1];
315
586
  } else {
316
- element.class += ' ' + m2[2];
587
+ working.class += ' ' + m3[1];
317
588
  }
318
589
  } else {
319
- if (m2[3] === 'id') {
320
- if (element.id == null) {
321
- element.id = m2[4];
322
- }
323
- } else if (m2[3] === 'class') {
324
- if (element.class == null) {
325
- element.class = m2[4]
326
- } else {
327
- element.class += ' ' + m2[4]
328
- }
329
- } else {
330
- element.attrs[m2[3]] = m2[4];
590
+ // TODO: Preserve quoting style around attribute values
591
+ let value = m3[4] ?? m3[5] ?? m3[6];
592
+
593
+ switch (m3[3]) {
594
+ case 'id':
595
+ if (!working.id) {
596
+ working.id = value;
597
+ }
598
+ break;
599
+ case 'class':
600
+ if (!working.class) {
601
+ working.class = value;
602
+ } else {
603
+ working.class += ' ' + value;
604
+ }
605
+ break;
606
+ default:
607
+ working.attrs[m3[3]] = value;
331
608
  }
332
609
  }
333
610
  }
@@ -347,26 +624,24 @@ export function longform(doc: string, debug: (...d: unknown[]) => void = () => {
347
624
  applyIndent(indent);
348
625
  break;
349
626
  }
350
-
351
- if (!pr && tx != null) {
352
- element.text = tx;
353
- } else if (pr) {
627
+
628
+ if (preformattedType !== undefined) {
354
629
  verbatimFirst = true;
355
630
  verbatimIndent = indent;
356
- verbatimSerialize = m2[4] === '{';
631
+ verbatimSerialize = preformattedType === '{{';
632
+ } else if (inlineText !== undefined) {
633
+ element.text = inlineText;
357
634
  }
358
635
 
359
636
  break;
360
637
  }
361
638
 
362
639
  attribute1.lastIndex = 0;
363
- m2 = m1[2] != null
364
- ? null
365
- : attribute1.exec(m1[0]);
640
+ m2 = m1[ATTR] !== undefined
641
+ ? attribute1.exec(m1[LINE])
642
+ : undefined;
366
643
 
367
644
  if (m2 != null && element.tag != null) {
368
- debug('a', m2[2], m2[3]);
369
-
370
645
  if (m2[2] === 'id') {
371
646
  if (element.id == null) {
372
647
  element.id = m2[3].trim();
@@ -385,71 +660,6 @@ export function longform(doc: string, debug: (...d: unknown[]) => void = () => {
385
660
 
386
661
  break;
387
662
  }
388
-
389
- directive1.lastIndex = 0;
390
- m2 = m1[3] != null
391
- ? null
392
- : directive1.exec(m1[0]);
393
-
394
- if (m2 != null) {
395
- const indent = (m2[1]?.length ?? 0) / 2;
396
-
397
- if (element.tag != null || textIndent != null) {
398
- applyIndent(indent);
399
- }
400
-
401
- debug(indent, 'd', m2[2], m2[3]);
402
-
403
- switch (m2[2]) {
404
- case 'doctype': {
405
- fragment.html += `<!doctype ${m2[3] ?? 'html'}>`;
406
- break;
407
- }
408
- case 'xml': {
409
- fragment.html += `<?xml ${m2[3] ?? 'version="1.0" encoding="UTF-8"'}?>`;
410
- break;
411
- }
412
- case 'template': {
413
- let indented = false;
414
- fragment.template = indent === 0;
415
-
416
- templateLinesRe.lastIndex = sniffTestRe.lastIndex;
417
- while ((m2 = templateLinesRe.exec(doc))) {
418
- if (m2[1] == null && !indented && fragment.id == null) {
419
- id1.lastIndex = 0;
420
- m3 = id1.exec(m2[0]);
421
-
422
- if (m3 != null) fragment.id = m3[3];
423
-
424
- fragment.html += m2[0];
425
- } else if (m2[1] == null && indented) {
426
- sniffTestRe.lastIndex = templateLinesRe.lastIndex - m2[0].length;
427
- break;
428
- } else {
429
- fragment.html += '\n' + m2[0];
430
- if (m2[1] != null) indented = true;
431
- }
432
- }
433
-
434
- applyIndent(0);
435
- break;
436
- }
437
- case 'mount': {
438
- if (m2[3] == null) {
439
- throw new Error('Mount points must have a name');
440
- } else if (fragment.type !== 'root') {
441
- throw new Error('Mounting is only allowed on a root element');
442
- }
443
-
444
- fragment.mountable = true;
445
- element.mount = m2[3].trim();
446
- break;
447
- }
448
- }
449
-
450
- break;
451
- }
452
-
453
663
  }
454
664
  default: {
455
665
  m2 = text1.exec(m1[0]) as RegExpExecArray;
@@ -460,8 +670,6 @@ export function longform(doc: string, debug: (...d: unknown[]) => void = () => {
460
670
  const indent = m2[1].length / 2;
461
671
  const tx = m2[2].trim();
462
672
 
463
- debug(indent, 't', m2[2]);
464
-
465
673
  if (element.tag != null) {
466
674
  applyIndent(indent);
467
675
 
@@ -575,6 +783,9 @@ export function longform(doc: string, debug: (...d: unknown[]) => void = () => {
575
783
  };
576
784
  }
577
785
 
786
+ output.id = doc?.id;
787
+ output.lang = doc?.lang;
788
+
578
789
  return output;
579
790
  }
580
791