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