@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 +30 -2
- package/dist/longform.cjs +309 -148
- package/dist/longform.cjs.map +1 -1
- package/dist/longform.d.ts +4 -4
- package/dist/longform.js +309 -148
- package/dist/longform.js.br +0 -0
- package/dist/longform.js.gz +0 -0
- package/dist/longform.js.map +1 -1
- package/dist/longform.min.js +309 -148
- package/dist/longform.min.js.br +0 -0
- package/dist/longform.min.js.gz +0 -0
- package/dist/longform.min.js.map +1 -1
- package/dist/types.d.ts +71 -1
- package/lib/longform.ts +376 -165
- package/lib/types.ts +92 -1
- package/package.json +21 -12
- package/lib/longform.test.ts +0 -347
package/lib/longform.ts
CHANGED
|
@@ -1,17 +1,40 @@
|
|
|
1
|
-
import type { FragmentType, ParsedResult, WorkingElement, WorkingFragment
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
,
|
|
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
|
-
|
|
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
|
|
65
|
-
* @
|
|
149
|
+
* @param input - The Longform document to parse.
|
|
150
|
+
* @param args - Arguments for the Longform parser.
|
|
66
151
|
*/
|
|
67
|
-
export function longform(
|
|
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
|
|
243
|
+
if (element.mount !== undefined) {
|
|
109
244
|
fragment.html += ` data-lf-mount="${element.mount}"`;
|
|
110
245
|
}
|
|
111
246
|
|
|
112
|
-
if (element.id
|
|
247
|
+
if (element.id !== undefined) {
|
|
113
248
|
fragment.html += ' id="' + element.id + '"';
|
|
114
249
|
}
|
|
115
250
|
|
|
116
|
-
if (element.class
|
|
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(
|
|
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
|
-
|
|
186
|
-
m2 =
|
|
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[
|
|
393
|
+
if (m1[LINE].trim() === '') {
|
|
228
394
|
continue;
|
|
229
395
|
}
|
|
230
396
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
238
|
-
const indent = (m2[1]?.length ?? 0) / 2;
|
|
422
|
+
const indent = (m1[INDENT].length ?? 0) / 2;
|
|
239
423
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
case '@': {
|
|
449
|
+
const indent = m1[INDENT].length / 2;
|
|
246
450
|
|
|
247
|
-
|
|
451
|
+
if (element.tag != null || textIndent != null) {
|
|
452
|
+
applyIndent(indent);
|
|
453
|
+
}
|
|
248
454
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
587
|
+
working.class += ' ' + m3[1];
|
|
317
588
|
}
|
|
318
589
|
} else {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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 (
|
|
352
|
-
element.text = tx;
|
|
353
|
-
} else if (pr) {
|
|
627
|
+
|
|
628
|
+
if (preformattedType !== undefined) {
|
|
354
629
|
verbatimFirst = true;
|
|
355
630
|
verbatimIndent = indent;
|
|
356
|
-
verbatimSerialize =
|
|
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[
|
|
364
|
-
?
|
|
365
|
-
:
|
|
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
|
|