markdown-magic 2.6.1 → 3.0.0
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 +6 -10
- package/cli.js +5 -82
- package/lib/block-parser-js.test.js +179 -0
- package/lib/block-parser.js +389 -0
- package/lib/{utils/new-parser.test.js → block-parser.test.js} +168 -50
- package/lib/cli.js +234 -0
- package/lib/cli.test.js +409 -0
- package/lib/defaults.js +12 -0
- package/lib/index.js +319 -184
- package/lib/index.test.js +11 -0
- package/lib/options-parser.js +498 -0
- package/lib/options-parser.test.js +1237 -0
- package/lib/process-contents.js +330 -0
- package/lib/process-file.js +34 -0
- package/lib/transforms/code.js +67 -22
- package/lib/transforms/file.js +13 -10
- package/lib/transforms/remote.js +9 -6
- package/lib/transforms/toc.js +136 -64
- package/lib/transforms/wordCount.js +5 -0
- package/lib/utils/fs.js +340 -0
- package/lib/utils/fs.test.js +268 -0
- package/lib/utils/html-to-json/compat.js +42 -0
- package/lib/utils/html-to-json/format.js +64 -0
- package/lib/utils/html-to-json/index.js +37 -0
- package/lib/utils/html-to-json/lexer.js +345 -0
- package/lib/utils/html-to-json/parser.js +146 -0
- package/lib/utils/html-to-json/stringify.js +37 -0
- package/lib/utils/html-to-json/tags.js +171 -0
- package/lib/utils/index.js +19 -0
- package/{cli-utils.js → lib/utils/load-config.js} +2 -6
- package/lib/utils/logs.js +89 -0
- package/lib/utils/md/filters.js +20 -0
- package/lib/utils/md/find-code-blocks.js +80 -0
- package/lib/utils/md/find-date.js +32 -0
- package/lib/utils/md/find-frontmatter.js +94 -0
- package/lib/utils/md/find-frontmatter.test.js +17 -0
- package/lib/utils/md/find-html-tags.js +105 -0
- package/lib/utils/md/find-images.js +102 -0
- package/lib/utils/md/find-links.js +202 -0
- package/lib/utils/md/find-unmatched-html-tags.js +33 -0
- package/lib/utils/md/fixtures/2022-01-22-date-in-filename.md +14 -0
- package/lib/utils/md/fixtures/file-with-frontmatter.md +32 -0
- package/lib/utils/md/fixtures/file-with-links.md +143 -0
- package/lib/utils/md/md.test.js +37 -0
- package/lib/utils/md/parse.js +122 -0
- package/lib/utils/md/utils.js +19 -0
- package/lib/utils/regex-timeout.js +83 -0
- package/lib/utils/regex.js +38 -5
- package/lib/utils/remoteRequest.js +54 -0
- package/lib/utils/syntax.js +79 -0
- package/lib/utils/text.js +260 -0
- package/lib/utils/text.test.js +311 -0
- package/package.json +25 -25
- package/index.js +0 -46
- package/lib/processFile.js +0 -154
- package/lib/transforms/index.js +0 -114
- package/lib/updateContents.js +0 -125
- package/lib/utils/_md.test.js +0 -63
- package/lib/utils/new-parser.js +0 -412
- package/lib/utils/weird-parse.js +0 -230
- package/lib/utils/weird-parse.test.js +0 -217
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
const {
|
|
2
|
+
startsWith,
|
|
3
|
+
endsWith,
|
|
4
|
+
stringIncludes,
|
|
5
|
+
arrayIncludes
|
|
6
|
+
} = require('./compat')
|
|
7
|
+
|
|
8
|
+
function feedPosition(position, str, len) {
|
|
9
|
+
const start = position.index
|
|
10
|
+
const end = position.index = start + len
|
|
11
|
+
for (let i = start; i < end; i++) {
|
|
12
|
+
const char = str.charAt(i)
|
|
13
|
+
if (char === '\n') {
|
|
14
|
+
position.line++
|
|
15
|
+
position.column = 0
|
|
16
|
+
} else {
|
|
17
|
+
position.column++
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function jumpPosition (position, str, end) {
|
|
23
|
+
const len = end - position.index
|
|
24
|
+
return feedPosition(position, str, len)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function makeInitialPosition () {
|
|
28
|
+
return {
|
|
29
|
+
index: 0,
|
|
30
|
+
column: 0,
|
|
31
|
+
line: 1
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function copyPositionStart(position) {
|
|
36
|
+
return {
|
|
37
|
+
index: position.index,
|
|
38
|
+
line: position.line,
|
|
39
|
+
column: position.column + 1
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function copyPositionEnd(position) {
|
|
44
|
+
return {
|
|
45
|
+
index: position.index + 1,
|
|
46
|
+
line: position.line,
|
|
47
|
+
column: position.column + 1
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Old offset was index/column was 1 off in some cases
|
|
52
|
+
function copyPosition(position) {
|
|
53
|
+
return {
|
|
54
|
+
index: position.index,
|
|
55
|
+
line: position.line,
|
|
56
|
+
column: position.column
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function lexer (str, options) {
|
|
61
|
+
const state = {
|
|
62
|
+
str,
|
|
63
|
+
options,
|
|
64
|
+
position: makeInitialPosition(),
|
|
65
|
+
tokens: []
|
|
66
|
+
}
|
|
67
|
+
lex(state)
|
|
68
|
+
return state.tokens
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function lex (state) {
|
|
72
|
+
const {str, options: {childlessTags}} = state
|
|
73
|
+
const len = str?.length || 0
|
|
74
|
+
while (state.position.index < len) {
|
|
75
|
+
const start = state.position.index
|
|
76
|
+
lexText(state)
|
|
77
|
+
if (state.position.index === start) {
|
|
78
|
+
const isComment = startsWith(str, '!--', start + 1)
|
|
79
|
+
if (isComment) {
|
|
80
|
+
lexComment(state)
|
|
81
|
+
} else {
|
|
82
|
+
const tagName = lexTag(state)
|
|
83
|
+
const safeTag = tagName.toLowerCase()
|
|
84
|
+
if (arrayIncludes(childlessTags, safeTag)) {
|
|
85
|
+
lexSkipTag(tagName, state)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const alphanumeric = /[A-Za-z0-9]/
|
|
93
|
+
function findTextEnd (str, index) {
|
|
94
|
+
while (true) {
|
|
95
|
+
const textEnd = str.indexOf('<', index)
|
|
96
|
+
if (textEnd === -1) {
|
|
97
|
+
return textEnd
|
|
98
|
+
}
|
|
99
|
+
const char = str.charAt(textEnd + 1)
|
|
100
|
+
if (char === '/' || char === '!' || alphanumeric.test(char)) {
|
|
101
|
+
return textEnd
|
|
102
|
+
}
|
|
103
|
+
index = textEnd + 1
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function lexText (state) {
|
|
108
|
+
const type = 'text'
|
|
109
|
+
const {str, position} = state
|
|
110
|
+
let textEnd = findTextEnd(str, position.index)
|
|
111
|
+
if (textEnd === position.index) return
|
|
112
|
+
if (textEnd === -1) {
|
|
113
|
+
textEnd = str.length
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const start = copyPositionStart(position)
|
|
117
|
+
const content = str.slice(position.index, textEnd)
|
|
118
|
+
jumpPosition(position, str, textEnd)
|
|
119
|
+
const end = copyPositionEnd(position)
|
|
120
|
+
state.tokens.push({type, content, position: {start, end}})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function lexComment (state) {
|
|
124
|
+
const {str, position} = state
|
|
125
|
+
const start = copyPositionStart(position)
|
|
126
|
+
feedPosition(position, str, 4) // "<!--".length
|
|
127
|
+
let contentEnd = str.indexOf('-->', position.index)
|
|
128
|
+
let commentEnd = contentEnd + 3 // "-->".length
|
|
129
|
+
if (contentEnd === -1) {
|
|
130
|
+
contentEnd = commentEnd = str.length
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const content = str.slice(position.index, contentEnd)
|
|
134
|
+
jumpPosition(position, str, commentEnd)
|
|
135
|
+
state.tokens.push({
|
|
136
|
+
type: 'comment',
|
|
137
|
+
content,
|
|
138
|
+
position: {
|
|
139
|
+
start,
|
|
140
|
+
end: copyPositionEnd(position)
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function lexTag (state) {
|
|
146
|
+
const {str, position} = state
|
|
147
|
+
{
|
|
148
|
+
const secondChar = str.charAt(position.index + 1)
|
|
149
|
+
const close = secondChar === '/'
|
|
150
|
+
const start = copyPositionStart(position, 0)
|
|
151
|
+
feedPosition(position, str, close ? 2 : 1)
|
|
152
|
+
state.tokens.push({type: 'tag-start', close, position: {start}})
|
|
153
|
+
}
|
|
154
|
+
const tagName = lexTagName(state)
|
|
155
|
+
lexTagAttributes(state)
|
|
156
|
+
{
|
|
157
|
+
const firstChar = str.charAt(position.index)
|
|
158
|
+
const close = firstChar === '/'
|
|
159
|
+
feedPosition(position, str, close ? 2 : 1)
|
|
160
|
+
const end = copyPositionEnd(position)
|
|
161
|
+
state.tokens.push({type: 'tag-end', close, position: {end}})
|
|
162
|
+
}
|
|
163
|
+
return tagName
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#special-white-space
|
|
167
|
+
const whitespace = /\s/
|
|
168
|
+
function isWhitespaceChar (char) {
|
|
169
|
+
return whitespace.test(char)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function lexTagName (state) {
|
|
173
|
+
const {str, position} = state
|
|
174
|
+
const len = str.length
|
|
175
|
+
let start = position.index
|
|
176
|
+
while (start < len) {
|
|
177
|
+
const char = str.charAt(start)
|
|
178
|
+
const isTagChar = !(isWhitespaceChar(char) || char === '/' || char === '>')
|
|
179
|
+
if (isTagChar) break
|
|
180
|
+
start++
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let end = start + 1
|
|
184
|
+
while (end < len) {
|
|
185
|
+
const char = str.charAt(end)
|
|
186
|
+
const isTagChar = !(isWhitespaceChar(char) || char === '/' || char === '>')
|
|
187
|
+
if (!isTagChar) break
|
|
188
|
+
end++
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
jumpPosition(position, str, end)
|
|
192
|
+
const tagName = str.slice(start, end)
|
|
193
|
+
state.tokens.push({
|
|
194
|
+
type: 'tag',
|
|
195
|
+
content: tagName
|
|
196
|
+
})
|
|
197
|
+
return tagName
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function lexTagAttributes (state) {
|
|
201
|
+
const {str, position, tokens} = state
|
|
202
|
+
let cursor = position.index
|
|
203
|
+
let quote = null // null, single-, or double-quote
|
|
204
|
+
let wordBegin = cursor // index of word start
|
|
205
|
+
const words = [] // "key", "key=value", "key='value'", etc
|
|
206
|
+
const len = str.length
|
|
207
|
+
while (cursor < len) {
|
|
208
|
+
const char = str.charAt(cursor)
|
|
209
|
+
if (quote) {
|
|
210
|
+
const isQuoteEnd = char === quote
|
|
211
|
+
if (isQuoteEnd) {
|
|
212
|
+
quote = null
|
|
213
|
+
}
|
|
214
|
+
cursor++
|
|
215
|
+
continue
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const isTagEnd = char === '/' || char === '>'
|
|
219
|
+
if (isTagEnd) {
|
|
220
|
+
if (cursor !== wordBegin) {
|
|
221
|
+
words.push(str.slice(wordBegin, cursor))
|
|
222
|
+
}
|
|
223
|
+
break
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const isWordEnd = isWhitespaceChar(char)
|
|
227
|
+
if (isWordEnd) {
|
|
228
|
+
if (cursor !== wordBegin) {
|
|
229
|
+
words.push(str.slice(wordBegin, cursor))
|
|
230
|
+
}
|
|
231
|
+
wordBegin = cursor + 1
|
|
232
|
+
cursor++
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const isQuoteStart = char === '\'' || char === '"'
|
|
237
|
+
if (isQuoteStart) {
|
|
238
|
+
quote = char
|
|
239
|
+
cursor++
|
|
240
|
+
continue
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
cursor++
|
|
244
|
+
}
|
|
245
|
+
jumpPosition(position, str, cursor)
|
|
246
|
+
|
|
247
|
+
const wLen = words.length
|
|
248
|
+
const type = 'attribute'
|
|
249
|
+
for (let i = 0; i < wLen; i++) {
|
|
250
|
+
const word = words[i]
|
|
251
|
+
const isNotPair = word.indexOf('=') === -1
|
|
252
|
+
if (isNotPair) {
|
|
253
|
+
const secondWord = words[i + 1]
|
|
254
|
+
if (secondWord && startsWith(secondWord, '=')) {
|
|
255
|
+
if (secondWord.length > 1) {
|
|
256
|
+
const newWord = word + secondWord
|
|
257
|
+
tokens.push({type, content: newWord})
|
|
258
|
+
i += 1
|
|
259
|
+
continue
|
|
260
|
+
}
|
|
261
|
+
const thirdWord = words[i + 2]
|
|
262
|
+
i += 1
|
|
263
|
+
if (thirdWord) {
|
|
264
|
+
const newWord = word + '=' + thirdWord
|
|
265
|
+
tokens.push({type, content: newWord})
|
|
266
|
+
i += 1
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (endsWith(word, '=')) {
|
|
272
|
+
const secondWord = words[i + 1]
|
|
273
|
+
if (secondWord && !stringIncludes(secondWord, '=')) {
|
|
274
|
+
const newWord = word + secondWord
|
|
275
|
+
tokens.push({type, content: newWord})
|
|
276
|
+
i += 1
|
|
277
|
+
continue
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const newWord = word.slice(0, -1)
|
|
281
|
+
tokens.push({type, content: newWord})
|
|
282
|
+
continue
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
tokens.push({type, content: word})
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const push = [].push
|
|
290
|
+
|
|
291
|
+
function lexSkipTag (tagName, state) {
|
|
292
|
+
const {str, position, tokens} = state
|
|
293
|
+
const safeTagName = tagName.toLowerCase()
|
|
294
|
+
const len = str.length
|
|
295
|
+
let index = position.index
|
|
296
|
+
while (index < len) {
|
|
297
|
+
const nextTag = str.indexOf('</', index)
|
|
298
|
+
if (nextTag === -1) {
|
|
299
|
+
lexText(state)
|
|
300
|
+
break
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const tagStartPosition = copyPositionStart(position)
|
|
304
|
+
jumpPosition(tagStartPosition, str, nextTag)
|
|
305
|
+
const tagState = {str, position: tagStartPosition, tokens: []}
|
|
306
|
+
const name = lexTag(tagState)
|
|
307
|
+
if (safeTagName !== name.toLowerCase()) {
|
|
308
|
+
index = tagState.position.index
|
|
309
|
+
continue
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (nextTag !== position.index) {
|
|
313
|
+
const textStart = copyPositionStart(position)
|
|
314
|
+
jumpPosition(position, str, nextTag)
|
|
315
|
+
tokens.push({
|
|
316
|
+
type: 'text',
|
|
317
|
+
content: str.slice(textStart.index, nextTag),
|
|
318
|
+
position: {
|
|
319
|
+
start: textStart,
|
|
320
|
+
end: copyPositionEnd(position)
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
push.apply(tokens, tagState.tokens)
|
|
326
|
+
jumpPosition(position, str, tagState.position.index)
|
|
327
|
+
break
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
module.exports = {
|
|
332
|
+
feedPosition,
|
|
333
|
+
jumpPosition,
|
|
334
|
+
makeInitialPosition,
|
|
335
|
+
copyPosition,
|
|
336
|
+
lexer,
|
|
337
|
+
lex,
|
|
338
|
+
findTextEnd,
|
|
339
|
+
lexText,
|
|
340
|
+
lexTag,
|
|
341
|
+
isWhitespaceChar,
|
|
342
|
+
lexTagName,
|
|
343
|
+
lexTagAttributes,
|
|
344
|
+
lexSkipTag,
|
|
345
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const { optionsParse } = require('../../options-parser')
|
|
2
|
+
const { arrayIncludes }= require('./compat')
|
|
3
|
+
|
|
4
|
+
function parser (tokens, options) {
|
|
5
|
+
const root = {tagName: null, children: []}
|
|
6
|
+
const state = {tokens, options, cursor: 0, stack: [root]}
|
|
7
|
+
parse(state)
|
|
8
|
+
return root.children
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function hasTerminalParent (tagName, stack, terminals) {
|
|
12
|
+
const tagParents = terminals[tagName]
|
|
13
|
+
if (tagParents) {
|
|
14
|
+
let currentIndex = stack.length - 1
|
|
15
|
+
while (currentIndex >= 0) {
|
|
16
|
+
const parentTagName = stack[currentIndex].tagName
|
|
17
|
+
if (parentTagName === tagName) {
|
|
18
|
+
break
|
|
19
|
+
}
|
|
20
|
+
if (arrayIncludes(tagParents, parentTagName)) {
|
|
21
|
+
return true
|
|
22
|
+
}
|
|
23
|
+
currentIndex--
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function rewindStack (stack, newLength, childrenEndPosition, endPosition) {
|
|
30
|
+
stack[newLength].position.end = endPosition
|
|
31
|
+
for (let i = newLength + 1, len = stack.length; i < len; i++) {
|
|
32
|
+
stack[i].position.end = childrenEndPosition
|
|
33
|
+
}
|
|
34
|
+
stack.splice(newLength)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parse (state) {
|
|
38
|
+
const {tokens, options} = state
|
|
39
|
+
let {stack} = state
|
|
40
|
+
let nodes = stack[stack.length - 1].children
|
|
41
|
+
const len = tokens.length
|
|
42
|
+
let {cursor} = state
|
|
43
|
+
while (cursor < len) {
|
|
44
|
+
const token = tokens[cursor]
|
|
45
|
+
if (token.type !== 'tag-start') {
|
|
46
|
+
nodes.push(token)
|
|
47
|
+
cursor++
|
|
48
|
+
continue
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const tagToken = tokens[++cursor]
|
|
52
|
+
cursor++
|
|
53
|
+
const tagName = tagToken.content.toLowerCase()
|
|
54
|
+
if (token.close) {
|
|
55
|
+
let index = stack.length
|
|
56
|
+
let shouldRewind = false
|
|
57
|
+
while (--index > -1) {
|
|
58
|
+
if (stack[index].tagName === tagName) {
|
|
59
|
+
shouldRewind = true
|
|
60
|
+
break
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
while (cursor < len) {
|
|
64
|
+
const endToken = tokens[cursor]
|
|
65
|
+
if (endToken.type !== 'tag-end') break
|
|
66
|
+
cursor++
|
|
67
|
+
}
|
|
68
|
+
if (shouldRewind) {
|
|
69
|
+
rewindStack(stack, index, token.position.start, tokens[cursor - 1].position.end)
|
|
70
|
+
break
|
|
71
|
+
} else {
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const isClosingTag = arrayIncludes(options.closingTags, tagName)
|
|
77
|
+
let shouldRewindToAutoClose = isClosingTag
|
|
78
|
+
if (shouldRewindToAutoClose) {
|
|
79
|
+
const { closingTagAncestorBreakers: terminals } = options
|
|
80
|
+
shouldRewindToAutoClose = !hasTerminalParent(tagName, stack, terminals)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (shouldRewindToAutoClose) {
|
|
84
|
+
// rewind the stack to just above the previous
|
|
85
|
+
// closing tag of the same name
|
|
86
|
+
let currentIndex = stack.length - 1
|
|
87
|
+
while (currentIndex > 0) {
|
|
88
|
+
if (tagName === stack[currentIndex].tagName) {
|
|
89
|
+
rewindStack(stack, currentIndex, token.position.start, token.position.start)
|
|
90
|
+
const previousIndex = currentIndex - 1
|
|
91
|
+
nodes = stack[previousIndex].children
|
|
92
|
+
break
|
|
93
|
+
}
|
|
94
|
+
currentIndex = currentIndex - 1
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// let attributes = []
|
|
99
|
+
let propsRaw = ''
|
|
100
|
+
let attrToken
|
|
101
|
+
while (cursor < len) {
|
|
102
|
+
attrToken = tokens[cursor]
|
|
103
|
+
if (attrToken.type === 'tag-end') break
|
|
104
|
+
// attributes.push(attrToken.content)
|
|
105
|
+
propsRaw+= ((propsRaw !== '') ? ' ' : '') + attrToken.content
|
|
106
|
+
cursor++
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
cursor++
|
|
110
|
+
const children = []
|
|
111
|
+
const position = {
|
|
112
|
+
start: token.position.start,
|
|
113
|
+
end: attrToken.position.end
|
|
114
|
+
}
|
|
115
|
+
const elementNode = {
|
|
116
|
+
type: 'element',
|
|
117
|
+
tagName: tagToken.content,
|
|
118
|
+
// attributes,
|
|
119
|
+
props: (propsRaw) ? optionsParse(propsRaw) : {},
|
|
120
|
+
propsRaw,
|
|
121
|
+
children,
|
|
122
|
+
position
|
|
123
|
+
}
|
|
124
|
+
nodes.push(elementNode)
|
|
125
|
+
|
|
126
|
+
const hasChildren = !(attrToken.close || arrayIncludes(options.voidTags, tagName))
|
|
127
|
+
if (hasChildren) {
|
|
128
|
+
const size = stack.push({tagName, children, position})
|
|
129
|
+
const innerState = {tokens, options, cursor, stack}
|
|
130
|
+
parse(innerState)
|
|
131
|
+
cursor = innerState.cursor
|
|
132
|
+
const rewoundInElement = stack.length === size
|
|
133
|
+
if (rewoundInElement) {
|
|
134
|
+
elementNode.position.end = tokens[cursor - 1].position.end
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
state.cursor = cursor
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = {
|
|
142
|
+
parser,
|
|
143
|
+
hasTerminalParent,
|
|
144
|
+
rewindStack,
|
|
145
|
+
parse,
|
|
146
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const { arrayIncludes }= require('./compat')
|
|
2
|
+
|
|
3
|
+
// Old
|
|
4
|
+
function formatAttributes(attributes) {
|
|
5
|
+
return attributes.reduce((attrs, attribute) => {
|
|
6
|
+
const {key, value} = attribute
|
|
7
|
+
if (value === null) {
|
|
8
|
+
return `${attrs} ${key}`
|
|
9
|
+
}
|
|
10
|
+
const quoteEscape = value.indexOf('\'') !== -1
|
|
11
|
+
const quote = quoteEscape ? '"' : '\''
|
|
12
|
+
return `${attrs} ${key}=${quote}${value}${quote}`
|
|
13
|
+
}, '')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function toHTML(tree, options) {
|
|
17
|
+
return tree.map((node) => {
|
|
18
|
+
if (node.type === 'text') {
|
|
19
|
+
return node.content
|
|
20
|
+
}
|
|
21
|
+
if (node.type === 'comment') {
|
|
22
|
+
return `<!--${node.content}-->`
|
|
23
|
+
}
|
|
24
|
+
const {tagName, propsRaw, children} = node
|
|
25
|
+
// @TODO update prop parsing to keep new lines
|
|
26
|
+
const propsString = (propsRaw) ? ` ${propsRaw}` : ''
|
|
27
|
+
const isSelfClosing = arrayIncludes(options.voidTags, tagName.toLowerCase())
|
|
28
|
+
return isSelfClosing
|
|
29
|
+
? `<${tagName}${propsString}>`
|
|
30
|
+
: `<${tagName}${propsString}>${toHTML(children, options)}</${tagName}>`
|
|
31
|
+
}).join('')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
formatAttributes,
|
|
36
|
+
toHTML
|
|
37
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tags which contain arbitrary non-parsed content
|
|
3
|
+
For example: <script> JavaScript should not be parsed
|
|
4
|
+
*/
|
|
5
|
+
const childlessTags = ['style', 'script', 'template']
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
Tags which auto-close because they cannot be nested
|
|
9
|
+
For example: <p>Outer<p>Inner is <p>Outer</p><p>Inner</p>
|
|
10
|
+
*/
|
|
11
|
+
const closingTags = [
|
|
12
|
+
'html', 'head', 'body', 'p', 'dt', 'dd', 'li', 'option',
|
|
13
|
+
'thead', 'th', 'tbody', 'tr', 'td', 'tfoot', 'colgroup'
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
Closing tags which have ancestor tags which
|
|
18
|
+
may exist within them which prevent the
|
|
19
|
+
closing tag from auto-closing.
|
|
20
|
+
For example: in <li><ul><li></ul></li>,
|
|
21
|
+
the top-level <li> should not auto-close.
|
|
22
|
+
*/
|
|
23
|
+
const closingTagAncestorBreakers = {
|
|
24
|
+
li: ['ul', 'ol', 'menu'],
|
|
25
|
+
dt: ['dl'],
|
|
26
|
+
dd: ['dl'],
|
|
27
|
+
tbody: ['table'],
|
|
28
|
+
thead: ['table'],
|
|
29
|
+
tfoot: ['table'],
|
|
30
|
+
tr: ['table'],
|
|
31
|
+
td: ['table']
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
Tags which do not need the closing tag
|
|
36
|
+
For example: <img> does not need </img>
|
|
37
|
+
*/
|
|
38
|
+
const voidTags = [
|
|
39
|
+
'!doctype', 'area', 'base', 'br', 'col', 'command',
|
|
40
|
+
'embed', 'hr', 'img', 'input', 'keygen', 'link',
|
|
41
|
+
'meta', 'param', 'source', 'track', 'wbr'
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
// https://github.com/sindresorhus/html-tags/blob/main/html-tags.json
|
|
45
|
+
const htmlTags = [
|
|
46
|
+
"a",
|
|
47
|
+
"abbr",
|
|
48
|
+
"address",
|
|
49
|
+
"area",
|
|
50
|
+
"article",
|
|
51
|
+
"aside",
|
|
52
|
+
"audio",
|
|
53
|
+
"b",
|
|
54
|
+
"base",
|
|
55
|
+
"bdi",
|
|
56
|
+
"bdo",
|
|
57
|
+
"blockquote",
|
|
58
|
+
"body",
|
|
59
|
+
"br",
|
|
60
|
+
"button",
|
|
61
|
+
"canvas",
|
|
62
|
+
"caption",
|
|
63
|
+
"cite",
|
|
64
|
+
"code",
|
|
65
|
+
"col",
|
|
66
|
+
"colgroup",
|
|
67
|
+
"data",
|
|
68
|
+
"datalist",
|
|
69
|
+
"dd",
|
|
70
|
+
"del",
|
|
71
|
+
"details",
|
|
72
|
+
"dfn",
|
|
73
|
+
"dialog",
|
|
74
|
+
"div",
|
|
75
|
+
"dl",
|
|
76
|
+
"dt",
|
|
77
|
+
"em",
|
|
78
|
+
"embed",
|
|
79
|
+
"fieldset",
|
|
80
|
+
"figcaption",
|
|
81
|
+
"figure",
|
|
82
|
+
"footer",
|
|
83
|
+
"form",
|
|
84
|
+
"h1",
|
|
85
|
+
"h2",
|
|
86
|
+
"h3",
|
|
87
|
+
"h4",
|
|
88
|
+
"h5",
|
|
89
|
+
"h6",
|
|
90
|
+
"head",
|
|
91
|
+
"header",
|
|
92
|
+
"hgroup",
|
|
93
|
+
"hr",
|
|
94
|
+
"html",
|
|
95
|
+
"i",
|
|
96
|
+
"iframe",
|
|
97
|
+
"img",
|
|
98
|
+
"input",
|
|
99
|
+
"ins",
|
|
100
|
+
"kbd",
|
|
101
|
+
"label",
|
|
102
|
+
"legend",
|
|
103
|
+
"li",
|
|
104
|
+
"link",
|
|
105
|
+
"main",
|
|
106
|
+
"map",
|
|
107
|
+
"mark",
|
|
108
|
+
"math",
|
|
109
|
+
"menu",
|
|
110
|
+
"menuitem",
|
|
111
|
+
"meta",
|
|
112
|
+
"meter",
|
|
113
|
+
"nav",
|
|
114
|
+
"noscript",
|
|
115
|
+
"object",
|
|
116
|
+
"ol",
|
|
117
|
+
"optgroup",
|
|
118
|
+
"option",
|
|
119
|
+
"output",
|
|
120
|
+
"p",
|
|
121
|
+
"param",
|
|
122
|
+
"picture",
|
|
123
|
+
"pre",
|
|
124
|
+
"progress",
|
|
125
|
+
"q",
|
|
126
|
+
"rb",
|
|
127
|
+
"rp",
|
|
128
|
+
"rt",
|
|
129
|
+
"rtc",
|
|
130
|
+
"ruby",
|
|
131
|
+
"s",
|
|
132
|
+
"samp",
|
|
133
|
+
"script",
|
|
134
|
+
"section",
|
|
135
|
+
"select",
|
|
136
|
+
"slot",
|
|
137
|
+
"small",
|
|
138
|
+
"source",
|
|
139
|
+
"span",
|
|
140
|
+
"strong",
|
|
141
|
+
"style",
|
|
142
|
+
"sub",
|
|
143
|
+
"summary",
|
|
144
|
+
"sup",
|
|
145
|
+
"svg",
|
|
146
|
+
"table",
|
|
147
|
+
"tbody",
|
|
148
|
+
"td",
|
|
149
|
+
"template",
|
|
150
|
+
"textarea",
|
|
151
|
+
"tfoot",
|
|
152
|
+
"th",
|
|
153
|
+
"thead",
|
|
154
|
+
"time",
|
|
155
|
+
"title",
|
|
156
|
+
"tr",
|
|
157
|
+
"track",
|
|
158
|
+
"u",
|
|
159
|
+
"ul",
|
|
160
|
+
"var",
|
|
161
|
+
"video",
|
|
162
|
+
"wbr"
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
module.exports = {
|
|
166
|
+
childlessTags,
|
|
167
|
+
closingTags,
|
|
168
|
+
closingTagAncestorBreakers,
|
|
169
|
+
voidTags,
|
|
170
|
+
htmlTags
|
|
171
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
function onlyUnique(value, index, self) {
|
|
3
|
+
return self.indexOf(value) === index
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function getCodeLocation(srcPath, line, column = '0') {
|
|
7
|
+
return `${srcPath}:${line}:${column}`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function pluralize(thing, single = '', plural = '') {
|
|
11
|
+
const count = Array.isArray(thing) ? thing.length : Number(thing)
|
|
12
|
+
return count === 1 ? single : plural
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
pluralize,
|
|
17
|
+
onlyUnique,
|
|
18
|
+
getCodeLocation
|
|
19
|
+
}
|