hy-app 0.5.11 → 0.5.12

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.
@@ -4,86 +4,98 @@
4
4
 
5
5
  // 配置
6
6
  const config = {
7
- // 信任的标签(保持标签名不变)
8
- trustTags: makeMap('a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,ruby,rt,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video'),
9
-
10
- // 块级标签(转为 div,其他的非信任标签转为 span)
11
- blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,pre,section'),
12
-
13
- // #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3
14
- // 行内标签
15
- inlineTags: makeMap('abbr,b,big,code,del,em,i,ins,label,q,small,span,strong,sub,sup'),
16
- // #endif
17
-
18
- // 要移除的标签
19
- ignoreTags: makeMap('area,base,canvas,embed,frame,head,iframe,input,link,map,meta,param,rp,script,source,style,textarea,title,track,wbr'),
20
-
21
- // 自闭合的标签
22
- voidTags: makeMap('area,base,br,col,circle,ellipse,embed,frame,hr,img,input,line,link,meta,param,path,polygon,rect,source,track,use,wbr'),
23
-
24
- // html 实体
25
- entities: {
26
- lt: '<',
27
- gt: '>',
28
- quot: '"',
29
- apos: "'",
30
- ensp: '\u2002',
31
- emsp: '\u2003',
32
- nbsp: '\xA0',
33
- semi: ';',
34
- ndash: '',
35
- mdash: '',
36
- middot: '·',
37
- lsquo: '‘',
38
- rsquo: '',
39
- ldquo: '',
40
- rdquo: '',
41
- bull: '',
42
- hellip: '',
43
- larr: '',
44
- uarr: '',
45
- rarr: '',
46
- darr: ''
47
- },
48
-
49
- // 默认的标签样式
50
- tagStyle: {
51
- // #ifndef APP-PLUS-NVUE
52
- address: 'font-style:italic',
53
- big: 'display:inline;font-size:1.2em',
54
- caption: 'display:table-caption;text-align:center',
55
- center: 'text-align:center',
56
- cite: 'font-style:italic',
57
- dd: 'margin-left:40px',
58
- mark: 'background-color:yellow',
59
- pre: 'font-family:monospace;white-space:pre',
60
- s: 'text-decoration:line-through',
61
- small: 'display:inline;font-size:0.8em',
62
- strike: 'text-decoration:line-through',
63
- u: 'text-decoration:underline'
7
+ // 信任的标签(保持标签名不变)
8
+ trustTags: makeMap(
9
+ 'a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,ruby,rt,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video'
10
+ ),
11
+
12
+ // 块级标签(转为 div,其他的非信任标签转为 span)
13
+ blockTags: makeMap(
14
+ 'address,article,aside,body,caption,center,cite,footer,header,html,nav,pre,section'
15
+ ),
16
+
17
+ // #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3
18
+ // 行内标签
19
+ inlineTags: makeMap('abbr,b,big,code,del,em,i,ins,label,q,small,span,strong,sub,sup'),
20
+ // #endif
21
+
22
+ // 要移除的标签
23
+ ignoreTags: makeMap(
24
+ 'area,base,canvas,embed,frame,head,iframe,input,link,map,meta,param,rp,script,source,style,textarea,title,track,wbr'
25
+ ),
26
+
27
+ // 自闭合的标签
28
+ voidTags: makeMap(
29
+ 'area,base,br,col,circle,ellipse,embed,frame,hr,img,input,line,link,meta,param,path,polygon,rect,source,track,use,wbr'
30
+ ),
31
+
32
+ // html 实体
33
+ entities: {
34
+ lt: '<',
35
+ gt: '>',
36
+ quot: '"',
37
+ apos: "'",
38
+ ensp: '\u2002',
39
+ emsp: '\u2003',
40
+ nbsp: '\xA0',
41
+ semi: ';',
42
+ ndash: '',
43
+ mdash: '',
44
+ middot: '·',
45
+ lsquo: '',
46
+ rsquo: '',
47
+ ldquo: '“',
48
+ rdquo: '”',
49
+ bull: '•',
50
+ hellip: '…',
51
+ larr: '←',
52
+ uarr: '',
53
+ rarr: '',
54
+ darr: ''
55
+ },
56
+
57
+ // 默认的标签样式
58
+ tagStyle: {
59
+ // #ifndef APP-PLUS-NVUE
60
+ address: 'font-style:italic',
61
+ big: 'display:inline;font-size:1.2em',
62
+ caption: 'display:table-caption;text-align:center',
63
+ center: 'text-align:center',
64
+ cite: 'font-style:italic',
65
+ dd: 'margin-left:40px',
66
+ mark: 'background-color:yellow',
67
+ pre: 'font-family:monospace;white-space:pre',
68
+ s: 'text-decoration:line-through',
69
+ small: 'display:inline;font-size:0.8em',
70
+ strike: 'text-decoration:line-through',
71
+ u: 'text-decoration:underline'
72
+ // #endif
73
+ },
74
+
75
+ // svg 大小写对照表
76
+ svgDict: {
77
+ animatetransform: 'animateTransform',
78
+ lineargradient: 'linearGradient',
79
+ viewbox: 'viewBox',
80
+ attributename: 'attributeName',
81
+ repeatcount: 'repeatCount',
82
+ repeatdur: 'repeatDur',
83
+ foreignobject: 'foreignObject'
84
+ }
85
+ }
86
+ const tagSelector = {}
87
+ let windowWidth, system
88
+ // #ifdef MP-WEIXIN
89
+ if (uni.canIUse('getWindowInfo')) {
90
+ windowWidth = uni.getWindowInfo().windowWidth
91
+ system = uni.getDeviceInfo().system
92
+ } else {
64
93
  // #endif
65
- },
66
-
67
- // svg 大小写对照表
68
- svgDict: {
69
- animatetransform: 'animateTransform',
70
- lineargradient: 'linearGradient',
71
- viewbox: 'viewBox',
72
- attributename: 'attributeName',
73
- repeatcount: 'repeatCount',
74
- repeatdur: 'repeatDur'
75
- }
94
+ const systemInfo = uni.getSystemInfoSync()
95
+ windowWidth = systemInfo.windowWidth
96
+ // #ifdef MP-WEIXIN
97
+ system = systemInfo.system
76
98
  }
77
- const tagSelector={}
78
- // #ifdef APP || H5 || MP-WEIXIN
79
- const { windowWidth } = uni.getWindowInfo()
80
- const { system } = uni.getDeviceInfo()
81
- // #endif
82
- // #ifndef APP || H5 || MP-WEIXIN
83
- const {
84
- windowWidth,
85
- system
86
- } = uni.getSystemInfoSync()
87
99
  // #endif
88
100
  const blankChar = makeMap(' ,\r,\n,\t,\f')
89
101
  let idIndex = 0
@@ -103,13 +115,13 @@ config.ignoreTags.style = undefined
103
115
  * @description 创建 map
104
116
  * @param {String} str 逗号分隔
105
117
  */
