• 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 componentValidator = require('./component_validator')
17const Parser = require('../parse/parser/index')
18const path = require('path')
19let compileResult
20const EVENT_START_REGEXP = /^(on:|on|@|grab:)/
21const REGEXP_TEXT = /^#/
22const REGEXP_DATA = /^data-/
23
24/**
25 * Compile html file into ast object.
26 * @param {String} source Hml file content.
27 * @param {Function} operate The second number.
28 * @param {String} filePath File resource path.
29 */
30function parse(source, operate, filePath) {
31  const relativePath = replaceAll(path.sep, '/', path.relative(
32    process.env.aceModuleRoot || process.cwd(), filePath)).replace(path.parse(filePath).ext, '');
33  const result = { jsonTemplate: {}, deps: [], log: [] }
34  compileResult = result
35  const template = hmlParse(source, {
36    sourceCodeLocationInfo: true,
37  })
38  if (checkNullNode(template, operate) || checkRootNode(template, operate, relativePath)) {
39    return
40  }
41  const rootArray = template.childNodes.filter(function(currentValue) {
42    return currentValue.nodeName.indexOf('#') === -1
43  })
44  let rootIndex = 0
45  rootArray.forEach((root, index) => {
46    if(root.tagName !== 'element') {
47      rootIndex = index
48    }
49  })
50  generate(rootArray[rootIndex], filePath, undefined, relativePath)
51  operate(null, compileResult)
52}
53
54
55/**
56 * Use parse5 to get html conversion results.
57 * @param {String} code Hml file content.
58 * @param {Object} config parse5 parseFragment function config.
59 * @return {Object} html conversion results.
60 */
61function hmlParse(code, config) {
62  const parse = new Parser({ sourceCodeLocationInfo: true, componentValidator, compileResult })
63  const res = parse.parseFragment(code, config)
64  return res
65}
66
67/**
68 * Check if the hml file does not contain nodes.
69 * @param {String} template hml conversion results.
70 * @return {Boolean} Check result.
71 */
72function checkNullNode(template, operate) {
73  let errorFlag = false
74  if (template.childNodes.length === 0) {
75    compileResult.log.push({ reason: 'ERROR: parsing hml file failed' })
76    operate(null, compileResult)
77    errorFlag = true
78  }
79  return errorFlag
80}
81
82/**
83 * Check if the root node is legal.
84 * @param {String} template hml conversion results.
85 * @return {Boolean} Check result.
86 */
87function checkRootNode(template, operate, relativePath) {
88  let errorFlag = false
89  const rootArray = template.childNodes.filter(function(currentValue) {
90    return currentValue.nodeName.indexOf('#') === -1
91  })
92  let rootNum = 0
93  rootArray.forEach(root => {
94    if (root.nodeName !== 'element') {
95      rootNum ++
96    } else if (root.attrs && root.attrs.length) {
97      for (let index = 0; index < root.attrs.length; index++) {
98        const element = root.attrs[index];
99        if (element.name === 'name') {
100          componentValidator.elementNames[relativePath] = componentValidator.elementNames[relativePath] || []
101          componentValidator.elementNames[relativePath].push(element.value.toLowerCase())
102          break
103        }
104      }
105    }
106  })
107  if (rootNum !== 1) {
108    if (!rootNum) {
109      compileResult.log.push({
110        reason: 'ERROR: need a legal root node',
111        line: 1,
112        column: 1,
113      })
114    }
115    if (rootNum > 1) {
116      compileResult.log.push({
117        reason: 'ERROR: there can only be one root node',
118        line: 1,
119        column: 1,
120      })
121    }
122    operate(null, compileResult)
123    errorFlag = true
124  }
125  return errorFlag
126}
127/**
128 * Recursively parse every node.
129 * @param {Object} node Nodes that need to be resolved.
130 * @param {String} filePath File resource path.
131 * @param {Object} preNode the previous node.
132 */
133function generate(node, filePath, preNode, relativePath) {
134  componentValidator.validateTagName(node, compileResult, relativePath)
135  if (node.attrs && node.attrs.length !== 0) {
136    checkNodeAttrs(node, filePath, preNode, relativePath)
137  }
138  if (node.childNodes && node.childNodes.length !== 0) {
139    checkNodeChildren(node, filePath, relativePath)
140  }
141}
142
143/**
144 * Parse node properties.
145 * @param {Object} node Nodes that need to be resolved.
146 * @param {String} filePath File resource path.
147 * @param {Object} preNode the previous node.
148 */
149function checkNodeAttrs(node, filePath, preNode, relativePath) {
150  const attributes = node.attrs
151  const pos = {
152    line: node.sourceCodeLocation.startLine,
153    col: node.sourceCodeLocation.endLine,
154  }
155  attributes.forEach((attr, index) => {
156    const attrName = attr.name
157    const attrValue = attr.value
158    switch (attrName) {
159      case 'style':
160        componentValidator.validateStyle(attrValue, compileResult, pos, relativePath)
161        break
162      case 'class':
163        componentValidator.validateClass(attrValue, compileResult, pos, relativePath)
164        break
165      case 'id':
166        componentValidator.validateId(attrValue, compileResult, pos, relativePath)
167        break
168      case 'for':
169        checkAttrFor(node, attributes, pos);
170        componentValidator.validateFor(attrValue, compileResult, pos, relativePath)
171        break
172      case 'if':
173        componentValidator.validateIf(attrValue, compileResult, false, pos, relativePath)
174        break
175      case 'elif':
176        componentValidator.validateAttrElif(preNode, attrValue, attributes, index,
177          compileResult, pos, relativePath)
178        break
179      case 'else':
180        componentValidator.validateAttrElse(preNode, compileResult, pos, relativePath)
181        break
182      case 'append':
183        componentValidator.validateAppend(attrValue, compileResult, pos, relativePath)
184        break
185      default:
186        if (EVENT_START_REGEXP.test(attrName)) {
187          componentValidator.validateEvent(attrName, attrValue, compileResult, pos, relativePath)
188        } else if (REGEXP_DATA.test(attrName)) {
189          componentValidator.parseDataAttr(attrName, attrValue, compileResult, pos, relativePath)
190        } else {
191          componentValidator.validateAttr(
192              filePath,
193              attrName,
194              attrValue,
195              compileResult,
196              node.tagName,
197              pos,
198              relativePath
199          )
200        }
201    }
202  })
203}
204
205function checkAttrFor(node, attributes, pos) {
206  if (process.env.DEVICE_LEVEL === 'card') {
207    let tidExists = false
208    let ifExists = false
209    attributes.forEach(element => {
210      if(element.name === 'tid') {
211        tidExists = true
212      }
213      if(element.name === 'if' || element.name === 'elif' || element.name === 'else') {
214        ifExists = true
215      }
216    })
217    if (!tidExists) {
218      compileResult.log.push({
219        line: pos.line || 1,
220        column: pos.col || 1,
221        reason: `WARNING: The 'tid' is recommended here.`,
222      })
223    }
224    if (ifExists) {
225      compileResult.log.push({
226        line: pos.line || 1,
227        column: pos.col || 1,
228        reason: `ERROR: The 'for' and 'if' cannot be used in the same node.`,
229      })
230    }
231    let isParentFor = false
232    if (node && node.parentNode && node.parentNode.attrs) {
233      node.parentNode.attrs.forEach(element => {
234        if (element.name === 'for') {
235          isParentFor = true
236        }
237      })
238    }
239    if (isParentFor) {
240      compileResult.log.push({
241        line: pos.line || 1,
242        column: pos.col || 1,
243        reason: `ERROR: The nested 'for' is not supported.`,
244      })
245    }
246  }
247}
248
249function replaceAll(find, replace, str) {
250  find = find.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
251  return str.replace(new RegExp(find, 'g'), replace);
252}
253
254/**
255 * Parse node children.
256 * @param {Object} node Nodes that need to be resolved.
257 * @param {String} filePath File resource path.
258 * @param {Object} preNode the previous node.
259 */
260function checkNodeChildren(node, filePath, relativePath) {
261  const children = node.childNodes.filter((child) => {
262    return (
263      (child.nodeName === '#text' && child.value.trim()) ||
264      !REGEXP_TEXT.test(child.nodeName)
265    )
266  })
267  const temp=compileResult.jsonTemplate
268  for (let i = 0; i < children.length; i++) {
269    const child = children[i]
270    let preNode
271    if (i > 0) {
272      preNode = children[i - 1]
273    }
274    if (!REGEXP_TEXT.test(child.nodeName)) {
275      compileResult.jsonTemplate={}
276      temp.children=temp.children||[]
277      temp.children.push(compileResult.jsonTemplate)
278      generate(child, filePath, preNode, relativePath)
279    } else {
280      const pos = {
281        line: child.sourceCodeLocation.startLine,
282        col: child.sourceCodeLocation.endLine,
283      }
284      if (node.nodeName === 'option') {
285        componentValidator.validateAttr(filePath, 'content', child.value,
286          compileResult, child.tagName, pos, relativePath)
287        continue
288      }
289      componentValidator.validateAttr(filePath, 'value', child.value,
290        compileResult, child.tagName, pos, relativePath)
291    }
292  }
293  compileResult.jsonTemplate=temp
294}
295
296module.exports = { parse: parse }
297