@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/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=
|
|
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
|
|
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
|
|
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
|
'&': '&',
|
|
@@ -22,8 +35,17 @@ function escape(value) {
|
|
|
22
35
|
function makeElement(indent = 0) {
|
|
23
36
|
return {
|
|
24
37
|
indent,
|
|
25
|
-
|
|
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
|
|
45
|
-
* @
|
|
102
|
+
* @param input - The Longform document to parse.
|
|
103
|
+
* @param args - Arguments for the Longform parser.
|
|
46
104
|
*/
|
|
47
|
-
function longform(
|
|
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
|
|
169
|
+
if (element.mount !== undefined) {
|
|
76
170
|
fragment.html += ` data-lf-mount="${element.mount}"`;
|
|
77
171
|
}
|
|
78
|
-
if (element.id
|
|
172
|
+
if (element.id !== undefined) {
|
|
79
173
|
fragment.html += ' id="' + element.id + '"';
|
|
80
174
|
}
|
|
81
|
-
if (element.class
|
|
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(
|
|
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
|
-
|
|
139
|
-
m2 =
|
|
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 +=
|
|
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[
|
|
293
|
+
if (m1[LINE].trim() === '') {
|
|
176
294
|
continue;
|
|
177
295
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
196
|
-
|
|
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 (
|
|
199
|
-
|
|
388
|
+
else if (fragment.type !== 'root') {
|
|
389
|
+
throw new Error('Mounting is only allowed on a root element');
|
|
200
390
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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 (
|
|
245
|
-
if (
|
|
246
|
-
|
|
460
|
+
else if (m3[1] !== undefined) {
|
|
461
|
+
if (working.class == null) {
|
|
462
|
+
working.class = m3[1];
|
|
247
463
|
}
|
|
248
464
|
else {
|
|
249
|
-
|
|
465
|
+
working.class += ' ' + m3[1];
|
|
250
466
|
}
|
|
251
467
|
}
|
|
252
468
|
else {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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 (
|
|
287
|
-
element.text = tx;
|
|
288
|
-
}
|
|
289
|
-
else if (pr) {
|
|
505
|
+
if (preformattedType !== undefined) {
|
|
290
506
|
verbatimFirst = true;
|
|
291
507
|
verbatimIndent = indent;
|
|
292
|
-
verbatimSerialize =
|
|
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[
|
|
298
|
-
?
|
|
299
|
-
:
|
|
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;
|