-
Notifications
You must be signed in to change notification settings - Fork 67
/
Copy pathparse.js
128 lines (111 loc) · 3.4 KB
/
parse.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import parseTag from './parse-tag'
const tagRE = /<[a-zA-Z0-9\-\!\/](?:"[^"]*"|'[^']*'|[^'">])*>/g
const whitespaceRE = /^\s*$/
// re-used obj for quick lookups of components
const empty = Object.create(null)
export default function parse(html, options) {
options || (options = {})
options.components || (options.components = empty)
const result = []
const arr = []
let current
let level = -1
let inComponent = false
// handle text at top level
if (html.indexOf('<') !== 0) {
var end = html.indexOf('<')
result.push({
type: 'text',
content: end === -1 ? html : html.substring(0, end),
})
}
html.replace(tagRE, function (tag, index) {
if (inComponent) {
if (tag !== '</' + current.name + '>') {
return
} else {
inComponent = false
}
}
const isOpen = tag.charAt(1) !== '/'
const isComment = tag.startsWith('<!--')
const start = index + tag.length
const nextChar = html.charAt(start)
let parent
if (isComment) {
const comment = parseTag(tag)
// if we're at root, push new base node
if (level < 0) {
result.push(comment)
return result
}
parent = arr[level]
parent.children.push(comment)
return result
}
if (isOpen) {
level++
current = parseTag(tag)
if (current.type === 'tag' && options.components[current.name]) {
current.type = 'component'
inComponent = true
}
if (
!current.voidElement &&
!inComponent &&
nextChar &&
nextChar !== '<'
) {
current.children.push({
type: 'text',
content: html.slice(start, html.indexOf('<', start)),
})
}
// if we're at root, push new base node
if (level === 0) {
result.push(current)
}
parent = arr[level - 1]
if (parent) {
parent.children.push(current)
}
arr[level] = current
}
if (!isOpen || current.voidElement) {
if (
level > -1 &&
(current.voidElement || current.name === tag.slice(2, -1))
) {
level--
// move current up a level to match the end tag
current = level === -1 ? result : arr[level]
}
if (!inComponent && nextChar !== '<' && nextChar) {
// trailing text node
// if we're at the root, push a base text node. otherwise add as
// a child to the current node.
parent = level === -1 ? result : arr[level].children
// calculate correct end of the content slice in case there's
// no tag after the text node.
const end = html.indexOf('<', start)
let content = html.slice(start, end === -1 ? undefined : end)
// if a node is nothing but whitespace, collapse it as the spec states:
// https://www.w3.org/TR/html4/struct/text.html#h-9.1
if (whitespaceRE.test(content)) {
content = ' '
}
// don't add whitespace-only text nodes if they would be trailing text nodes
// or if they would be leading whitespace-only text nodes:
// * end > -1 indicates this is not a trailing text node
// * leading node is when level is -1 and parent has length 0
if ((end > -1 && level + parent.length >= 0) || content !== ' ') {
parent.push({
type: 'text',
content: content,
})
}
}
}
})
return result
}