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