@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/README.md CHANGED
@@ -12,7 +12,7 @@ parser implemented for server rendering of HTML documents and client side
12
12
  templating if required. Both the Longform language and parser are a work in progress.
13
13
 
14
14
 
15
- Read more about the Longform language in the <a href="https://occultist-dev.github.io/longform">Longform Markup Language document</a>.
15
+ Read more about the Longform language in the <a href=https://longform.occultist.dev>Longform Markup Language document</a>.
16
16
 
17
17
 
18
18
  ## Install
@@ -36,7 +36,7 @@ html::
36
36
  #fragment1
37
37
  div::
38
38
  p::
39
- This is a Longform fragment referencable by it's identifier.
39
+ This is a Longform fragment referenceable by it's identifier.
40
40
 
41
41
  #fragment2
42
42
  div::
@@ -53,3 +53,31 @@ console.log(result.root);
53
53
  console.log(result.fragments.fragment1.html);
54
54
  ```
55
55
 
56
+
57
+ ## Custom directives
58
+
59
+ This parser has limited support for custom directives with unstable support.
60
+
61
+ ```
62
+ const directives: DirectiveDef = {
63
+ 'foo:bar': {
64
+ beforeRender(ctx) {
65
+ // do stuff
66
+ },
67
+ },
68
+ };
69
+
70
+ const doc = `
71
+ @foo:bar:: arguments...
72
+ div::
73
+ ...
74
+ `;
75
+
76
+ const res = longform(doc, { directives });
77
+
78
+ ```
79
+
80
+ The `beforeRender()` directive hook is optional (albeit the only directive hook currently)
81
+ and is called before the element is rendered. The `ctx` object holds document and element
82
+ information. Currently only the `attrs` can be modified before the element is rendered.
83
+
package/dist/longform.cjs CHANGED
@@ -1,6 +1,19 @@
1
1
  'use strict';
2
2
 
3
- const sniffTestRe = /^(?:(?:(--).*)|(?: *(@|#).*)|(?: *[\w\-]+(?::[\w\-]+)?(?:[#.[][^\n]+)?(::).*)|(?: +([\["]).*)|(\ \ .*))$/gmi, element1 = /((?:\ \ )+)? ?([\w\-]+(?::[\w\-]+)?)([#\.\[][^\n]*)?::(?: ({{?|[^\n]+))?/gmi, directive1 = /((?:\ \ )+)? ?@([\w][\w\-]+)(?::: ?([^\n]+)?)?/gmi, attribute1 = /((?:\ \ )+)\[(\w[\w-]*(?::\w[\w-]*)?)(?:=([^\n]+))?\]/, preformattedClose = /[ \t]*}}?[ \t]*/, id1 = /((?:\ \ )+)?#(#)?([\w\-]+)(?: ([\["]))?/gmi, idnt1 = /^(\ \ )+/, text1 = /^((?:\ \ )+)([^ \n][^\n]*)$/i, paramsRe = /(?:(#|\.)([^#.\[\n]+)|(?:\[(\w[\w\-]*(?::\w[\w\-]*)?)(?:=([^\n\]]+))?\]))/g, refRe = /#\[([\w\-]+)\]/g, escapeRe = /([&<>"'#\[\]{}])/g, templateLinesRe = /^(\ \ )?([^\n]+)$/gmi, voids = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wrb']);
3
+ const LINE = 0, INDENT = 1, DIRECTIVE_KEY = 2, DIRECTIVE = 3, DIRECTIVE_INLINE_ARGS = 4, ID_TYPE = 5, ID = 6, FRAGMENT_TYPE = 7, ELEMENT = 8, ATTR = 9;
4
+ 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
5
+ // captures a single element definition which could be in a chain.
6
+ // id, class and attributes are matched as a single block for a later loop
7
+ // if in chained situation the regexp will need to be looped over for each element
8
+ // we know the element is the last if the final capture group has a positive match
9
+ // or if the line is consumed by the regexp.
10
+ //, outer = /([a-z][\w\-]*(?::[a-z][\w\-]*)?)([^:]+)*::(?: (?:({{?)|(.*)))?/gi
11
+ , outer = /([a-z][\w\-]*(?::[a-z][\w\-]*)?)((?:(?:[^:])|(?::(?!:)))*)::(?: (?:({{?)|(.*)))?/gi
12
+ //, outer = /([a-z][\w\-]*(?::[a-z][\w\-]*)?)(.+?(?=::))?(?: (?:({{?)|(.*)))?/gi
13
+ // captures each id, class and attribute declaration in an element
14
+ , inner = /(?:\.([a-z][\w\-]+)|#([a-z][\w\-]+)|\[([a-z][a-z\-]+(?::[a-z][a-z|\-]*)?)(?:=(?:"([^"]+)"|'([^']+)'|([^\]]+)))?\])/gi, attribute1 = /((?:\ \ )+)\[(\w[\w-]*(?::\w[\w-]*)?)(?:=([^\n]+))?\]/, preformattedClose = /[ \t]*}}?[ \t]*/, id1 = /((?:\ \ )+)?#(#)?([\w\-]+)(?: ([\["]))?/gmi, identRe = /^(\ \ )+/, text1 = /^((?:\ \ )+)([^ \n][^\n]*)$/i, refRe = /#\[([\w\-]+)\]/g, escapeRe = /([&<>"'#\[\]{}])/g, templateLinesRe = /^(\ \ )?([^\n]+)$/gmi
15
+ // TODO: Benchmark v Array.includes()
16
+ , voids = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wrb']);
4
17
  let m1, m2, m3;
5
18
  const entities = {
6
19
  '&': '&amp;',
@@ -22,8 +35,17 @@ function escape(value) {
22
35
  function makeElement(indent = 0) {
23
36
  return {
24
37
  indent,
25
- html: '',
38
+ key: undefined,
39
+ id: undefined,
40
+ tag: undefined,
41
+ class: undefined,
42
+ text: undefined,
26
43
  attrs: {},
44
+ html: '',
45
+ mount: undefined,
46
+ serializerConfig: undefined,
47
+ chain: undefined,
48
+ beforeRender: undefined,
27
49
  };
28
50
  }
29
51
  function makeFragment(type = 'bare') {
@@ -37,28 +59,100 @@ function makeFragment(type = 'bare') {
37
59
  mountPoints: [],
38
60
  };
39
61
  }
62
+ class Doc {
63
+ id = undefined;
64
+ lang = undefined;
65
+ meta = {};
66
+ #serializerConfig;
67
+ constructor(id, lang, allowAll, allowedAttributes, allowedElements) {
68
+ this.id = id;
69
+ this.lang = lang;
70
+ this.#serializerConfig = {
71
+ allowAll: allowAll ?? false,
72
+ allowedAttributes: allowedAttributes ?? [],
73
+ allowedElements: {},
74
+ };
75
+ if (allowedElements != null) {
76
+ for (let i = 0, l = allowedElements.length, el = allowedElements[i]; i < l; i++) {
77
+ if (typeof el === 'string') {
78
+ this.#serializerConfig.allowedElements[el] = {
79
+ tag: el,
80
+ attrs: [],
81
+ };
82
+ }
83
+ else {
84
+ this.#serializerConfig.allowedElements[el.tag] = el;
85
+ }
86
+ }
87
+ }
88
+ Object.freeze(this);
89
+ }
90
+ allowAll() {
91
+ }
92
+ allowAttributes() {
93
+ }
94
+ allowElements() {
95
+ }
96
+ }
97
+ const directiveValidator = /^[a-z][a-z\-]*\:[a-z][a-z\-]*$/gi;
40
98
  /**
41
99
  * Parses a longform document into a object containing the root and fragments
42
100
  * in the output format.
43
101
  *
44
- * @param {string} doc - The longform document to parse.
45
- * @returns {ParsedResult}
102
+ * @param input - The Longform document to parse.
103
+ * @param args - Arguments for the Longform parser.
46
104
  */
47
- function longform(doc, debug = () => { }) {
105
+ function longform(input, args) {
48
106
  let textIndent = null, verbatimSerialize = true, verbatimIndent = null, verbatimFirst = false, element = makeElement(), fragment = makeFragment()
49
107
  // the root fragment
50
- , root = null;
108
+ , root = null, id, lang, doc;
51
109
  // ids of claimed fragments
52
110
  const claimed = new Set()
53
111
  // parsed fragments
54
- , parsed = new Map(), output = Object.create(null);
112
+ , parsed = new Map(), output = Object.create(null), directives = {};
55
113
  output.fragments = Object.create(null);
56
114
  output.templates = Object.create(null);
115
+ if (args?.directives != null) {
116
+ const entries = Object.entries(args.directives);
117
+ for (let i = 0, l = entries.length; i < l; i++) {
118
+ if (!directiveValidator.test(entries[i][0])) {
119
+ console.warn(`Invalid custom directive name '${entries[i][0]}'`);
120
+ continue;
121
+ }
122
+ directives[entries[i][0]] = entries[i][1];
123
+ }
124
+ }
57
125
  /**
58
126
  * Closes any current in progress element definition
59
127
  * and creates a new working element.
60
128
  */
61
129
  function applyIndent(targetIndent) {
130
+ if (Array.isArray(element.beforeRender)) {
131
+ const el = {
132
+ id: element.id,
133
+ tag: element.tag,
134
+ class: element.class,
135
+ attrs: element.attrs,
136
+ };
137
+ const chain = [];
138
+ for (let i = 0, l = element.chain.length, el = element.chain[i]; i < l; i++) {
139
+ chain.push({
140
+ id: el.id,
141
+ tag: el.tag,
142
+ class: el.class,
143
+ attrs: el.attrs,
144
+ });
145
+ }
146
+ for (let i = 0, l = element.beforeRender.length, def = element.beforeRender[i]; i < l; i++) {
147
+ def.beforeRender({
148
+ el,
149
+ chain,
150
+ doc,
151
+ inlineArg: def.inlineArg,
152
+ blockArg: def.blockArg,
153
+ });
154
+ }
155
+ }
62
156
  if (element.tag != null) {
63
157
  const root = fragment.type === 'range'
64
158
  ? targetIndent < 2
@@ -72,13 +166,13 @@ function longform(doc, debug = () => { }) {
72
166
  fragment.html += ` data-lf="${fragment.id}"`;
73
167
  }
74
168
  }
75
- if (element.mount != null) {
169
+ if (element.mount !== undefined) {
76
170
  fragment.html += ` data-lf-mount="${element.mount}"`;
77
171
  }
78
- if (element.id != null) {
172
+ if (element.id !== undefined) {
79
173
  fragment.html += ' id="' + element.id + '"';
80
174
  }
81
- if (element.class != null) {
175
+ if (element.class !== undefined) {
82
176
  fragment.html += ' class="' + element.class + '"';
83
177
  }
84
178
  for (const attr of Object.entries(element.attrs)) {
@@ -90,6 +184,28 @@ function longform(doc, debug = () => { }) {
90
184
  }
91
185
  }
92
186
  fragment.html += '>';
187
+ if (Array.isArray(element.chain)) {
188
+ let chained;
189
+ for (let i = 0, l = element.chain.length; i < l; i++) {
190
+ chained = element.chain[i];
191
+ fragment.html += '<' + chained.tag;
192
+ if (chained.id !== undefined) {
193
+ fragment.html += ' id="' + chained.id + '"';
194
+ }
195
+ if (chained.class != undefined) {
196
+ fragment.html += ' class="' + chained.class + '"';
197
+ }
198
+ for (const attr of Object.entries(chained.attrs)) {
199
+ if (attr[1] === undefined) {
200
+ fragment.html += ' ' + attr[0];
201
+ }
202
+ else {
203
+ fragment.html += ` ${attr[0]}="${attr[1]}"`;
204
+ }
205
+ }
206
+ fragment.html += '>';
207
+ }
208
+ }
93
209
  if (!voids.has(element.tag) && element.text != null) {
94
210
  fragment.html += element.text;
95
211
  }
@@ -102,10 +218,14 @@ function longform(doc, debug = () => { }) {
102
218
  while (fragment.els.length !== 0 && (targetIndent == null ||
103
219
  fragment.els[fragment.els.length - 1].indent !== targetIndent - 1)) {
104
220
  const element = fragment.els.pop();
221
+ if (Array.isArray(element.chain)) {
222
+ for (let i = 0, l = element.chain.length; i < l; i++) {
223
+ fragment.html += `</${element.chain[i].tag}>`;
224
+ }
225
+ }
105
226
  fragment.html += `</${element?.tag}>`;
106
227
  }
107
228
  if (targetIndent === 0) {
108
- debug(0, '<', fragment.type, fragment.id);
109
229
  if (fragment.template) {
110
230
  output.templates[fragment.id] = fragment.html;
111
231
  }
@@ -122,7 +242,7 @@ function longform(doc, debug = () => { }) {
122
242
  element = makeElement(targetIndent);
123
243
  }
124
244
  }
125
- while ((m1 = sniffTestRe.exec(doc))) {
245
+ main: while ((m1 = sniffTestRe.exec(input))) {
126
246
  if (m1[1] === '--') {
127
247
  continue;
128
248
  }
@@ -135,14 +255,13 @@ function longform(doc, debug = () => { }) {
135
255
  // by ending the declaration with `:: {`.
136
256
  if (verbatimIndent != null) {
137
257
  // inside a script or preformatted block
138
- idnt1.lastIndex = 0;
139
- m2 = idnt1.exec(m1[0]);
258
+ identRe.lastIndex = 0;
259
+ m2 = identRe.exec(m1[0]);
140
260
  const indent = m2 == null
141
261
  ? null
142
262
  : m2[0].length / 2;
143
263
  if (m2 == null || indent <= verbatimIndent) {
144
264
  fragment.html += '\n';
145
- debug(indent, '}', m2?.[0]);
146
265
  applyIndent(indent);
147
266
  verbatimIndent = null;
148
267
  verbatimFirst = false;
@@ -153,7 +272,6 @@ function longform(doc, debug = () => { }) {
153
272
  }
154
273
  else {
155
274
  const line = m1[0].replace(' '.repeat(verbatimIndent + 1), '');
156
- debug(indent, '{', line);
157
275
  if (element.tag != null) {
158
276
  applyIndent(indent);
159
277
  }
@@ -164,69 +282,148 @@ function longform(doc, debug = () => { }) {
164
282
  fragment.html += '\n';
165
283
  }
166
284
  if (verbatimSerialize) {
167
- fragment.html += escape(line);
285
+ fragment.html += line;
168
286
  }
169
287
  else {
170
- fragment.html += line;
288
+ fragment.html += escape(line);
171
289
  }
172
290
  continue;
173
291
  }
174
292
  }
175
- if (m1[0].trim() === '') {
293
+ if (m1[LINE].trim() === '') {
176
294
  continue;
177
295
  }
178
- switch (m1[2] ?? m1[3] ?? m1[4]) {
179
- // deno-lint-ignore no-fallthrough
180
- case '#': {
296
+ // The id and lang directives should proceed all other directives and
297
+ // fragment declarations.
298
+ if (doc === undefined) {
299
+ const inlineArgs = m1[DIRECTIVE_INLINE_ARGS] ?? '';
300
+ switch (m1[DIRECTIVE]) {
301
+ case 'id': {
302
+ // TODO: Add id validation
303
+ id = inlineArgs.trim();
304
+ continue main;
305
+ }
306
+ case 'lang': {
307
+ lang = inlineArgs.trim();
308
+ continue main;
309
+ }
310
+ }
311
+ doc = new Doc(id, lang, args?.allowAll, args?.allowedAttributes, args?.allowedElements);
312
+ }
313
+ switch (m1[DIRECTIVE_KEY] ?? m1[ID_TYPE] ?? m1[ELEMENT] ?? m1[ATTR]) {
314
+ case '#':
315
+ case '##': {
181
316
  id1.lastIndex = 0;
182
- m2 = id1.exec(m1[0]);
183
- if (m2 != null) {
184
- const indent = (m2[1]?.length ?? 0) / 2;
185
- if (element.tag != null || textIndent != null) {
186
- applyIndent(indent);
187
- textIndent = null;
317
+ const indent = (m1[INDENT].length ?? 0) / 2;
318
+ if (element.tag != null || textIndent != null) {
319
+ applyIndent(indent);
320
+ textIndent = null;
321
+ }
322
+ fragment.id = m1[ID];
323
+ if (indent === 0) {
324
+ if (m1[FRAGMENT_TYPE] == '[') {
325
+ fragment.type = 'range';
326
+ }
327
+ else if (m1[FRAGMENT_TYPE] === '"') {
328
+ fragment.type = 'text';
329
+ }
330
+ else if (m1[ID_TYPE] === '##') {
331
+ fragment.type = 'bare';
332
+ }
333
+ else {
334
+ fragment.type = 'embed';
335
+ element.id = fragment.id;
336
+ }
337
+ }
338
+ else {
339
+ element.id = fragment.id;
340
+ }
341
+ break;
342
+ }
343
+ case '@': {
344
+ const indent = m1[INDENT].length / 2;
345
+ if (element.tag != null || textIndent != null) {
346
+ applyIndent(indent);
347
+ }
348
+ switch (m1[DIRECTIVE]) {
349
+ case 'doctype': {
350
+ const args = m1[DIRECTIVE_INLINE_ARGS] ?? 'html';
351
+ fragment.html += `<!doctype ${args.trim()}>`;
352
+ break;
188
353
  }
189
- debug(indent, 'id', m2[2], m2[3], m2[4]);
190
- fragment.id = m2[3];
191
- if (indent === 0) {
192
- if (m2[4] == '[') {
193
- fragment.type = 'range';
354
+ case 'xml': {
355
+ const args = m1[DIRECTIVE_INLINE_ARGS] ?? 'version="1.0" encoding="UTF-8"';
356
+ fragment.html += `<?xml ${args.trim()}?>`;
357
+ break;
358
+ }
359
+ case 'template': {
360
+ let indented = false;
361
+ fragment.template = indent === 0;
362
+ templateLinesRe.lastIndex = sniffTestRe.lastIndex;
363
+ while ((m2 = templateLinesRe.exec(input))) {
364
+ if (m2[1] == null && !indented && fragment.id == null) {
365
+ id1.lastIndex = 0;
366
+ m3 = id1.exec(m2[0]);
367
+ if (m3 != null)
368
+ fragment.id = m3[3];
369
+ fragment.html += m2[0];
370
+ }
371
+ else if (m2[1] == null && indented) {
372
+ sniffTestRe.lastIndex = templateLinesRe.lastIndex - m2[0].length;
373
+ break;
374
+ }
375
+ else {
376
+ fragment.html += '\n' + m2[0];
377
+ if (m2[1] != null)
378
+ indented = true;
379
+ }
194
380
  }
195
- else if (m2[4] === '"') {
196
- fragment.type = 'text';
381
+ applyIndent(0);
382
+ break;
383
+ }
384
+ case 'mount': {
385
+ if (m1[DIRECTIVE_INLINE_ARGS] == null) {
386
+ throw new Error('Mount points must have a name');
197
387
  }
198
- else if (m2[2] != null) {
199
- fragment.type = 'bare';
388
+ else if (fragment.type !== 'root') {
389
+ throw new Error('Mounting is only allowed on a root element');
200
390
  }
201
- else {
202
- fragment.type = 'embed';
203
- element.id = fragment.id;
391
+ fragment.mountable = true;
392
+ element.mount = m1[DIRECTIVE_INLINE_ARGS].trim();
393
+ break;
394
+ }
395
+ default: {
396
+ const def = directives[m1[DIRECTIVE]];
397
+ if (def == null)
398
+ break;
399
+ if (typeof def.beforeRender === 'function') {
400
+ if (element.id != null) {
401
+ applyIndent(indent);
402
+ }
403
+ if (element.beforeRender == null)
404
+ element.beforeRender = [];
405
+ // TODO: Parse block args
406
+ const applied = {
407
+ ...def,
408
+ inlineArg: m1[DIRECTIVE_INLINE_ARGS],
409
+ };
410
+ element.beforeRender.push(applied);
204
411
  }
205
412
  }
206
- break;
207
413
  }
414
+ break;
208
415
  }
209
- case '@':
210
416
  case '[':
211
- // deno-lint-ignore no-fallthrough
212
417
  case '::': {
213
- element1.lastIndex = 0;
214
- // fall through if m1[3] is a # or @
215
- m2 = m1[2] ?? m1[4] != null
216
- ? null
217
- : element1.exec(m1[0]);
218
- // if null then invalid element selector
219
- // allow the default text case to handle
220
- if (m2 != null) {
221
- const indent = (m2[1]?.length ?? 0) / 2, tg = m2[2], ar = m2[3], pr = m2[4] === '{' || m2[4] === '{{';
222
- const tx = pr ? null : m2[4];
223
- debug(indent, 'e', tg, pr, tx);
224
- if (element.tag != null ||
418
+ if (m1[ELEMENT] !== undefined) {
419
+ const indent = (m1[INDENT]?.length ?? 0) / 2;
420
+ let preformattedType;
421
+ let inlineText;
422
+ if (element.tag !== undefined ||
225
423
  element.indent > indent) {
226
424
  applyIndent(indent);
227
425
  }
228
426
  element.indent = indent;
229
- element.tag = tg;
230
427
  textIndent = null;
231
428
  if (indent === 0 && fragment.id == null) {
232
429
  if (root != null) ;
@@ -235,36 +432,58 @@ function longform(doc, debug = () => { }) {
235
432
  root = fragment;
236
433
  }
237
434
  }
238
- if (ar != null) {
239
- debug(indent, 'a', ar);
240
- while ((m2 = paramsRe.exec(ar))) {
241
- if (m2[1] === '#') {
242
- element.id = m2[2];
435
+ const parent = element;
436
+ outer.lastIndex = 0;
437
+ // Looping through chained element declarations
438
+ // foo#x.y::bar::free::
439
+ while ((m2 = outer.exec(m1[LINE]))) {
440
+ let working;
441
+ preformattedType = m2[3];
442
+ inlineText = m2[4];
443
+ if (element.tag === undefined) {
444
+ element.tag = m2[1];
445
+ working = element;
446
+ }
447
+ else {
448
+ if (parent.chain === undefined)
449
+ parent.chain = [];
450
+ working = makeElement(indent);
451
+ working.tag = m2[1];
452
+ parent.chain.push(working);
453
+ }
454
+ inner.lastIndex = 0;
455
+ // Looping through ids, classes and attrs
456
+ while ((m3 = inner.exec(m2[2]))) {
457
+ if (m3[2] !== undefined) {
458
+ working.id = m3[2];
243
459
  }
244
- else if (m2[1] === '.') {
245
- if (element.class == null) {
246
- element.class = m2[2];
460
+ else if (m3[1] !== undefined) {
461
+ if (working.class == null) {
462
+ working.class = m3[1];
247
463
  }
248
464
  else {
249
- element.class += ' ' + m2[2];
465
+ working.class += ' ' + m3[1];
250
466
  }
251
467
  }
252
468
  else {
253
- if (m2[3] === 'id') {
254
- if (element.id == null) {
255
- element.id = m2[4];
256
- }
257
- }
258
- else if (m2[3] === 'class') {
259
- if (element.class == null) {
260
- element.class = m2[4];
261
- }
262
- else {
263
- element.class += ' ' + m2[4];
264
- }
265
- }
266
- else {
267
- element.attrs[m2[3]] = m2[4];
469
+ // TODO: Preserve quoting style around attribute values
470
+ let value = m3[4] ?? m3[5] ?? m3[6];
471
+ switch (m3[3]) {
472
+ case 'id':
473
+ if (!working.id) {
474
+ working.id = value;
475
+ }
476
+ break;
477
+ case 'class':
478
+ if (!working.class) {
479
+ working.class = value;
480
+ }
481
+ else {
482
+ working.class += ' ' + value;
483
+ }
484
+ break;
485
+ default:
486
+ working.attrs[m3[3]] = value;
268
487
  }
269
488
  }
270
489
  }
@@ -283,22 +502,21 @@ function longform(doc, debug = () => { }) {
283
502
  applyIndent(indent);
284
503
  break;
285
504
  }
286
- if (!pr && tx != null) {
287
- element.text = tx;
288
- }
289
- else if (pr) {
505
+ if (preformattedType !== undefined) {
290
506
  verbatimFirst = true;
291
507
  verbatimIndent = indent;
292
- verbatimSerialize = m2[4] === '{';
508
+ verbatimSerialize = preformattedType === '{{';
509
+ }
510
+ else if (inlineText !== undefined) {
511
+ element.text = inlineText;
293
512
  }
294
513
  break;
295
514
  }
296
515
  attribute1.lastIndex = 0;
297
- m2 = m1[2] != null
298
- ? null
299
- : attribute1.exec(m1[0]);
516
+ m2 = m1[ATTR] !== undefined
517
+ ? attribute1.exec(m1[LINE])
518
+ : undefined;
300
519
  if (m2 != null && element.tag != null) {
301
- debug('a', m2[2], m2[3]);
302
520
  if (m2[2] === 'id') {
303
521
  if (element.id == null) {
304
522
  element.id = m2[3].trim();
@@ -320,64 +538,6 @@ function longform(doc, debug = () => { }) {
320
538
  }
321
539
  break;
322
540
  }
323
- directive1.lastIndex = 0;
324
- m2 = m1[3] != null
325
- ? null
326
- : directive1.exec(m1[0]);
327
- if (m2 != null) {
328
- const indent = (m2[1]?.length ?? 0) / 2;
329
- if (element.tag != null || textIndent != null) {
330
- applyIndent(indent);
331
- }
332
- debug(indent, 'd', m2[2], m2[3]);
333
- switch (m2[2]) {
334
- case 'doctype': {
335
- fragment.html += `<!doctype ${m2[3] ?? 'html'}>`;
336
- break;
337
- }
338
- case 'xml': {
339
- fragment.html += `<?xml ${m2[3] ?? 'version="1.0" encoding="UTF-8"'}?>`;
340
- break;
341
- }
342
- case 'template': {
343
- let indented = false;
344
- fragment.template = indent === 0;
345
- templateLinesRe.lastIndex = sniffTestRe.lastIndex;
346
- while ((m2 = templateLinesRe.exec(doc))) {
347
- if (m2[1] == null && !indented && fragment.id == null) {
348
- id1.lastIndex = 0;
349
- m3 = id1.exec(m2[0]);
350
- if (m3 != null)
351
- fragment.id = m3[3];
352
- fragment.html += m2[0];
353
- }
354
- else if (m2[1] == null && indented) {
355
- sniffTestRe.lastIndex = templateLinesRe.lastIndex - m2[0].length;
356
- break;
357
- }
358
- else {
359
- fragment.html += '\n' + m2[0];
360
- if (m2[1] != null)
361
- indented = true;
362
- }
363
- }
364
- applyIndent(0);
365
- break;
366
- }
367
- case 'mount': {
368
- if (m2[3] == null) {
369
- throw new Error('Mount points must have a name');
370
- }
371
- else if (fragment.type !== 'root') {
372
- throw new Error('Mounting is only allowed on a root element');
373
- }
374
- fragment.mountable = true;
375
- element.mount = m2[3].trim();
376
- break;
377
- }
378
- }
379
- break;
380
- }
381
541
  }
382
542
  default: {
383
543
  m2 = text1.exec(m1[0]);
@@ -386,7 +546,6 @@ function longform(doc, debug = () => { }) {
386
546
  }
387
547
  const indent = m2[1].length / 2;
388
548
  const tx = m2[2].trim();
389
- debug(indent, 't', m2[2]);
390
549
  if (element.tag != null) {
391
550
  applyIndent(indent);
392
551
  fragment.html += tx;
@@ -482,6 +641,8 @@ function longform(doc, debug = () => { }) {
482
641
  html: fragment.html,
483
642
  };
484
643
  }
644
+ output.id = doc?.id;
645
+ output.lang = doc?.lang;
485
646
  return output;
486
647
  }
487
648
  const templateRe = /(?:#{([\w][\w\-_]*)})|(?:#\[([\w][\w\-_]+)\])/g;