toc_obj.js 1.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. 'use strict';
  2. const { DomHandler, DomUtils, Parser } = require('htmlparser2');
  3. const escapeHTML = require('./escape_html');
  4. const nonWord = /^\s*[^a-zA-Z0-9]\s*$/;
  5. const parseHtml = html => {
  6. const handler = new DomHandler(null, {});
  7. new Parser(handler, {}).end(html);
  8. return handler.dom;
  9. };
  10. const getId = ({ attribs = {}, parent }) => {
  11. return attribs.id || (!parent ? '' : getId(parent));
  12. };
  13. /**
  14. * Identify a heading that to be unnumbered or not.
  15. */
  16. const isUnnumbered = ({ attribs = {} }) => {
  17. return attribs['data-toc-unnumbered'] === 'true';
  18. };
  19. function tocObj(str, options = {}) {
  20. const { min_depth, max_depth } = Object.assign({
  21. min_depth: 1,
  22. max_depth: 6
  23. }, options);
  24. const headingsSelector = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].slice(min_depth - 1, max_depth);
  25. const headings = DomUtils.find(({ tagName }) => headingsSelector.includes(tagName), parseHtml(str), true);
  26. const headingsLen = headings.length;
  27. if (!headingsLen) return [];
  28. const result = [];
  29. for (let i = 0; i < headingsLen; i++) {
  30. const el = headings[i];
  31. const level = +el.name[1];
  32. const id = getId(el);
  33. const unnumbered = isUnnumbered(el);
  34. let text = '';
  35. for (const element of el.children) {
  36. const elText = DomUtils.textContent(element);
  37. // Skip permalink symbol wrapped in <a>
  38. // permalink is a single non-word character, word = [a-Z0-9]
  39. // permalink may be wrapped in whitespace(s)
  40. if (element.name !== 'a' || !nonWord.test(elText)) {
  41. text += escapeHTML(elText);
  42. }
  43. }
  44. if (!text) text = escapeHTML(DomUtils.textContent(el));
  45. const res = { text, id, level };
  46. if (unnumbered) res.unnumbered = true;
  47. result.push(res);
  48. }
  49. return result;
  50. }
  51. module.exports = tocObj;