htmljs-parser 3.1.0 → 3.2.2
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 +343 -537
- package/dist/core/Parser.d.ts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +149 -140
- package/dist/index.mjs +147 -139
- package/dist/states/OPEN_TAG.d.ts +3 -3
- package/dist/util/constants.d.ts +37 -13
- package/dist/util/util.d.ts +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,591 +1,397 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
<!-- Logo -->
|
|
3
|
+
<br/>
|
|
4
|
+
htmljs-parser
|
|
5
|
+
<br/>
|
|
6
|
+
|
|
7
|
+
<!-- Format -->
|
|
8
|
+
<a href="https://github.com/prettier/prettier">
|
|
9
|
+
<img src="https://img.shields.io/badge/styled_with-prettier-ff69b4.svg" alt="Styled with prettier"/>
|
|
10
|
+
</a>
|
|
11
|
+
<!-- CI -->
|
|
12
|
+
<a href="https://github.com/marko-js/htmljs-parser/actions/workflows/ci.yml">
|
|
13
|
+
<img src="https://github.com/marko-js/htmljs-parser/actions/workflows/ci.yml/badge.svg" alt="Build status"/>
|
|
14
|
+
</a>
|
|
15
|
+
<!-- Coverage -->
|
|
16
|
+
<a href="https://codecov.io/gh/marko-js/htmljs-parser">
|
|
17
|
+
<img src="https://codecov.io/gh/marko-js/htmljs-parser/branch/main/graph/badge.svg?token=Sv8ePs16ix" alt="Code Coverage"/>
|
|
18
|
+
</a>
|
|
19
|
+
<!-- NPM Version -->
|
|
20
|
+
<a href="https://npmjs.org/package/htmljs-parser">
|
|
21
|
+
<img src="https://img.shields.io/npm/v/htmljs-parser.svg" alt="NPM version"/>
|
|
22
|
+
</a>
|
|
23
|
+
<!-- Downloads -->
|
|
24
|
+
<a href="https://npmjs.org/package/htmljs-parser">
|
|
25
|
+
<img src="https://img.shields.io/npm/dm/htmljs-parser.svg" alt="Downloads"/>
|
|
26
|
+
</a>
|
|
27
|
+
</h1>
|
|
28
|
+
|
|
29
|
+
An HTML parser with super powers used by [Marko](https://markojs.com/docs/syntax/).
|
|
2
30
|
|
|
3
|
-
|
|
4
|
-
attribute values as strings which makes it challenging to properly
|
|
5
|
-
describe a value's type (boolean, string, number, array, etc.)
|
|
6
|
-
or to provide a complex JavaScript expression as a value.
|
|
7
|
-
The ability to describe JavaScript expressions within attributes
|
|
8
|
-
is important for HTML-based template compilers.
|
|
9
|
-
|
|
10
|
-
For example, consider a HTML-based template that wishes to
|
|
11
|
-
support a custom tag named `<say-hello>` that supports an
|
|
12
|
-
attribute named `message` that can be a string literal or a JavaScript expression.
|
|
13
|
-
|
|
14
|
-
Ideally, the template compiler should be able to handle any of the following:
|
|
15
|
-
|
|
16
|
-
```html
|
|
17
|
-
<say-hello message="Hello world!" />
|
|
18
|
-
<say-hello message=("Hello " + personName + "!") />
|
|
19
|
-
<say-hello message="Hello ${personName}!" />
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
This parser extends the HTML grammar to add these important features:
|
|
23
|
-
|
|
24
|
-
- JavaScript expressions as attribute values
|
|
25
|
-
|
|
26
|
-
```html
|
|
27
|
-
<say-hello message=("Hello " + personName) count=2+2 large=true />
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
- Placeholders in the content of an element
|
|
31
|
-
|
|
32
|
-
```html
|
|
33
|
-
<div>Hello ${personName}</div>
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
- Placeholders within attribute value strings
|
|
31
|
+
# Installation
|
|
37
32
|
|
|
38
|
-
```
|
|
39
|
-
|
|
33
|
+
```console
|
|
34
|
+
npm install htmljs-parser
|
|
40
35
|
```
|
|
41
36
|
|
|
42
|
-
|
|
37
|
+
# Creating A Parser
|
|
43
38
|
|
|
44
|
-
|
|
45
|
-
<div for(a in b) />
|
|
46
|
-
<div if(a === b) />
|
|
47
|
-
```
|
|
39
|
+
First we must create a parser instance and pass it some handlers for the various parse events shown below.
|
|
48
40
|
|
|
49
|
-
|
|
41
|
+
Each parse event is called a `Range` and is an object with start and end properties which are zero-based offsets from the beginning of th parsed code.
|
|
50
42
|
|
|
51
|
-
|
|
52
|
-
<for (a in b)> <if (a in b)></if></for>
|
|
53
|
-
```
|
|
43
|
+
Additional meta data and nested ranges are exposed on some events shown below.
|
|
54
44
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
npm install htmljs-parser
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
# Usage
|
|
45
|
+
You can get the raw string from any range using `parser.read(range)`.
|
|
62
46
|
|
|
63
47
|
```javascript
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
48
|
+
import { createParser, ErrorCode, TagType } from "htmljs-parser";
|
|
49
|
+
|
|
50
|
+
const parser = createParser({
|
|
51
|
+
/**
|
|
52
|
+
* Called when the parser encounters an error.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* 1╭─ <a><b
|
|
56
|
+
* ╰─ ╰─ error(code: 19, message: "EOF reached while parsing open tag")
|
|
57
|
+
*/
|
|
58
|
+
onError(range) {
|
|
59
|
+
range.code; // An error code id. You can see the list of error codes in ErrorCode imported above.
|
|
60
|
+
range.message; // A human readable (hopefully) error message.
|
|
68
61
|
},
|
|
69
62
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Called when some static text is parsed within some body content.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* 1╭─ <div>Hi</div>
|
|
68
|
+
* ╰─ ╰─ text "Hi"
|
|
69
|
+
*/
|
|
70
|
+
onText(range) {},
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Called after parsing a placeholder within body content.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* 1╭─ <div>${hello} $!{world}</div>
|
|
77
|
+
* │ │ │ │ ╰─ placeholder.value "world"
|
|
78
|
+
* │ │ │ ╰─ placeholder "$!{world}"
|
|
79
|
+
* │ │ ╰─ placeholder:escape.value "hello"
|
|
80
|
+
* ╰─ ╰─ placeholder:escape "${hello}"
|
|
81
|
+
*/
|
|
82
|
+
onPlaceholder(range) {
|
|
83
|
+
range.escape; // true for ${} placeholders and false for $!{} placeholders.
|
|
84
|
+
range.value; // Another range that includes only the placeholder value itself without the wrapping braces.
|
|
79
85
|
},
|
|
80
86
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Called when we find a comment at the root of the document or within a tags contents.
|
|
89
|
+
* It will not be fired for comments within expressions, such as attribute values.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* 1╭─ <!-- hi -->
|
|
93
|
+
* │ │ ╰─ comment.value " hi "
|
|
94
|
+
* ╰─ ╰─ comment "<!-- hi -->"
|
|
95
|
+
* 2╭─ // hi
|
|
96
|
+
* │ │ ╰─ comment.value " hi"
|
|
97
|
+
* ╰─ ╰─ comment "// hi"
|
|
98
|
+
*/
|
|
99
|
+
onComment(range) {
|
|
100
|
+
range.value; // Another range that only includes the contents of the comment.
|
|
85
101
|
},
|
|
86
102
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Called after parsing a CDATA section.
|
|
105
|
+
* // https://developer.mozilla.org/en-US/docs/Web/API/CDATASection
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* 1╭─ <![CDATA[hi]]>
|
|
109
|
+
* │ │ ╰─ cdata.value "hi"
|
|
110
|
+
* ╰─ ╰─ cdata "<![CDATA[hi]]>"
|
|
111
|
+
*/
|
|
112
|
+
onCDATA(range) {
|
|
113
|
+
range.value; // Another range that only includes the contents of the CDATA.
|
|
91
114
|
},
|
|
92
115
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Called after parsing a DocType comment.
|
|
118
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/DocumentType
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* 1╭─ <!DOCTYPE html>
|
|
122
|
+
* │ │ ╰─ doctype.value "DOCTYPE html"
|
|
123
|
+
* ╰─ ╰─ doctype "<!DOCTYPE html>"
|
|
124
|
+
*/
|
|
125
|
+
onDoctype(range) {
|
|
126
|
+
range.value; // Another range that only includes the contents of the DocType.
|
|
98
127
|
},
|
|
99
128
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Called after parsing an XML declaration.
|
|
131
|
+
* https://developer.mozilla.org/en-US/docs/Web/XML/XML_introduction#xml_declaration
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* 1╭─ <?xml version="1.0" encoding="UTF-8"?>
|
|
135
|
+
* │ │ ╰─ declaration.value "xml version=\"1.0\" encoding=\"UTF-8\""
|
|
136
|
+
* ╰─ ╰─ declaration "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
|
|
137
|
+
*/
|
|
138
|
+
onDeclaration(range) {
|
|
139
|
+
range.value; // Another range that only includes the contents of the declaration.
|
|
104
140
|
},
|
|
105
141
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Called after parsing a scriptlet (new line followed by a $).
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* 1╭─ $ foo();
|
|
147
|
+
* │ │╰─ scriptlet.value "foo();"
|
|
148
|
+
* ╰─ ╰─ scriptlet " foo();"
|
|
149
|
+
* 2╭─ $ { bar(); }
|
|
150
|
+
* │ │ ╰─ scriptlet:block.value " bar(); "
|
|
151
|
+
* ╰─ ╰─ scriptlet:block " { bar(); }"
|
|
152
|
+
*/
|
|
153
|
+
onScriptlet(range) {
|
|
154
|
+
range.block; // true if the scriptlet was contained within braces.
|
|
155
|
+
range.value; // Another range that includes only the value itself without the leading $ or surrounding braces (if applicable).
|
|
112
156
|
},
|
|
113
157
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Called when a tag name, which can include placeholders, has been parsed.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* 1╭─ <div/>
|
|
163
|
+
* ╰─ ╰─ tagName "div"
|
|
164
|
+
* 2╭─ <hello-${test}-again/>
|
|
165
|
+
* │ │ │ ╰─ tagName.quasis[1] "-again"
|
|
166
|
+
* │ │ ╰─ tagName.expressions[0] "${test}"
|
|
167
|
+
* │ ├─ tagName.quasis[0] "hello-"
|
|
168
|
+
* ╰─ ╰─ tagName "hello-${test}-again"
|
|
169
|
+
*/
|
|
170
|
+
onTagName(range) {
|
|
171
|
+
range.concise; // true if this tag is a concise mode tag.
|
|
172
|
+
range.quasis; // An array of ranges that indicate the string literal parts of the tag name.
|
|
173
|
+
range.expressions; // A list of placeholder ranges (similar to whats emitted via onPlaceholder).
|
|
174
|
+
|
|
175
|
+
// Return a different tag type enum value to enter a different parse mode.
|
|
176
|
+
// Below is approximately what Marko uses:
|
|
177
|
+
switch (parser.read(range)) {
|
|
178
|
+
case "area":
|
|
179
|
+
case "base":
|
|
180
|
+
case "br":
|
|
181
|
+
case "col":
|
|
182
|
+
case "embed":
|
|
183
|
+
case "hr":
|
|
184
|
+
case "img":
|
|
185
|
+
case "input":
|
|
186
|
+
case "link":
|
|
187
|
+
case "meta":
|
|
188
|
+
case "param":
|
|
189
|
+
case "source":
|
|
190
|
+
case "track":
|
|
191
|
+
case "wbr":
|
|
192
|
+
// TagType.void makes this a void element (cannot have children).
|
|
193
|
+
return TagType.void;
|
|
194
|
+
case "html-comment":
|
|
195
|
+
case "script":
|
|
196
|
+
case "style":
|
|
197
|
+
case "textarea":
|
|
198
|
+
// TagType.text makes the child content text only (with placeholders).
|
|
199
|
+
return TagType.text;
|
|
200
|
+
case "class":
|
|
201
|
+
case "export":
|
|
202
|
+
case "import":
|
|
203
|
+
case "static":
|
|
204
|
+
// TagType.statement makes this a statement tag where the content following the tag name will be parsed as script code until we reach a new line, eg for `import x from "y"`).
|
|
205
|
+
return TagType.statement;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// TagType.html is the default which allows child content as html with placeholders.
|
|
209
|
+
return TagType.html;
|
|
120
210
|
},
|
|
121
211
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
212
|
+
/**
|
|
213
|
+
* Called when a shorthand id, which can include placeholders, has been parsed.
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* 1╭─ <div#hello-${test}-again/>
|
|
217
|
+
* │ ││ │ ╰─ tagShorthandId.quasis[1] "-again"
|
|
218
|
+
* │ ││ ╰─ tagShorthandId.expressions[0] "${test}"
|
|
219
|
+
* │ │╰─ tagShorthandId.quasis[0] "hello-"
|
|
220
|
+
* ╰─ ╰─ tagShorthandId "#hello-${test}-again"
|
|
221
|
+
*/
|
|
222
|
+
onTagShorthandId(range) {
|
|
223
|
+
range.quasis; // An array of ranges that indicate the string literal parts of the shorthand id name.
|
|
224
|
+
range.expressions; // A list of placeholder ranges (similar to whats emitted via onPlaceholder).
|
|
126
225
|
},
|
|
127
226
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
227
|
+
/**
|
|
228
|
+
* Called when a shorthand class name, which can include placeholders, has been parsed.
|
|
229
|
+
* Note there can be multiple of these.
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* 1╭─ <div.hello-${test}-again/>
|
|
233
|
+
* │ ││ │ ╰─ tagShorthandClassName.quasis[1] "-again"
|
|
234
|
+
* │ ││ ╰─ tagShorthandClassName.expressions[0] "${test}"
|
|
235
|
+
* │ │╰─ tagShorthandClassName.quasis[0] "hello-"
|
|
236
|
+
* ╰─ ╰─ tagShorthandClassName "#hello-${test}-again"
|
|
237
|
+
*/
|
|
238
|
+
onTagShorthandClass(range) {
|
|
239
|
+
range.quasis; // An array of ranges that indicate the string literal parts of the shorthand id name.
|
|
240
|
+
range.expressions; // A list of placeholder ranges (similar to whats emitted via onPlaceholder).
|
|
132
241
|
},
|
|
133
242
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Called after a tag variable has been parsed.
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* 1╭─ <div/el/>
|
|
248
|
+
* │ │╰─ tagVar.value "el"
|
|
249
|
+
* ╰─ ╰─ tagVar "/el"
|
|
250
|
+
*/
|
|
251
|
+
onTagVar(range) {
|
|
252
|
+
range.value; // Another range that includes only the tag var itself and not the leading slash.
|
|
139
253
|
},
|
|
140
|
-
});
|
|
141
254
|
|
|
142
|
-
|
|
143
|
-
|
|
255
|
+
/**
|
|
256
|
+
* Called after tag arguments have been parsed.
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* 1╭─ <if(x)>
|
|
260
|
+
* │ │╰─ tagArgs.value "x"
|
|
261
|
+
* ╰─ ╰─ tagArgs "(x)"
|
|
262
|
+
*/
|
|
263
|
+
onTagArgs(range) {
|
|
264
|
+
range.value; // Another range that includes only the args themselves and not the outer parenthesis.
|
|
265
|
+
},
|
|
144
266
|
|
|
145
|
-
|
|
267
|
+
/**
|
|
268
|
+
* Called after tag parameters have been parsed.
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* 1╭─ <for|item| of=list>
|
|
272
|
+
* │ │╰─ tagParams.value "item"
|
|
273
|
+
* ╰─ ╰─ tagParams "|item|"
|
|
274
|
+
*/
|
|
275
|
+
onTagParams(range) {
|
|
276
|
+
range.value; // Another range that includes only the params themselves and not the outer pipes.
|
|
277
|
+
},
|
|
146
278
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
279
|
+
/**
|
|
280
|
+
* Called after an attribute name as been parsed.
|
|
281
|
+
* Note this may be followed by the related AttrArgs, AttrValue or AttrMethod. It can also be directly followed by another AttrName, AttrSpread or the OpenTagEnd if this is a boolean attribute.
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* 1╭─ <div class="hi">
|
|
285
|
+
* ╰─ ╰─ attrName "class"
|
|
286
|
+
*/
|
|
287
|
+
onAttrName(range) {},
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Called after attr arguments have been parsed.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* 1╭─ <div if(x)>
|
|
294
|
+
* │ │╰─ attrArgs.value "x"
|
|
295
|
+
* ╰─ ╰─ attrArgs "(x)"
|
|
296
|
+
*/
|
|
297
|
+
onAttrArgs(range) {
|
|
298
|
+
range.value; // Another range that includes only the args themselves and not the outer parenthesis.
|
|
299
|
+
},
|
|
150
300
|
|
|
151
|
-
|
|
301
|
+
/**
|
|
302
|
+
* Called after an attr value has been parsed.
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* 1╭─ <input name="hi" value:=x>
|
|
306
|
+
* │ ││ │ ╰─ attrValue:bound.value
|
|
307
|
+
* │ ││ ╰─ attrValue:bound ":=x"
|
|
308
|
+
* │ │╰─ attrValue.value "\"hi\""
|
|
309
|
+
* ╰─ ╰─ attrValue "=\"hi\""
|
|
310
|
+
*/
|
|
311
|
+
onAttrValue(range) {
|
|
312
|
+
range.bound; // true if the attribute value was preceded by :=.
|
|
313
|
+
range.value; // Another range that includes only the value itself without the leading = or :=.
|
|
314
|
+
},
|
|
152
315
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
316
|
+
/**
|
|
317
|
+
* Called after an attribute method shorthand has been parsed.
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* 1╭─ <div onClick(ev) { foo(); }>
|
|
321
|
+
* │ ││ │╰─ attrMethod.body.value " foo(); "
|
|
322
|
+
* │ ││ ╰─ attrMethod.body "{ foo(); }"
|
|
323
|
+
* │ │╰─ attrMethod.params.value "ev"
|
|
324
|
+
* │ ├─ attrMethod.params "(ev)"
|
|
325
|
+
* ╰─ ╰─ attrMethod "(ev) { foo(); }"
|
|
326
|
+
*/
|
|
327
|
+
onAttrMethod(range) {
|
|
328
|
+
range.params; // Another range which includes the params for the method.
|
|
329
|
+
range.params.value; // Another range which includes the method params without outer parenthesis.
|
|
330
|
+
|
|
331
|
+
range.body; // Another range which includes the entire body block.
|
|
332
|
+
range.body.value; // Another range which includes the body block without outer braces.
|
|
333
|
+
},
|
|
156
334
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
335
|
+
/**
|
|
336
|
+
* Called after we've parsed a spread attribute.
|
|
337
|
+
*
|
|
338
|
+
* @example
|
|
339
|
+
* 1╭─ <div ...attrs>
|
|
340
|
+
* │ │ ╰─ attrSpread.value "attrs"
|
|
341
|
+
* ╰─ ╰─ attrSpread "...attrs"
|
|
342
|
+
*/
|
|
343
|
+
onAttrSpread(range) {
|
|
344
|
+
range.value; // Another range that includes only the value itself without the leading ...
|
|
345
|
+
},
|
|
160
346
|
|
|
161
|
-
|
|
162
|
-
|
|
347
|
+
/**
|
|
348
|
+
* Called once we've completed parsing the open tag.
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* 1╭─ <div><span/></div>
|
|
352
|
+
* │ │ ╰─ openTagEnd:selfClosed "/>"
|
|
353
|
+
* ╰─ ╰─ openTagEnd ">"
|
|
354
|
+
*/
|
|
355
|
+
onOpenTagEnd(range) {
|
|
356
|
+
range.selfClosed; // true if this tag was self closed (onCloseTag would not be called if so).
|
|
357
|
+
},
|
|
163
358
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
// look for placeholders and the closing tag.
|
|
177
|
-
parser.enterParsedTextContentState();
|
|
178
|
-
break;
|
|
179
|
-
case 'dummy'
|
|
180
|
-
// treat content within <dummy>...</dummy> as raw
|
|
181
|
-
// text and ignore other tags and placeholders
|
|
182
|
-
parser.enterStaticTextContentState();
|
|
183
|
-
break;
|
|
184
|
-
default:
|
|
185
|
-
// The parser will switch to HTML content parsing mode
|
|
186
|
-
// if the parsing mode is not explicitly changed by
|
|
187
|
-
// "onOpenTag" function.
|
|
188
|
-
}
|
|
189
|
-
}
|
|
359
|
+
/**
|
|
360
|
+
* Called once the closing tag (or in concise mode an outdent or eof) is parsed.
|
|
361
|
+
* Note this is not called for selfClosed, void or statement tags.
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* 1╭─ <div><span/></div>
|
|
365
|
+
* │ │ ╰─ closeTag(div).value "div"
|
|
366
|
+
* ╰─ ╰─ closeTag(div) "</div>"
|
|
367
|
+
*/
|
|
368
|
+
onCloseTag(range) {
|
|
369
|
+
range.value; // The raw content of the closing tag (undefined in concise mode).
|
|
370
|
+
},
|
|
190
371
|
});
|
|
191
|
-
|
|
192
|
-
parser.parse(str);
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
## Parsing Events
|
|
196
|
-
|
|
197
|
-
The `htmljs-parser` is an event-based parser which means that it will emit
|
|
198
|
-
events as it is parsing the document. Events are emitted via calls
|
|
199
|
-
to `on<eventname>` function which are supplied as properties in the options
|
|
200
|
-
via call to `require('htmljs-parser').createParser(options)`.
|
|
201
|
-
|
|
202
|
-
### onOpenTag
|
|
203
|
-
|
|
204
|
-
The `onOpenTag` function will be called each time an opening tag is
|
|
205
|
-
encountered.
|
|
206
|
-
|
|
207
|
-
**EXAMPLE: Simple tag**
|
|
208
|
-
|
|
209
|
-
INPUT:
|
|
210
|
-
|
|
211
|
-
```html
|
|
212
|
-
<div></div>
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
OUTPUT EVENT:
|
|
216
|
-
|
|
217
|
-
```javascript
|
|
218
|
-
{
|
|
219
|
-
type: 'openTag',
|
|
220
|
-
tagName: 'div',
|
|
221
|
-
attributes: []
|
|
222
|
-
}
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
**EXAMPLE: Tag with literal attribute values**
|
|
226
|
-
|
|
227
|
-
INPUT:
|
|
228
|
-
|
|
229
|
-
```html
|
|
230
|
-
<div class="demo" disabled="false" data-number="123"></div>
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
OUTPUT EVENT:
|
|
234
|
-
|
|
235
|
-
```javascript
|
|
236
|
-
{
|
|
237
|
-
type: 'openTag',
|
|
238
|
-
tagName: 'div',
|
|
239
|
-
attributes: [
|
|
240
|
-
{
|
|
241
|
-
name: 'class',
|
|
242
|
-
value: '"demo"'
|
|
243
|
-
},
|
|
244
|
-
{
|
|
245
|
-
name: 'disabled',
|
|
246
|
-
value: 'false'
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
name: 'data-number',
|
|
250
|
-
value: '123'
|
|
251
|
-
}
|
|
252
|
-
]
|
|
253
|
-
}
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
**EXAMPLE: Tag with expression attribute**
|
|
257
|
-
|
|
258
|
-
INPUT:
|
|
259
|
-
|
|
260
|
-
```html
|
|
261
|
-
<say-something message=("Hello "+data.name)/>
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
OUTPUT EVENT:
|
|
265
|
-
|
|
266
|
-
```javascript
|
|
267
|
-
{
|
|
268
|
-
type: 'openTag',
|
|
269
|
-
tagName: 'div',
|
|
270
|
-
attributes: [
|
|
271
|
-
{
|
|
272
|
-
name: 'message',
|
|
273
|
-
value: '"Hello "+data.name'
|
|
274
|
-
}
|
|
275
|
-
]
|
|
276
|
-
}
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
**EXAMPLE: Tag with an argument**
|
|
280
|
-
|
|
281
|
-
INPUT:
|
|
282
|
-
|
|
283
|
-
```html
|
|
284
|
-
<for(var i="0;" i < 10; i++)></for(var>
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
OUTPUT EVENT:
|
|
288
|
-
|
|
289
|
-
```javascript
|
|
290
|
-
{
|
|
291
|
-
type: 'openTag',
|
|
292
|
-
tagName: 'for',
|
|
293
|
-
argument: {
|
|
294
|
-
value: 'var i = 0; i < 10; i++',
|
|
295
|
-
pos: ... // Integer
|
|
296
|
-
},
|
|
297
|
-
attributes: []
|
|
298
|
-
}
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
**EXAMPLE: Attribute with an argument**
|
|
302
|
-
|
|
303
|
-
INPUT:
|
|
304
|
-
|
|
305
|
-
```html
|
|
306
|
-
<div if(x>y)></div>
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
OUTPUT EVENT:
|
|
310
|
-
|
|
311
|
-
```javascript
|
|
312
|
-
{
|
|
313
|
-
type: 'openTag',
|
|
314
|
-
tagName: 'div',
|
|
315
|
-
attributes: [
|
|
316
|
-
{
|
|
317
|
-
name: 'if',
|
|
318
|
-
argument: {
|
|
319
|
-
value: 'x > y',
|
|
320
|
-
pos: ... // Integer
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
]
|
|
324
|
-
}
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
### onCloseTag
|
|
328
|
-
|
|
329
|
-
The `onCloseTag` function will be called each time a closing tag is
|
|
330
|
-
encountered.
|
|
331
|
-
|
|
332
|
-
**EXAMPLE: Simple close tag**
|
|
333
|
-
|
|
334
|
-
INPUT:
|
|
335
|
-
|
|
336
|
-
```html
|
|
337
|
-
</div>
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
OUTPUT EVENT:
|
|
341
|
-
|
|
342
|
-
```javascript
|
|
343
|
-
{
|
|
344
|
-
type: 'closeTag',
|
|
345
|
-
tagName: 'div'
|
|
346
|
-
}
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
### onText
|
|
350
|
-
|
|
351
|
-
The `onText` function will be called each time within an element
|
|
352
|
-
when textual data is encountered.
|
|
353
|
-
|
|
354
|
-
**NOTE:** Text within `<![CDATA[` `]]>` will be emitted via call
|
|
355
|
-
to `onCDATA`.
|
|
356
|
-
|
|
357
|
-
**EXAMPLE**
|
|
358
|
-
|
|
359
|
-
In the following example code, the `TEXT` sequences will be emitted as
|
|
360
|
-
text events.
|
|
361
|
-
|
|
362
|
-
INPUT:
|
|
363
|
-
|
|
364
|
-
```html
|
|
365
|
-
Simple text
|
|
366
372
|
```
|
|
367
373
|
|
|
368
|
-
|
|
374
|
+
Finally after setting up the parser with it's handlers, it's time to pass in some source code to parse.
|
|
369
375
|
|
|
370
376
|
```javascript
|
|
371
|
-
|
|
372
|
-
type: 'text',
|
|
373
|
-
value: 'Simple text'
|
|
374
|
-
}
|
|
377
|
+
parser.parse("<div></div>");
|
|
375
378
|
```
|
|
376
379
|
|
|
377
|
-
|
|
380
|
+
# Parser Helpers
|
|
378
381
|
|
|
379
|
-
The
|
|
380
|
-
is encountered.
|
|
381
|
-
|
|
382
|
-
**EXAMPLE:**
|
|
383
|
-
|
|
384
|
-
INPUT:
|
|
385
|
-
|
|
386
|
-
```html
|
|
387
|
-
<![CDATA[This is text]]>
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
OUTPUT EVENT:
|
|
382
|
+
The parser instance provides a few helpers to make it easier to work with the parsed content.
|
|
391
383
|
|
|
392
384
|
```javascript
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
value: 'This is text'
|
|
396
|
-
}
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
### onPlaceholder
|
|
400
|
-
|
|
401
|
-
The `onPlaceholder` function will be called each time a placeholder
|
|
402
|
-
is encountered.
|
|
403
|
-
|
|
404
|
-
If the placeholder starts with the `$!{` sequence then `event.escape`
|
|
405
|
-
will be `false`.
|
|
406
|
-
|
|
407
|
-
If the placeholder starts with the `${` sequence then `event.escape` will be
|
|
408
|
-
`true`.
|
|
409
|
-
|
|
410
|
-
Text within `<![CDATA[` `]]>` and `<!--` `-->` will not be parsed so you
|
|
411
|
-
cannot use placeholders for these blocks of code.
|
|
412
|
-
|
|
413
|
-
**EXAMPLE:**
|
|
414
|
-
|
|
415
|
-
INPUT:
|
|
416
|
-
|
|
417
|
-
```html
|
|
418
|
-
${"This is an escaped placeholder"} $!{"This is a non-escaped placeholder"}
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
OUTPUT EVENTS
|
|
422
|
-
|
|
423
|
-
```html
|
|
424
|
-
${name}
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
```javascript
|
|
428
|
-
{
|
|
429
|
-
type: 'placeholder',
|
|
430
|
-
value: 'name',
|
|
431
|
-
escape: true
|
|
432
|
-
}
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
---
|
|
436
|
-
|
|
437
|
-
```html
|
|
438
|
-
$!{name}
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
```javascript
|
|
442
|
-
{
|
|
443
|
-
type: 'placeholder',
|
|
444
|
-
value: 'name',
|
|
445
|
-
escape: true
|
|
446
|
-
}
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
**NOTE:**
|
|
450
|
-
The `escape` flag is merely informational. The application code is responsible
|
|
451
|
-
for interpreting this flag to properly escape the expression.
|
|
452
|
-
|
|
453
|
-
Here's an example of modifying the expression based on the `event.escape` flag:
|
|
454
|
-
|
|
455
|
-
```javascript
|
|
456
|
-
onPlaceholder: function(event) {
|
|
457
|
-
if (event.escape) {
|
|
458
|
-
event.value = 'escapeXml(' + event.value + ')';
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
### onDocumentType
|
|
464
|
-
|
|
465
|
-
The `onDocumentType` function will be called when the document type declaration
|
|
466
|
-
is encountered _anywhere_ in the content.
|
|
467
|
-
|
|
468
|
-
**EXAMPLE:**
|
|
385
|
+
// Pass any range object into this method to get the raw string from the source for the range.
|
|
386
|
+
parser.read(range);
|
|
469
387
|
|
|
470
|
-
|
|
388
|
+
// Given an zero based offset within the source code, returns a position object that contains line and column properties.
|
|
389
|
+
parser.positionAt(offset);
|
|
471
390
|
|
|
472
|
-
|
|
473
|
-
|
|
391
|
+
// Given a range object returns a location object with start and end properties which are each position objects as returned from the "positionAt" api.
|
|
392
|
+
parser.locationAt(range);
|
|
474
393
|
```
|
|
475
394
|
|
|
476
|
-
|
|
395
|
+
# Code of Conduct
|
|
477
396
|
|
|
478
|
-
|
|
479
|
-
{
|
|
480
|
-
type: 'documentType',
|
|
481
|
-
value: 'DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0//EN"'
|
|
482
|
-
}
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
### onDeclaration
|
|
486
|
-
|
|
487
|
-
The `onDeclaration` function will be called when an XML declaration
|
|
488
|
-
is encountered _anywhere_ in the content.
|
|
489
|
-
|
|
490
|
-
**EXAMPLE:**
|
|
491
|
-
|
|
492
|
-
INPUT:
|
|
493
|
-
|
|
494
|
-
```html
|
|
495
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
OUTPUT EVENT:
|
|
499
|
-
|
|
500
|
-
```javascript
|
|
501
|
-
{
|
|
502
|
-
type: 'declaration',
|
|
503
|
-
value: 'xml version="1.0" encoding="UTF-8"'
|
|
504
|
-
}
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
### onComment
|
|
508
|
-
|
|
509
|
-
The `onComment` function will be called when text within `<!--` `-->`
|
|
510
|
-
is encountered.
|
|
511
|
-
|
|
512
|
-
**EXAMPLE:**
|
|
513
|
-
|
|
514
|
-
INPUT:
|
|
515
|
-
|
|
516
|
-
```html
|
|
517
|
-
<!--This is a comment-->
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
OUTPUT EVENT:
|
|
521
|
-
|
|
522
|
-
```javascript
|
|
523
|
-
{
|
|
524
|
-
type: 'comment',
|
|
525
|
-
value: 'This is a comment'
|
|
526
|
-
}
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
### onScriptlet
|
|
530
|
-
|
|
531
|
-
The `onScriptlet` function will be called when text within `<%` `%>`
|
|
532
|
-
is encountered.
|
|
533
|
-
|
|
534
|
-
**EXAMPLE:**
|
|
535
|
-
|
|
536
|
-
INPUT:
|
|
537
|
-
|
|
538
|
-
```html
|
|
539
|
-
<% console.log("Hello World!"); %>
|
|
540
|
-
```
|
|
541
|
-
|
|
542
|
-
OUTPUT EVENT:
|
|
543
|
-
|
|
544
|
-
```javascript
|
|
545
|
-
{
|
|
546
|
-
type: 'scriptlet',
|
|
547
|
-
value: ' console.log("Hello World!"); '
|
|
548
|
-
}
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
### onError
|
|
552
|
-
|
|
553
|
-
The `onError` function will be called when malformed content is detected.
|
|
554
|
-
The most common cause for an error is due to reaching the end of the
|
|
555
|
-
input while still parsing an open tag, close tag, XML comment, CDATA section,
|
|
556
|
-
DTD, XML declaration, or placeholder.
|
|
557
|
-
|
|
558
|
-
Possible error codes:
|
|
559
|
-
|
|
560
|
-
- `MISSING_END_TAG`
|
|
561
|
-
- `MISSING_END_DELIMITER`
|
|
562
|
-
- `MALFORMED_OPEN_TAG`
|
|
563
|
-
- `MALFORMED_CLOSE_TAG`
|
|
564
|
-
- `MALFORMED_CDATA`
|
|
565
|
-
- `MALFORMED_PLACEHOLDER`
|
|
566
|
-
- `MALFORMED_DOCUMENT_TYPE`
|
|
567
|
-
- `MALFORMED_DECLARATION`
|
|
568
|
-
- `MALFORMED_COMMENT`
|
|
569
|
-
- `EXTRA_CLOSING_TAG`
|
|
570
|
-
- `MISMATCHED_CLOSING_TAG`
|
|
571
|
-
- ...
|
|
572
|
-
|
|
573
|
-
**EXAMPLE:**
|
|
574
|
-
|
|
575
|
-
INPUT:
|
|
576
|
-
|
|
577
|
-
```html
|
|
578
|
-
<a href="
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
OUTPUT EVENT:
|
|
582
|
-
|
|
583
|
-
```javascript
|
|
584
|
-
{
|
|
585
|
-
type: 'error',
|
|
586
|
-
code: 'MALFORMED_OPEN_TAG',
|
|
587
|
-
message: 'EOF reached while parsing open tag.',
|
|
588
|
-
pos: 0,
|
|
589
|
-
endPos: 9
|
|
590
|
-
}
|
|
591
|
-
```
|
|
397
|
+
This project adheres to the [eBay Code of Conduct](./.github/CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
|