Skip to content

Instantly share code, notes, and snippets.

@ClassicOldSong
Last active June 1, 2021 12:22
Show Gist options
  • Save ClassicOldSong/6bba3be1e73af51d50e077ee37b8de6d to your computer and use it in GitHub Desktop.
Save ClassicOldSong/6bba3be1e73af51d50e077ee37b8de6d to your computer and use it in GitHub Desktop.
ef-ast-xml-compiler
/* Usage:
import parseEft from 'eft-parser'
import compileToXML from 'ef-ast-xml-compiler-with-data.js'
const ast = parseEft(`
>h1
.Hello {{name}}!
-mountPoint
+listMountPoint
`)
const compiled = compileToXML(ast, {name: 'Yukino'}, {mountPoint: compileToXML(...), listMountPoint: [compileToXML(...)]})
console.log(compiled)
*/
const selfClosingTags = {
area: true,
base: true,
br: true,
col: true,
command: true,
embed: true,
hr: true,
img: true,
input: true,
keygen: true,
link: true,
menuitem: true,
meta: true,
param: true,
source: true,
track: true,
wbr: true
}
const mixStr = (strs, ...exprs) => {
let string = ''
for (let i = 0; i < exprs.length; i++) {
if (typeof exprs[i] === 'undefined') string += strs[i]
else string += (strs[i] + exprs[i])
}
return string + strs[strs.length - 1]
}
const escapeXML = str => `${str}`
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;")
const getVal = (pathArr, data) => pathArr.reduce((p, c) => (p ? p[c] : p), data)
const getEscapedVal = (expr, data) => {
const [pathArr, defaultVal] = expr
const val = getVal(pathArr, data)
if (typeof val === 'undefined') {
if (typeof defaultVal === 'undefined') return ''
return escapeXML(defaultVal)
}
return escapeXML(val)
}
const getAttr = (attr, val, data) => {
const attrValStrs = [` ${attr}`]
if (val) {
if (typeof val === 'string') {
attrValStrs.push(`="${escapeXML(val)}"`)
} else {
const [strs, ...exprs] = val
if (strs === 0) {
const [pathArr, defaultVal] = exprs[0]
const val = getVal(pathArr, data)
if (typeof val === 'undefined') {
/* eslint {max-depth: 'off'} */
if (typeof defaultVal === 'undefined') attrValStrs.shift()
else {
attrValStrs.push('="')
attrValStrs.push(escapeXML(defaultVal))
attrValStrs.push('"')
}
} else {
attrValStrs.push('="')
attrValStrs.push(escapeXML(val))
attrValStrs.push('"')
}
} else {
attrValStrs.push('="')
const _exprs = exprs.map(expr => getEscapedVal(expr, data))
attrValStrs.push(mixStr(strs, _exprs))
attrValStrs.push('"')
}
}
}
return attrValStrs.join('')
}
const compileToXML = (ast, data = {}, mountPoints = {}) => {
// Handle static string
if (typeof ast === 'string') return escapeXML(ast)
// Handle mount points
const {n: mountPointName, t: mountPointType} = ast
if (typeof mountPointName === 'string' && typeof mountPointType === 'number') {
const mountPoint = mountPoints[mountPointName]
if (!mountPoint) return ''
if (mountPointType) {
return mountPoint.join('')
}
return mountPoint
}
// Handle if is an mustache
if (Array.isArray(ast[0])) {
return getEscapedVal(ast, data)
}
const [{t: tag, a: attrs}, ...children] = ast
const xmlStringFrags = []
if (tag) xmlStringFrags.push(`<${tag}`)
if (attrs) xmlStringFrags.push(...Object.entries(attrs).map(([attr, val]) => getAttr(attr, val, data)))
if (children.length > 0) {
if (tag) xmlStringFrags.push('>')
xmlStringFrags.push(...children.map(item => compileToXML(item, data, mountPoints)))
if (tag) xmlStringFrags.push(`</${tag}>`)
} else if (tag) {
if (selfClosingTags[tag]) xmlStringFrags.push('/>')
else xmlStringFrags.push(`></${tag}>`)
}
return xmlStringFrags.join('')
}
export default compileToXML
/* Usage:
import parseEft from 'eft-parser'
import compileToXML from 'ef-ast-xml-renderer.js'
const ast = parseEft(`
>h1
.Your efml
`)
const compiled = compileToXML(ast)
console.log(compiled)
*/
const selfClosingTags = {
area: true,
base: true,
br: true,
col: true,
command: true,
embed: true,
hr: true,
img: true,
input: true,
keygen: true,
link: true,
menuitem: true,
meta: true,
param: true,
source: true,
track: true,
wbr: true
}
const escapeXML = str => str
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;")
const getAttr = ([attr, val]) => {
if (val) return ` ${attr}="${escapeXML(val)}"`
return ` ${attr}`
}
const compileToXML = (ast) => {
if (typeof ast === 'string') return escapeXML(ast)
const [{t: tag, a: attrs}, ...children] = ast
const xmlStringFrags = []
if (tag) xmlStringFrags.push(`<${tag}`)
if (attrs) xmlStringFrags.push(...Object.entries(attrs).map(getAttr))
if (children.length > 0) {
if (tag) xmlStringFrags.push('>')
xmlStringFrags.push(...children.map(compileToXML))
if (tag) xmlStringFrags.push(`</${tag}>`)
} else if (tag) {
if (selfClosingTags[tag]) xmlStringFrags.push('/>')
else xmlStringFrags.push(`></${tag}>`)
}
return xmlStringFrags.join('')
}
export default compileToXML
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment