|
|
|
// '<div /><div>a<my-name /><span><h1>Gekko</h1></span>b</div>'; |
|
let input = '<div names={{boo-foo}} age="12" name="{{boo}}12" ...attributes {{foo-bar}} a=2/><a>{{boo}}</a>{{#foo}}<b />{{/foo}}'; |
|
|
|
function parse(input, isNested = false) { |
|
let stack = [['entry', '']]; |
|
let context = 'entry'; |
|
let chain = []; |
|
let str = input.split('').reverse(); |
|
let stackValue = []; |
|
|
|
let createStack = (name) => { |
|
if (name.startsWith('open')) { |
|
chain.push(name.replace('open-','')); |
|
} else if (name.startsWith('close')) { |
|
let found = false; |
|
let sname = name.replace('close-', ''); |
|
chain = chain.reverse().filter(e => { |
|
if (found) { |
|
return true; |
|
} else { |
|
if (e === sname) { |
|
found = true; |
|
return false; |
|
} else { |
|
return true; |
|
} |
|
} |
|
}).reverse(); |
|
} |
|
let prevStack = context; |
|
context = name; |
|
if (stack.length) { |
|
let vals = stackValue.join(''); |
|
if (isNested) { |
|
stack.push([prevStack, vals]); |
|
} else { |
|
let maybeValue = normalize(parse(vals, true)); |
|
if (maybeValue.length === 0) { |
|
maybeValue = vals; |
|
} |
|
stack.push([prevStack, prevStack === 'attribute-value' ? maybeValue : vals]); |
|
} |
|
} |
|
//stack.push(['open', context]); |
|
stackValue = []; |
|
} |
|
|
|
let addValue = (value) => { |
|
stackValue.push(value); |
|
} |
|
|
|
while (str.length) { |
|
let char = str.pop(); |
|
|
|
let nextChar = str[str.length - 1]; |
|
|
|
if (context === 'close-mustache' && char === ' ') { |
|
if (chain.includes('element')) { |
|
createStack('element-attribute'); |
|
} |
|
} |
|
|
|
if (context === 'attribute-value' && char === ' ') { |
|
createStack('element-attribute'); |
|
} else if (char === '=' && context === 'element-attribute') { |
|
createStack('attribute-value'); |
|
} else if (char === ' ' && context === 'open-element') { |
|
createStack('element-attribute') |
|
} else if (char === '{') { |
|
if (context !== 'open-mustache' && context !== 'attribute-value') { |
|
createStack('open-mustache'); |
|
} else { |
|
if (context === 'attribute-value') { |
|
addValue(char); |
|
} |
|
} |
|
} else if (char === '}') { |
|
if (context === 'open-mustache-block' || context === 'close-mustache-block' || context === 'close-mustache' || context === 'attribute-value') { |
|
|
|
if (context === 'attribute-value') { |
|
addValue(char); |
|
} |
|
|
|
} else { |
|
createStack('close-mustache'); |
|
|
|
} |
|
} else if (char === '<' && nextChar !== '/') { |
|
createStack('open-element'); |
|
} else if (char === '<' && nextChar === '/') { |
|
createStack('close-element'); |
|
} else if (char === '/' && nextChar === '>') { |
|
createStack('self-close-element'); |
|
} else if (char === '>') { |
|
createStack('in-element'); |
|
} else if (context === 'open-mustache' && char === '#') { |
|
context = 'open-mustache-block'; |
|
} else if (context === 'open-mustache' && char === '/') { |
|
context = 'close-mustache-block'; |
|
} else { |
|
if (char !== '/') { |
|
addValue(char); |
|
} |
|
} |
|
} |
|
createStack('in-element'); |
|
|
|
return stack; |
|
|
|
} |
|
|
|
|
|
|
|
function normalize(stack) { |
|
let newStack = []; |
|
stack.forEach(([name, value]) => { |
|
if (name !== 'entry') { |
|
newStack.push([name, value]); |
|
} |
|
}); |
|
return newStack; |
|
} |
|
|
|
function domFromStack(stack) { |
|
let cursor = {}; |
|
let cursors = [ |
|
{ |
|
type: 'Template', |
|
content: [], |
|
} |
|
]; |
|
let lastCursor = null; |
|
let last = () => { |
|
return cursors[cursors.length - 1]; |
|
} |
|
stack.forEach(([kind, value]) => { |
|
if (kind === 'element-attribute') { |
|
last().attributes.push({ |
|
type: 'attribute', |
|
name: value, |
|
value: null, |
|
}) |
|
} else if (kind === 'attribute-value') { |
|
last().attributes[last().attributes.length-1].value = Array.isArray(value) ? domFromStack(value).content : value; |
|
} else if (kind === 'open-element') { |
|
cursor = { |
|
type: 'element', |
|
value: value, |
|
content: [], |
|
attributes: [], |
|
modifiers: [], |
|
fillContent: false, |
|
isClosed: false, |
|
selfClosed: false, |
|
} |
|
last().content.push(cursor); |
|
cursors.push(cursor); |
|
} else if (kind === 'open-mustache') { |
|
cursor = { |
|
type: 'mustache', |
|
value: value, |
|
content: [], |
|
attributes: [], |
|
fillContent: false, |
|
isClosed: false, |
|
selfClosed: false, |
|
} |
|
if (last().isClosed) { |
|
last().content.push(cursor); |
|
cursors.push(cursor); |
|
} else { |
|
if (last().fillContent) { |
|
last().content.push(cursor); |
|
} else { |
|
if (last().modifiers) { |
|
last().modifiers.push(cursor); |
|
} else { |
|
// element attribute concat case |
|
last().content.push(cursor); |
|
} |
|
} |
|
} |
|
} else if (kind === 'open-mustache-block') { |
|
cursor = { |
|
type: 'mustache-block', |
|
value: value, |
|
content: [], |
|
fillContent: false, |
|
isClosed: false, |
|
selfClosed: false, |
|
} |
|
last().content.push(cursor); |
|
cursors.push(cursor); |
|
} else if (kind === 'close-mustache') { |
|
if (last().isClosed) { |
|
lastCursor = cursors.pop(); |
|
} |
|
} else if (kind === 'close-mustache-block') { |
|
lastCursor = cursors.pop(); |
|
} else if (kind === 'close-element') { |
|
lastCursor = cursors.pop(); |
|
lastCursor.isClosed = true; |
|
} else if (kind === 'in-element') { |
|
last().fillContent = true; |
|
if (value !== '') { |
|
cursors[cursors.length - 1].content.push({ |
|
type: 'text', |
|
value: value |
|
}); |
|
} |
|
} else if (kind === 'self-close-element') { |
|
lastCursor = cursors.pop(); |
|
lastCursor.selfClosed = true; |
|
lastCursor.isClosed = true; |
|
} |
|
}); |
|
return cursors.find((e) => e.type === 'Template') || lastCursor; |
|
} |
|
|
|
|
|
|
|
console.log(JSON.stringify(normalize(parse(input)), null, 1)); |
|
|
|
console.log(JSON.stringify(domFromStack(normalize(parse(input))), null, 2)); |