2

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>
4
  • There is a libray called underscore.js. You can use it underscorejs.org/#template Commented Jan 31, 2020 at 8:05
  • 1
    Please define "did not work". What does the code do instead of the expected results? Any error messages in the console? Commented Jan 31, 2020 at 8:07
  • I got [undefined, undefined] array. And I just don't know how to write htmlBuilder function to use tagBuilder function for every element from template, and return complite html. I need help with function htmlBuilder. Commented Jan 31, 2020 at 8:16
  • 6
    Hey, this is Stack Overflow's 60 millionth question! stackoverflow.com/q/60000000 Commented Jan 31, 2020 at 8:34

2 Answers 2

4

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));

Sign up to request clarification or add additional context in comments.

3 Comments

thank you for helpfull advice. your template looks more informative But now we have what we have. ( And I need build html from templates with this structure.
Well, you did say "And may by all my way is wrong" - trying to construct HTML from such a weird structure is a strange thing to want to do, I'd consider having to work with such a structure in the first place to be one of the big problems. You can still test and extract the types of arguments from the array, then use the same logic as before, but it looks quite ugly IMO
You right this is "weird structure". :( Thank you for help!
1

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>

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.