UNPKG

hbs2htl

Version:
193 lines (191 loc) 8.6 kB
const htmlParser = require('htmlparser2'); const TagStack = require('./tagStack'); const ERROR_MSG = 'Some problem occurred while parsing handlebar template.'; module.exports = class CreateDom { constructor(htmlString, options = {}, contextualTags) { const { resolveToData } = options; this.importCounter = 0; this.contextualTags = contextualTags || new TagStack(); this.newText = ''; const firstLevelRef = ['data']; this.firstLevelRef = firstLevelRef; const parser = new htmlParser.Parser({ onopentag: (tag, props) => { if (tag === 'hbs-render') { this.newText += this.getRenderingElement(props, 'data-text', firstLevelRef, resolveToData); } else if (tag === 'hbs-if') { let slyTag = '<sly'; const exprKeys = Object.keys(props); if (exprKeys.length > 1) { throw new Error(ERROR_MSG); } exprKeys.forEach(key => { slyTag += ` data-sly-test="${this.getRenderingElement(props, key, firstLevelRef, resolveToData)}"`; }); slyTag += '>'; this.newText += slyTag; } else if (tag === 'hbs-each') { const contextRef = `list${this.contextualTags.length}`; let slyTag = '<sly'; const exprKeys = Object.keys(props); if (exprKeys.length > 1) { throw new Error(ERROR_MSG); } exprKeys.forEach(key => { slyTag += ` data-sly-list.${contextRef}="${this.getRenderingElement(props, key, firstLevelRef, resolveToData)}"`; }); slyTag += '>'; this.newText += slyTag; this.contextualTags.push({ tag, contextRef }); } else if (tag === 'hbs-import') { const refs = []; const assignments = []; Object.keys(props).filter(key => !['data-path', 'data-template'].includes(key)).forEach(key => { const currentValue = decodeURIComponent(props[key]).trim(); const [v, assignable] = currentValue.split('='); if (assignable) { assignments.push({ v, assignable }); } else { refs.push(currentValue); } if (refs.length > 1) { throw new Error(ERROR_MSG); } }); const params = refs.map(ref => `data=${this.getRenderedKey(ref, firstLevelRef, resolveToData)}`); assignments.forEach(({ v, assignable }) => { if (assignable.startsWith('"') || assignable.startsWith('\'')) { params.push(`${v}=${assignable}`); } else { params.push(`${v}=${this.getRenderedKey(assignable, firstLevelRef, resolveToData)}`); } }); let slyTag = '<sly'; slyTag += ` data-sly-use.module${this.importCounter}="${props['data-path']}"`; slyTag += ` data-sly-call="$\{module${this.importCounter++}.${props['data-template']} @ ${ params.join(',') }\}">`; this.newText += slyTag; } else if (tag.startsWith('hbs-') && typeof options.transform === 'function') { this.newText += options.transform.apply(this, [{ tag, props, type: 'tag', event: 'onopentag' }]); } else { const attr = (Object.keys(props).map(key => { let value = decodeURIComponent(props[key]).trim(); if (value.startsWith('<')) { value = (new CreateDom(value, options, this.contextualTags)).html(); } return `${key}="${value}"`; }).join(' ')).trim(); this.newText += `<${tag}${attr.length ? ` ${attr}` : ''}>`; } }, onclosetag: tag => { if (['hbs-if', 'hbs-each', 'hbs-import'].includes(tag)) { if (tag === 'hbs-each') { this.contextualTags.pop(); } this.newText += '</sly>'; } else if (tag.startsWith('hbs-') && typeof options.transform === 'function') { this.newText += options.transform.apply(this, [{ tag, props, type: 'tag', event: 'onclosetag' }]); } else if (!['hbs-render'].includes(tag)) { this.newText += `</${tag}>`; } }, ontext: text => { this.newText += text; } }, { xmlMode: true, recognizeSelfClosing: true }); parser.write(htmlString); parser.end(); } html() { return this.newText; } getTemplate(templateName) { return `<sly data-sly-template.${templateName}="$\{@ ${this.firstLevelRef.join(',')}\}">\n${this.html()}\n</sly>`; } resolveNumber(str) { if (typeof str === 'string') { const strParts = str.split(',').filter(part => !isNaN(part.trim())); if (strParts.length) { return (strParts[0] + strParts.slice(1).map(part => { part = part.trim(); if (isNaN(part)) { return `['${part}']`; } return `[${part}]`; }).join('')); } } return str; } getRenderedKey(renderedKey, firstLevelRef, resolveToData) { let rootRef = 'data'; if (this.contextualTags.length > 0) { let topIndex = 0; if (renderedKey.includes('@root')) { if (renderedKey.startsWith('@root.')) { renderedKey = renderedKey.replace('@root.', `${rootRef}.`); } if (renderedKey === '@root') { renderedKey = rootRef; } } else { let hasSubProp = renderedKey.startsWith('@'); if (hasSubProp) { renderedKey = renderedKey.substring(1); } while (renderedKey.indexOf('../') === 0) { topIndex += 1; renderedKey = renderedKey.substring(3); } const referredContext = this.contextualTags.item(topIndex); if (referredContext && referredContext.contextRef) { rootRef = `${referredContext.contextRef}${hasSubProp ? 'Item' : ''}`; } } } return this.resolveNumber(this.handleThis(renderedKey, firstLevelRef, resolveToData, rootRef)); } getRenderingVar(props, key, firstLevelRef, resolveToData) { let renderedKey = decodeURIComponent(props[key]).trim(); return this.getRenderedKey(renderedKey, firstLevelRef, resolveToData); } getRenderingElement(props, ...args) { return `$\{${this.getRenderingVar(props, ...args)}${props.context ? ` @ context='${props.context}'` : ''}\}`; } handleThis(text, firstLevelRef, resolveToData, rootRef = 'data') { if (text.startsWith('this.')) { text = `${rootRef}.${text.substring('this.'.length)}`; } if (text === 'this') { text = rootRef; } if (!resolveToData) { const dataParts = text.split('.'); if (dataParts.length > 1) { if (!firstLevelRef.includes(dataParts[0]) && dataParts[0] !== rootRef) { firstLevelRef.push(dataParts[0]); } return text; } } if (firstLevelRef.includes(text) || text === rootRef) { return text; } return `${rootRef}.${text}`; } }