16a23e08bSopenharmony_ci/*
26a23e08bSopenharmony_ci * Copyright (c) 2021 Huawei Device Co., Ltd.
36a23e08bSopenharmony_ci * Licensed under the Apache License, Version 2.0 (the "License");
46a23e08bSopenharmony_ci * you may not use this file except in compliance with the License.
56a23e08bSopenharmony_ci * You may obtain a copy of the License at
66a23e08bSopenharmony_ci *
76a23e08bSopenharmony_ci *     http://www.apache.org/licenses/LICENSE-2.0
86a23e08bSopenharmony_ci *
96a23e08bSopenharmony_ci * Unless required by applicable law or agreed to in writing, software
106a23e08bSopenharmony_ci * distributed under the License is distributed on an "AS IS" BASIS,
116a23e08bSopenharmony_ci * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
126a23e08bSopenharmony_ci * See the License for the specific language governing permissions and
136a23e08bSopenharmony_ci * limitations under the License.
146a23e08bSopenharmony_ci */
156a23e08bSopenharmony_ci
166a23e08bSopenharmony_ciconst componentValidator = require('./component_validator')
176a23e08bSopenharmony_ciconst parse5 = require('../parse/index')
186a23e08bSopenharmony_ciconst path = require('path')
196a23e08bSopenharmony_cilet compileResult
206a23e08bSopenharmony_ciconst EVENT_START_REGEXP = /^(on:|on|@|grab:)/
216a23e08bSopenharmony_ciconst REGEXP_TEXT = /^#/
226a23e08bSopenharmony_ciconst REGEXP_DATA = /^data-/
236a23e08bSopenharmony_ci
246a23e08bSopenharmony_ci/**
256a23e08bSopenharmony_ci * Compile html file into ast object.
266a23e08bSopenharmony_ci * @param {String} source Hml file content.
276a23e08bSopenharmony_ci * @param {Function} operate The second number.
286a23e08bSopenharmony_ci * @param {String} filePath File resource path.
296a23e08bSopenharmony_ci */
306a23e08bSopenharmony_cifunction parse(source, operate, filePath) {
316a23e08bSopenharmony_ci  const relativePath = replaceAll(path.sep, '/', path.relative(
326a23e08bSopenharmony_ci    process.env.aceModuleRoot || process.cwd(), filePath)).replace(path.parse(filePath).ext, '');
336a23e08bSopenharmony_ci  const result = { jsonTemplate: {}, deps: [], log: [] }
346a23e08bSopenharmony_ci  compileResult = result
356a23e08bSopenharmony_ci  const template = hmlParse(source, {
366a23e08bSopenharmony_ci    sourceCodeLocationInfo: true,
376a23e08bSopenharmony_ci    componentValidator,
386a23e08bSopenharmony_ci    compileResult,
396a23e08bSopenharmony_ci  })
406a23e08bSopenharmony_ci  if (checkNullNode(template, operate) || checkRootNode(template, operate, relativePath)) {
416a23e08bSopenharmony_ci    return
426a23e08bSopenharmony_ci  }
436a23e08bSopenharmony_ci  const rootArray = template.childNodes.filter(function(currentValue) {
446a23e08bSopenharmony_ci    return currentValue.nodeName.indexOf('#') === -1
456a23e08bSopenharmony_ci  })
466a23e08bSopenharmony_ci  let rootIndex = 0
476a23e08bSopenharmony_ci  rootArray.forEach((root, index) => {
486a23e08bSopenharmony_ci    if(root.tagName !== 'element') {
496a23e08bSopenharmony_ci      rootIndex = index
506a23e08bSopenharmony_ci    }
516a23e08bSopenharmony_ci  })
526a23e08bSopenharmony_ci  generate(rootArray[rootIndex], filePath, undefined, relativePath)
536a23e08bSopenharmony_ci  operate(null, compileResult)
546a23e08bSopenharmony_ci}
556a23e08bSopenharmony_ci
566a23e08bSopenharmony_ci
576a23e08bSopenharmony_ci/**
586a23e08bSopenharmony_ci * Use parse5 to get html conversion results.
596a23e08bSopenharmony_ci * @param {String} code Hml file content.
606a23e08bSopenharmony_ci * @param {Object} config parse5 parseFragment function config.
616a23e08bSopenharmony_ci * @return {Object} html conversion results.
626a23e08bSopenharmony_ci */
636a23e08bSopenharmony_cifunction hmlParse(code, config) {
646a23e08bSopenharmony_ci  const res = parse5.parseFragment(code, config)
656a23e08bSopenharmony_ci  return res
666a23e08bSopenharmony_ci}
676a23e08bSopenharmony_ci
686a23e08bSopenharmony_ci/**
696a23e08bSopenharmony_ci * Check if the hml file does not contain nodes.
706a23e08bSopenharmony_ci * @param {String} template hml conversion results.
716a23e08bSopenharmony_ci * @return {Boolean} Check result.
726a23e08bSopenharmony_ci */
736a23e08bSopenharmony_cifunction checkNullNode(template, operate) {
746a23e08bSopenharmony_ci  let errorFlag = false
756a23e08bSopenharmony_ci  if (template.childNodes.length === 0) {
766a23e08bSopenharmony_ci    compileResult.log.push({ reason: 'ERROR: parsing hml file failed' })
776a23e08bSopenharmony_ci    operate(null, compileResult)
786a23e08bSopenharmony_ci    errorFlag = true
796a23e08bSopenharmony_ci  }
806a23e08bSopenharmony_ci  return errorFlag
816a23e08bSopenharmony_ci}
826a23e08bSopenharmony_ci
836a23e08bSopenharmony_ci/**
846a23e08bSopenharmony_ci * Check if the root node is legal.
856a23e08bSopenharmony_ci * @param {String} template hml conversion results.
866a23e08bSopenharmony_ci * @return {Boolean} Check result.
876a23e08bSopenharmony_ci */
886a23e08bSopenharmony_cifunction checkRootNode(template, operate, relativePath) {
896a23e08bSopenharmony_ci  let errorFlag = false
906a23e08bSopenharmony_ci  const rootArray = template.childNodes.filter(function(currentValue) {
916a23e08bSopenharmony_ci    return currentValue.nodeName.indexOf('#') === -1
926a23e08bSopenharmony_ci  })
936a23e08bSopenharmony_ci  let rootNum = 0
946a23e08bSopenharmony_ci  rootArray.forEach(root => {
956a23e08bSopenharmony_ci    if (root.nodeName !== 'element') {
966a23e08bSopenharmony_ci      rootNum ++
976a23e08bSopenharmony_ci    } else if (root.attrs && root.attrs.length) {
986a23e08bSopenharmony_ci      for (let index = 0; index < root.attrs.length; index++) {
996a23e08bSopenharmony_ci        const element = root.attrs[index];
1006a23e08bSopenharmony_ci        if (element.name === 'name') {
1016a23e08bSopenharmony_ci          componentValidator.elementNames[relativePath] = componentValidator.elementNames[relativePath] || []
1026a23e08bSopenharmony_ci          componentValidator.elementNames[relativePath].push(element.value.toLowerCase())
1036a23e08bSopenharmony_ci          break
1046a23e08bSopenharmony_ci        }
1056a23e08bSopenharmony_ci      }
1066a23e08bSopenharmony_ci    }
1076a23e08bSopenharmony_ci  })
1086a23e08bSopenharmony_ci  if (rootNum !== 1) {
1096a23e08bSopenharmony_ci    if (!rootNum) {
1106a23e08bSopenharmony_ci      compileResult.log.push({
1116a23e08bSopenharmony_ci        reason: 'ERROR: need a legal root node',
1126a23e08bSopenharmony_ci        line: 1,
1136a23e08bSopenharmony_ci        column: 1,
1146a23e08bSopenharmony_ci      })
1156a23e08bSopenharmony_ci    }
1166a23e08bSopenharmony_ci    if (rootNum > 1) {
1176a23e08bSopenharmony_ci      compileResult.log.push({
1186a23e08bSopenharmony_ci        reason: 'ERROR: there can only be one root node',
1196a23e08bSopenharmony_ci        line: 1,
1206a23e08bSopenharmony_ci        column: 1,
1216a23e08bSopenharmony_ci      })
1226a23e08bSopenharmony_ci    }
1236a23e08bSopenharmony_ci    operate(null, compileResult)
1246a23e08bSopenharmony_ci    errorFlag = true
1256a23e08bSopenharmony_ci  }
1266a23e08bSopenharmony_ci  return errorFlag
1276a23e08bSopenharmony_ci}
1286a23e08bSopenharmony_ci/**
1296a23e08bSopenharmony_ci * Recursively parse every node.
1306a23e08bSopenharmony_ci * @param {Object} node Nodes that need to be resolved.
1316a23e08bSopenharmony_ci * @param {String} filePath File resource path.
1326a23e08bSopenharmony_ci * @param {Object} preNode the previous node.
1336a23e08bSopenharmony_ci */
1346a23e08bSopenharmony_cifunction generate(node, filePath, preNode, relativePath) {
1356a23e08bSopenharmony_ci  componentValidator.validateTagName(node, compileResult, relativePath)
1366a23e08bSopenharmony_ci  if (node.attrs && node.attrs.length !== 0) {
1376a23e08bSopenharmony_ci    checkNodeAttrs(node, filePath, preNode, relativePath)
1386a23e08bSopenharmony_ci  }
1396a23e08bSopenharmony_ci  if (node.childNodes && node.childNodes.length !== 0) {
1406a23e08bSopenharmony_ci    checkNodeChildren(node, filePath, relativePath)
1416a23e08bSopenharmony_ci  }
1426a23e08bSopenharmony_ci}
1436a23e08bSopenharmony_ci
1446a23e08bSopenharmony_ci/**
1456a23e08bSopenharmony_ci * Parse node properties.
1466a23e08bSopenharmony_ci * @param {Object} node Nodes that need to be resolved.
1476a23e08bSopenharmony_ci * @param {String} filePath File resource path.
1486a23e08bSopenharmony_ci * @param {Object} preNode the previous node.
1496a23e08bSopenharmony_ci */
1506a23e08bSopenharmony_cifunction checkNodeAttrs(node, filePath, preNode, relativePath) {
1516a23e08bSopenharmony_ci  const attributes = node.attrs
1526a23e08bSopenharmony_ci  const pos = {
1536a23e08bSopenharmony_ci    line: node.sourceCodeLocation.startLine,
1546a23e08bSopenharmony_ci    col: node.sourceCodeLocation.endLine,
1556a23e08bSopenharmony_ci  }
1566a23e08bSopenharmony_ci  attributes.forEach((attr, index) => {
1576a23e08bSopenharmony_ci    const attrName = attr.name
1586a23e08bSopenharmony_ci    const attrValue = attr.value
1596a23e08bSopenharmony_ci    switch (attrName) {
1606a23e08bSopenharmony_ci      case 'style':
1616a23e08bSopenharmony_ci        componentValidator.validateStyle(attrValue, compileResult, pos, relativePath)
1626a23e08bSopenharmony_ci        break
1636a23e08bSopenharmony_ci      case 'class':
1646a23e08bSopenharmony_ci        componentValidator.validateClass(attrValue, compileResult, pos, relativePath)
1656a23e08bSopenharmony_ci        break
1666a23e08bSopenharmony_ci      case 'id':
1676a23e08bSopenharmony_ci        componentValidator.validateId(attrValue, compileResult, pos, relativePath)
1686a23e08bSopenharmony_ci        break
1696a23e08bSopenharmony_ci      case 'for':
1706a23e08bSopenharmony_ci        checkAttrFor(node, attributes, pos);
1716a23e08bSopenharmony_ci        componentValidator.validateFor(attrValue, compileResult, pos, relativePath)
1726a23e08bSopenharmony_ci        break
1736a23e08bSopenharmony_ci      case 'if':
1746a23e08bSopenharmony_ci        componentValidator.validateIf(attrValue, compileResult, false, pos, relativePath)
1756a23e08bSopenharmony_ci        break
1766a23e08bSopenharmony_ci      case 'elif':
1776a23e08bSopenharmony_ci        componentValidator.validateAttrElif(preNode, attrValue, attributes, index,
1786a23e08bSopenharmony_ci          compileResult, pos, relativePath)
1796a23e08bSopenharmony_ci        break
1806a23e08bSopenharmony_ci      case 'else':
1816a23e08bSopenharmony_ci        componentValidator.validateAttrElse(preNode, compileResult, pos, relativePath)
1826a23e08bSopenharmony_ci        break
1836a23e08bSopenharmony_ci      case 'append':
1846a23e08bSopenharmony_ci        componentValidator.validateAppend(attrValue, compileResult, pos, relativePath)
1856a23e08bSopenharmony_ci        break
1866a23e08bSopenharmony_ci      default:
1876a23e08bSopenharmony_ci        if (EVENT_START_REGEXP.test(attrName)) {
1886a23e08bSopenharmony_ci          componentValidator.validateEvent(attrName, attrValue, compileResult, pos, relativePath)
1896a23e08bSopenharmony_ci        } else if (REGEXP_DATA.test(attrName)) {
1906a23e08bSopenharmony_ci          componentValidator.parseDataAttr(attrName, attrValue, compileResult, pos, relativePath)
1916a23e08bSopenharmony_ci        } else {
1926a23e08bSopenharmony_ci          componentValidator.validateAttr(
1936a23e08bSopenharmony_ci              filePath,
1946a23e08bSopenharmony_ci              attrName,
1956a23e08bSopenharmony_ci              attrValue,
1966a23e08bSopenharmony_ci              compileResult,
1976a23e08bSopenharmony_ci              node.tagName,
1986a23e08bSopenharmony_ci              pos,
1996a23e08bSopenharmony_ci              relativePath
2006a23e08bSopenharmony_ci          )
2016a23e08bSopenharmony_ci        }
2026a23e08bSopenharmony_ci    }
2036a23e08bSopenharmony_ci  })
2046a23e08bSopenharmony_ci}
2056a23e08bSopenharmony_ci
2066a23e08bSopenharmony_cifunction checkAttrFor(node, attributes, pos) {
2076a23e08bSopenharmony_ci  if (process.env.DEVICE_LEVEL === 'card') {
2086a23e08bSopenharmony_ci    let tidExists = false
2096a23e08bSopenharmony_ci    let ifExists = false
2106a23e08bSopenharmony_ci    attributes.forEach(element => {
2116a23e08bSopenharmony_ci      if(element.name === 'tid') {
2126a23e08bSopenharmony_ci        tidExists = true
2136a23e08bSopenharmony_ci      }
2146a23e08bSopenharmony_ci      if(element.name === 'if' || element.name === 'elif' || element.name === 'else') {
2156a23e08bSopenharmony_ci        ifExists = true
2166a23e08bSopenharmony_ci      }
2176a23e08bSopenharmony_ci    })
2186a23e08bSopenharmony_ci    if (!tidExists) {
2196a23e08bSopenharmony_ci      compileResult.log.push({
2206a23e08bSopenharmony_ci        line: pos.line || 1,
2216a23e08bSopenharmony_ci        column: pos.col || 1,
2226a23e08bSopenharmony_ci        reason: `WARNING: The 'tid' is recommended here.`,
2236a23e08bSopenharmony_ci      })
2246a23e08bSopenharmony_ci    }
2256a23e08bSopenharmony_ci    if (ifExists) {
2266a23e08bSopenharmony_ci      compileResult.log.push({
2276a23e08bSopenharmony_ci        line: pos.line || 1,
2286a23e08bSopenharmony_ci        column: pos.col || 1,
2296a23e08bSopenharmony_ci        reason: `ERROR: The 'for' and 'if' cannot be used in the same node.`,
2306a23e08bSopenharmony_ci      })
2316a23e08bSopenharmony_ci    }
2326a23e08bSopenharmony_ci    let isParentFor = false
2336a23e08bSopenharmony_ci    if (node && node.parentNode && node.parentNode.attrs) {
2346a23e08bSopenharmony_ci      node.parentNode.attrs.forEach(element => {
2356a23e08bSopenharmony_ci        if (element.name === 'for') {
2366a23e08bSopenharmony_ci          isParentFor = true
2376a23e08bSopenharmony_ci        }
2386a23e08bSopenharmony_ci      })
2396a23e08bSopenharmony_ci    }
2406a23e08bSopenharmony_ci    if (isParentFor) {
2416a23e08bSopenharmony_ci      compileResult.log.push({
2426a23e08bSopenharmony_ci        line: pos.line || 1,
2436a23e08bSopenharmony_ci        column: pos.col || 1,
2446a23e08bSopenharmony_ci        reason: `ERROR: The nested 'for' is not supported.`,
2456a23e08bSopenharmony_ci      })
2466a23e08bSopenharmony_ci    }
2476a23e08bSopenharmony_ci  }
2486a23e08bSopenharmony_ci}
2496a23e08bSopenharmony_ci
2506a23e08bSopenharmony_cifunction replaceAll(find, replace, str) {
2516a23e08bSopenharmony_ci  find = find.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
2526a23e08bSopenharmony_ci  return str.replace(new RegExp(find, 'g'), replace);
2536a23e08bSopenharmony_ci}
2546a23e08bSopenharmony_ci
2556a23e08bSopenharmony_ci/**
2566a23e08bSopenharmony_ci * Parse node children.
2576a23e08bSopenharmony_ci * @param {Object} node Nodes that need to be resolved.
2586a23e08bSopenharmony_ci * @param {String} filePath File resource path.
2596a23e08bSopenharmony_ci * @param {Object} preNode the previous node.
2606a23e08bSopenharmony_ci */
2616a23e08bSopenharmony_cifunction checkNodeChildren(node, filePath, relativePath) {
2626a23e08bSopenharmony_ci  const children = node.childNodes.filter((child) => {
2636a23e08bSopenharmony_ci    return (
2646a23e08bSopenharmony_ci      (child.nodeName === '#text' && child.value.trim()) ||
2656a23e08bSopenharmony_ci      !REGEXP_TEXT.test(child.nodeName)
2666a23e08bSopenharmony_ci    )
2676a23e08bSopenharmony_ci  })
2686a23e08bSopenharmony_ci  const temp=compileResult.jsonTemplate
2696a23e08bSopenharmony_ci  for (let i = 0; i < children.length; i++) {
2706a23e08bSopenharmony_ci    const child = children[i]
2716a23e08bSopenharmony_ci    let preNode
2726a23e08bSopenharmony_ci    if (i > 0) {
2736a23e08bSopenharmony_ci      preNode = children[i - 1]
2746a23e08bSopenharmony_ci    }
2756a23e08bSopenharmony_ci    if (!REGEXP_TEXT.test(child.nodeName)) {
2766a23e08bSopenharmony_ci      compileResult.jsonTemplate={}
2776a23e08bSopenharmony_ci      temp.children=temp.children||[]
2786a23e08bSopenharmony_ci      temp.children.push(compileResult.jsonTemplate)
2796a23e08bSopenharmony_ci      generate(child, filePath, preNode, relativePath)
2806a23e08bSopenharmony_ci    } else {
2816a23e08bSopenharmony_ci      const pos = {
2826a23e08bSopenharmony_ci        line: child.sourceCodeLocation.startLine,
2836a23e08bSopenharmony_ci        col: child.sourceCodeLocation.endLine,
2846a23e08bSopenharmony_ci      }
2856a23e08bSopenharmony_ci      if (node.nodeName === 'option') {
2866a23e08bSopenharmony_ci        componentValidator.validateAttr(filePath, 'content', child.value,
2876a23e08bSopenharmony_ci          compileResult, child.tagName, pos, relativePath)
2886a23e08bSopenharmony_ci        continue
2896a23e08bSopenharmony_ci      }
2906a23e08bSopenharmony_ci      componentValidator.validateAttr(filePath, 'value', child.value,
2916a23e08bSopenharmony_ci        compileResult, child.tagName, pos, relativePath)
2926a23e08bSopenharmony_ci    }
2936a23e08bSopenharmony_ci  }
2946a23e08bSopenharmony_ci  compileResult.jsonTemplate=temp
2956a23e08bSopenharmony_ci}
2966a23e08bSopenharmony_ci
2976a23e08bSopenharmony_cimodule.exports = { parse: parse }
298