skip to Main Content

Help me please! There is a source line html from which it is necessary to create Element sequentially. That is, there is a way to create everything at once, simply by parsing the string through innerHTML or DOMParser, but then to iterate over all the elements, you need getElementsByTagName("*"), which is very slow. Is there a way to build html sequentially so that it takes each element in a loop?

Let’s say we have an html string:

<div>abc<span class="abc">123</span>567</div>

And, from it to make some code like this:

let parentDiv = document.createElement("div");
for(let i=0;i<3;i++){
  switch(i){
    case 0:
     let txt1 = document.createTextNode("abc")
     parentDiv.appendChild(txt1);
    break
    case 1:
      let el1 = document.createElement("div");
      el1.setAttribute("class","abc");
      for(let j=0;j<1;j++){
        switch(j){
          case 0:
            let txt1 = document.createTextNode("123")
            el1.appendChild(txt1);
          break;
        }
      }
      parentDiv.appendChild(el1);
     break
     case 2:
      let txt2 = document.createTextNode("567")
      parentDiv.appendChild(txt2);
     break
  }
}

Only to generate such code for any valid html string.

I will be very grateful for the answer!

I think that a template will be needed here to create such code, as well as a regular expression regexp for tags. It’s just hard to figure out how to do it.

2

Answers


  1. The problem

    Note: I am not sure if I understand your question correctly. If I misunderstood, please correct me, and I will edit the answer.

    If in understand your question correctly, you want to create a JavaScript function which takes a string with an HTML code (e.g. '<div>abc</div>') as an argument and creates the corresponding DOM elements. The string can contain any HTML code (e.g. divs, spans, tables, etc.). Moreover, you do not want the elements to be created at once but sequentially in a loop.

    Furthermore, you do not want to use the innerHTML property to create all of the elements at once and then iterate over all the elements using getElementsByTagName("*") method because you feel that it is too slow.

    Answer

    I think that the JavaScript function you want to create could be created but it would be a VERY complex function. You would first need to identify the individual elements (possibly using regular expressions to find opening and closing tags) including their respective classes and other attributes (such as id, name, href, etc.). Then you would need to create the individual DOM elements in a loop.

    I think that much simpler, safer (less prone to errors), and more clear way would be to use the innerHTML property and the getElementsByTagName("*") method. I do not know why you think that the method is very slow. According to this test, getElementsByTagName("*") is not slow at all as you can run millions of these operations in a single second. I think that your custom complex JavaScript function would probably be much slower because it would have to complete all of the steps described in the previous paragraph.

    If you decide to use the innerHTML property and the getElementsByTagName("*") method, the code could look like this:

    function parse_html_string(html_string){
      let parent_div = document.createElement("parent_div");
      parent_div.innerHTML = html_string  // create DOM elements inside a parent div
      all_elems = parent_div.getElementsByTagName("*")  // get all elements inside the parent div
      for (let i=0, max=all_elems.length; i < max; i++) {
        console.log(all_elems[i].nodeName)  // do whatever you need with each element, e.g. print its type to console
      }
      document.body.appendChild(parent_div);
    }
    
    var html_string = '<div>abc<span class="abc">123</span>567</div>'
    parse_html_string(html_string)
    <html>
      <head>
        <meta charset="UTF-8">
      </head>
      <body>
        <script src="code.js"></script>
      </body>
    </html>

    However, if you really want to create your custom function, you can try to read through the source code of JourneyApps’s domparser and use it to create your own custom function.

    Additional notes

    I do not understand, why you would use the loops and switch statements in your example JavaScript code. Your JavaScript code could be rewritten simply as:

    let parentDiv = document.createElement("div");
    
    parentDiv.appendChild(document.createTextNode("abc"));
    
    let el1 = document.createElement("div");
    el1.setAttribute("class","abc");
    el1.appendChild(document.createTextNode("123"));
    parentDiv.appendChild(el1);
    
    parentDiv.appendChild(document.createTextNode("567"));
    Login or Signup to reply.
  2. After parsing the string to HTML, you can use the TreeWalker API to sequentially walk (in preorder traversal) the DOM subtree of a node via nextNode(). As you visit each node, you can check the nodeType and other properties to determine what code to generate (i.e. if it’s an element node, use getAttributeNames() to extract each attribute and generate the code for setting each attribute, and tagName to get the tags, etc.).

    The only caveat is that the API doesn’t expose an obvious method for determining whether the next Node is a child, sibling or somewhere up the parent chain. So you will likely need a way of tracking the current node’s depth, but I believe if you implement this code generation somewhat recursively, you should get that behavior for free.

    Here is what the very high-level pseudocode might look like:

    let htmlStr = '<div>abc<span class="abc">123</span>567</div>';
    let htmlElt = parseToHTML(htmlStr);
    let walker = document.createTreeWalker(htmlElt, NodeFilter.SHOW_ALL);
    
    function codeGenSeq(rootNode) {
      if (!rootNode) return "";
    
      let output = "";
      // ... check nodeType
      // ... extract attributes, tagName, nodeValue, etc. depending on the type
      // ... append the code for generating the node to `output`
      let childNodes = Array.from(rootNode.childNodes);
      walker.nextNode();
      if (childNodes.length > 0) {
        // add for loop and switch statement scaffolding to `output`
        while (childNodes.includes(walker.currentNode)) {
          let innerCodeGen = codeGenSeq(walker.currentNode);
          // ... wrap `innerCodeGen` in a new case block and append to `output`
          walker.nextNode();
        }
      }
     
      return output;
    }
    
    codeGenSeq(walker.currentNode);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search