106
- function makeMap (str) {
107
- const map = Object.create(null)
108
- const list = str.split(',')
109
- for (let i = list.length; i--;) {
110
- map[list[i]] = true
111
- }
112
- return map
118
+ function makeMap(str) {
119
+ const map = Object.create(null)
120
+ const list = str.split(',')
121
+ for (let i = list.length; i--; ) {
122
+ map[list[i]] = true
123
+ }
124
+ return map
113
125
  }
114
126
 
115
127
  /**
@@ -118,64 +130,74 @@ function makeMap (str) {
118
130
  * @param {Boolean} amp 要不要解码 &amp;
119
131
  * @returns {String} 解码后的字符串
120
132
  */
121
- function decodeEntity (str, amp) {
122
- let i = str.indexOf('&')
123
- while (i !== -1) {
124
- const j = str.indexOf(';', i + 3)
125
- let code
126
- if (j === -1) break
127
- if (str[i + 1] === '#') {
128
- // &#123; 形式的实体
129
- code = parseInt((str[i + 2] === 'x' ? '0' : '') + str.substring(i + 2, j))
130
- if (!isNaN(code)) {
131
- str = str.substr(0, i) + String.fromCharCode(code) + str.substr(j + 1)
132
- }
133
- } else {
134
- // &nbsp; 形式的实体
135
- code = str.substring(i + 1, j)
136
- if (config.entities[code] || (code === 'amp' && amp)) {
137
- str = str.substr(0, i) + (config.entities[code] || '&') + str.substr(j + 1)
138
- }
133
+ function decodeEntity(str, amp) {
134
+ let i = str.indexOf('&')
135
+ while (i !== -1) {
136
+ const j = str.indexOf(';', i + 3)
137
+ let code
138
+ if (j === -1) break
139
+ if (str[i + 1] === '#') {
140
+ // &#123; 形式的实体
141
+ code = parseInt((str[i + 2] === 'x' ? '0' : '') + str.substring(i + 2, j))
142
+ if (!isNaN(code)) {
143
+ str = str.substr(0, i) + String.fromCharCode(code) + str.substr(j + 1)
144
+ }
145
+ } else {
146
+ // &nbsp; 形式的实体
147
+ code = str.substring(i + 1, j)
148
+ if (config.entities[code] || (code === 'amp' && amp)) {
149
+ str = str.substr(0, i) + (config.entities[code] || '&') + str.substr(j + 1)
150
+ }
151
+ }
152
+ i = str.indexOf('&', i + 1)
139
153
  }
140
- i = str.indexOf('&', i + 1)
141
- }
142
- return str
154
+ return str
143
155
  }
144
156
 
145
157
  /**
146
158
  * @description 合并多个块级标签,加快长内容渲染
147
159
  * @param {Array} nodes 要合并的标签数组
148
160
  */
149
- function mergeNodes (nodes) {
150
- let i = nodes.length - 1
151
- for (let j = i; j >= -1; j--) {
152
- if (j === -1 || nodes[j].c || !nodes[j].name || (nodes[j].name !== 'div' && nodes[j].name !== 'p' && nodes[j].name[0] !== 'h') || (nodes[j].attrs.style || '').includes('inline')) {
153
- if (i - j >= 5) {
154
- nodes.splice(j + 1, i - j, {
155
- name: 'div',
156
- attrs: {},
157
- children: nodes.slice(j + 1, i + 1)
158
- })
159
- }
160
- i = j - 1
161
+ function mergeNodes(nodes) {
162
+ let i = nodes.length - 1
163
+ for (let j = i; j >= -1; j--) {
164
+ if (
165
+ j === -1 ||
166
+ nodes[j].c ||
167
+ !nodes[j].name ||
168
+ (nodes[j].name !== 'div' && nodes[j].name !== 'p' && nodes[j].name[0] !== 'h') ||
169
+ (nodes[j].attrs.style || '').includes('inline')
170
+ ) {
171
+ if (i - j >= 5) {
172
+ nodes.splice(j + 1, i - j, {
173
+ name: 'div',
174
+ attrs: {},
175
+ children: nodes.slice(j + 1, i + 1)
176
+ })
177
+ }
178
+ i = j - 1
179
+ }
161
180
  }
162
- }
163
181
  }
164
182
 
165
183
  /**
166
184
  * @description html 解析器
167
185
  * @param {Object} vm 组件实例
168
186
  */
169
- function Parser (vm) {
170
- this.options = vm || {}
171
- this.tagStyle = Object.assign({}, config.tagStyle, this.options.tagStyle)
172
- this.imgList = vm.imgList || []
173
- this.imgList._unloadimgs = 0
174
- this.plugins = vm.plugins || []
175
- this.attrs = Object.create(null)
176
- this.stack = []
177
- this.nodes = []
178
- this.pre = (this.options.containerStyle || '').includes('white-space') && this.options.containerStyle.includes('pre') ? 2 : 0
187
+ function Parser(vm) {
188
+ this.options = vm || {}
189
+ this.tagStyle = Object.assign({}, config.tagStyle, this.options.tagStyle)
190
+ this.imgList = vm.imgList || []
191
+ this.imgList._unloadimgs = 0
192
+ this.plugins = vm.plugins || []
193
+ this.attrs = Object.create(null)
194
+ this.stack = []
195
+ this.nodes = []
196
+ this.pre =
197
+ (this.options.containerStyle || '').includes('white-space') &&
198
+ this.options.containerStyle.includes('pre')
199
+ ? 2
200
+ : 0
179
201
  }
180
202
 
181
203
  /**
@@ -183,35 +205,35 @@ function Parser (vm) {
183
205
  * @param {String} content 要解析的文本
184
206
  */
185
207
  Parser.prototype.parse = function (content) {
186
- // 插件处理
187
- for (let i = this.plugins.length; i--;) {
188
- if (this.plugins[i].onUpdate) {
189
- content = this.plugins[i].onUpdate(content, config) || content
208
+ // 插件处理
209
+ for (let i = this.plugins.length; i--; ) {
210
+ if (this.plugins[i].onUpdate) {
211
+ content = this.plugins[i].onUpdate(content, config) || content
212
+ }
213
+ }
214
+
215
+ new Lexer(this).parse(content)
216
+ // 出栈未闭合的标签
217
+ while (this.stack.length) {
218
+ this.popNode()
219
+ }
220
+ if (this.nodes.length > 50) {
221
+ mergeNodes(this.nodes)
190
222
  }
191
- }
192
-
193
- new Lexer(this).parse(content)
194
- // 出栈未闭合的标签
195
- while (this.stack.length) {
196
- this.popNode()
197
- }
198
- if (this.nodes.length > 50) {
199
- mergeNodes(this.nodes)
200
- }
201
- return this.nodes
223
+ return this.nodes
202
224
  }
203
225
 
204
226
  /**
205
227
  * @description 将标签暴露出来(不被 rich-text 包含)
206
228
  */
207
229
  Parser.prototype.expose = function () {
208
- // #ifndef APP-PLUS-NVUE
209
- for (let i = this.stack.length; i--;) {
210
- const item = this.stack[i]
211
- if (item.c || item.name === 'a' || item.name === 'video' || item.name === 'audio') return
212
- item.c = 1
213
- }
214
- // #endif
230
+ // #ifndef APP-PLUS-NVUE
231
+ for (let i = this.stack.length; i--; ) {
232
+ const item = this.stack[i]
233
+ if (item.c || item.name === 'a' || item.name === 'video' || item.name === 'audio') return
234
+ item.c = 1
235
+ }
236
+ // #endif
215
237
  }
216
238
 
217
239
  /**
@@ -220,12 +242,12 @@ Parser.prototype.expose = function () {
220
242
  * @returns {Boolean} 是否要移除此标签
221
243
  */
222
244
  Parser.prototype.hook = function (node) {
223
- for (let i = this.plugins.length; i--;) {
224
- if (this.plugins[i].onParse && this.plugins[i].onParse(node, this) === false) {
225
- return false
245
+ for (let i = this.plugins.length; i--; ) {
246
+ if (this.plugins[i].onParse && this.plugins[i].onParse(node, this) === false) {
247
+ return false
248
+ }
226
249
  }
227
- }
228
- return true
250
+ return true
229
251
  }
230
252
 
231
253
  /**
@@ -234,25 +256,25 @@ Parser.prototype.hook = function (node) {
234
256
  * @returns {String} 拼接后的链接
235
257
  */
236
258
  Parser.prototype.getUrl = function (url) {
237
- const domain = this.options.domain
238
- if (url[0] === '/') {
239
- if (url[1] === '/') {
240
- // // 开头的补充协议名
241
- url = (domain ? domain.split('://')[0] : 'http') + ':' + url
242
- } else if (domain) {
243
- // 否则补充整个域名
244
- url = domain + url
245
- } /* #ifdef APP-PLUS */ else {
246
- url = plus.io.convertLocalFileSystemURL(url)
247
- } /* #endif */
248
- } else if (!url.includes('data:') && !url.includes('://')) {
249
- if (domain) {
250
- url = domain + '/' + url
251
- } /* #ifdef APP-PLUS */ else {
252
- url = plus.io.convertLocalFileSystemURL(url)
253
- } /* #endif */
254
- }
255
- return url
259
+ const domain = this.options.domain
260
+ if (url[0] === '/') {
261
+ if (url[1] === '/') {
262
+ // // 开头的补充协议名
263
+ url = (domain ? domain.split('://')[0] : 'http') + ':' + url
264
+ } else if (domain) {
265
+ // 否则补充整个域名
266
+ url = domain + url
267
+ } /* #ifdef APP-PLUS */ else {
268
+ url = plus.io.convertLocalFileSystemURL(url)
269
+ } /* #endif */
270
+ } else if (!url.includes('data:') && !url.includes('://')) {
271
+ if (domain) {
272
+ url = domain + '/' + url
273
+ } /* #ifdef APP-PLUS */ else {
274
+ url = plus.io.convertLocalFileSystemURL(url)
275
+ } /* #endif */
276
+ }
277
+ return url
256
278
  }
257
279
 
258
280
  /**
@@ -261,59 +283,71 @@ Parser.prototype.getUrl = function (url) {
261
283
  * @returns {Object}
262
284
  */
263
285
  Parser.prototype.parseStyle = function (node) {
264
- const attrs = node.attrs
265
- const list = (this.tagStyle[node.name] || '').split(';').concat((attrs.style || '').split(';'))
266
- const styleObj = {}
267
- let tmp = ''
268
-
269
- if (attrs.id && !this.xml) {
270
- // 暴露锚点
271
- if (this.options.useAnchor) {
272
- this.expose()
273
- } else if (node.name !== 'img' && node.name !== 'a' && node.name !== 'video' && node.name !== 'audio') {
274
- attrs.id = undefined
286
+ const attrs = node.attrs
287
+ const list = (this.tagStyle[node.name] || '').split(';').concat((attrs.style || '').split(';'))
288
+ const styleObj = {}
289
+ let tmp = ''
290
+
291
+ if (attrs.id && !this.xml) {
292
+ // 暴露锚点
293
+ if (this.options.useAnchor) {
294
+ this.expose()
295
+ } else if (
296
+ node.name !== 'img' &&
297
+ node.name !== 'a' &&
298
+ node.name !== 'video' &&
299
+ node.name !== 'audio'
300
+ ) {
301
+ attrs.id = undefined
302
+ }
303
+ }
304
+
305
+ // 转换 width 和 height 属性
306
+ if (attrs.width) {
307
+ styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px')
308
+ attrs.width = undefined
275
309
  }
276
- }
277
-
278
- // 转换 width 和 height 属性
279
- if (attrs.width) {
280
- styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px')
281
- attrs.width = undefined
282
- }
283
- if (attrs.height) {
284
- styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px')
285
- attrs.height = undefined
286
- }
287
-
288
- for (let i = 0, len = list.length; i < len; i++) {
289
- const info = list[i].split(':')
290
- if (info.length < 2) continue
291
- const key = info.shift().trim().toLowerCase()
292
- let value = info.join(':').trim()
293
- if ((value[0] === '-' && value.lastIndexOf('-') > 0) || value.includes('safe')) {
294
- // 兼容性的 css 不压缩
295
- tmp += `;${key}:${value}`
296
- } else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import')) {
297
- // 重复的样式进行覆盖
298
- if (value.includes('url')) {
299
- // 填充链接
300
- let j = value.indexOf('(') + 1
301
- if (j) {
302
- while (value[j] === '"' || value[j] === "'" || blankChar[value[j]]) {
303
- j++
304
- }
305
- value = value.substr(0, j) + this.getUrl(value.substr(j))
310
+ if (attrs.height) {
311
+ styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px')
312
+ attrs.height = undefined
313
+ }
314
+
315
+ for (let i = 0, len = list.length; i < len; i++) {
316
+ const info = list[i].split(':')
317
+ if (info.length < 2) continue
318
+ const key = info.shift().trim().toLowerCase()
319
+ let value = info.join(':').trim()
320
+ if ((value[0] === '-' && value.lastIndexOf('-') > 0) || value.includes('safe')) {
321
+ // 兼容性的 css 不压缩
322
+ tmp += `;${key}:${value}`
323
+ } else if (
324
+ !styleObj[key] ||
325
+ value.includes('import') ||
326
+ !styleObj[key].includes('import')
327
+ ) {
328
+ // 重复的样式进行覆盖
329
+ if (value.includes('url')) {
330
+ // 填充链接
331
+ let j = value.indexOf('(') + 1
332
+ if (j) {
333
+ while (value[j] === '"' || value[j] === "'" || blankChar[value[j]]) {
334
+ j++
335
+ }
336
+ value = value.substr(0, j) + this.getUrl(value.substr(j))
337
+ }
338
+ } else if (value.includes('rpx')) {
339
+ // 转换 rpx(rich-text 内部不支持 rpx)
340
+ value = value.replace(
341
+ /[0-9.]+\s*rpx/g,
342
+ ($) => (parseFloat($) * windowWidth) / 750 + 'px'
343
+ )
344
+ }
345
+ styleObj[key] = value
306
346
  }
307
- } else if (value.includes('rpx')) {
308
- // 转换 rpx(rich-text 内部不支持 rpx)
309
- value = value.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * windowWidth / 750 + 'px')
310
- }
311
- styleObj[key] = value
312
347
  }
313
- }
314
348
 
315
- node.attrs.style = tmp
316
- return styleObj
349
+ node.attrs.style = tmp
350
+ return styleObj
317
351
  }
318
352
 
319
353
  /**
@@ -322,10 +356,11 @@ Parser.prototype.parseStyle = function (node) {
322
356
  * @private
323
357
  */
324
358
  Parser.prototype.onTagName = function (name) {
325
- this.tagName = this.xml ? name : name.toLowerCase()
326
- if (this.tagName === 'svg') {
327
- this.xml = (this.xml || 0) + 1 // svg 标签内大小写敏感
328
- }
359
+ this.tagName = this.xml ? name : name.toLowerCase()
360
+ if (this.tagName === 'svg') {
361
+ this.xml = (this.xml || 0) + 1 // svg 标签内大小写敏感
362
+ config.ignoreTags.style = undefined // svg 标签内 style 可用
363
+ }
329
364
  }
330
365
 
331
366
  /**
@@ -334,22 +369,28 @@ Parser.prototype.onTagName = function (name) {
334
369
  * @private
335
370
  */
336
371
  Parser.prototype.onAttrName = function (name) {
337
- name = this.xml ? name : name.toLowerCase()
338
- if (name.substr(0, 5) === 'data-') {
339
- if (name === 'data-src' && !this.attrs.src) {
340
- // data-src 自动转为 src
341
- this.attrName = 'src'
342
- } else if (this.tagName === 'img' || this.tagName === 'a') {
343
- // a 和 img 标签保留 data- 的属性,可以在 imgTap 和 linkTap 事件中使用
344
- this.attrName = name
372
+ name = this.xml ? name : name.toLowerCase()
373
+ // #ifdef (VUE3 && (H5 || APP-PLUS)) || APP-PLUS-NVUE
374
+ if (name.includes('?') || name.includes(';')) {
375
+ this.attrName = undefined
376
+ return
377
+ }
378
+ // #endif
379
+ if (name.substr(0, 5) === 'data-') {
380
+ if (name === 'data-src' && !this.attrs.src) {
381
+ // data-src 自动转为 src
382
+ this.attrName = 'src'
383
+ } else if (this.tagName === 'img' || this.tagName === 'a') {
384
+ // a 和 img 标签保留 data- 的属性,可以在 imgtap 和 linktap 事件中使用
385
+ this.attrName = name
386
+ } else {
387
+ // 剩余的移除以减小大小
388
+ this.attrName = undefined
389
+ }
345
390
  } else {
346
- // 剩余的移除以减小大小
347
- this.attrName = undefined
391
+ this.attrName = name
392
+ this.attrs[name] = 'T' // boolean 型属性缺省设置
348
393
  }
349
- } else {
350
- this.attrName = name
351
- this.attrs[name] = 'T' // boolean 型属性缺省设置
352
- }
353
394
  }
354
395
 
355
396
  /**
@@ -358,16 +399,16 @@ Parser.prototype.onAttrName = function (name) {
358
399
  * @private
359
400
  */
360
401
  Parser.prototype.onAttrVal = function (val) {
361
- const name = this.attrName || ''
362
- if (name === 'style' || name === 'href') {
363
- // 部分属性进行实体解码
364
- this.attrs[name] = decodeEntity(val, true)
365
- } else if (name.includes('src')) {
366
- // 拼接主域名
367
- this.attrs[name] = this.getUrl(decodeEntity(val, true))
368
- } else if (name) {
369
- this.attrs[name] = val
370
- }
402
+ const name = this.attrName || ''
403
+ if (name === 'style' || name === 'href') {
404
+ // 部分属性进行实体解码
405
+ this.attrs[name] = decodeEntity(val, true)
406
+ } else if (name.includes('src')) {
407
+ // 拼接主域名
408
+ this.attrs[name] = this.getUrl(decodeEntity(val, true))
409
+ } else if (name) {
410
+ this.attrs[name] = val
411
+ }
371
412
  }
372
413
 
373
414
  /**
@@ -376,212 +417,265 @@ Parser.prototype.onAttrVal = function (val) {
376
417
  * @private
377
418
  */
378
419
  Parser.prototype.onOpenTag = function (selfClose) {
379
- // 拼装 node
380
- const node = Object.create(null)
381
- node.name = this.tagName
382
- node.attrs = this.attrs
383
- // 避免因为自动 diff 使得 type 被设置为 null 导致部分内容不显示
384
- if (this.options.nodes.length) {
385
- node.type = 'node'
386
- }
387
- this.attrs = Object.create(null)
388
-
389
- const attrs = node.attrs
390
- const parent = this.stack[this.stack.length - 1]
391
- const siblings = parent ? parent.children : this.nodes
392
- const close = this.xml ? selfClose : config.voidTags[node.name]
393
-
394
- // 替换标签名选择器
395
- if (tagSelector[node.name]) {
396
- attrs.class = tagSelector[node.name] + (attrs.class ? ' ' + attrs.class : '')
397
- }
398
-
399
- // 转换 embed 标签
400
- if (node.name === 'embed') {
401
- // #ifndef H5 || APP-PLUS
402
- const src = attrs.src || ''
403
- // 按照后缀名和 type 将 embed 转为 video 或 audio
404
- if (src.includes('.mp4') || src.includes('.3gp') || src.includes('.m3u8') || (attrs.type || '').includes('video')) {
405
- node.name = 'video'
406
- } else if (src.includes('.mp3') || src.includes('.wav') || src.includes('.aac') || src.includes('.m4a') || (attrs.type || '').includes('audio')) {
407
- node.name = 'audio'
408
- }
409
- if (attrs.autostart) {
410
- attrs.autoplay = 'T'
411
- }
412
- attrs.controls = 'T'
413
- // #endif
414
- // #ifdef H5 || APP-PLUS
415
- this.expose()
416
- // #endif
417
- }
418
-
419
- // #ifndef APP-PLUS-NVUE
420
- // 处理音视频
421
- if (node.name === 'video' || node.name === 'audio') {
422
- // 设置 id 以便获取 context
423
- if (node.name === 'video' && !attrs.id) {
424
- attrs.id = 'v' + idIndex++
420
+ // 拼装 node
421
+ const node = Object.create(null)
422
+ node.name = this.tagName
423
+ node.attrs = this.attrs
424
+ // 避免因为自动 diff 使得 type 被设置为 null 导致部分内容不显示
425
+ if (this.options.nodes.length) {
426
+ node.type = 'node'
425
427
  }
426
- // 没有设置 controls 也没有设置 autoplay 的自动设置 controls
427
- if (!attrs.controls && !attrs.autoplay) {
428
- attrs.controls = 'T'
429
- }
430
- // 用数组存储所有可用的 source
431
- node.src = []
432
- if (attrs.src) {
433
- node.src.push(attrs.src)
434
- attrs.src = undefined
435
- }
436
- this.expose()
437
- }
438
- // #endif
428
+ this.attrs = Object.create(null)
439
429
 
440
- // 处理自闭合标签
441
- if (close) {
442
- if (!this.hook(node) || config.ignoreTags[node.name]) {
443
- // 通过 base 标签设置主域名
444
- if (node.name === 'base' && !this.options.domain) {
445
- this.options.domain = attrs.href
446
- } /* #ifndef APP-PLUS-NVUE */ else if (node.name === 'source' && parent && (parent.name === 'video' || parent.name === 'audio') && attrs.src) {
447
- // 设置 source 标签(仅父节点为 video audio 时有效)
448
- parent.src.push(attrs.src)
449
- } /* #endif */
450
- return
430
+ const attrs = node.attrs
431
+ const parent = this.stack[this.stack.length - 1]
432
+ const siblings = parent ? parent.children : this.nodes
433
+ const close = this.xml ? selfClose : config.voidTags[node.name]
434
+
435
+ // 替换标签名选择器
436
+ if (tagSelector[node.name]) {
437
+ attrs.class = tagSelector[node.name] + (attrs.class ? ' ' + attrs.class : '')
451
438
  }
452
439
 
453
- // 解析 style
454
- const styleObj = this.parseStyle(node)
440
+ // 转换 embed 标签
441
+ if (node.name === 'embed') {
442
+ // #ifndef H5 || APP-PLUS
443
+ const src = attrs.src || ''
444
+ // 按照后缀名和 type 将 embed 转为 video 或 audio
445
+ if (
446
+ src.includes('.mp4') ||
447
+ src.includes('.3gp') ||
448
+ src.includes('.m3u8') ||
449
+ (attrs.type || '').includes('video')
450
+ ) {
451
+ node.name = 'video'
452
+ } else if (
453
+ src.includes('.mp3') ||
454
+ src.includes('.wav') ||
455
+ src.includes('.aac') ||
456
+ src.includes('.m4a') ||
457
+ (attrs.type || '').includes('audio')
458
+ ) {
459
+ node.name = 'audio'
460
+ }
461
+ if (attrs.autostart) {
462
+ attrs.autoplay = 'T'
463
+ }
464
+ attrs.controls = 'T'
465
+ // #endif
466
+ // #ifdef H5 || APP-PLUS
467
+ this.expose()
468
+ // #endif
469
+ }
455
470
 
456
- // 处理图片
457
- if (node.name === 'img') {
458
- if (attrs.src) {
459
- // 标记 webp
460
- if (attrs.src.includes('webp')) {
461
- node.webp = 'T'
471
+ // #ifndef APP-PLUS-NVUE
472
+ // 处理音视频
473
+ if (node.name === 'video' || node.name === 'audio') {
474
+ // 设置 id 以便获取 context
475
+ if (node.name === 'video' && !attrs.id) {
476
+ attrs.id = 'v' + idIndex++
462
477
  }
463
- // data url 图片如果没有设置 original-src 默认为不可预览的小图片
464
- if (attrs.src.includes('data:') && !attrs['original-src']) {
465
- attrs.ignore = 'T'
478
+ // 没有设置 controls 也没有设置 autoplay 的自动设置 controls
479
+ if (!attrs.controls && !attrs.autoplay) {
480
+ attrs.controls = 'T'
481
+ }
482
+ // 用数组存储所有可用的 source
483
+ node.src = []
484
+ if (attrs.src) {
485
+ node.src.push(attrs.src)
486
+ attrs.src = undefined
487
+ }
488
+ this.expose()
489
+ }
490
+ // #endif
491
+
492
+ // 处理自闭合标签
493
+ if (close) {
494
+ if (!this.hook(node) || config.ignoreTags[node.name]) {
495
+ // 通过 base 标签设置主域名
496
+ if (node.name === 'base' && !this.options.domain) {
497
+ this.options.domain = attrs.href
498
+ } /* #ifndef APP-PLUS-NVUE */ else if (
499
+ node.name === 'source' &&
500
+ parent &&
501
+ (parent.name === 'video' || parent.name === 'audio') &&
502
+ attrs.src
503
+ ) {
504
+ // 设置 source 标签(仅父节点为 video 或 audio 时有效)
505
+ parent.src.push(attrs.src)
506
+ } /* #endif */
507
+ return
466
508
  }
467
- if (!attrs.ignore || node.webp || attrs.src.includes('cloud://')) {
468
- for (let i = this.stack.length; i--;) {
469
- const item = this.stack[i]
470
- if (item.name === 'a') {
471
- node.a = item.attrs
509
+
510
+ // 解析 style
511
+ const styleObj = this.parseStyle(node)
512
+
513
+ // 处理图片
514
+ if (node.name === 'img') {
515
+ if (attrs.src) {
516
+ // 标记 webp
517
+ if (attrs.src.includes('webp')) {
518
+ node.webp = 'T'
519
+ }
520
+ // data url 图片如果没有设置 original-src 默认为不可预览的小图片
521
+ if (
522
+ attrs.src.includes('data:') &&
523
+ this.options.previewImg !== 'all' &&
524
+ !attrs['original-src']
525
+ ) {
526
+ attrs.ignore = 'T'
527
+ }
528
+ if (!attrs.ignore || node.webp || attrs.src.includes('cloud://')) {
529
+ for (let i = this.stack.length; i--; ) {
530
+ const item = this.stack[i]
531
+ if (item.name === 'a') {
532
+ node.a = item.attrs
533
+ }
534
+ if (
535
+ item.name === 'table' &&
536
+ !node.webp &&
537
+ !attrs.src.includes('cloud://')
538
+ ) {
539
+ if (!styleObj.display || styleObj.display.includes('inline')) {
540
+ node.t = 'inline-block'
541
+ } else {
542
+ node.t = styleObj.display
543
+ }
544
+ styleObj.display = undefined
545
+ }
546
+ // #ifndef H5 || APP-PLUS
547
+ const style = item.attrs.style || ''
548
+ if (
549
+ style.includes('flex:') &&
550
+ !style.includes('flex:0') &&
551
+ !style.includes('flex: 0') &&
552
+ (!styleObj.width || parseInt(styleObj.width) > 100)
553
+ ) {
554
+ styleObj.width = '100% !important'
555
+ styleObj.height = ''
556
+ for (let j = i + 1; j < this.stack.length; j++) {
557
+ this.stack[j].attrs.style = (
558
+ this.stack[j].attrs.style || ''
559
+ ).replace('inline-', '')
560
+ }
561
+ } else if (style.includes('flex') && styleObj.width === '100%') {
562
+ for (let j = i + 1; j < this.stack.length; j++) {
563
+ const style = this.stack[j].attrs.style || ''
564
+ if (
565
+ !style.includes(';width') &&
566
+ !style.includes(' width') &&
567
+ style.indexOf('width') !== 0
568
+ ) {
569
+ styleObj.width = ''
570
+ break
571
+ }
572
+ }
573
+ } else if (style.includes('inline-block')) {
574
+ if (
575
+ styleObj.width &&
576
+ styleObj.width[styleObj.width.length - 1] === '%'
577
+ ) {
578
+ item.attrs.style += ';max-width:' + styleObj.width
579
+ styleObj.width = ''
580
+ } else {
581
+ item.attrs.style += ';max-width:100%'
582
+ }
583
+ }
584
+ // #endif
585
+ item.c = 1
586
+ }
587
+ attrs.i = this.imgList.length.toString()
588
+ let src = attrs['original-src'] || attrs.src
589
+ // #ifndef H5 || MP-ALIPAY || APP-PLUS || MP-360
590
+ if (this.imgList.includes(src)) {
591
+ // 如果有重复的链接则对域名进行随机大小写变换避免预览时错位
592
+ let i = src.indexOf('://')
593
+ if (i !== -1) {
594
+ i += 3
595
+ let newSrc = src.substr(0, i)
596
+ for (; i < src.length; i++) {
597
+ if (src[i] === '/') break
598
+ newSrc += Math.random() > 0.5 ? src[i].toUpperCase() : src[i]
599
+ }
600
+ newSrc += src.substr(i)
601
+ src = newSrc
602
+ }
603
+ }
604
+ // #endif
605
+ this.imgList.push(src)
606
+ if (!node.t) {
607
+ this.imgList._unloadimgs += 1
608
+ }
609
+ // #ifdef H5 || APP-PLUS
610
+ if (this.options.lazyLoad) {
611
+ attrs['data-src'] = attrs.src
612
+ attrs.src = undefined
613
+ }
614
+ // #endif
615
+ }
472
616
  }
473
- if (item.name === 'table' && !node.webp && !attrs.src.includes('cloud://')) {
474
- if (!styleObj.display || styleObj.display.includes('inline')) {
475
- node.t = 'inline-block'
476
- } else {
477
- node.t = styleObj.display
478
- }
479
- styleObj.display = undefined
617
+ if (styleObj.display === 'inline') {
618
+ styleObj.display = ''
480
619
  }
481
- // #ifndef H5 || APP-PLUS
482
- const style = item.attrs.style || ''
483
- if (style.includes('flex:') && !style.includes('flex:0') && !style.includes('flex: 0') && (!styleObj.width || parseInt(styleObj.width) > 100)) {
484
- styleObj.width = '100% !important'
485
- styleObj.height = ''
486
- for (let j = i + 1; j < this.stack.length; j++) {
487
- this.stack[j].attrs.style = (this.stack[j].attrs.style || '').replace('inline-', '')
488
- }
489
- } else if (style.includes('flex') && styleObj.width === '100%') {
490
- for (let j = i + 1; j < this.stack.length; j++) {
491
- const style = this.stack[j].attrs.style || ''
492
- if (!style.includes(';width') && !style.includes(' width') && style.indexOf('width') !== 0) {
493
- styleObj.width = ''
494
- break
495
- }
496
- }
497
- } else if (style.includes('inline-block')) {
498
- if (styleObj.width && styleObj.width[styleObj.width.length - 1] === '%') {
499
- item.attrs.style += ';max-width:' + styleObj.width
500
- styleObj.width = ''
501
- } else {
502
- item.attrs.style += ';max-width:100%'
503
- }
620
+ // #ifndef APP-PLUS-NVUE
621
+ if (attrs.ignore) {
622
+ styleObj['max-width'] = styleObj['max-width'] || '100%'
623
+ attrs.style += ';-webkit-touch-callout:none'
504
624
  }
505
625
  // #endif
506
- item.c = 1
507
- }
508
- attrs.i = this.imgList.length.toString()
509
- let src = attrs['original-src'] || attrs.src
510
- // #ifndef H5 || MP-ALIPAY || APP-PLUS || MP-360
511
- if (this.imgList.includes(src)) {
512
- // 如果有重复的链接则对域名进行随机大小写变换避免预览时错位
513
- let i = src.indexOf('://')
514
- if (i !== -1) {
515
- i += 3
516
- let newSrc = src.substr(0, i)
517
- for (; i < src.length; i++) {
518
- if (src[i] === '/') break
519
- newSrc += Math.random() > 0.5 ? src[i].toUpperCase() : src[i]
520
- }
521
- newSrc += src.substr(i)
522
- src = newSrc
626
+ // 设置的宽度超出屏幕,为避免变形,高度转为自动
627
+ if (parseInt(styleObj.width) > windowWidth) {
628
+ styleObj.height = undefined
523
629
  }
524
- }
525
- // #endif
526
- this.imgList.push(src)
527
- if (!node.t) {
528
- this.imgList._unloadimgs += 1
529
- }
530
- // #ifdef H5 || APP-PLUS
531
- if (this.options.lazyLoad) {
532
- attrs['data-src'] = attrs.src
533
- attrs.src = undefined
534
- }
535
- // #endif
630
+ // 记录是否设置了宽高
631
+ if (!isNaN(parseInt(styleObj.width))) {
632
+ node.w = 'T'
633
+ }
634
+ if (
635
+ !isNaN(parseInt(styleObj.height)) &&
636
+ (!styleObj.height.includes('%') ||
637
+ (parent && (parent.attrs.style || '').includes('height')))
638
+ ) {
639
+ node.h = 'T'
640
+ }
641
+ if (node.w && node.h && styleObj['object-fit']) {
642
+ if (styleObj['object-fit'] === 'contain') {
643
+ node.m = 'aspectFit'
644
+ } else if (styleObj['object-fit'] === 'cover') {
645
+ node.m = 'aspectFill'
646
+ }
647
+ }
648
+ } else if (node.name === 'svg') {
649
+ siblings.push(node)
650
+ this.stack.push(node)
651
+ this.popNode()
652
+ return
536
653
  }
537
- }
538
- if (styleObj.display === 'inline') {
539
- styleObj.display = ''
540
- }
541
- // #ifndef APP-PLUS-NVUE
542
- if (attrs.ignore) {
543
- styleObj['max-width'] = styleObj['max-width'] || '100%'
544
- attrs.style += ';-webkit-touch-callout:none'
545
- }
546
- // #endif
547
- // 设置的宽度超出屏幕,为避免变形,高度转为自动
548
- if (parseInt(styleObj.width) > windowWidth) {
549
- styleObj.height = undefined
550
- }
551
- // 记录是否设置了宽高
552
- if (!isNaN(parseInt(styleObj.width))) {
553
- node.w = 'T'
554
- }
555
- if (!isNaN(parseInt(styleObj.height)) && (!styleObj.height.includes('%') || (parent && (parent.attrs.style || '').includes('height')))) {
556
- node.h = 'T'
557
- }
558
- } else if (node.name === 'svg') {
559
- siblings.push(node)
560
- this.stack.push(node)
561
- this.popNode()
562
- return
563
- }
564
- for (const key in styleObj) {
565
- if (styleObj[key]) {
566
- attrs.style += `;${key}:${styleObj[key].replace(' !important', '')}`
567
- }
568
- }
569
- attrs.style = attrs.style.substr(1) || undefined
570
- // #ifdef (MP-WEIXIN || MP-QQ) && VUE3
571
- if (!attrs.style) {
572
- delete attrs.style
573
- }
574
- // #endif
575
- } else {
576
- if ((node.name === 'pre' || ((attrs.style || '').includes('white-space') && attrs.style.includes('pre'))) && this.pre !== 2) {
577
- this.pre = node.pre = 1
654
+ for (const key in styleObj) {
655
+ if (styleObj[key]) {
656
+ attrs.style += `;${key}:${styleObj[key].replace(' !important', '')}`
657
+ }
658
+ }
659
+ attrs.style = attrs.style.substr(1) || undefined
660
+ // #ifdef (MP-WEIXIN || MP-QQ) && VUE3
661
+ if (!attrs.style) {
662
+ delete attrs.style
663
+ }
664
+ // #endif
665
+ } else {
666
+ if (
667
+ (node.name === 'pre' ||
668
+ ((attrs.style || '').includes('white-space') && attrs.style.includes('pre'))) &&
669
+ this.pre !== 2
670
+ ) {
671
+ this.pre = node.pre = 1
672
+ }
673
+ node.children = []
674
+ this.stack.push(node)
578
675
  }
579
- node.children = []
580
- this.stack.push(node)
581
- }
582
676
 
583
- // 加入节点树
584
- siblings.push(node)
677
+ // 加入节点树
678
+ siblings.push(node)
585
679
  }
586
680
 
587
681
  /**
@@ -590,26 +684,26 @@ Parser.prototype.onOpenTag = function (selfClose) {
590
684
  * @private
591
685
  */
592
686
  Parser.prototype.onCloseTag = function (name) {
593
- // 依次出栈到匹配为止
594
- name = this.xml ? name : name.toLowerCase()
595
- let i
596
- for (i = this.stack.length; i--;) {
597
- if (this.stack[i].name === name) break
598
- }
599
- if (i !== -1) {
600
- while (this.stack.length > i) {
601
- this.popNode()
687
+ // 依次出栈到匹配为止
688
+ name = this.xml ? name : name.toLowerCase()
689
+ let i
690
+ for (i = this.stack.length; i--; ) {
691
+ if (this.stack[i].name === name) break
692
+ }
693
+ if (i !== -1) {
694
+ while (this.stack.length > i) {
695
+ this.popNode()
696
+ }
697
+ } else if (name === 'p' || name === 'br') {
698
+ const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes
699
+ siblings.push({
700
+ name,
701
+ attrs: {
702
+ class: tagSelector[name] || '',
703
+ style: this.tagStyle[name] || ''
704
+ }
705
+ })
602
706
  }
603
- } else if (name === 'p' || name === 'br') {
604
- const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes
605
- siblings.push({
606
- name,
607
- attrs: {
608
- class: tagSelector[name] || '',
609
- style: this.tagStyle[name] || ''
610
- }
611
- })
612
- }
613
707
  }
614
708
 
615
709
  /**
@@ -617,469 +711,564 @@ Parser.prototype.onCloseTag = function (name) {
617
711
  * @private
618
712
  */
619
713
  Parser.prototype.popNode = function () {
620
- const node = this.stack.pop()
621
- let attrs = node.attrs
622
- const children = node.children
623
- const parent = this.stack[this.stack.length - 1]
624
- const siblings = parent ? parent.children : this.nodes
625
-
626
- if (!this.hook(node) || config.ignoreTags[node.name]) {
627
- // 获取标题
628
- if (node.name === 'title' && children.length && children[0].type === 'text' && this.options.setTitle) {
629
- uni.setNavigationBarTitle({
630
- title: children[0].text
631
- })
714
+ const node = this.stack.pop()
715
+ let attrs = node.attrs
716
+ const children = node.children
717
+ const parent = this.stack[this.stack.length - 1]
718
+ const siblings = parent ? parent.children : this.nodes
719
+
720
+ if (!this.hook(node) || config.ignoreTags[node.name]) {
721
+ // 获取标题
722
+ if (
723
+ node.name === 'title' &&
724
+ children.length &&
725
+ children[0].type === 'text' &&
726
+ this.options.setTitle
727
+ ) {
728
+ uni.setNavigationBarTitle({
729
+ title: children[0].text
730
+ })
731
+ }
732
+ siblings.pop()
733
+ return
632
734
  }
633
- siblings.pop()
634
- return
635
- }
636
-
637
- if (node.pre && this.pre !== 2) {
638
- // 是否合并空白符标识
639
- this.pre = node.pre = undefined
640
- for (let i = this.stack.length; i--;) {
641
- if (this.stack[i].pre) {
642
- this.pre = 1
643
- }
735
+
736
+ if (node.pre && this.pre !== 2) {
737
+ // 是否合并空白符标识
738
+ this.pre = node.pre = undefined
739
+ for (let i = this.stack.length; i--; ) {
740
+ if (this.stack[i].pre) {
741
+ this.pre = 1
742
+ }
743
+ }
644
744
  }
645
- }
646
745
 
647
- const styleObj = {}
746
+ const styleObj = {}
648
747
 
649
- // 转换 svg
650
- if (node.name === 'svg') {
651
- if (this.xml > 1) {
652
- // 多层 svg 嵌套
653
- this.xml--
654
- return
655
- }
656
- // #ifdef APP-PLUS-NVUE
657
- (function traversal (node) {
658
- if (node.name) {
659
- // 调整 svg 的大小写
660
- node.name = config.svgDict[node.name] || node.name
661
- for (const item in node.attrs) {
662
- if (config.svgDict[item]) {
663
- node.attrs[config.svgDict[item]] = node.attrs[item]
664
- node.attrs[item] = undefined
665
- }
748
+ // 转换 svg
749
+ if (node.name === 'svg') {
750
+ if (this.xml > 1) {
751
+ // 多层 svg 嵌套
752
+ this.xml--
753
+ return
666
754
  }
667
- for (let i = 0; i < (node.children || []).length; i++) {
668
- traversal(node.children[i])
755
+ // #ifdef APP-PLUS-NVUE
756
+ ;(function traversal(node) {
757
+ if (node.name) {
758
+ // 调整 svg 的大小写
759
+ node.name = config.svgDict[node.name] || node.name
760
+ for (const item in node.attrs) {
761
+ if (config.svgDict[item]) {
762
+ node.attrs[config.svgDict[item]] = node.attrs[item]
763
+ node.attrs[item] = undefined
764
+ }
765
+ }
766
+ for (let i = 0; i < (node.children || []).length; i++) {
767
+ traversal(node.children[i])
768
+ }
769
+ }
770
+ })(node)
771
+ // #endif
772
+ // #ifndef APP-PLUS-NVUE
773
+ let src = ''
774
+ const style = attrs.style
775
+ attrs.style = ''
776
+ attrs.xmlns = 'http://www.w3.org/2000/svg'
777
+ ;(function traversal(node) {
778
+ if (node.type === 'text') {
779
+ src += node.text
780
+ return
781
+ }
782
+ const name = config.svgDict[node.name] || node.name
783
+ if (name === 'foreignObject') {
784
+ for (const child of node.children || []) {
785
+ if (child.attrs && !child.attrs.xmlns) {
786
+ child.attrs.xmlns = 'http://www.w3.org/1999/xhtml'
787
+ break
788
+ }
789
+ }
790
+ }
791
+ src += '<' + name
792
+ for (const item in node.attrs) {
793
+ const val = node.attrs[item]
794
+ if (val) {
795
+ src += ` ${config.svgDict[item] || item}="${val.replace(/"/g, '')}"`
796
+ }
797
+ }
798
+ if (!node.children) {
799
+ src += '/>'
800
+ } else {
801
+ src += '>'
802
+ for (let i = 0; i < node.children.length; i++) {
803
+ traversal(node.children[i])
804
+ }
805
+ src += '</' + name + '>'
806
+ }
807
+ })(node)
808
+ node.name = 'img'
809
+ node.attrs = {
810
+ src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'),
811
+ style,
812
+ ignore: 'T'
669
813
  }
670
- }
671
- })(node)
672
- // #endif
673
- // #ifndef APP-PLUS-NVUE
674
- let src = ''
675
- const style = attrs.style
676
- attrs.style = ''
677
- attrs.xmlns = 'http://www.w3.org/2000/svg';
678
- (function traversal (node) {
679
- if (node.type === 'text') {
680
- src += node.text
814
+ node.children = undefined
815
+ // #endif
816
+ this.xml = false
817
+ config.ignoreTags.style = true
681
818
  return
682
- }
683
- const name = config.svgDict[node.name] || node.name
684
- src += '<' + name
685
- for (const item in node.attrs) {
686
- const val = node.attrs[item]
687
- if (val) {
688
- src += ` ${config.svgDict[item] || item}="${val}"`
689
- }
690
- }
691
- if (!node.children) {
692
- src += '/>'
693
- } else {
694
- src += '>'
695
- for (let i = 0; i < node.children.length; i++) {
696
- traversal(node.children[i])
697
- }
698
- src += '</' + name + '>'
699
- }
700
- })(node)
701
- node.name = 'img'
702
- node.attrs = {
703
- src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'),
704
- style,
705
- ignore: 'T'
706
819
  }
707
- node.children = undefined
708
- // #endif
709
- this.xml = false
710
- return
711
- }
712
-
713
- // #ifndef APP-PLUS-NVUE
714
- // 转换 align 属性
715
- if (attrs.align) {
716
- if (node.name === 'table') {
717
- if (attrs.align === 'center') {
718
- styleObj['margin-inline-start'] = styleObj['margin-inline-end'] = 'auto'
719
- } else {
720
- styleObj.float = attrs.align
721
- }
722
- } else {
723
- styleObj['text-align'] = attrs.align
724
- }
725
- attrs.align = undefined
726
- }
727
-
728
- // 转换 dir 属性
729
- if (attrs.dir) {
730
- styleObj.direction = attrs.dir
731
- attrs.dir = undefined
732
- }
733
-
734
- // 转换 font 标签的属性
735
- if (node.name === 'font') {
736
- if (attrs.color) {
737
- styleObj.color = attrs.color
738
- attrs.color = undefined
820
+
821
+ // #ifndef APP-PLUS-NVUE
822
+ // 转换 align 属性
823
+ if (attrs.align) {
824
+ if (node.name === 'table') {
825
+ if (attrs.align === 'center') {
826
+ styleObj['margin-inline-start'] = styleObj['margin-inline-end'] = 'auto'
827
+ } else {
828
+ styleObj.float = attrs.align
829
+ }
830
+ } else {
831
+ styleObj['text-align'] = attrs.align
832
+ }
833
+ attrs.align = undefined
739
834
  }
740
- if (attrs.face) {
741
- styleObj['font-family'] = attrs.face
742
- attrs.face = undefined
835
+
836
+ // 转换 dir 属性
837
+ if (attrs.dir) {
838
+ styleObj.direction = attrs.dir
839
+ attrs.dir = undefined
743
840
  }
744
- if (attrs.size) {
745
- let size = parseInt(attrs.size)
746
- if (!isNaN(size)) {
747
- if (size < 1) {
748
- size = 1
749
- } else if (size > 7) {
750
- size = 7
841
+
842
+ // 转换 font 标签的属性
843
+ if (node.name === 'font') {
844
+ if (attrs.color) {
845
+ styleObj.color = attrs.color
846
+ attrs.color = undefined
847
+ }
848
+ if (attrs.face) {
849
+ styleObj['font-family'] = attrs.face
850
+ attrs.face = undefined
851
+ }
852
+ if (attrs.size) {
853
+ let size = parseInt(attrs.size)
854
+ if (!isNaN(size)) {
855
+ if (size < 1) {
856
+ size = 1
857
+ } else if (size > 7) {
858
+ size = 7
859
+ }
860
+ styleObj['font-size'] = [
861
+ 'x-small',
862
+ 'small',
863
+ 'medium',
864
+ 'large',
865
+ 'x-large',
866
+ 'xx-large',
867
+ 'xxx-large'
868
+ ][size - 1]
869
+ }
870
+ attrs.size = undefined
751
871
  }
752
- styleObj['font-size'] = ['x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', 'xxx-large'][size - 1]
753
- }
754
- attrs.size = undefined
755
872
  }
756
- }
757
- // #endif
758
-
759
- // 一些编辑器的自带 class
760
- if ((attrs.class || '').includes('align-center')) {
761
- styleObj['text-align'] = 'center'
762
- }
763
-
764
- Object.assign(styleObj, this.parseStyle(node))
765
-
766
- if (node.name !== 'table' && parseInt(styleObj.width) > windowWidth) {
767
- styleObj['max-width'] = '100%'
768
- styleObj['box-sizing'] = 'border-box'
769
- }
770
-
771
- // #ifndef APP-PLUS-NVUE
772
- if (config.blockTags[node.name]) {
773
- node.name = 'div'
774
- } else if (!config.trustTags[node.name] && !this.xml) {
775
- // 未知标签转为 span,避免无法显示
776
- node.name = 'span'
777
- }
778
-
779
- if (node.name === 'a' || node.name === 'ad'
780
- // #ifdef H5 || APP-PLUS
781
- || node.name === 'iframe' // eslint-disable-line
782
873
  // #endif
783
- ) {
784
- this.expose()
785
- } else if (node.name === 'video') {
786
- if ((styleObj.height || '').includes('auto')) {
787
- styleObj.height = undefined
788
- }
789
- /* #ifdef APP-PLUS */
790
- let str = '<video style="width:100%;height:100%"'
791
- for (const item in attrs) {
792
- if (attrs[item]) {
793
- str += ' ' + item + '="' + attrs[item] + '"'
794
- }
795
- }
796
- if (this.options.pauseVideo) {
797
- str += ' onplay="this.dispatchEvent(new CustomEvent(\'vplay\',{bubbles:!0}));for(var e=document.getElementsByTagName(\'video\'),t=0;t<e.length;t++)e[t]!=this&&e[t].pause()"'
798
- }
799
- str += '>'
800
- for (let i = 0; i < node.src.length; i++) {
801
- str += '<source src="' + node.src[i] + '">'
802
- }
803
- str += '</video>'
804
- node.html = str
805
- /* #endif */
806
- } else if ((node.name === 'ul' || node.name === 'ol') && node.c) {
807
- // 列表处理
808
- const types = {
809
- a: 'lower-alpha',
810
- A: 'upper-alpha',
811
- i: 'lower-roman',
812
- I: 'upper-roman'
813
- }
814
- if (types[attrs.type]) {
815
- attrs.style += ';list-style-type:' + types[attrs.type]
816
- attrs.type = undefined
817
- }
818
- for (let i = children.length; i--;) {
819
- if (children[i].name === 'li') {
820
- children[i].c = 1
821
- }
874
+
875
+ // 一些编辑器的自带 class
876
+ if ((attrs.class || '').includes('align-center')) {
877
+ styleObj['text-align'] = 'center'
822
878
  }
823
- } else if (node.name === 'table') {
824
- // 表格处理
825
- // cellpadding、cellspacing、border 这几个常用表格属性需要通过转换实现
826
- let padding = parseFloat(attrs.cellpadding)
827
- let spacing = parseFloat(attrs.cellspacing)
828
- const border = parseFloat(attrs.border)
829
- const bordercolor = styleObj['border-color']
830
- const borderstyle = styleObj['border-style']
831
- if (node.c) {
832
- // padding 和 spacing 默认 2
833
- if (isNaN(padding)) {
834
- padding = 2
835
- }
836
- if (isNaN(spacing)) {
837
- spacing = 2
838
- }
879
+
880
+ Object.assign(styleObj, this.parseStyle(node))
881
+
882
+ if (node.name !== 'table' && parseInt(styleObj.width) > windowWidth) {
883
+ styleObj['max-width'] = '100%'
884
+ styleObj['box-sizing'] = 'border-box'
839
885
  }
840
- if (border) {
841
- attrs.style += `;border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'}`
886
+
887
+ // #ifndef APP-PLUS-NVUE
888
+ if (config.blockTags[node.name]) {
889
+ node.name = 'div'
890
+ } else if (!config.trustTags[node.name] && !this.xml) {
891
+ // 未知标签转为 span,避免无法显示
892
+ node.name = 'span'
842
893
  }
843
- if (node.flag && node.c) {
844
- // 有 colspan 或 rowspan 且含有链接的表格通过 grid 布局实现
845
- styleObj.display = 'grid'
846
- if (spacing) {
847
- styleObj['grid-gap'] = spacing + 'px'
848
- styleObj.padding = spacing + 'px'
849
- } else if (border) {
850
- // 无间隔的情况下避免边框重叠
851
- attrs.style += ';border-left:0;border-top:0'
852
- }
853
-
854
- const width = [] // 表格的列宽
855
- const trList = [] // tr 列表
856
- const cells = [] // 保存新的单元格
857
- const map = {}; // 被合并单元格占用的格子
858
-
859
- (function traversal (nodes) {
860
- for (let i = 0; i < nodes.length; i++) {
861
- if (nodes[i].name === 'tr') {
862
- trList.push(nodes[i])
863
- } else {
864
- traversal(nodes[i].children || [])
865
- }
894
+
895
+ if (
896
+ node.name === 'a' ||
897
+ node.name === 'ad' ||
898
+ // #ifdef H5 || APP-PLUS
899
+ node.name === 'iframe' // eslint-disable-line
900
+ // #endif
901
+ ) {
902
+ this.expose()
903
+ } else if (node.name === 'video') {
904
+ if ((styleObj.height || '').includes('auto')) {
905
+ styleObj.height = undefined
866
906
  }
867
- })(children)
868
-
869
- for (let row = 1; row <= trList.length; row++) {
870
- let col = 1
871
- for (let j = 0; j < trList[row - 1].children.length; j++) {
872
- const td = trList[row - 1].children[j]
873
- if (td.name === 'td' || td.name === 'th') {
874
- // 这个格子被上面的单元格占用,则列号++
875
- while (map[row + '.' + col]) {
876
- col++
907
+ /* #ifdef APP-PLUS */
908
+ let str = '<video style="width:100%;height:100%"'
909
+ for (const item in attrs) {
910
+ if (attrs[item]) {
911
+ str += ' ' + item + '="' + attrs[item] + '"'
877
912
  }
878
- let style = td.attrs.style || ''
879
- let start = style.indexOf('width') ? style.indexOf(';width') : 0
880
- // 提取出 td 的宽度
881
- if (start !== -1) {
882
- let end = style.indexOf(';', start + 6)
883
- if (end === -1) {
884
- end = style.length
885
- }
886
- if (!td.attrs.colspan) {
887
- width[col] = style.substring(start ? start + 7 : 6, end)
888
- }
889
- style = style.substr(0, start) + style.substr(end)
913
+ }
914
+ if (this.options.pauseVideo) {
915
+ str +=
916
+ " onplay=\"this.dispatchEvent(new CustomEvent('vplay',{bubbles:!0}));for(var e=document.getElementsByTagName('video'),t=0;t<e.length;t++)e[t]!=this&&e[t].pause()\""
917
+ }
918
+ str += '>'
919
+ for (let i = 0; i < node.src.length; i++) {
920
+ str += '<source src="' + node.src[i] + '">'
921
+ }
922
+ str += '</video>'
923
+ node.html = str
924
+ /* #endif */
925
+ } else if ((node.name === 'ul' || node.name === 'ol') && node.c) {
926
+ // 列表处理
927
+ const types = {
928
+ a: 'lower-alpha',
929
+ A: 'upper-alpha',
930
+ i: 'lower-roman',
931
+ I: 'upper-roman'
932
+ }
933
+ if (types[attrs.type]) {
934
+ attrs.style += ';list-style-type:' + types[attrs.type]
935
+ attrs.type = undefined
936
+ }
937
+ for (let i = children.length; i--; ) {
938
+ if (children[i].name === 'li') {
939
+ children[i].c = 1
890
940
  }
891
- // 设置竖直对齐
892
- style += ';display:flex'
893
- start = style.indexOf('vertical-align')
894
- if (start !== -1) {
895
- const val = style.substr(start + 15, 10)
896
- if (val.includes('middle')) {
897
- style += ';align-items:center'
898
- } else if (val.includes('bottom')) {
899
- style += ';align-items:flex-end'
900
- }
901
- } else {
902
- style += ';align-items:center'
941
+ }
942
+ } else if (node.name === 'table') {
943
+ // 表格处理
944
+ // cellpadding、cellspacing、border 这几个常用表格属性需要通过转换实现
945
+ let padding = parseFloat(attrs.cellpadding)
946
+ let spacing = parseFloat(attrs.cellspacing)
947
+ const border = parseFloat(attrs.border)
948
+ const bordercolor = styleObj['border-color']
949
+ const borderstyle = styleObj['border-style']
950
+ if (node.c) {
951
+ // padding 和 spacing 默认 2
952
+ if (isNaN(padding)) {
953
+ padding = 2
954
+ }
955
+ if (isNaN(spacing)) {
956
+ spacing = 2
903
957
  }
904
- // 设置水平对齐
905
- start = style.indexOf('text-align')
906
- if (start !== -1) {
907
- const val = style.substr(start + 11, 10)
908
- if (val.includes('center')) {
909
- style += ';justify-content: center'
910
- } else if (val.includes('right')) {
911
- style += ';justify-content: right'
912
- }
958
+ }
959
+ if (border) {
960
+ attrs.style += `;border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'}`
961
+ }
962
+ if (node.flag && node.c) {
963
+ // colspan 或 rowspan 且含有链接的表格通过 grid 布局实现
964
+ styleObj.display = 'grid'
965
+ if (styleObj['border-collapse'] === 'collapse') {
966
+ styleObj['border-collapse'] = undefined
967
+ spacing = 0
913
968
  }
914
- style = (border ? `;border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'}` + (spacing ? '' : ';border-right:0;border-bottom:0') : '') + (padding ? `;padding:${padding}px` : '') + ';' + style
915
- // 处理列合并
916
- if (td.attrs.colspan) {
917
- style += `;grid-column-start:${col};grid-column-end:${col + parseInt(td.attrs.colspan)}`
918
- if (!td.attrs.rowspan) {
919
- style += `;grid-row-start:${row};grid-row-end:${row + 1}`
920
- }
921
- col += parseInt(td.attrs.colspan) - 1
969
+ if (spacing) {
970
+ styleObj['grid-gap'] = spacing + 'px'
971
+ styleObj.padding = spacing + 'px'
972
+ } else if (border) {
973
+ // 无间隔的情况下避免边框重叠
974
+ attrs.style += ';border-left:0;border-top:0'
922
975
  }
923
- // 处理行合并
924
- if (td.attrs.rowspan) {
925
- style += `;grid-row-start:${row};grid-row-end:${row + parseInt(td.attrs.rowspan)}`
926
- if (!td.attrs.colspan) {
927
- style += `;grid-column-start:${col};grid-column-end:${col + 1}`
928
- }
929
- // 记录下方单元格被占用
930
- for (let rowspan = 1; rowspan < td.attrs.rowspan; rowspan++) {
931
- for (let colspan = 0; colspan < (td.attrs.colspan || 1); colspan++) {
932
- map[(row + rowspan) + '.' + (col - colspan)] = 1
976
+
977
+ const width = [] // 表格的列宽
978
+ const trList = [] // tr 列表
979
+ const cells = [] // 保存新的单元格
980
+ const map = {} // 被合并单元格占用的格子
981
+
982
+ ;(function traversal(nodes) {
983
+ for (let i = 0; i < nodes.length; i++) {
984
+ if (nodes[i].name === 'tr') {
985
+ trList.push(nodes[i])
986
+ } else if (nodes[i].name === 'colgroup') {
987
+ let colI = 1
988
+ for (const col of nodes[i].children || []) {
989
+ if (col.name === 'col') {
990
+ const style = col.attrs.style || ''
991
+ const start = style.indexOf('width') ? style.indexOf(';width') : 0
992
+ // 提取出宽度
993
+ if (start !== -1) {
994
+ let end = style.indexOf(';', start + 6)
995
+ if (end === -1) {
996
+ end = style.length
997
+ }
998
+ width[colI] = style.substring(start ? start + 7 : 6, end)
999
+ }
1000
+ colI += 1
1001
+ }
1002
+ }
1003
+ } else {
1004
+ traversal(nodes[i].children || [])
1005
+ }
1006
+ }
1007
+ })(children)
1008
+
1009
+ for (let row = 1; row <= trList.length; row++) {
1010
+ let col = 1
1011
+ for (let j = 0; j < trList[row - 1].children.length; j++) {
1012
+ const td = trList[row - 1].children[j]
1013
+ if (td.name === 'td' || td.name === 'th') {
1014
+ // 这个格子被上面的单元格占用,则列号++
1015
+ while (map[row + '.' + col]) {
1016
+ col++
1017
+ }
1018
+ let style = td.attrs.style || ''
1019
+ let start = style.indexOf('width') ? style.indexOf(';width') : 0
1020
+ // 提取出 td 的宽度
1021
+ if (start !== -1) {
1022
+ let end = style.indexOf(';', start + 6)
1023
+ if (end === -1) {
1024
+ end = style.length
1025
+ }
1026
+ if (!td.attrs.colspan) {
1027
+ width[col] = style.substring(start ? start + 7 : 6, end)
1028
+ }
1029
+ style = style.substr(0, start) + style.substr(end)
1030
+ }
1031
+ // 设置竖直对齐
1032
+ style += ';display:flex'
1033
+ start = style.indexOf('vertical-align')
1034
+ if (start !== -1) {
1035
+ const val = style.substr(start + 15, 10)
1036
+ if (val.includes('middle')) {
1037
+ style += ';align-items:center'
1038
+ } else if (val.includes('bottom')) {
1039
+ style += ';align-items:flex-end'
1040
+ }
1041
+ } else {
1042
+ style += ';align-items:center'
1043
+ }
1044
+ // 设置水平对齐
1045
+ start = style.indexOf('text-align')
1046
+ if (start !== -1) {
1047
+ const val = style.substr(start + 11, 10)
1048
+ if (val.includes('center')) {
1049
+ style += ';justify-content: center'
1050
+ } else if (val.includes('right')) {
1051
+ style += ';justify-content: right'
1052
+ }
1053
+ }
1054
+ style =
1055
+ (border
1056
+ ? `;border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'}` +
1057
+ (spacing ? '' : ';border-right:0;border-bottom:0')
1058
+ : '') +
1059
+ (padding ? `;padding:${padding}px` : '') +
1060
+ ';' +
1061
+ style
1062
+ // 处理列合并
1063
+ if (td.attrs.colspan) {
1064
+ style += `;grid-column-start:${col};grid-column-end:${col + parseInt(td.attrs.colspan)}`
1065
+ if (!td.attrs.rowspan) {
1066
+ style += `;grid-row-start:${row};grid-row-end:${row + 1}`
1067
+ }
1068
+ col += parseInt(td.attrs.colspan) - 1
1069
+ }
1070
+ // 处理行合并
1071
+ if (td.attrs.rowspan) {
1072
+ style += `;grid-row-start:${row};grid-row-end:${row + parseInt(td.attrs.rowspan)}`
1073
+ if (!td.attrs.colspan) {
1074
+ style += `;grid-column-start:${col};grid-column-end:${col + 1}`
1075
+ }
1076
+ // 记录下方单元格被占用
1077
+ for (let rowspan = 1; rowspan < td.attrs.rowspan; rowspan++) {
1078
+ for (
1079
+ let colspan = 0;
1080
+ colspan < (td.attrs.colspan || 1);
1081
+ colspan++
1082
+ ) {
1083
+ map[row + rowspan + '.' + (col - colspan)] = 1
1084
+ }
1085
+ }
1086
+ }
1087
+ if (style) {
1088
+ td.attrs.style = style
1089
+ }
1090
+ cells.push(td)
1091
+ col++
1092
+ }
1093
+ }
1094
+ if (row === 1) {
1095
+ let temp = ''
1096
+ for (let i = 1; i < col; i++) {
1097
+ temp += (width[i] ? width[i] : 'auto') + ' '
1098
+ }
1099
+ styleObj['grid-template-columns'] = temp
933
1100
  }
934
- }
935
1101
  }
936
- if (style) {
937
- td.attrs.style = style
1102
+ node.children = cells
1103
+ } else {
1104
+ // 没有使用合并单元格的表格通过 table 布局实现
1105
+ if (node.c) {
1106
+ styleObj.display = 'table'
1107
+ }
1108
+ if (!isNaN(spacing)) {
1109
+ styleObj['border-spacing'] = spacing + 'px'
1110
+ }
1111
+ if (border || padding) {
1112
+ // 遍历
1113
+ ;(function traversal(nodes) {
1114
+ for (let i = 0; i < nodes.length; i++) {
1115
+ const td = nodes[i]
1116
+ if (td.name === 'th' || td.name === 'td') {
1117
+ if (border) {
1118
+ td.attrs.style = `border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'};${td.attrs.style || ''}`
1119
+ }
1120
+ if (padding) {
1121
+ td.attrs.style = `padding:${padding}px;${td.attrs.style || ''}`
1122
+ }
1123
+ } else if (td.children) {
1124
+ traversal(td.children)
1125
+ }
1126
+ }
1127
+ })(children)
938
1128
  }
939
- cells.push(td)
940
- col++
941
- }
942
1129
  }
943
- if (row === 1) {
944
- let temp = ''
945
- for (let i = 1; i < col; i++) {
946
- temp += (width[i] ? width[i] : 'auto') + ' '
947
- }
948
- styleObj['grid-template-columns'] = temp
1130
+ // 给表格添加一个单独的横向滚动层
1131
+ if (this.options.scrollTable && !(attrs.style || '').includes('inline')) {
1132
+ const table = Object.assign({}, node)
1133
+ node.name = 'div'
1134
+ node.attrs = {
1135
+ style: 'overflow:auto'
1136
+ }
1137
+ node.children = [table]
1138
+ attrs = table.attrs
949
1139
  }
950
- }
951
- node.children = cells
952
- } else {
953
- // 没有使用合并单元格的表格通过 table 布局实现
954
- if (node.c) {
955
- styleObj.display = 'table'
956
- }
957
- if (!isNaN(spacing)) {
958
- styleObj['border-spacing'] = spacing + 'px'
959
- }
960
- if (border || padding) {
961
- // 遍历
962
- (function traversal (nodes) {
963
- for (let i = 0; i < nodes.length; i++) {
964
- const td = nodes[i]
965
- if (td.name === 'th' || td.name === 'td') {
966
- if (border) {
967
- td.attrs.style = `border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'};${td.attrs.style || ''}`
968
- }
969
- if (padding) {
970
- td.attrs.style = `padding:${padding}px;${td.attrs.style || ''}`
971
- }
972
- } else if (td.children) {
973
- traversal(td.children)
1140
+ } else if ((node.name === 'tbody' || node.name === 'tr') && node.flag && node.c) {
1141
+ node.flag = undefined
1142
+ ;(function traversal(nodes) {
1143
+ for (let i = 0; i < nodes.length; i++) {
1144
+ if (nodes[i].name === 'td') {
1145
+ // 颜色样式设置给单元格避免丢失
1146
+ for (const style of ['color', 'background', 'background-color']) {
1147
+ if (styleObj[style]) {
1148
+ nodes[i].attrs.style =
1149
+ style + ':' + styleObj[style] + ';' + (nodes[i].attrs.style || '')
1150
+ }
1151
+ }
1152
+ } else {
1153
+ traversal(nodes[i].children || [])
1154
+ }
974
1155
  }
975
- }
976
1156
  })(children)
977
- }
978
- }
979
- // 给表格添加一个单独的横向滚动层
980
- if (this.options.scrollTable && !(attrs.style || '').includes('inline')) {
981
- const table = Object.assign({}, node)
982
- node.name = 'div'
983
- node.attrs = {
984
- style: 'overflow:auto'
985
- }
986
- node.children = [table]
987
- attrs = table.attrs
988
- }
989
- } else if ((node.name === 'td' || node.name === 'th') && (attrs.colspan || attrs.rowspan)) {
990
- for (let i = this.stack.length; i--;) {
991
- if (this.stack[i].name === 'table') {
992
- this.stack[i].flag = 1 // 指示含有合并单元格
993
- break
994
- }
995
- }
996
- } else if (node.name === 'ruby') {
997
- // 转换 ruby
998
- node.name = 'span'
999
- for (let i = 0; i < children.length - 1; i++) {
1000
- if (children[i].type === 'text' && children[i + 1].name === 'rt') {
1001
- children[i] = {
1002
- name: 'div',
1003
- attrs: {
1004
- style: 'display:inline-block;text-align:center'
1005
- },
1006
- children: [{
1007
- name: 'div',
1008
- attrs: {
1009
- style: 'font-size:50%;' + (children[i + 1].attrs.style || '')
1010
- },
1011
- children: children[i + 1].children
1012
- }, children[i]]
1157
+ } else if ((node.name === 'td' || node.name === 'th') && (attrs.colspan || attrs.rowspan)) {
1158
+ for (let i = this.stack.length; i--; ) {
1159
+ if (
1160
+ this.stack[i].name === 'table' ||
1161
+ this.stack[i].name === 'tbody' ||
1162
+ this.stack[i].name === 'tr'
1163
+ ) {
1164
+ this.stack[i].flag = 1 // 指示含有合并单元格
1165
+ }
1166
+ }
1167
+ } else if (node.name === 'ruby') {
1168
+ // 转换 ruby
1169
+ node.name = 'span'
1170
+ for (let i = 0; i < children.length - 1; i++) {
1171
+ if (children[i].type === 'text' && children[i + 1].name === 'rt') {
1172
+ children[i] = {
1173
+ name: 'div',
1174
+ attrs: {
1175
+ style: 'display:inline-block;text-align:center'
1176
+ },
1177
+ children: [
1178
+ {
1179
+ name: 'div',
1180
+ attrs: {
1181
+ style: 'font-size:50%;' + (children[i + 1].attrs.style || '')
1182
+ },
1183
+ children: children[i + 1].children
1184
+ },
1185
+ children[i]
1186
+ ]
1187
+ }
1188
+ children.splice(i + 1, 1)
1189
+ }
1013
1190
  }
1014
- children.splice(i + 1, 1)
1015
- }
1191
+ } else if (node.c) {
1192
+ ;(function traversal(node) {
1193
+ node.c = 2
1194
+ for (let i = node.children.length; i--; ) {
1195
+ const child = node.children[i]
1196
+ // #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3
1197
+ if (
1198
+ child.name &&
1199
+ (config.inlineTags[child.name] ||
1200
+ ((child.attrs.style || '').includes('inline') && child.children)) &&
1201
+ !child.c
1202
+ ) {
1203
+ traversal(child)
1204
+ }
1205
+ // #endif
1206
+ if (!child.c || child.name === 'table') {
1207
+ node.c = 1
1208
+ }
1209
+ }
1210
+ })(node)
1016
1211
  }
1017
- } else if (node.c) {
1018
- (function traversal (node) {
1019
- node.c = 2
1020
- for (let i = node.children.length; i--;) {
1021
- const child = node.children[i]
1022
- // #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3
1023
- if (child.name && (config.inlineTags[child.name] || ((child.attrs.style || '').includes('inline') && child.children)) && !child.c) {
1024
- traversal(child)
1212
+
1213
+ if ((styleObj.display || '').includes('flex') && !node.c) {
1214
+ for (let i = children.length; i--; ) {
1215
+ const item = children[i]
1216
+ if (item.f) {
1217
+ item.attrs.style = (item.attrs.style || '') + item.f
1218
+ item.f = undefined
1219
+ }
1025
1220
  }
1221
+ }
1222
+ // flex 布局时部分样式需要提取到 rich-text 外层
1223
+ const flex =
1224
+ parent &&
1225
+ ((parent.attrs.style || '').includes('flex') ||
1226
+ (parent.attrs.style || '').includes('grid')) &&
1227
+ // #ifdef MP-WEIXIN
1228
+ // 检查基础库版本 virtualHost 是否可用
1229
+ !(node.c && wx.getNFCAdapter) && // eslint-disable-line
1026
1230
  // #endif
1027
- if (!child.c || child.name === 'table') {
1028
- node.c = 1
1029
- }
1030
- }
1031
- })(node)
1032
- }
1033
-
1034
- if ((styleObj.display || '').includes('flex') && !node.c) {
1035
- for (let i = children.length; i--;) {
1036
- const item = children[i]
1037
- if (item.f) {
1038
- item.attrs.style = (item.attrs.style || '') + item.f
1039
- item.f = undefined
1040
- }
1231
+ // #ifndef MP-WEIXIN || MP-QQ || MP-BAIDU || MP-TOUTIAO
1232
+ !node.c // eslint-disable-line
1233
+ // #endif
1234
+ if (flex) {
1235
+ node.f = ';max-width:100%'
1236
+ }
1237
+
1238
+ if (children.length >= 50 && node.c && !(styleObj.display || '').includes('flex')) {
1239
+ mergeNodes(children)
1041
1240
  }
1042
- }
1043
- // flex 布局时部分样式需要提取到 rich-text 外层
1044
- const flex = parent && ((parent.attrs.style || '').includes('flex') || (parent.attrs.style || '').includes('grid'))
1045
- // #ifdef MP-WEIXIN
1046
- // 检查基础库版本 virtualHost 是否可用
1047
- && !(node.c && wx.getNFCAdapter) // eslint-disable-line
1048
1241
  // #endif
1049
- // #ifndef MP-WEIXIN || MP-QQ || MP-BAIDU || MP-TOUTIAO
1050
- && !node.c // eslint-disable-line
1051
- // #endif
1052
- if (flex) {
1053
- node.f = ';max-width:100%'
1054
- }
1055
-
1056
- if (children.length >= 50 && node.c && !(styleObj.display || '').includes('flex')) {
1057
- mergeNodes(children)
1058
- }
1059
- // #endif
1060
-
1061
- for (const key in styleObj) {
1062
- if (styleObj[key]) {
1063
- const val = `;${key}:${styleObj[key].replace(' !important', '')}`
1064
- /* #ifndef APP-PLUS-NVUE */
1065
- if (flex && ((key.includes('flex') && key !== 'flex-direction') || key === 'align-self' || key.includes('grid') || styleObj[key][0] === '-' || (key.includes('width') && val.includes('%')))) {
1066
- node.f += val
1067
- if (key === 'width') {
1068
- attrs.style += ';width:100%'
1242
+
1243
+ for (const key in styleObj) {
1244
+ if (styleObj[key]) {
1245
+ const val = `;${key}:${styleObj[key].replace(' !important', '')}`
1246
+ /* #ifndef APP-PLUS-NVUE */
1247
+ if (
1248
+ flex &&
1249
+ ((key.includes('flex') && key !== 'flex-direction') ||
1250
+ key === 'align-self' ||
1251
+ key.includes('grid') ||
1252
+ styleObj[key][0] === '-' ||
1253
+ (key.includes('width') && val.includes('%')))
1254
+ ) {
1255
+ node.f += val
1256
+ if (key === 'width') {
1257
+ attrs.style += ';width:100%'
1258
+ }
1259
+ } /* #endif */ else {
1260
+ attrs.style += val
1261
+ }
1069
1262
  }
1070
- } else /* #endif */ {
1071
- attrs.style += val
1072
- }
1073
1263
  }
1074
- }
1075
- attrs.style = attrs.style.substr(1) || undefined
1076
- // #ifdef (MP-WEIXIN || MP-QQ) && VUE3
1077
- for (const key in attrs) {
1078
- if (!attrs[key]) {
1079
- delete attrs[key]
1264
+ attrs.style = attrs.style.substr(1) || undefined
1265
+ // #ifdef (MP-WEIXIN || MP-QQ) && VUE3
1266
+ for (const key in attrs) {
1267
+ if (!attrs[key]) {
1268
+ delete attrs[key]
1269
+ }
1080
1270
  }
1081
- }
1082
- // #endif
1271
+ // #endif
1083
1272
  }
1084
1273
 
1085
1274
  /**
@@ -1087,57 +1276,61 @@ Parser.prototype.popNode = function () {
1087
1276
  * @param {String} text 文本内容
1088
1277
  */
1089
1278
  Parser.prototype.onText = function (text) {
1090
- if (!this.pre) {
1091
- // 合并空白符
1092
- let trim = ''
1093
- let flag
1094
- for (let i = 0, len = text.length; i < len; i++) {
1095
- if (!blankChar[text[i]]) {
1096
- trim += text[i]
1097
- } else {
1098
- if (trim[trim.length - 1] !== ' ') {
1099
- trim += ' '
1279
+ if (!this.pre) {
1280
+ // 合并空白符
1281
+ let trim = ''
1282
+ let flag
1283
+ for (let i = 0, len = text.length; i < len; i++) {
1284
+ if (!blankChar[text[i]]) {
1285
+ trim += text[i]
1286
+ } else {
1287
+ if (trim[trim.length - 1] !== ' ') {
1288
+ trim += ' '
1289
+ }
1290
+ if (text[i] === '\n' && !flag) {
1291
+ flag = true
1292
+ }
1293
+ }
1100
1294
  }
1101
- if (text[i] === '\n' && !flag) {
1102
- flag = true
1295
+ // 去除含有换行符的空串
1296
+ if (trim === ' ') {
1297
+ if (flag) return
1298
+ // #ifdef VUE3
1299
+ else {
1300
+ const parent = this.stack[this.stack.length - 1]
1301
+ if (parent && parent.name[0] === 't') return
1302
+ }
1303
+ // #endif
1103
1304
  }
1104
- }
1105
- }
1106
- // 去除含有换行符的空串
1107
- if (trim === ' ') {
1108
- if (flag) return
1109
- // #ifdef VUE3
1110
- else {
1111
- const parent = this.stack[this.stack.length - 1]
1112
- if (parent && parent.name[0] === 't') return
1113
- }
1114
- // #endif
1115
- }
1116
- text = trim
1117
- }
1118
- const node = Object.create(null)
1119
- node.type = 'text'
1120
- // #ifdef (MP-BAIDU || MP-ALIPAY || MP-TOUTIAO) && VUE3
1121
- node.attrs = {}
1122
- // #endif
1123
- node.text = decodeEntity(text)
1124
- if (this.hook(node)) {
1125
- // #ifdef MP-WEIXIN
1126
- if (this.options.selectable === 'force' && system.includes('iOS') && !uni.canIUse('rich-text.user-select')) {
1127
- this.expose()
1305
+ text = trim
1128
1306
  }
1307
+ const node = Object.create(null)
1308
+ node.type = 'text'
1309
+ // #ifdef (MP-BAIDU || MP-ALIPAY || MP-TOUTIAO) && VUE3
1310
+ node.attrs = {}
1129
1311
  // #endif
1130
- const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes
1131
- siblings.push(node)
1132
- }
1312
+ node.text = decodeEntity(text)
1313
+ if (this.hook(node)) {
1314
+ // #ifdef MP-WEIXIN
1315
+ if (
1316
+ this.options.selectable === 'force' &&
1317
+ system.includes('iOS') &&
1318
+ !uni.canIUse('rich-text.user-select')
1319
+ ) {
1320
+ this.expose()
1321
+ }
1322
+ // #endif
1323
+ const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes
1324
+ siblings.push(node)
1325
+ }
1133
1326
  }
1134
1327
 
1135
1328
  /**
1136
1329
  * @description html 词法分析器
1137
1330
  * @param {Object} handler 高层处理器
1138
1331
  */
1139
- function Lexer (handler) {
1140
- this.handler = handler
1332
+ function Lexer(handler) {
1333
+ this.handler = handler
1141
1334
  }
1142
1335
 
1143
1336
  /**
@@ -1145,13 +1338,13 @@ function Lexer (handler) {
1145
1338
  * @param {String} content 要解析的文本
1146
1339
  */
1147
1340
  Lexer.prototype.parse = function (content) {
1148
- this.content = content || ''
1149
- this.i = 0 // 标记解析位置
1150
- this.start = 0 // 标记一个单词的开始位置
1151
- this.state = this.text // 当前状态
1152
- for (let len = this.content.length; this.i !== -1 && this.i < len;) {
1153
- this.state()
1154
- }
1341
+ this.content = content || ''
1342
+ this.i = 0 // 标记解析位置
1343
+ this.start = 0 // 标记一个单词的开始位置
1344
+ this.state = this.text // 当前状态
1345
+ for (let len = this.content.length; this.i !== -1 && this.i < len; ) {
1346
+ this.state()
1347
+ }
1155
1348
  }
1156
1349
 
1157
1350
  /**
@@ -1161,27 +1354,27 @@ Lexer.prototype.parse = function (content) {
1161
1354
  * @private
1162
1355
  */
1163
1356
  Lexer.prototype.checkClose = function (method) {
1164
- const selfClose = this.content[this.i] === '/'
1165
- if (this.content[this.i] === '>' || (selfClose && this.content[this.i + 1] === '>')) {
1166
- if (method) {
1167
- this.handler[method](this.content.substring(this.start, this.i))
1168
- }
1169
- this.i += selfClose ? 2 : 1
1170
- this.start = this.i
1171
- this.handler.onOpenTag(selfClose)
1172
- if (this.handler.tagName === 'script') {
1173
- this.i = this.content.indexOf('</', this.i)
1174
- if (this.i !== -1) {
1175
- this.i += 2
1357
+ const selfClose = this.content[this.i] === '/'
1358
+ if (this.content[this.i] === '>' || (selfClose && this.content[this.i + 1] === '>')) {
1359
+ if (method) {
1360
+ this.handler[method](this.content.substring(this.start, this.i))
1361
+ }
1362
+ this.i += selfClose ? 2 : 1
1176
1363
  this.start = this.i
1177
- }
1178
- this.state = this.endTag
1179
- } else {
1180
- this.state = this.text
1364
+ this.handler.onOpenTag(selfClose)
1365
+ if (this.handler.tagName === 'script') {
1366
+ this.i = this.content.indexOf('</', this.i)
1367
+ if (this.i !== -1) {
1368
+ this.i += 2
1369
+ this.start = this.i
1370
+ }
1371
+ this.state = this.endTag
1372
+ } else {
1373
+ this.state = this.text
1374
+ }
1375
+ return true
1181
1376
  }
1182
- return true
1183
- }
1184
- return false
1377
+ return false
1185
1378
  }
1186
1379
 
1187
1380
  /**
@@ -1189,47 +1382,47 @@ Lexer.prototype.checkClose = function (method) {
1189
1382
  * @private
1190
1383
  */
1191
1384
  Lexer.prototype.text = function () {
1192
- this.i = this.content.indexOf('<', this.i) // 查找最近的标签
1193
- if (this.i === -1) {
1194
- // 没有标签了
1195
- if (this.start < this.content.length) {
1196
- this.handler.onText(this.content.substring(this.start, this.content.length))
1197
- }
1198
- return
1199
- }
1200
- const c = this.content[this.i + 1]
1201
- if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
1202
- // 标签开头
1203
- if (this.start !== this.i) {
1204
- this.handler.onText(this.content.substring(this.start, this.i))
1205
- }
1206
- this.start = ++this.i
1207
- this.state = this.tagName
1208
- } else if (c === '/' || c === '!' || c === '?') {
1209
- if (this.start !== this.i) {
1210
- this.handler.onText(this.content.substring(this.start, this.i))
1211
- }
1212
- const next = this.content[this.i + 2]
1213
- if (c === '/' && ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) {
1214
- // 标签结尾
1215
- this.i += 2
1216
- this.start = this.i
1217
- this.state = this.endTag
1218
- return
1219
- }
1220
- // 处理注释
1221
- let end = '-->'
1222
- if (c !== '!' || this.content[this.i + 2] !== '-' || this.content[this.i + 3] !== '-') {
1223
- end = '>'
1385
+ this.i = this.content.indexOf('<', this.i) // 查找最近的标签
1386
+ if (this.i === -1) {
1387
+ // 没有标签了
1388
+ if (this.start < this.content.length) {
1389
+ this.handler.onText(this.content.substring(this.start, this.content.length))
1390
+ }
1391
+ return
1224
1392
  }
1225
- this.i = this.content.indexOf(end, this.i)
1226
- if (this.i !== -1) {
1227
- this.i += end.length
1228
- this.start = this.i
1393
+ const c = this.content[this.i + 1]
1394
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
1395
+ // 标签开头
1396
+ if (this.start !== this.i) {
1397
+ this.handler.onText(this.content.substring(this.start, this.i))
1398
+ }
1399
+ this.start = ++this.i
1400
+ this.state = this.tagName
1401
+ } else if (c === '/' || c === '!' || c === '?') {
1402
+ if (this.start !== this.i) {
1403
+ this.handler.onText(this.content.substring(this.start, this.i))
1404
+ }
1405
+ const next = this.content[this.i + 2]
1406
+ if (c === '/' && ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) {
1407
+ // 标签结尾
1408
+ this.i += 2
1409
+ this.start = this.i
1410
+ this.state = this.endTag
1411
+ return
1412
+ }
1413
+ // 处理注释
1414
+ let end = '-->'
1415
+ if (c !== '!' || this.content[this.i + 2] !== '-' || this.content[this.i + 3] !== '-') {
1416
+ end = '>'
1417
+ }
1418
+ this.i = this.content.indexOf(end, this.i)
1419
+ if (this.i !== -1) {
1420
+ this.i += end.length
1421
+ this.start = this.i
1422
+ }
1423
+ } else {
1424
+ this.i++
1229
1425
  }
1230
- } else {
1231
- this.i++
1232
- }
1233
1426
  }
1234
1427
 
1235
1428
  /**
@@ -1237,17 +1430,17 @@ Lexer.prototype.text = function () {
1237
1430
  * @private
1238
1431
  */
1239
1432
  Lexer.prototype.tagName = function () {
1240
- if (blankChar[this.content[this.i]]) {
1241
- // 解析到标签名
1242
- this.handler.onTagName(this.content.substring(this.start, this.i))
1243
- while (blankChar[this.content[++this.i]]);
1244
- if (this.i < this.content.length && !this.checkClose()) {
1245
- this.start = this.i
1246
- this.state = this.attrName
1433
+ if (blankChar[this.content[this.i]]) {
1434
+ // 解析到标签名
1435
+ this.handler.onTagName(this.content.substring(this.start, this.i))
1436
+ while (blankChar[this.content[++this.i]]);
1437
+ if (this.i < this.content.length && !this.checkClose()) {
1438
+ this.start = this.i
1439
+ this.state = this.attrName
1440
+ }
1441
+ } else if (!this.checkClose('onTagName')) {
1442
+ this.i++
1247
1443
  }
1248
- } else if (!this.checkClose('onTagName')) {
1249
- this.i++
1250
- }
1251
1444
  }
1252
1445
 
1253
1446
  /**
@@ -1255,34 +1448,34 @@ Lexer.prototype.tagName = function () {
1255
1448
  * @private
1256
1449
  */
1257
1450
  Lexer.prototype.attrName = function () {
1258
- let c = this.content[this.i]
1259
- if (blankChar[c] || c === '=') {
1260
- // 解析到属性名
1261
- this.handler.onAttrName(this.content.substring(this.start, this.i))
1262
- let needVal = c === '='
1263
- const len = this.content.length
1264
- while (++this.i < len) {
1265
- c = this.content[this.i]
1266
- if (!blankChar[c]) {
1267
- if (this.checkClose()) return
1268
- if (needVal) {
1269
- // 等号后遇到第一个非空字符
1270
- this.start = this.i
1271
- this.state = this.attrVal
1272
- return
1273
- }
1274
- if (this.content[this.i] === '=') {
1275
- needVal = true
1276
- } else {
1277
- this.start = this.i
1278
- this.state = this.attrName
1279
- return
1451
+ let c = this.content[this.i]
1452
+ if (blankChar[c] || c === '=') {
1453
+ // 解析到属性名
1454
+ this.handler.onAttrName(this.content.substring(this.start, this.i))
1455
+ let needVal = c === '='
1456
+ const len = this.content.length
1457
+ while (++this.i < len) {
1458
+ c = this.content[this.i]
1459
+ if (!blankChar[c]) {
1460
+ if (this.checkClose()) return
1461
+ if (needVal) {
1462
+ // 等号后遇到第一个非空字符
1463
+ this.start = this.i
1464
+ this.state = this.attrVal
1465
+ return
1466
+ }
1467
+ if (this.content[this.i] === '=') {
1468
+ needVal = true
1469
+ } else {
1470
+ this.start = this.i
1471
+ this.state = this.attrName
1472
+ return
1473
+ }
1474
+ }
1280
1475
  }
1281
- }
1476
+ } else if (!this.checkClose('onAttrName')) {
1477
+ this.i++
1282
1478
  }
1283
- } else if (!this.checkClose('onAttrName')) {
1284
- this.i++
1285
- }
1286
1479
  }
1287
1480
 
1288
1481
  /**
@@ -1290,28 +1483,28 @@ Lexer.prototype.attrName = function () {
1290
1483
  * @private
1291
1484
  */
1292
1485
  Lexer.prototype.attrVal = function () {
1293
- const c = this.content[this.i]
1294
- const len = this.content.length
1295
- if (c === '"' || c === "'") {
1296
- // 有冒号的属性
1297
- this.start = ++this.i
1298
- this.i = this.content.indexOf(c, this.i)
1299
- if (this.i === -1) return
1300
- this.handler.onAttrVal(this.content.substring(this.start, this.i))
1301
- } else {
1302
- // 没有冒号的属性
1303
- for (; this.i < len; this.i++) {
1304
- if (blankChar[this.content[this.i]]) {
1486
+ const c = this.content[this.i]
1487
+ const len = this.content.length
1488
+ if (c === '"' || c === "'") {
1489
+ // 有冒号的属性
1490
+ this.start = ++this.i
1491
+ this.i = this.content.indexOf(c, this.i)
1492
+ if (this.i === -1) return
1305
1493
  this.handler.onAttrVal(this.content.substring(this.start, this.i))
1306
- break
1307
- } else if (this.checkClose('onAttrVal')) return
1494
+ } else {
1495
+ // 没有冒号的属性
1496
+ for (; this.i < len; this.i++) {
1497
+ if (blankChar[this.content[this.i]]) {
1498
+ this.handler.onAttrVal(this.content.substring(this.start, this.i))
1499
+ break
1500
+ } else if (this.checkClose('onAttrVal')) return
1501
+ }
1502
+ }
1503
+ while (blankChar[this.content[++this.i]]);
1504
+ if (this.i < len && !this.checkClose()) {
1505
+ this.start = this.i
1506
+ this.state = this.attrName
1308
1507
  }
1309
- }
1310
- while (blankChar[this.content[++this.i]]);
1311
- if (this.i < len && !this.checkClose()) {
1312
- this.start = this.i
1313
- this.state = this.attrName
1314
- }
1315
1508
  }
1316
1509
 
1317
1510
  /**
@@ -1320,18 +1513,18 @@ Lexer.prototype.attrVal = function () {
1320
1513
  * @private
1321
1514
  */
1322
1515
  Lexer.prototype.endTag = function () {
1323
- const c = this.content[this.i]
1324
- if (blankChar[c] || c === '>' || c === '/') {
1325
- this.handler.onCloseTag(this.content.substring(this.start, this.i))
1326
- if (c !== '>') {
1327
- this.i = this.content.indexOf('>', this.i)
1328
- if (this.i === -1) return
1516
+ const c = this.content[this.i]
1517
+ if (blankChar[c] || c === '>' || c === '/') {
1518
+ this.handler.onCloseTag(this.content.substring(this.start, this.i))
1519
+ if (c !== '>') {
1520
+ this.i = this.content.indexOf('>', this.i)
1521
+ if (this.i === -1) return
1522
+ }
1523
+ this.start = ++this.i
1524
+ this.state = this.text
1525
+ } else {
1526
+ this.i++
1329
1527
  }
1330
- this.start = ++this.i
1331
- this.state = this.text
1332
- } else {
1333
- this.i++
1334
- }
1335
1528
  }
1336
1529
 
1337
1530
  export default Parser