Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add arrays into multi-dimensional array or object

Tags:

javascript

I'm parsing content generated by a wysiwyg into a table of contents widget in React.

So far I'm looping through the headers and adding them into an array.

How can I get them all into one multi-dimensional array or object (what's the best way) so that it looks more like:

h1-1
    h2-1
        h3-1

h1-2
    h2-2
        h3-2

h1-3
    h2-3
        h3-3

and then I can render it with an ordered list in the UI.

const str = "<h1>h1-1</h1><h2>h2-1</h2><h3>h3-1</h3><p>something</p><h1>h1-2</h1><h2>h2-2</h2><h3>h3-2</h3>";

const patternh1 = /<h1>(.*?)<\/h1>/g;
const patternh2 = /<h2>(.*?)<\/h2>/g;
const patternh3 = /<h3>(.*?)<\/h3>/g;

let h1s = [];
let h2s = [];
let h3s = [];

let matchh1, matchh2, matchh3;

while (matchh1 = patternh1.exec(str))
    h1s.push(matchh1[1])

while (matchh2 = patternh2.exec(str))
    h2s.push(matchh2[1])
    
while (matchh3 = patternh3.exec(str))
    h3s.push(matchh3[1])
    
console.log(h1s)
console.log(h2s)
console.log(h3s)
like image 235
totalnoob Avatar asked May 17 '18 07:05

totalnoob


People also ask

What is multidimensional array object?

A multidimensional array in MATLAB® is an array with more than two dimensions. In a matrix, the two dimensions are represented by rows and columns. Each element is defined by two subscripts, the row index and the column index.

Can an array be multi-dimensional?

A multi-dimensional array is an array with more than one level or dimension. For example, a 2D array, or two-dimensional array, is an array of arrays, meaning it is a matrix of rows and columns (think of a table).


2 Answers

I don't know about you, but I hate parsing HTML using regexes. Instead, I think it's a better idea to let the DOM handle this:

const str = `<h1>h1-1</h1>
  <h3>h3-1</h3>
  <h3>h3-2</h3>
  <p>something</p>
  <h1>h1-2</h1>
  <h2>h2-2</h2>
  <h3>h3-2</h3>`;

const wrapper = document.createElement('div');
wrapper.innerHTML = str.trim();

let tree = [];
let leaf = null;

for (const node of wrapper.querySelectorAll("h1, h2, h3, h4, h5, h6")) {
  const nodeLevel = parseInt(node.tagName[1]);
  const newLeaf = {
    level: nodeLevel,
    text: node.textContent,
    children: [],
    parent: leaf
  };

  while (leaf && newLeaf.level <= leaf.level)
    leaf = leaf.parent;

  if (!leaf)
    tree.push(newLeaf);
  else
    leaf.children.push(newLeaf);

  leaf = newLeaf;
}

console.log(tree);

This answer does not require h3 to follow h2; h3 can follow h1 if you so please. If you want to turn this into an ordered list, that can also be done:

const str = `<h1>h1-1</h1>
      <h3>h3-1</h3>
      <h3>h3-2</h3>
      <p>something</p>
      <h1>h1-2</h1>
      <h2>h2-2</h2>
      <h3>h3-2</h3>`;

const wrapper = document.createElement('div');
wrapper.innerHTML = str.trim();

let tree = [];
let leaf = null;

for (const node of wrapper.querySelectorAll("h1, h2, h3, h4, h5, h6")) {
  const nodeLevel = parseInt(node.tagName[1]);
  const newLeaf = {
    level: nodeLevel,
    text: node.textContent,
    children: [],
    parent: leaf
  };

  while (leaf && newLeaf.level <= leaf.level)
    leaf = leaf.parent;

  if (!leaf)
    tree.push(newLeaf);
  else
    leaf.children.push(newLeaf);

  leaf = newLeaf;
}


const ol = document.createElement("ol");

(function makeOl(ol, leaves) {
  for (const leaf of leaves) {
    const li = document.createElement("li");
    li.appendChild(new Text(leaf.text));

    if (leaf.children.length > 0) {
      const subOl = document.createElement("ol");
      makeOl(subOl, leaf.children);
      li.appendChild(subOl);
    }

    ol.appendChild(li);
  }
})(ol, tree);

// add it to the DOM
document.body.appendChild(ol);

// or get it as text
const result = ol.outerHTML;

Since the HTML is parsed by the DOM and not by a regex, this solution will not encounter any errors if the h1 tags have attributes, for example.

like image 124
laptou Avatar answered Oct 12 '22 22:10

laptou


You can simply gather all h* and then iterate over them to construct a tree as such:

Using ES6 (I inferred this is ok from your usage of const and let)

const str = `
    <h1>h1-1</h1>
    <h2>h2-1</h2>
    <h3>h3-1</h3>
    <p>something</p>
    <h1>h1-2</h1>
    <h2>h2-2</h2>
    <h3>h3-2</h3>
`
const patternh = /<h(\d)>(.*?)<\/h(\d)>/g;

let hs = [];

let matchh;

while (matchh = patternh.exec(str))
    hs.push({ lev: matchh[1], text: matchh[2] })

console.log(hs)

// constructs a tree with the format [{ value: ..., children: [{ value: ..., children: [...] }, ...] }, ...]
const add = (res, lev, what) => {
  if (lev === 0) {
    res.push({ value: what, children: [] });
  } else {
    add(res[res.length - 1].children, lev - 1, what);
  }
}

// reduces all hs found into a tree using above method starting with an empty list
const tree = hs.reduce((res, { lev, text }) => {
  add(res, lev-1, text);
  return res;
}, []);

console.log(tree);

But because your html headers are not in a tree structure themselves (which I guess is your use case) this only works under certain assumptions, e.g. you cannot have a <h3> unless there's a <h2> above it and a <h1> above that. It will also assume a lower-level header will always belong to the latest header of an immediately higher level.

If you want to further use the tree structure for e.g. rendering a representative ordered-list for a TOC, you can do something like:

// function to render a bunch of <li>s
const renderLIs = children => children.map(child => `<li>${renderOL(child)}</li>`).join('');

// function to render an <ol> from a tree node
const renderOL = tree => tree.children.length > 0 ? `<ol>${tree.value}${renderLIs(tree.children)}</ol>` : tree.value;

// use a root node for the TOC
const toc = renderOL({ value: 'TOC', children: tree });

console.log(toc);

Hope it helps.

like image 45
Ovidiu Dolha Avatar answered Oct 12 '22 22:10

Ovidiu Dolha