• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16const bind = require('./bind')
17const styler = require('../styler')
18const styleValidator = require('../styler/lib/validator')
19const OHOS_THEME_PROP_GROUPS = require('../theme/ohosStyles')
20const path = require('path')
21const projectPath = process.env.aceModuleRoot || process.cwd()
22const transContent = require('./content')
23
24const REG_TAG_DATA_ATTR = /^data-\w+/
25const REG_EVENT = /([^(]*)\((.+)\)/
26const REG_DATA_BINDING = /{{{(.+?)}}}|{{(.+?)}}/
27const { DEVICE_LEVEL, PLATFORM } = require('../../lib/lite/lite-enum')
28const { richCommonTag, richNativeTag } = require('./rich_component_map')
29const { liteCommonTag, liteNativeTag } = require('./lite_component_map')
30const { cardCommonTag, cardNativeTag } = require('./card_component_map')
31
32const card = process.env.DEVICE_LEVEL === DEVICE_LEVEL.CARD
33const nativeTag = process.env.DEVICE_LEVEL === DEVICE_LEVEL.LITE ? liteNativeTag :
34  card ? cardNativeTag: richNativeTag
35const commonTag = process.env.DEVICE_LEVEL === DEVICE_LEVEL.LITE ? liteCommonTag :
36  card ? cardCommonTag: richCommonTag
37
38const tagSelfClosing = []
39const tagWithoutRoot = []
40const tagWithoutChild = []
41const tagWithTextContent = []
42const tagWithAll = []
43const tagWithPath = []
44const aliasTagMap = {}
45const attrTagMap = {}
46const funcAttrTagMap = {}
47const defaultAttrTagMap = {}
48const requireAttrTagMap = {}
49const enumAttrTagMap = {}
50const rootAttrTagMap = {}
51const parentsTagMap = {}
52const childrenTagMap = {}
53const unSupportedChildren = {}
54const eventsTagMap = {}
55let elementNames = {}
56
57/**
58 * prepare criteria for
59 * checking tag and attribute
60 *
61 * @type {{Object}}
62 */
63;(function initRules() {
64  for (const tag of Object.keys(nativeTag)) {
65    // tag with selfClosing attribute
66    if (nativeTag[tag].selfClosing) {
67      tagSelfClosing.push(tag)
68    }
69
70    // tag can not be root
71    if (nativeTag[tag].excludeRoot) {
72      tagWithoutRoot.push(tag)
73    }
74
75    // tag can not have children
76    if (nativeTag[tag].atomic) {
77      tagWithoutChild.push(tag)
78    }
79
80    // tag support text content
81    if (nativeTag[tag].textContent) {
82      tagWithTextContent.push(tag)
83    }
84
85    // store all tags
86    tagWithAll.push(tag)
87
88    // tags with other name
89    if (nativeTag[tag].alias && nativeTag[tag].alias.length) {
90      for (const alia of nativeTag[tag].alias) {
91        aliasTagMap[alia] = tag
92      }
93    }
94
95    // store private and common attr
96    const attrTagSet = {}
97
98    if (nativeTag[tag].uattrs) {
99      Object.assign(attrTagSet, nativeTag[tag].uattrs, {})
100    } else {
101      Object.assign(attrTagSet, nativeTag[tag].attrs, commonTag.attrs)
102    }
103
104    attrTagMap[tag] = attrTagSet
105    const checkFuncSet = {}
106    const attrDefaultSet = {}
107    const attrRequireArr = []
108    const attrEnumSet = {}
109    const rootNoAttr = []
110
111    for (const attr of Object.keys(attrTagSet)) {
112      if (attrTagSet[attr].checkFunc) {
113        checkFuncSet[attr] = attrTagSet[attr].checkFunc
114      }
115
116      if (attrTagSet[attr].def) {
117        attrDefaultSet[attr] = attrTagSet[attr].def
118      }
119
120      if (attrTagSet[attr].required) {
121        attrRequireArr.push(attr)
122      }
123
124      if (attrTagSet[attr].enum && attrTagSet[attr].enum.length > 0) {
125        attrEnumSet[attr] = attrTagSet[attr].enum
126        attrDefaultSet[attr] = attrTagSet[attr].enum[0]
127      }
128
129      if (attrTagSet[attr].excludeRoot) {
130        rootNoAttr.push(attr)
131      }
132
133      if (attrTagSet[attr].checkPath) {
134        tagWithPath.push(attr)
135      }
136    }
137
138    funcAttrTagMap[tag] = checkFuncSet
139    defaultAttrTagMap[tag] = attrDefaultSet
140    requireAttrTagMap[tag] = attrRequireArr
141    enumAttrTagMap[tag] = attrEnumSet
142    rootAttrTagMap[tag] = rootNoAttr
143
144    if (nativeTag[tag].parents) {
145      parentsTagMap[tag] = nativeTag[tag].parents.concat(commonTag.parents)
146    }
147
148    if (nativeTag[tag].children) {
149      childrenTagMap[tag] = nativeTag[tag].children.concat(commonTag.children)
150    }
151
152    if (nativeTag[tag].unSupportedChildren) {
153      unSupportedChildren[tag] = nativeTag[tag].unSupportedChildren.concat(commonTag.unSupportedChildren)
154    }
155
156    if (nativeTag[tag].uevents) {
157      eventsTagMap[tag] = nativeTag[tag].uevents.concat(nativeTag[tag].events)
158    } else {
159      eventsTagMap[tag] = [].concat(commonTag.events).concat(nativeTag[tag].events)
160    }
161  }
162})()
163
164/**
165 * verify the validity of tag
166 *
167 * @param {Object} domNode
168 * @param {Object} out
169 */
170function validateTagName(domNode, out, relativePath) {
171  const tagName = domNode.tagName
172  const children = domNode.childNodes || []
173  const nodeLoc = domNode.sourceCodeLocation ? {
174    line: domNode.sourceCodeLocation.startLine,
175    col: domNode.sourceCodeLocation.endLine
176  } : {}
177  const depends = out.deps
178  const jsonTemplate = out.jsonTemplate
179  const log = out.log
180  const oneChildNode = ['dialog', 'popup', 'badge', 'list-item']
181  const logType = process.env.DEVICE_LEVEL === DEVICE_LEVEL.LITE ? 'ERROR' : 'WARNING'
182  let pos = domNode.__location || domNode.sourceCodeLocation ? {
183    line: domNode.sourceCodeLocation.startLine,
184    col: domNode.sourceCodeLocation.endLine
185  } : {}
186  const elementNamesInFile = elementNames[relativePath] || []
187
188  // validate tag
189  if (!tagWithAll.includes(tagName) && tagName !== 'img' && !elementNamesInFile.includes(tagName)) {
190    log.push({
191      line: nodeLoc.line || 1,
192      column: nodeLoc.col || 1,
193      reason: logType + ': The `' + tagName + '` tag is not supported.',
194    })
195  }
196
197  // validate dialog tag
198  if (process.env.DEVICE_LEVEL === DEVICE_LEVEL.RICH && oneChildNode.includes(tagName)) {
199    checkOneChild(tagName, children, pos, log)
200  }
201
202  // preprocess attribute
203  if (!depends[tagName] && typeof tagName === 'string') {
204    depends.push(tagName)
205  }
206  const domNodeAttrs = domNode.attrs || []
207  const domNodeAttrName = []
208  for (const attr of domNodeAttrs) {
209    domNodeAttrName.push(attr.name.toLowerCase())
210  }
211  setDebugLine(jsonTemplate, relativePath, nodeLoc.line)
212
213  validateAliasTagMap(tagName, jsonTemplate, nodeLoc, log)
214  validateTagWithoutRoot(domNode, tagName, domNodeAttrName, nodeLoc, log)
215  validateName(tagName, children, nodeLoc, log)
216  validateForAttr(tagName, domNodeAttrs, domNodeAttrName, nodeLoc, log)
217  validatorLite(tagName, domNodeAttrs, domNodeAttrName, log, nodeLoc)
218}
219
220function validateName(tagName, children, nodeLoc, log) {
221  validateAtomicTag(tagName, children, nodeLoc, log)
222  validateTagWithAll(tagName, children, nodeLoc, log)
223}
224
225function validateForAttr(tagName, domNodeAttrs, domNodeAttrName, nodeLoc, log) {
226  validateAttrTagMap(tagName, domNodeAttrName, nodeLoc, log)
227  validateDefaultAttrTagMap(tagName, domNodeAttrs, domNodeAttrName, nodeLoc, log)
228  validateRequireAttrTagMap(tagName, domNodeAttrName, nodeLoc, log)
229  validateEnumAttrTagMap(tagName, domNodeAttrName, domNodeAttrs, nodeLoc, log)
230  validateFuncAttrTagMap(tagName, domNodeAttrName, domNodeAttrs, nodeLoc, log)
231  validateEventsTagMap(tagName, domNodeAttrName, nodeLoc, log)
232}
233
234/**
235 * verify the dialog tag
236 *
237 * @param {String} tagName
238 * @param {Array} children
239 * @param {Object} nodeLoc
240 * @param {Array} log
241 */
242function checkOneChild(name, children, pos, log) {
243  const oneChild = children.filter((child) => {
244      return child && child.nodeName !== '#text' && child.nodeName !== '#comment'
245  })
246  if (oneChild.length > 1) {
247    log.push({
248      line: pos.line || 1,
249      column: pos.col || 1,
250      reason: 'ERROR: The `' + name + '` tag can have only one child node.'
251    })
252  }
253}
254
255/**
256 * whether it can be root
257 * or be the root, check the
258 * attr can not have
259 *
260 * @param {Object} domNode
261 * @param {String} tagName
262 * @param {Object} domNodeAttrName
263 * @param {Object} nodeLoc
264 * @param {Object} log
265 */
266function validateTagWithoutRoot(domNode, tagName, domNodeAttrName, nodeLoc, log) {
267  if (domNode._isroot) {
268    if (tagWithoutRoot.indexOf(tagName) !== -1) {
269      log.push({
270        line: nodeLoc.line || 1,
271        column: nodeLoc.col || 1,
272        reason: 'ERROR: component `' + tagName + '` can not as root component.',
273      })
274    }
275    if (rootAttrTagMap[tagName]) {
276      rootAttrTagMap.forEach(function(attrName) {
277        if (domNodeAttrName[attrName]) {
278          log.push({
279            line: nodeLoc.line || 1,
280            column: nodeLoc.col || 1,
281            reason: 'ERROR: root node `' + tagName + '` can not use attr `' + attrName + '`',
282          })
283        }
284      })
285    }
286  }
287}
288
289/**
290 * whether tag can have alias name
291 *
292 * @param {String} tagName
293 * @param {Object} result
294 * @param {Object} nodeLoc
295 * @param {Object} log
296 */
297function validateAliasTagMap(tagName, result, nodeLoc, log) {
298  if (aliasTagMap[tagName]) {
299    if (tagName !== 'img') {
300      log.push({
301        line: nodeLoc.line || 1,
302        column: nodeLoc.col || 1,
303        reason: 'NOTE: tag name `' + tagName + '` is automatically changed to `' + aliasTagMap[tagName] + '`',
304      })
305    }
306    tagName = aliasTagMap[tagName]
307  }
308  result.type = tagName
309}
310
311/**
312 * whether tag can have children
313 * how many text child the tag have
314 *
315 * @param {String} tagName
316 * @param {Object} children
317 * @param {Object} nodeLoc
318 * @param {Object} log
319 */
320function validateAtomicTag(tagName, children, nodeLoc, log) {
321  if (tagWithoutChild.indexOf(tagName) >= 0 && children.length > 0 && !isSupportedSelfClosing(tagName)) {
322    if (tagWithTextContent.indexOf(tagName) < 0) {
323      children.every(
324          function(child) {
325            return child.nodeName === '#text' || child.nodeName === '#comment' ||
326            log.push({
327              line: nodeLoc.Line || 1,
328              column: nodeLoc.Col || 1,
329              reason: 'ERROR: tag `' + tagName + '` should just have one text node only',
330            })
331          },
332      )
333    } else {
334      children.every(
335          function(child) {
336            return child.nodeName === '#text' || child.nodeName === '#comment' ||
337            log.push({
338              line: nodeLoc.Line || 1,
339              column: nodeLoc.Col || 1,
340              reason: 'ERROR: tag `' + tagName + '` should not have children',
341            })
342          },
343      )
344    }
345  }
346}
347
348/**
349 * verify the tag whether
350 * support its attr
351 *
352 * @param {String} tagName
353 * @param {Object} domNodeAttrName
354 * @param {Object} nodeLoc
355 * @param {Object} log
356 */
357function validateAttrTagMap(tagName, domNodeAttrName, nodeLoc, log) {
358  if (tagName === 'img') {
359    tagName = 'image'
360  }
361  if (attrTagMap[tagName]) {
362    domNodeAttrName.forEach(function(attrKey) {
363      if (attrKey === 'stroke-width') {
364        attrKey = 'strokeWidth'
365      } else if (attrKey === 'fill-opacity') {
366        attrKey = 'fillOpacity'
367      } else if (attrKey === 'stroke-dasharray') {
368        attrKey = 'strokeDasharray'
369      } else if (attrKey === 'stroke-dashoffset') {
370        attrKey = 'strokeDashoffset'
371      } else if (attrKey === 'stroke-linecap') {
372        attrKey = 'strokeLinecap'
373      } else if (attrKey === 'fill-rule') {
374        attrKey = 'fillRule'
375      } else if (attrKey === 'stroke-linejoin') {
376        attrKey = 'strokeLinejoin'
377      } else if (attrKey === 'stroke-miterlimit') {
378        attrKey = 'strokeMiterlimit'
379      } else if (attrKey === 'font-size') {
380        attrKey = 'fontSize'
381      } else if (attrKey === 'stroke-opacity') {
382        attrKey = 'strokeOpacity'
383      }
384      if (!attrKey.match(EVENT_START_REGEXP) && !(attrKey in attrTagMap[tagName])) {
385        if (attrKey in commonTag.attrs) {
386          log.push({
387            line: nodeLoc.line || 1,
388            column: nodeLoc.col || 1,
389            reason: 'WARNING: tag `' + tagName + '` not support attr `' + attrKey + '`',
390          })
391        } else if (!validateDataAttr(attrKey)) {
392          log.push({
393            line: nodeLoc.line || 1,
394            column: nodeLoc.col || 1,
395            reason: 'WARNING: tag `' + tagName + '` use customize attr `' + attrKey + '`',
396          })
397        }
398      }
399    })
400  }
401}
402
403/**
404 * validate the default attr can be
405 * supported by the tag
406 *
407 * @param {String} tagName
408 * @param {Object} domNodeAttrs
409 * @param {Object} domNodeAttrName
410 * @param {Object} nodeLoc
411 * @param {Object} log
412 */
413function validateDefaultAttrTagMap(tagName, domNodeAttrs, domNodeAttrName, nodeLoc, log) {
414  if (defaultAttrTagMap[tagName]) {
415    Object.keys(defaultAttrTagMap[tagName]).forEach(function(attrKey) {
416      const n = domNodeAttrName.indexOf(attrKey)
417      if (n >= 0 && domNodeAttrs[n].value === '') {
418        domNodeAttrs[n].value = defaultAttrTagMap[tagName][attrKey]
419        if (attrKey !== 'else') {
420          log.push({
421            line: nodeLoc.line || 1,
422            column: nodeLoc.col || 1,
423            reason: 'WARNING: tag `' + tagName + '` attr `' + attrKey + '`' + 'is null',
424          })
425        }
426      }
427    })
428  }
429}
430
431/**
432 * validate whether the required tag
433 * included in the tag
434 *
435 * @param {String} tagName
436 * @param {Object} domNodeAttrName
437 * @param {Object} nodeLoc
438 * @param {Object} log
439 */
440function validateRequireAttrTagMap(tagName, domNodeAttrName, nodeLoc, log) {
441  if (requireAttrTagMap[tagName]) {
442    requireAttrTagMap[tagName].forEach(function(attrKey) {
443      if (domNodeAttrName.indexOf(attrKey) < 0) {
444        log.push({
445          line: nodeLoc.line || 1,
446          column: nodeLoc.col || 1,
447          reason: 'ERROR: tag `' + tagName + '` not define attr `' + attrKey + '`',
448        })
449      }
450    })
451  }
452}
453
454/**
455 * validate whether the tag include
456 * illegal enum attr
457 *
458 * @param {String} tagName
459 * @param {Object} domNodeAttrName
460 * @param {Object} nodeAttrs
461 * @param {Object} nodeLoc
462 * @param {Object} log
463 */
464function validateEnumAttrTagMap(tagName, domNodeAttrName, domNodeAttrs, nodeLoc, log) {
465  if (enumAttrTagMap[tagName]) {
466    Object.keys(enumAttrTagMap[tagName]).forEach(function(attrKey) {
467      const index = domNodeAttrName.indexOf(attrKey)
468      if (index >= 0) {
469        const v = domNodeAttrs[index].value.trim()
470        if (!REG_DATA_BINDING.test(v)) {
471          const attrValueEnum = enumAttrTagMap[tagName][attrKey]
472          if (attrValueEnum.indexOf(v) < 0) {
473            domNodeAttrs[index].value = attrValueEnum[0]
474            log.push({
475              line: nodeLoc.line || 1,
476              column: nodeLoc.col || 1,
477              reason: 'ERROR: tag `' + tagName + '` attr `' + attrKey + '` value `' + v + '` is illegal',
478            })
479          }
480        }
481      }
482    })
483  }
484}
485
486/**
487 * verify the validity of func
488 * attr of the tag
489 *
490 * @param {String} tagName
491 * @param {Object} domNodeAttrName
492 * @param {Object} domNodeAttrs
493 * @param {Object} nodeLoc
494 * @param {Object} log
495 */
496function validateFuncAttrTagMap(tagName, domNodeAttrName, domNodeAttrs, nodeLoc, log) {
497  if (funcAttrTagMap[tagName]) {
498    Object.keys(funcAttrTagMap[tagName]).forEach(function(attrKey) {
499      const n = domNodeAttrName.indexOf(attrKey)
500      if (n >= 0) {
501        const v = domNodeAttrs[n].value
502        if (!REG_DATA_BINDING.test(v)) {
503          const func = funcAttrTagMap[tagName][attrKey]
504          const validator = styleValidator.validateFuncMap[func]
505          if (typeof validator == 'function') {
506            const res = validator(v)
507            if (res && res.reason) {
508              domNodeAttrs[n].value = res.value
509              const rea = res.reason(attrKey, v, res.value)
510              log.push({
511                line: nodeLoc.line || 1,
512                column: nodeLoc.col || 1,
513                reason: rea,
514              })
515            }
516          }
517        }
518      }
519    })
520  }
521}
522
523/**
524 * verify the validity of event
525 * attr in this tag
526 *
527 * @param {String} tagName
528 * @param {Object} domNodeAttrName
529 * @param {Object} nodeLoc
530 * @param {Object} log
531 */
532function validateEventsTagMap(tagName, domNodeAttrName, nodeLoc, log) {
533  if (eventsTagMap[tagName]) {
534    const eventArray = eventsTagMap[tagName]
535    domNodeAttrName.forEach(function(attrKey) {
536      if (attrKey.match(EVENT_START_REGEXP)) {
537        const tempName = attrKey.replace(EVENT_START_REGEXP, '')
538        const eventName = (tempName.match(TOUCH_CAPTURE_EVENT_REGEXP) && process.env.DEVICE_LEVEL === DEVICE_LEVEL.RICH &&
539          process.env.PLATFORM_VERSION === PLATFORM.VERSION6) ? tempName : tempName.replace(EVENT_END_REGEXP, '')
540        if (eventArray.indexOf(eventName.toLowerCase()) < 0) {
541          log.push({
542            line: nodeLoc.line || 1,
543            column: nodeLoc.col || 1,
544            reason: 'WARNING: tag `' + tagName + '` not support event `' + eventName + '`',
545          })
546        }
547      }
548    })
549  }
550}
551
552/**
553 * verify validity of tag children
554 * verify the children is validity
555 *
556 * @param {String} tagName
557 * @param {Object} children
558 * @param {Object} nodeLoc
559 * @param {Object} log
560 */
561function validateTagWithAll(tagName, children, nodeLoc, log) {
562  if (tagWithAll.indexOf(tagName) >= 0 && children.length > 0) {
563    const childrenArray = childrenTagMap[tagName]
564    const unSupportedChildrenArray = unSupportedChildren[tagName]
565    const isTabs = tagName === 'tabs'
566    let tabContentCount = void 0
567    let tabBarCount = void 0
568    if (isTabs) {
569      tabBarCount = 0
570      tabContentCount = 0
571    }
572    children.forEach(function(child) {
573      if (tagWithAll.indexOf(child.nodeName) >= 0) {
574        const t = parentsTagMap[child.nodeNames]
575        if ((t && t.indexOf(tagName) < 0) || (childrenArray && childrenArray.indexOf(child.nodeName) < 0) ||
576          (unSupportedChildrenArray && unSupportedChildrenArray.indexOf(child.nodeName) >= 0)) {
577          log.push({
578            line: nodeLoc.line || 1,
579            column: nodeLoc.col || 1,
580            reason: 'ERROR: tag `' + tagName + '` not support child tag `' + child.nodeName + '`',
581          })
582          if (isTabs) {
583            let count = 0
584            if (child.nodeName === 'tab-content') {
585              count = ++tabContentCount
586            }
587            if (child.nodeName === 'tab-bar') {
588              count = ++tabBarCount
589            }
590            if (count > 1) {
591              log.push({
592                line: nodeLoc.line || 1,
593                column: nodeLoc.col || 1,
594                reason: 'ERROR: tag  `tabs` child tag `' + child.nodeName + '` support at most one',
595              })
596            }
597          }
598        }
599      }
600    })
601  }
602}
603
604/**
605 * validate attr of lite device
606 *
607 * @param {String} tagName
608 * @param {Object} domNodeAttrs
609 * @param {Object} domNodeAttrName
610 * @param {Object} log
611 * @param {Object} nodeLoc
612 */
613function validatorLite(tagName, domNodeAttrs, domNodeAttrName, log, nodeLoc) {
614  if (process.env.DEVICE_LEVEL === DEVICE_LEVEL.LITE) {
615    inputLite(tagName, domNodeAttrs, domNodeAttrName, log, nodeLoc)
616
617    // in lite device, not support writing `if` and `for` in the same component
618    if (ismatchIfAndFor(domNodeAttrName)) {
619      log.push({
620        line: nodeLoc.line || 1,
621        column: nodeLoc.col || 1,
622        reason: 'ERROR: in tag `' + tagName + '` not support writing `if` and `for` in the same component',
623      })
624    }
625    // in lite device, class selector does not support data binding
626    classLite(tagName, domNodeAttrs, log, nodeLoc)
627  }
628}
629
630/**
631 * validate the type when
632 * event change
633 *
634 * @param {String} tagName
635 * @param {Object} domNodeAttrs
636 * @param {Object} domNodeAttrName
637 * @param {Object} log
638 * @param {Object} nodeLoc
639 */
640function inputLite(tagName, domNodeAttrs, domNodeAttrName, log, nodeLoc) {
641  let typeValue
642  if (tagName=== 'input') {
643    domNodeAttrs.map(function(item) {
644      if (item.name === 'type') {
645        typeValue = item.value
646      }
647    })
648    if (isMatchChange(typeValue, domNodeAttrName)) {
649      log.push({
650        line: nodeLoc.line || 1,
651        column: nodeLoc.col || 1,
652        reason: 'ERROR: tag `' + tagName + '` not support event `change` when the type is not checkbox and radio',
653      })
654    }
655  }
656}
657
658function isMatchChange(typeValue, nodeAttrName) {
659  return !['checkbox', 'password', 'radio', 'text'].includes(typeValue) && (nodeAttrName.includes('onchange') || nodeAttrName.includes('@change'))
660}
661
662function ismatchIfAndFor(nodeAttrName) {
663  return nodeAttrName.includes('if') && nodeAttrName.includes('for')
664}
665
666function classLite(tagName, domNodeAttrs, log, nodeLoc) {
667  // in lite device, class selector does not support data binding
668  domNodeAttrs.map(function(item) {
669    if (item.name === 'class' && bind.containExp(item.value)) {
670      log.push({
671        line: nodeLoc.line || 1,
672        column: nodeLoc.col || 1,
673        reason: 'ERROR: in tag `' + tagName + '` class selector does not support data binding.',
674      })
675    }
676  })
677}
678
679/**
680 * parse class for tag
681 *
682 * @param {String} classNames
683 * @param {Object} out
684 */
685function validateClass(classNames, out, nodeLoc, relativePath) {
686  classNames = classNames.trim()
687  setDebugLine(out.jsonTemplate, relativePath, nodeLoc.line, classNames)
688  if (bind.containExp(classNames)) {
689    out.jsonTemplate.classList = eval(bind.transExpForList(classNames))
690  } else {
691    out.jsonTemplate.classList = classNames.split(/\s+/)
692  }
693}
694
695function preprocessSystemResourceReference(styleContent, cssStyle) {
696  if (styleContent.length !== 2) {
697    return false;
698  }
699  const key = styleContent[0].trim().replace(/-([a-z])/g, function(s, m) { return m.toUpperCase() })
700  let value = styleContent[1].trim()
701  let result
702  // target format: {{$r("ohos.id_color_background")}} or {{$r('ohos.id_color_background')}}
703  let ResourceRefReg = /{{\s*\$r\s*\(\s*(['"]ohos\.(?<resName>\w+)['"])\s*\)\s*}}/
704  result = ResourceRefReg.exec(value);
705  if (result) {
706    const resourceName = result.groups['resName']
707    if (resourceName && OHOS_THEME_PROP_GROUPS[resourceName]) {
708      cssStyle[key] = "@ohos_id_" + OHOS_THEME_PROP_GROUPS[resourceName]
709      return true
710    }
711  }
712  // target format: {{$r("sys.type.id_color_background")}} or {{$r('sys.type.id_color_background')}}
713  // The "type" field can be "float", "color", "string" and so on.
714  let SysResourceTypeRefReg = /{{\s*\$r\s*\(\s*(['"]sys\.(?<resType>\w+)\.(?<resName>\w+)['"])\s*\)\s*}}/
715  result = SysResourceTypeRefReg.exec(value);
716  if (result) {
717    const resourceName = result.groups['resName']
718    const resourceType = result.groups['resType']
719    if (resourceName && resourceType && OHOS_THEME_PROP_GROUPS[resourceName]) {
720      cssStyle[key] = "@sys." + resourceType + "." + OHOS_THEME_PROP_GROUPS[resourceName]
721      return true
722    }
723  }
724  // target format: {{$r("app.type.developer_defined_color")}} or {{$r('app.type.developer_defined_color')}}
725  // The "type" field can be "float", "color", "string" and so on.
726  let AppResourceTypeRefReg = /{{\s*\$r\s*\(\s*(['"]app\.(?<resType>\w+)\.(?<resName>\w+)['"])\s*\)\s*}}/
727  result = AppResourceTypeRefReg.exec(value);
728  if (result) {
729    const resourceName = result.groups['resName']
730    const resourceType = result.groups['resType']
731    if (resourceName && resourceType) {
732      cssStyle[key] = "@app." + resourceType + "." + resourceName
733      return true
734    }
735  }
736  return false
737}
738
739function validateItem(key, value, nodeLoc, log) {
740  const valuejsonTemplate = styler.validateItem(key, value)
741  if (valuejsonTemplate.log) {
742    log.push({
743      line: nodeLoc.line || 1,
744      column: nodeLoc.col || 1,
745      reason: valuejsonTemplate.log.reason
746    })
747  }
748  return valuejsonTemplate
749}
750
751/**
752 * parse style for tag
753 *
754 * @param {String} css
755 * @param {Object} out
756 * @param {Object} nodeLoc
757 */
758function validateStyle(css, out, nodeLoc, relativePath) {
759  const log = out.log
760  if (css) {
761    const cssStyle = {}
762    const cssArray = css.split(';')
763    processCssArray(css, out, nodeLoc, cssStyle, cssArray, log)
764    out.jsonTemplate.style = cssStyle
765    setDebugLine(out.jsonTemplate, relativePath, nodeLoc.line)
766  } else {
767    log.push({
768      line: nodeLoc.line || 1,
769      column: nodeLoc.col || 1,
770      reason: 'WARNING: style attr is null'
771    })
772  }
773}
774
775function processCssArray(css, out, nodeLoc, cssStyle, cssArray, log) {
776  for (let i = 0; i < cssArray.length; i++) {
777    let styleContent = cssArray[i].trim().split(':')
778    if (preprocessSystemResourceReference(styleContent, cssStyle)) {
779      continue;
780    }
781    if (styleContent.length === 2) {
782      const key = styleContent[0].trim().replace(/-([a-z])/g, function(s, m) { return m.toUpperCase() })
783      let value = bind(styleContent[1].trim(), undefined, true, out, nodeLoc)
784      const contentValue = styleContent[1].trim().toString();
785      if (contentValue.match(/^linear-gradient/) && contentValue.match(/\(.*\{\{.*\}\}.*\)/)) {
786        log.push({
787          line: nodeLoc.line || 1,
788          column: nodeLoc.col || 1,
789          reason: `ERROR: can not bind data for linear-gradient in inline style at ${css}`
790        })
791      }
792      if (key === 'flex' && typeof value === 'string') {
793        expandFlex(key, value, cssStyle, nodeLoc, log)
794      } else {
795        const valuejsonTemplate = validateItem(key, value, nodeLoc, log)
796        value = valuejsonTemplate.value
797        if (['[object Number]', '[object String]', '[object Function]', '[object Object]', '[object Array]'].includes(
798          Object.prototype.toString.call(value))) {
799          expandStyle(key, value, valuejsonTemplate, cssStyle, out, nodeLoc, log)
800        }
801      }
802    }
803    if (styleContent.length > 2) {
804      styleContent[1] = styleContent.slice(1).join(':')
805      styleContent = styleContent.slice(0, 2)
806      if (REG_DATA_BINDING.test(styleContent[1])) {
807        styleContent[0] = styleContent[0].trim().replace(/-([a-z])/g, function(s, m) { return m.toUpperCase() })
808        cssStyle[styleContent[0]] = bind(styleContent[1], undefined, true, out, nodeLoc)
809      } else {
810        // handle special cases like "background-image: url('https://xxx.jpg')"
811        const key = styleContent[0].trim().replace(/-([a-z])/g, function(s, m) { return m.toUpperCase() })
812        if (key === 'backgroundImage') {
813          let value = styleContent[1].trim()
814          const valuejsonTemplate = validateItem(key, value, nodeLoc, log)
815          cssStyle[key] = valuejsonTemplate.value
816        }
817      }
818    }
819  }
820}
821
822function expandFlex(key, value, cssStyle, nodeLoc, log) {
823  const valueArray = value.split(/\s+/)
824  if (valueArray.length === 1) {
825    expandFlexOne(key, valueArray, cssStyle, nodeLoc, log)
826  } else if (valueArray.length === 2) {
827    expandFlexTwo(key, value, valueArray, cssStyle, nodeLoc, log)
828  } else if (valueArray.length === 3) {
829    expandFlexThree(key, value, valueArray, cssStyle, nodeLoc, log)
830  } else {
831    log.push({
832      line: nodeLoc.line,
833      column: nodeLoc.column,
834      reason: 'ERROR: Value `' + value + '` of the `' + key + '` attribute is incorrect.',
835    })
836  }
837}
838
839
840function expandFlexOne(key, valueArray, cssStyle, nodeLoc, log) {
841  const array = ['none', 'auto', 'initial']
842  if (array.includes(valueArray[0])) {
843    cssStyle['flex'] = valueArray[0]
844  } else if (styler.getUnit(valueArray[0]) === 'px') {
845    cssStyle['flexBasis'] = valueArray[0]
846  } else if (styler.getUnit(valueArray[0]) === 'none') {
847    cssStyle['flexGrow'] = valueArray[0]
848  } else {
849    log.push({
850      line: nodeLoc.line,
851      column: nodeLoc.column,
852      reason: 'ERROR: Value `' + valueArray[0] + '` of the `' + key + '` attribute is incorrect.' +
853        'It must be a number, a number with unit `' + 'px`' + ', none, auto, or initial.',
854    })
855  }
856}
857
858function expandFlexTwo(key, value, valueArray, cssStyle, nodeLoc, log) {
859  if (styler.getUnit(valueArray[0]) === 'none') {
860    cssStyle['flexGrow'] = valueArray[0]
861    if (styler.getUnit(valueArray[1]) === 'px') {
862      cssStyle['flexBasis'] = valueArray[1]
863    } else if (styler.getUnit(valueArray[1]) === 'none') {
864      cssStyle['flexShrink'] = valueArray[1]
865    } else {
866      log.push({
867        line: nodeLoc.line,
868        column: nodeLoc.column,
869        reason: 'ERROR: Value `' + value + '` of the `' + key + '` attribute is incorrect. Value `' +
870          valueArray[1] + '` must be a number or a number with unit `' + 'px`.',
871      })
872    }
873  } else {
874    log.push({
875      line: nodeLoc.line,
876      column: nodeLoc.column,
877      reason: 'ERROR: Value `' + value + '` of the `' + key + '` attribute is incorrect. Value `' +
878        valueArray[0] + '` must be a number.',
879    })
880  }
881}
882
883function expandFlexThree(key, value, valueArray, cssStyle, nodeLoc, log) {
884  if (styler.getUnit(valueArray[0]) === 'none' && styler.getUnit(valueArray[1]) === 'none' &&
885    styler.getUnit(valueArray[2]) === 'px') {
886    cssStyle['flexGrow'] = valueArray[0]
887    cssStyle['flexShrink'] = valueArray[1]
888    cssStyle['flexBasis'] = valueArray[2]
889  } else {
890    log.push({
891      line: nodeLoc.line,
892      column: nodeLoc.column,
893      reason: 'ERROR: Value `' + value + '` of the `' + key +
894        '` attribute is incorrect. It must be in the format of (1, 1, 1px).',
895    })
896  }
897}
898
899function expandStyle(key, value, valuejsonTemplate, cssStyle, out, nodeLoc, log) {
900  if (Object.values(styler.util.SPLECIAL_ATTR).includes(key) && typeof value !== 'function') {
901    styler.expand(valuejsonTemplate, key, cssStyle)
902  } else if (process.env.DEVICE_LEVEL === DEVICE_LEVEL.LITE && key === 'border' && typeof value === 'function') {
903    log.push({
904      line: nodeLoc.line || 1,
905      column: nodeLoc.col || 1,
906      reason: 'ERROR: ' + key + ' shorthand inline style does not support data binding.',
907    })
908  } else if (process.env.DEVICE_LEVEL === DEVICE_LEVEL.LITE && out.jsonTemplate.type === 'list' &&
909      key == 'flexDirection' && typeof value === 'function') {
910    log.push({
911      line: nodeLoc.line || 1,
912      column: nodeLoc.col || 1,
913      reason: 'ERROR: `list` tag `flex-direction` inline style does not support data binding.',
914    })
915  } else {
916    cssStyle[key] = value
917  }
918}
919
920/**
921 * parse if and else expression
922 *
923 * @param {String} val
924 * @param {Object} out
925 * @param {Boolean} flag
926 * @param {Object} nodeLoc
927 */
928function validateIf(val, out, flag, nodeLoc, relativePath) {
929  if (!REG_DATA_BINDING.test(val)) {
930    if (val.trim() === 'false') {
931      val = card ? false : '{{false}}'
932    } else if (val.trim() === 'true') {
933      val = card ? true : '{{true}}'
934    } else {
935      const log = out.log
936      log.push({
937        line: nodeLoc.line || 1,
938        column: nodeLoc.col || 1,
939        reason: 'ERROR: if value cannot be ' + val + '. The default value is true or false.'
940      })
941    }
942  }
943  if (flag) {
944    const content = val.replace('{{', '').replace('}}', '')
945    val = !card ? '{{!(' + content + ')}}' :
946      REG_DATA_BINDING.test(val) ? '!{{' + content + '}}' : '!' + content
947  }
948  const value = bind(val, undefined, false, out, nodeLoc)
949  const show = card && flag && /\$f/.test(value) ? value.substr(3, value.length - 4) : value
950  out.jsonTemplate.shown = show
951  setDebugLine(out.jsonTemplate, relativePath, nodeLoc.line)
952}
953
954/**
955 * parse elif and else expression
956 *
957 * @param {String} val
958 * @param {Object} out
959 * @param {Boolean} flag
960 */
961function validateElif(val, out, flag, nodeLoc, relativePath) {
962  if (!REG_DATA_BINDING.test(val)) {
963    val = card ? val : '{{' + val + '}}'
964  }
965  if (val) {
966    if (flag) {
967      const first = val.indexOf('&&')
968      const content = !card ?
969        '{{!(' + val.substr(2, first - 2) + ')' :
970        REG_DATA_BINDING.test(val) ?
971        '!{{' + val.substr(2, first - 2) :
972        '!' + val.substr(0, first)
973      val = content + val.substr(first, val.length)
974    }
975    const value = bind(val, undefined, false, out, nodeLoc)
976    out.jsonTemplate.shown = card && /\$f/.test(value) ? value.substr(3, value.length - 4) : value
977    setDebugLine(out.jsonTemplate, relativePath, nodeLoc.line)
978  }
979}
980
981/**
982 *
983 * parse for expression
984 *
985 * @param {String} val
986 * @param {Object} out
987 * @param {Object} nodeLoc
988 */
989function validateFor(val, out, nodeLoc, relativePath) {
990  const log = out.log
991  if (val) {
992    if (val.startsWith('{{') && val.endsWith('}}')) {
993      val = val.substr(2, val.length - 4)
994    }
995    let forMatch
996    const suffix = val.match(/(?<=in\s)(.*)/)
997    const prefix = val.match(/(.*)(?=\s+in\s+)/)
998    if (suffix && prefix) {
999      const v = prefix[0].match(/\((.*),(.*)\)/)
1000      forMatch = { exp: bind(('{{' + suffix[0].trim() + '}}'), undefined, false, out, nodeLoc) }
1001      if (v) {
1002        forMatch.key = v[1].trim()
1003        forMatch.value = v[2].trim()
1004      } else {
1005        forMatch.value = prefix[0].trim()
1006      }
1007    } else {
1008      forMatch = bind('{{' + val + '}}', undefined, false, out, nodeLoc)
1009    }
1010    out.jsonTemplate.repeat = forMatch
1011    setDebugLine(out.jsonTemplate, relativePath, nodeLoc.line)
1012  } else {
1013    log.push({
1014      line: nodeLoc.line || 1,
1015      column: nodeLoc.col || 1,
1016      reason: 'WARNING: for attr is null',
1017    })
1018  }
1019}
1020
1021/**
1022 * parse id for tag
1023 *
1024 * @param {String} id
1025 * @param {Object} out
1026 */
1027function validateId(id, out, nodeLoc, relativePath) {
1028  if (id) {
1029    out.jsonTemplate.id = REG_DATA_BINDING.test(id) ? bind(id, undefined, true, out, nodeLoc) : id
1030    setDebugLine(out.jsonTemplate, relativePath, nodeLoc.line)
1031    if (!card) {
1032      out.jsonTemplate.attr = out.jsonTemplate.attr || {}
1033      out.jsonTemplate.attr[styler.util.hyphenedToCamelCase('id')] = bind(id, undefined, true, out, nodeLoc)
1034    }
1035  }
1036}
1037
1038/**
1039 * parse append for tag
1040 *
1041 * @param {String} val
1042 * @param {Object} out
1043 */
1044function validateAppend(val, out, nodeLoc, relativePath) {
1045  if (val) {
1046    out.jsonTemplate.append = bind(val, undefined, true, out, nodeLoc)
1047    setDebugLine(out.jsonTemplate, relativePath, nodeLoc.line)
1048  }
1049}
1050
1051const EVENT_START_REGEXP = /^(on:|on|@|grab:)/;
1052const EVENT_END_REGEXP = /(\.bubble|\.capture)$/;
1053const START_CATCH_REGEXP  = /^(grab:)/;
1054const END_CAPTURE_REGEXP = /(\.capture)$/;
1055const TOUCH_EVENT_REGEXP = /^(touch)/;
1056const CLICK_EVENT_REGEXP = /^(click)$/;
1057const TOUCH_CAPTURE_EVENT_REGEXP = /^(?!touch).*?(\.capture)$/;
1058
1059/**
1060 * parse event for tag
1061 *
1062 * @param {String} eventName
1063 * @param {Object} val
1064 * @param {Object} out
1065 */
1066function validateEvent(eventName, val, out, pos, relativePath) {
1067  const tempName = eventName.replace(EVENT_START_REGEXP, '')
1068  const name = (tempName.match(TOUCH_CAPTURE_EVENT_REGEXP) && process.env.PLATFORM_VERSION === PLATFORM.VERSION6 &&
1069    process.env.DEVICE_LEVEL === DEVICE_LEVEL.RICH) ? tempName : tempName.replace(EVENT_END_REGEXP, '')
1070  if (name && val) {
1071    if (card && val.match(/(.*)\((.*)\)/)) {
1072      out.log.push({
1073        line: pos.line || 1,
1074        column: pos.col || 1,
1075        reason: 'ERROR: The event `' + val + '` does not support.'
1076      })
1077      return
1078    }
1079    if (REG_DATA_BINDING.test(val)) {
1080      val = bind.removeAllExpFix(val.trim())
1081    }
1082    // empty event param e.g. onclick="test()"
1083    const empty = val.match(/(.*)\(\)$/)
1084    if (empty) {
1085      val = empty[1]
1086    } else {
1087      const content = val.match(REG_EVENT)
1088      if (content) {
1089        const functionName = content[1]
1090        let paramList = content[2]
1091        if (paramList) {
1092          paramList = transContent.parseExpression(paramList, true)
1093          val = eval('(function (evt) {' + bind('{{' + functionName + '(' + paramList + ',evt)}}',
1094          false, true, out, pos) + '})')
1095        }
1096      }
1097    }
1098    distributeEvent(out, eventName, name, val)
1099    setDebugLine(out.jsonTemplate, relativePath, pos.line)
1100  }
1101}
1102
1103function distributeEvent(out, eventName, name, val) {
1104  if ((process.env.DEVICE_LEVEL === DEVICE_LEVEL.LITE || TOUCH_EVENT_REGEXP.test(name)) &&
1105    process.env.PLATFORM_VERSION !== PLATFORM.VERSION3) {
1106    if (eventName.match(START_CATCH_REGEXP)) {
1107      if (eventName.match(END_CAPTURE_REGEXP)) {
1108        out.jsonTemplate.catchCaptureEvents = out.jsonTemplate.catchCaptureEvents || {}
1109        out.jsonTemplate.catchCaptureEvents[name] = val
1110      } else {
1111        out.jsonTemplate.catchBubbleEvents = out.jsonTemplate.catchBubbleEvents || {}
1112        out.jsonTemplate.catchBubbleEvents[name] = val
1113      }
1114    } else if (eventName.match(END_CAPTURE_REGEXP)) {
1115      out.jsonTemplate.onCaptureEvents = out.jsonTemplate.onCaptureEvents || {}
1116      out.jsonTemplate.onCaptureEvents[name] = val
1117    } else {
1118      out.jsonTemplate.onBubbleEvents = out.jsonTemplate.onBubbleEvents || {}
1119      out.jsonTemplate.onBubbleEvents[name] = val
1120    }
1121  } else if (process.env.DEVICE_LEVEL === DEVICE_LEVEL.RICH && CLICK_EVENT_REGEXP.test(name) &&
1122  !eventName.match(END_CAPTURE_REGEXP) && process.env.PLATFORM_VERSION === PLATFORM.VERSION6) {
1123  if (eventName.match(START_CATCH_REGEXP)) {
1124    out.jsonTemplate.catchBubbleEvents = out.jsonTemplate.catchBubbleEvents || {}
1125    out.jsonTemplate.catchBubbleEvents[name] = val
1126  } else {
1127    out.jsonTemplate.onBubbleEvents = out.jsonTemplate.onBubbleEvents || {}
1128    out.jsonTemplate.onBubbleEvents[name] = val
1129  }
1130} else {
1131  out.jsonTemplate.events = out.jsonTemplate.events || {}
1132  out.jsonTemplate.events[name] = val
1133  }
1134}
1135
1136const transArray = [/\\a/g, /\\b/g, /\\f/g, /\\n/g, /\\r/g, /\\t/g,
1137  /\\v/g, /\\\\/g, /\\'/g, /\\"/g, /\\0/g]
1138const transReplaceArray = ['\a', '\b', '\f', '\n', '\r', '\t',
1139    '\v', '\\', '\'', '\"', '\0']
1140
1141function validateAttr(resourcePath, attrName, attrValue, out, tagName, nodeLoc, relativePath) {
1142  if (card && attrName === 'tid' && bind.isExp(attrValue)) {
1143    out.log.push({
1144      line: nodeLoc.line,
1145      column: nodeLoc.col,
1146      reason: `ERROR: The 'tid' does not support the expression.`
1147    })
1148  }
1149  if (attrName === 'value') {
1150    transArray.forEach((trans, index) => {
1151      attrValue = attrValue.replace(trans, transReplaceArray[index])
1152    })
1153  }
1154  if (attrName && (typeof attrValue === 'string' || typeof attrValue === 'number') && attrValue) {
1155    if (attrName === 'value' && tagName === 'text') {
1156      out.log.push({
1157        line: nodeLoc.line,
1158        column: nodeLoc.col,
1159        reason: 'WARNING: `value` content could be written between <text> and </text>',
1160      })
1161    }
1162
1163    // dealing with <image src = "{{$r('sys.media.sys_background_image')}}"></image>
1164    let result
1165    // target format: {{$r('sys.media.sys_background_image')}}
1166    let SysResourceTypeRefReg = /{{\s*\$r\s*\(\s*(['"]sys\.media\.(?<resName>\w+)['"])\s*\)\s*}}/
1167    result = SysResourceTypeRefReg.exec(attrValue);
1168    getAttrValue(result, attrValue, true);
1169    // target format: {{$r('app.media.customized_background_image')}}
1170    let AppResourceTypeRefReg = /{{\s*\$r\s*\(\s*(['"]app\.media\.(?<resName>\w+)['"])\s*\)\s*}}/
1171    result = AppResourceTypeRefReg.exec(attrValue);
1172    getAttrValue(result, attrValue, false);
1173
1174    if (tagWithPath.indexOf(attrName) >= 0 && attrValue.match(/^\.\.\/|^\.\//)) {
1175      if (!resourcePath) {
1176        return
1177      }
1178      if (attrValue.indexOf('./') >= 0) {
1179        resourcePath = resourcePath.substring(0, resourcePath.lastIndexOf(path.sep) + 1)
1180        attrValue = path.resolve(resourcePath, attrValue)
1181      }
1182      attrValue = attrValue.replace(projectPath, '').replace(/\\/g, '/')
1183    }
1184    out.jsonTemplate.attr = out.jsonTemplate.attr || {}
1185    out.jsonTemplate.attr[attrName.replace(/-([a-z])/g, function (s, m) { return m.toUpperCase() })] =
1186      bind(attrValue, undefined, true, out, nodeLoc)
1187    setDebugLine(out.jsonTemplate, relativePath, nodeLoc.line)
1188  }
1189}
1190
1191function getAttrValue(result, attrValue, isSys) {
1192  if (result && isSys) {
1193    const resourceName = result.groups['resName']
1194    if (resourceName && OHOS_THEME_PROP_GROUPS[resourceName]) {
1195      attrValue = "@sys.media." + OHOS_THEME_PROP_GROUPS[resourceName]
1196    }
1197  } else if (result && !isSys) {
1198    const resourceName = result.groups['resName']
1199    if (resourceName) {
1200      attrValue = "@app.media." + resourceName
1201    }
1202  }
1203}
1204
1205/**
1206 * parse elif attr
1207 *
1208 * @param {Object} preNode
1209 * @param {String} attrValue
1210 * @param {Object} hmlAttrs
1211 * @param {Number} i
1212 * @param {Object} out
1213 */
1214function validateAttrElif(preNode, attrValue, hmlAttrs, i, out, nodeLoc, relativePath) {
1215  if (preNode && preNode.attrs) {
1216    for (let j = 0; j < preNode.attrs.length; j++) {
1217      const prop = preNode.attrs[j]
1218      const newAttrValue = attrValue.replace('{{', '').replace('}}', '')
1219      if (prop.name === 'if') {
1220        const newPropValue = prop.value.replace('{{', '').replace('}}', '')
1221        attrValue = !card ?
1222        '{{' + newAttrValue + '&&!(' + newPropValue + ')}}' :
1223          REG_DATA_BINDING.test(attrValue) ?
1224          '{{' + newAttrValue + '}}' + '&&!{{' + newPropValue + '}}' :
1225          newAttrValue + ' && !' + newPropValue
1226        validateElif(attrValue, out, false, nodeLoc, relativePath)
1227      }
1228      if (prop.name === 'elif') {
1229        const first = prop.value.indexOf('&&')
1230        const firstPropValue = prop.value.substr(0, first).replace("{{", '')
1231        const lengthPropValue = prop.value.substr(first, prop.value.length)
1232        attrValue = !card ?
1233          '{{' + newAttrValue + '&&!(' + firstPropValue + ')' + lengthPropValue :
1234          REG_DATA_BINDING.test(attrValue) ?
1235          '{{' + newAttrValue + '}}' + '&&!{{' + firstPropValue + lengthPropValue :
1236          newAttrValue + ' && !' + firstPropValue + lengthPropValue
1237        validateElif(attrValue, out, false, nodeLoc, relativePath)
1238      }
1239    }
1240  }
1241  hmlAttrs[i].value = attrValue
1242}
1243
1244/**
1245 * parse else attr
1246 *
1247 * @param {Object} preNode
1248 * @param {Object} out
1249 * @param {Object} nodeLoc
1250 */
1251function validateAttrElse(preNode, out, nodeLoc, relativePath) {
1252  if (preNode && preNode.attrs) {
1253    for (let j = 0; j < preNode.attrs.length; j++) {
1254      const prop = preNode.attrs[j]
1255      if (prop.name === 'if') {
1256        validateIf(prop.value, out, true, nodeLoc, relativePath)
1257      }
1258      if (prop.name === 'elif') {
1259        validateElif(prop.value, out, true, nodeLoc, relativePath)
1260      }
1261    }
1262  }
1263}
1264
1265function parseDataAttr(name, value, out) {
1266  const childName = name.replace('data-', '')
1267  out.jsonTemplate.attr = out.jsonTemplate.attr || {}
1268  out.jsonTemplate.attr.$data = out.jsonTemplate.attr.$data || {}
1269  out.jsonTemplate.attr.$data[childName] = bind(value, undefined, true, out)
1270}
1271
1272function isSupportedSelfClosing(tagName) {
1273  if (tagName && typeof tagName === 'string') {
1274    const n = aliasTagMap[tagName]
1275    if (n) {
1276      if (nativeTag[n] && tagSelfClosing.indexOf(n) !== -1) {
1277        return true
1278      }
1279    } else {
1280      if (nativeTag[tagName] && tagSelfClosing.indexOf(tagName) !== -1) {
1281        return true
1282      }
1283    }
1284  }
1285  return false
1286}
1287
1288function validateDataAttr(e) {
1289  return REG_TAG_DATA_ATTR.test(e)
1290}
1291
1292function isReservedTag(tagName) {
1293  return tagWithAll.indexOf(tagName) !== -1
1294}
1295
1296function setDebugLine(jsonTemplate, relativePath, line, className) {
1297  jsonTemplate.attr = jsonTemplate.attr || {}
1298  if (process.env.DEVICE_LEVEL === DEVICE_LEVEL.RICH && process.env.buildMode === 'debug') {
1299    jsonTemplate.attr['debugLine'] =  relativePath + ':' + line
1300    if (className) {
1301      jsonTemplate.attr['className'] = className
1302    }
1303  }
1304}
1305
1306module.exports = {
1307  validateTagName: validateTagName,
1308  validateId: validateId,
1309  validateClass: validateClass,
1310  validateStyle: validateStyle,
1311  validateIf: validateIf,
1312  validateElif: validateElif,
1313  validateFor: validateFor,
1314  validateAppend: validateAppend,
1315  validateEvent: validateEvent,
1316  validateAttr: validateAttr,
1317  validateAttrElse: validateAttrElse,
1318  validateAttrElif: validateAttrElif,
1319  parseDataAttr: parseDataAttr,
1320
1321  isReservedTag: isReservedTag,
1322  isSupportedSelfClosing: isSupportedSelfClosing,
1323  elementNames: elementNames
1324}
1325