UNPKG

@adobe/htlengine

Version:

Javascript Based HTL (Sightly) parser

213 lines (207 loc) 7.63 kB
/* * Copyright 2018 Adobe. All rights reserved. * This file is licensed to you under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. You may obtain a copy * of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ const NullLiteral = require('../parser/htl/nodes/NullLiteral'); const ArrayLiteral = require('../parser/htl/nodes/ArrayLiteral'); const NumericConstant = require('../parser/htl/nodes/NumericConstant'); const StringConstant = require('../parser/htl/nodes/StringConstant'); const BooleanConstant = require('../parser/htl/nodes/BooleanConstant'); const PropertyAccess = require('../parser/htl/nodes/PropertyAccess'); const Expression = require('../parser/htl/nodes/Expression'); const Interpolation = require('../parser/htl/nodes/Interpolation'); const Identifier = require('../parser/htl/nodes/Identifier'); const PropertyIdentifier = require('../parser/htl/nodes/PropertyIdentifier'); const MapLiteral = require('../parser/htl/nodes/MapLiteral'); const BinaryOperation = require('../parser/htl/nodes/BinaryOperation'); const MultiOperation = require('../parser/htl/nodes/MultiOperation'); const UnaryOperation = require('../parser/htl/nodes/UnaryOperation'); const UnaryOperator = require('../parser/htl/nodes/UnaryOperator'); const TernaryOperation = require('../parser/htl/nodes/TernaryOperation'); const RuntimeCall = require('../parser/htl/nodes/RuntimeCall'); const ExpressionNode = require('../parser/htl/nodes/ExpressionNode'); /** * Visitor that recreates the parsed text and stores it in {@code result}. * @type {module.DebugVisitor} */ module.exports = class ExpressionFormatter { static escapeVariable(ident) { return ident.replace(':', '$'); } static format(expression) { if (expression instanceof ExpressionNode) { const v = new ExpressionFormatter(); expression.accept(v); return v.result; } return expression; } constructor() { this.result = ''; } visit(node) { if (node.hasParens) { this.result += '('; } if (node instanceof Interpolation) { node.fragments.forEach((frag) => { if (frag.expression) { frag.expression.accept(this); } else { this.result += `${frag.text}`; } }); } else if (node instanceof ArrayLiteral) { this.result += '['; node.children.forEach((i, idx) => { if (idx > 0) { this.result += ', '; } i.accept(this); }); this.result += ']'; } else if (node instanceof PropertyAccess) { if (node.target) { node.target.accept(this); } const property = node.property instanceof ExpressionNode ? '' : node.property; if (property) { this.result += property; } else { this.result += '['; node.property.accept(this); this.result += ']'; } } else if (node instanceof BinaryOperation) { if (node.operator.sym === 'in') { this.result += '$.col.rel('; node.leftOperand.accept(this); this.result += ','; node.rightOperand.accept(this); this.result += ')'; } else { const fn = node.operator.isNumeric ? 'Number' : ''; this.result += `${fn}(`; node.leftOperand.accept(this); this.result += `) ${node.operator.sym} ${fn}(`; node.rightOperand.accept(this); this.result += ')'; } } else if (node instanceof MultiOperation) { const fn = node.operator.isNumeric ? 'Number' : ''; node.operands.forEach((op, idx) => { if (idx > 0) { this.result += ` ${node.operator.sym} `; } this.result += `${fn}(`; op.accept(this); this.result += ')'; }); } else if (node instanceof TernaryOperation) { node.condition.accept(this); this.result += ' ? '; node.thenBranch.accept(this); this.result += ' : '; node.elseBranch.accept(this); } else if (node instanceof UnaryOperation) { if (node.operator === UnaryOperator.LENGTH) { this.result += '$.col.len('; node.target.accept(this); this.result += ')'; } else if (node.operator === UnaryOperator.IS_EMPTY) { this.result += '$.col.empty('; node.target.accept(this); this.result += ')'; } else if (node.operator === UnaryOperator.NOT && node.target instanceof ArrayLiteral) { this.result += '!'; node.target.accept(this); this.result += '.length'; } else { this.result += node.operator.sym; node.target.accept(this); } } else if (node instanceof Expression) { this.result += '${'; node.root.accept(this); Object.keys(node.options).forEach((key, idx) => { const option = node.options[key]; if (idx === 0) { this.result += ' @ '; } else { this.result += ', '; } this.result += key; if (!(option instanceof NullLiteral)) { this.result += '='; option.accept(this); } }); this.result += '}'; } else if (node instanceof PropertyIdentifier) { this.result += ExpressionFormatter.escapeVariable(node.name.toLowerCase()); } else if (node instanceof Identifier) { this.result += ExpressionFormatter.escapeVariable(node.name); } else if (node instanceof NumericConstant) { this.result += node.value; } else if (node instanceof StringConstant) { this.result += JSON.stringify(node.text); } else if (node instanceof BooleanConstant) { this.result += node.value; } else if (node instanceof NullLiteral) { this.result += 'null'; } else if (node instanceof RuntimeCall) { // special handling for xss. todo: make more generic let delim = ''; if (node.functionName === 'xss') { this.result += 'yield $.xss('; } else if (node.functionName === 'listInfo') { this.result += '$.listInfo('; } else if (node.functionName === 'use') { this.result += 'yield $.use('; } else if (node.functionName === 'call') { throw new Error('$.call not supported via expression formatter'); } else if (node.functionName === 'resource') { this.result += 'yield $.resource('; } else if (node.functionName === 'include') { this.result += 'yield $.include('; } else { this.result += `$.exec("${node.functionName}"`; delim = ', '; } if (node.expression) { this.result += delim; node.expression.accept(this); delim = ', '; } node.args.forEach((arg) => { this.result += delim; arg.accept(this); delim = ', '; }); this.result += ')'; } else if (node instanceof MapLiteral) { this.result += '{'; Object.keys(node.map).forEach((key) => { const exp = node.map[key]; if (exp) { this.result += `"${key}": `; node.map[key].accept(this); this.result += ', '; } }); this.result += '}'; } else { throw new Error(`unexpected node: ${node.constructor.name}`); } if (node.hasParens) { this.result += ')'; } } };