src/node.js
function attrEscape (str) {
return str.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/"/g, '"')
.replace(/\t/g, '	')
.replace(/\n/g, '
')
.replace(/\r/g, '
');
}
function escape (str) {
return str.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/\r/g, '
');
}
export const HEAD = Symbol('head');
export function props (...keys) {
return (target) => {
for (const key of keys) {
target.elements.push({
key,
kind: 'method',
placement: 'prototype',
descriptor: {
get () {
if (this.attributes) {
return this.attributes[key];
}
},
set (value) {
if (this.attributes === undefined) {
this.attributes = {};
}
this.attributes[key] = value;
},
configurable: true,
enumerable: true
}
});
}
return target;
};
}
export class Node {
constructor (attributes = {}, children = [], name) {
for (const key of Object.keys(attributes)) {
this[key] = attributes[key];
}
this.children = children;
this.__name = name || this.constructor.name.substring(1);
}
render () {
function walk (tree) {
const name = tree.__name;
const { attributes, children } = tree;
const tokens = [];
if (tree[HEAD]) {
tokens.push(tree[HEAD]);
}
tokens.push(`<${name}`);
for (const key of Object.keys(attributes || {})) {
let v = attributes[key];
if (v === undefined) continue;
if (typeof v === 'string') {
v = attrEscape(v);
}
if (typeof v === 'boolean') {
v = v ? 1 : 0;
}
tokens.push(` ${key}="${v}"`);
}
if (!children.length) {
tokens.push('/>');
return tokens;
}
tokens.push('>');
for (const child of children) {
if (child instanceof Node) {
tokens.push(child.render());
} else if (typeof child === 'string') {
tokens.push(escape(child));
} else {
tokens.push(child.toString());
}
}
tokens.push(`</${name}>`);
return tokens;
}
return walk(this).join('');
}
}