Reputation: 115
I have template, and i need to build html. I can parse each element but stuck when trying to build all html. My code did not work for now. And may by all my way is wrong, and it shuld be another method to make html from template. Can any one help?
const data = ['html', [
['head', [
['title', 'titletext'],
]],
['body', { class: 'bodyClass' }, [
['h1', { class: 'headerClass' }, 'h1text'],
['div', [
['span', 'span1text'],
['span', 'span2text'],
]],
]],
]];
const tagBuilder = (tagArray) => {
if (tagArray[1] instanceof Array) {
return tagBuilder(tagArray[1]);
}
if (tagArray[1] instanceof Object) {
return `<${tagArray[0]} class="${tagArray[1].class}">` + String(tagArray[2]) + `</${tagArray[0]}>`;
}
return `<${tagArray[0]}>` + String(tagArray[1]) + `</${tagArray[0]}>`;
}
const htmpBuilder = (data) => {
return data.map(element => tagBuilder(element));
};
document.getElementById("output").textContent = htmpBuilder(data);
<pre id="output">
</pre>
Output i need:
<html>
<head>
<title>titletext</title>
</head>
<body class="bodyClass">
<h1 class="headerClass">h1text</h1>
<div>
<span>span1text</span>
<span>span2text</span>
</div>
</body>
</html>
Upvotes: 2
Views: 483
Reputation: 370689
You might consider creating a structure with objects instead - that way, you can just look up the .class
property of the object, or the .contents
property of the object, or the .tag
property of the object, the process will probably make a whole lot more sense at a glance. The only real non-trivial logic involved is checking if the .contents
is an array (in which case, recursively .map
them by tagBuilder
and join by the empty string):
const data = {
tag: 'html',
contents: [
{
tag: 'head',
contents: [{
tag: 'title',
contents: 'titletext'
}]
}, {
tag: 'body',
class: 'bodyClass',
contents: [{
tag: 'h1',
class: 'headerClass',
contents: 'h1text'
}, {
tag: 'div',
contents: [{
tag: 'span',
contents: 'span1text'
}, {
tag: 'span',
contents: 'span2text'
}]
}]
}
]
};
const tagBuilder = ({ tag, class: className, contents = '' }) => {
const contentsStr = Array.isArray(contents)
? contents.map(tagBuilder).join('')
: contents;
return `<${tag}${className ? ` class="${className}"` : ''}>${contentsStr}</${tag}>`;
}
console.log(tagBuilder(data));
If you need newlines between tags and pretty-printed HTML too (which usually shouldn't matter), then pass along an indent
parameter too, which gets added to the start of tag lines (and the start of end-tag lines, when that tag contains non-text children):
const data = {
tag: 'html',
contents: [
{
tag: 'head',
contents: [{
tag: 'title',
contents: 'titletext'
}]
}, {
tag: 'body',
class: 'bodyClass',
contents: [{
tag: 'h1',
class: 'headerClass',
contents: 'h1text'
}, {
tag: 'div',
contents: [{
tag: 'span',
contents: 'span1text'
}, {
tag: 'span',
contents: 'span2text'
}]
}]
}
]
};
const tagBuilder = ({ tag, class: className, contents = '' }, indent = 0) => {
const contentsStr = Array.isArray(contents)
? '\n' + contents.map(item => ' '.repeat(indent + 2) + tagBuilder(item, indent + 2)).join('\n') + '\n'
: contents;
return `<${tag}${className ? ` class="${className}"` : ''}>${contentsStr}${Array.isArray(contents) ? ' '.repeat(indent) : ''}</${tag}>`;
}
console.log(tagBuilder(data));
If you have to use your original data
structure (which I'd consider to be a mistake, since it requires confusing logic), then extract the tag, contents, and className from the array, then do the exact same thing:
const data = ['html', [
['head', [
['title', 'titletext'],
]],
['body', { class: 'bodyClass' }, [
['h1', { class: 'headerClass' }, 'h1text'],
['div', [
['span', 'span1text'],
['span', 'span2text'],
]],
]],
]];
const tagBuilder = (arr, indent = 0) => {
const tag = arr.shift();
const className = !Array.isArray(arr[0]) && typeof arr[0] === 'object'
? arr.shift().class
: '';
const contents = arr.length
? arr.shift()
: '';
const contentsStr = Array.isArray(contents)
? '\n' + contents.map(item => ' '.repeat(indent + 2) + tagBuilder(item, indent + 2)).join('\n') + '\n'
: contents;
return `<${tag}${className ? ` class="${className}"` : ''}>${contentsStr}${Array.isArray(contents) ? ' '.repeat(indent) : ''}</${tag}>`;
}
console.log(tagBuilder(data));
Upvotes: 4
Reputation: 23814
Your data definition uses too many arrays. It is needless to put the child nodes into an array, because they are already part of an array.
The following data definition is sufficient. It is similar to what the Schemers do in SXML.
const data = ['html',
['head',
['title', 'titletext']],
['body', { class: 'bodyClass' },
['h1', { class: 'headerClass' }, 'h1text'],
['div',
['span', 'span1text'],
['span', 'span2text']]]];
function escape_text (text)
{
let t = document.createTextNode (text);
var s = document.createElement('span');
s.appendChild (t);
return s.innerHTML;
}
function escape_attribute (attr)
{
return JSON.stringify (attr);
}
function element (data)
{
let result = '';
let child = 1;
result += '<' + data[0];
if (typeof data[1] === 'object' && !Array.isArray (data[1]))
{
for (let k in data[1])
{
result += ' ' + k;
result += '=' + escape_attribute (data[1][k]);
}
child++;
}
result += '>';
for (;child < data.length; child++)
{
if (Array.isArray (data[child]))
result += element (data[child]);
else
result += escape_text (data[child]);
}
result += '</' + data[0] + '>';
return result;
}
document.getElementById("html").textContent = element(data);
<pre id="html"></pre>
Upvotes: 1