Добавление массивов в многомерный массив или объект

Я разбираю контент, созданный wysiwyg в виджет оглавления в React.

Пока я просматриваю заголовки и добавляю их в массив.

Как я могу получить их все в один многомерный массив или объект (что лучший способ), чтобы он больше напоминал:

h1-1
    h2-1
        h3-1

h1-2
    h2-2
        h3-2

h1-3
    h2-3
        h3-3

и затем я могу отобразить его с упорядоченным списком в пользовательском интерфейсе.

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)

Ответы

Ответ 1

Я не знаю о вас, но я ненавижу разбор HTML с помощью регулярных выражений. Вместо этого, я думаю, это лучшая идея, чтобы позволить DOM справиться с этим:

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

Ответ 2

Вы можете просто собрать все h* и затем перебрать их, чтобы построить дерево как таковое:

Используя ES6 (я предполагал, что это нормально из вашего использования const и 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);

Но поскольку ваши заголовки html не находятся в самой древовидной структуре (что, я полагаю, является вашим прецедентом), это работает только при определенных предположениях, например, вы не можете иметь <h3> если там не указано <h2> и <h1> выше тот. Он также предположит, что заголовок более низкого уровня всегда будет принадлежать последнему заголовку сразу более высокого уровня.

Если вы хотите далее использовать древовидную структуру, например, для создания репрезентативного упорядоченного списка для TOC, вы можете сделать что-то вроде:

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

Надеюсь, поможет.

Ответ 3

То, что вы хотите сделать, известно как (вариант a) схемы документа, например. создавая вложенный список из заголовков документа, соблюдая их иерархию.

Простая реализация для браузера с использованием API DOM и DOMParser выглядит следующим образом (помещается на HTML-страницу и закодирована в ES5 для легкого тестирования):

<!DOCTYPE html>
<html>
<head>
<title>Document outline</title>
</head>
<body>
<div id="outline"></div>
<script>

// test string wrapped in a document (and body) element
var str = "<html><body><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></body></html>";

// util for traversing a DOM and emit SAX startElement events
function emitSAXLikeEvents(node, handler) {
    handler.startElement(node)
    for (var i = 0; i < node.children.length; i++)
        emitSAXLikeEvents(node.children.item(i), handler)
    handler.endElement(node)
}

var outline = document.getElementById('outline')
var rank = 0
var context = outline
emitSAXLikeEvents(
    (new DOMParser()).parseFromString(str, "text/html").body,
    {
        startElement: function(node) {
            if (/h[1-6]/.test(node.localName)) {
                var newRank = +node.localName.substr(1, 1)

                // set context li node to append
                while (newRank <= rank--)
                    context = context.parentNode.parentNode

                rank = newRank

                // create (if 1st li) or
                // get (if 2nd or subsequent li) ol element
                var ol
                if (context.children.length > 0)
                    ol = context.children[0]
                else {
                    ol = document.createElement('ol')
                    context.appendChild(ol)
                }

                // create and append li with text from
                // heading element
                var li = document.createElement('li')
                li.appendChild(
                  document.createTextNode(node.innerText))
                ol.appendChild(li)

                context = li
            }
        },
        endElement: function(node) {}
    })
</script>
</body>
</html>

Сначала я разбираю фрагмент в Document, а затем startElement() к нему, чтобы создать вызовы, startElement() SAX- startElement(). В функции startElement() ранг элемента заголовка проверяется по рангу последнего созданного элемента списка (если есть). Затем добавляется новый элемент списка на правильном уровне иерархии, и, возможно, элемент ol создается как контейнер для него. Обратите внимание, что алгоритм, поскольку он не будет работать с "прыжком" от h1 до h3 в иерархии, но может быть легко адаптирован.

Если вы хотите создать контур/таблицу содержимого на узле.js, код может быть выполнен для работы на стороне сервера, но для этого требуется приличная библиотека разбора HTML (например, для полиса заполнения DOMParser для node.js). Существуют также https://github.com/h5o/h5o-js и https://github.com/hoyois/html5outliner пакеты для создания контуров, хотя я их не тестировал. Эти пакеты, возможно, также могут обрабатывать угловые случаи, такие как элементы заголовка в iframe и элементы quote которые вы обычно не хотите в контуре вашего документа.

Тема создания контура HTML5 имеет долгую историю; см., например. http://html5doctor.com/computer-says-no-to-html5-document-outline/. Практика использования HTML4 без использования корней для секционирования (в HTML5 parlance) обертки для секционирования и размещения заголовков и контента на одном уровне иерархии называется "плоской земной разметкой". SGML имеет функцию RANK для работы с элементами ранжирования H1, H2 и т.д., И их можно сделать так, чтобы выводить пропущенные элементы section, таким образом, автоматически создавать контуры с HTML4-подобной "плоской разметки" в простых случаях (например, где только section или другой отдельный элемент разрешен как корень секционирования).

Ответ 4

Я использую одно регулярное выражение, чтобы получить содержимое <hx></hx> а затем отсортировать их по x с помощью методов Array.reduce.


Вот база, но она еще не закончилась:

// The string you need to parse
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>";

// The regex that will cut down the <hx>something</hx>
const regex = /<h[0-9]{1}>(.*?)<\/h[0-9]{1}>/g;

// We get the matches now
const matches = str.match(regex);

// We match the hx togethers as requested
const matchesSorted = Object.values(matches.reduce((tmp, x) => {
  // We get the number behind hx ---> the x
  const hNumber = x[2];

  // If the container do not exist, create it
  if (!tmp[hNumber]) {
    tmp[hNumber] = [];
  }

  // Push the new parsed content into the array
  // 4 is to start after <hx>
  // length - 9 is to get all except <hx></hx>
  tmp[hNumber].push(x.substr(4, x.length - 9));

  return tmp;
}, {}));

console.log(matchesSorted);

Ответ 5

Я пишу этот код с JQuery. (Пожалуйста, не DV. Возможно, кому-то нужен ответ jquery позже)

Эта рекурсивная функция создает li строки, и если один элемент имеет некоторый childern, он преобразует их в ol.

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

function strToList(stri) {
  const tags = $(stri);

  function partToList(el) {
    let output = "<li>";
    if ($(el).children().length) {
      output += "<ol>";
      $(el)
        .children()
        .each(function() {
          output += partToList($(this));
        });
      output += "</ol>";
    } else {
      output += $(el).text();
    }
    return output + "</li>";
  }

  let output = "<ol>";

  tags.each(function(itm) {
    output += partToList($(this));
  });

  return output + "</ol>";
}

$("#output").append(strToList(str));
li {
  padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="output"></div>