4

I use javascript and have a div element with some html tags inside it like below, some of the elements are nested and I want to keep them in the same level, first html is like this:

<div>
  <p>
    some text on here 1
    <i>
      some italic 
      <b>text</b>
      here
    </i> text text text
  </p>
  <b>
    some bold text on here
  </b>
</div>

I don't know how I can extract those nested elements and keep them on the same level, something like this:

<div>
  <p>
    some text on here 1
  </p>
  <i>
    some italic 
  </i>
  <b>text</b>
  <i>
    here
  </i>
  <p>
    text text text
  </p>
  <b>
    some bold text on here
  </b>
</div>

update 1

The performance of the code is not too important!

0

2 Answers 2

1

You can clone original node using .cloneNode() with parameter true; iterate the .childNodes of parent <div>, if node .previousElementSibling is not null and .parentElement is not original parent element passed to function push .parentElement .tagName and .textContent to array, else .nodeName is #text, push .textContent to an array; if .nodeName does not equal #text and only #text nodes exist within element, push .outerHTML to array; if node .tagName does not equal #text and node .firstChild node is #text, push node .tagName, .firstChild .textContent, closing tag to array, remove .firstChild; else recursively call function

const clone = document.querySelector("div").cloneNode(true);

let flattenNodes = (el, not = el, res = []) => {
  for (let node of el.childNodes) {
    if (node.nodeName === "#text" && !node.childNodes.length) {
      if (node.previousElementSibling && node.parentElement !== not) {
        let tag = node.parentElement.tagName.toLowerCase();
        res.push(`<${tag}>${node.textContent}</${tag}>`);
      } else {
        res.push(node.textContent);
      }
    }
    if (node.nodeName !== "#text" 
      && Array.from(node.childNodes).every(e => e.nodeName === "#text")) {
        res.push(node.outerHTML);
    } else {
      if (node.tagName && node.tagName !== "#text" 
        && node.firstChild.nodeName === "#text") {
          let tag = node.tagName.toLowerCase();
          res.push(`<${tag}>${node.firstChild.textContent}</${tag}>`);
          node.firstChild.remove();
          flattenNodes(node, not, res);
      }
    }
  }
  return res
}

let res = flattenNodes(clone);

document.querySelector("pre")
.textContent = res.join("");
<div>
  <p>
    some text on here 1
    <i>
      some italic 
      <b>text</b>
      here
    </i> text text text
  </p>
  <b>
    some bold text on here
  </b>
</div>

<pre></pre>

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

1 Comment

everything looks ok especially place of the here and text text text but why this two text is not inside an <i> and <p> tag ?
1

How often does this operation need to happen? Is it a performance hazard or is it a rare occasion?

If it's not a bottleneck, you can just select all descendants of your div and append them to the div. When you append an existing node, it will be automatically moved to the appropriate position.

const myDiv = document
      .querySelector('#my-div')

const allDescendants = Array.from(myDiv.getElementsByTagName('*'))

allDescendants.forEach(d => {
  myDiv.appendChild(d);
});

console.log(myDiv.innerHTML);
<div id="my-div">
  <p>
    some text on here 1
    <i>
      some italic 
      <b>text</b>
      here
    </i> text text text
  </p>
  <b>
    some bold text on here
  </b>
</div>

Not sure if this is intentional but based on your example, you only want to move elements and leave other nodes such as text nodes where they were. A specific example is the here text portion in the first <i>.

To achieve this, you can perform a pre-order DFS through all descendants, and build the new flat tree. Once finished, replace the old element with the new one.

const myDiv = document
      .querySelector('#my-div')

const flatDiv = document.createElement('div')
function recurseAndFlatten(parent) {
  parent.childNodes.forEach(child => {
    if (child.nodeType === Node.ELEMENT_NODE) {
      flatDiv.appendChild(child);
      recurseAndFlatten(child);
    }
  });
}

recurseAndFlatten(myDiv);

myDiv.replaceWith(flatDiv);

console.log(flatDiv.innerHTML);
<div id="my-div">
  <p>
    some text on here 1
    <i>
      some italic 
      <b>text</b>
      here
    </i> text text text
  </p>
  <b>
    some bold text on here
  </b>
</div>

6 Comments

Should "here" be child of first <i> element?
@guest271314 oh I see, I'm not handling text nodes separately. I'll be back
@guest271314, no the here have to be in a new <i> element and place after the bold
@nem035, thanks to send answer, everything looks good but a little problem with the order and place of nodes like here that's you say about it on the comment
@Mojtaba Would here become it's own element if it was on the same line as the <b> before it? In other words, do child text nodes become a new top-level element only when they come after a new line? Or is it after an element?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.