I'm looking to add numbers to headers like below. The problem is that there are documents that may be infinitely nested and need to be counted independently. In the original document, the headings are text only, with no numbered prefix.
<div id="text_output">
<h1>Document 1</h1>
<h2>1 Heading</h2>
<h3>1.1 Subheading</h3>
<h3>1.2 Subheading</h3>
<div class="text_output_expansion">
<h1>Document 2</h1>
<h2>1 Heading</h2>
<h3>1.1 Subheading</h3>
<h2>2 Heading</h2>
<h3>2.1 Subheading</h3>
</div>
<h3>1.3 Subheading</h3>
<h2>2 Heading</h2>
<h3>2.1 Subheading</h3>
<h3>2.2 Subheading</h3>
<div class="text_output_expansion">
<h1>Document 3</h1>
<h2>1 Heading</h2>
<h3>1.1 Subheading</h3>
<h2>2 Heading</h2>
<h3>2.1 Subheading</h3>
<div class="text_output_expansion">
<h1>Document 4</h1>
<h2>1 Heading</h2>
<h3>1.1 Subheading</h3>
<h2>2 Heading</h2>
<h3>2.1 Subheading</h3>
</div>
<h3>2.2 Subheading</h3>
</div>
<h3>2.3 Subheading</h3>
<h2>3 Heading</h2>
<h3>3.1 Subheading</h3>
<h3>3.2 Subheading</h3>
</div>
I'll post a CSS solution, but I'm just struggling to get this working in javascript instead. The idea is so that I can copy and paste the document with the numbers intact (CSS solution won't allow that). Admittedly my javascript isn't great, but my struggle seems to be how js loops through the elements from top to bottom.
CodePudding user response:
One solution is to use CSS counters to append text to the headers instead. Unfortunately, the weakness (or strength) of this approach is that it is visible to the user, but it cannot be copy pasted from the browser, nor is it easy to get the value if you need it elsewhere in your script.
#text_output {
counter-reset: h2
}
#text_output h2 {
counter-reset: h3
}
#text_output .text_output_expansion .card-body{
counter-reset: h2
}
#text_output .text_output_expansion .card-body h2 {
counter-reset: h3
}
#text_output h2:before {
counter-increment: h2;
content: counter(h2) ". "
}
#text_output h3:before,
h3.md-focus.md-heading:before {
counter-increment: h3;
content: counter(h2) "."counter(h3) ". "
}
CodePudding user response:
You can recursively traverse the structure by grouping on h
tag blocks:
var div = document.createElement("div");
div.innerHTML = `<div id="text_output">
<h1>Document</h1>
<h2>Heading</h2>
<h3>Subheading</h3>
<h3>Subheading</h3>
<div class="text_output_expansion">
<h1>Document</h1>
<h2>Heading</h2>
<h3>Subheading</h3>
<h2>Heading</h2>
<h3>Subheading</h3>
</div>
<h3>Subheading</h3>
<h2>Heading</h2>
<h3>Subheading</h3>
<h3>Subheading</h3>
<div class="text_output_expansion">
<h1>Document</h1>
<h2>Heading</h2>
<h3>Subheading</h3>
<h2>Heading</h2>
<h3>Subheading</h3>
<div class="text_output_expansion">
<h1>Document</h1>
<h2>Heading</h2>
<h3>Subheading</h3>
<h2>Heading</h2>
<h3>Subheading</h3>
</div>
<h3>Subheading</h3>
</div>
<h3>Subheading</h3>
<h2>Heading</h2>
<h3>Subheading</h3>
<h3>Subheading</h3>
</div>`
document.querySelector('body').appendChild(div)
function number_headings(root){
var h1 = 1;
function label_headings(nodes, p){
var [groups, group, h] = [[], [], null]
for (var i of nodes){
if (i.tagName[0].toLowerCase() != 'h' || (h != null && i.tagName != h.tagName)){
group.push(i)
}
else if (h === null){
h = i;
}
else{
groups.push({node:h, block:group.slice()});
group = [];
h = i;
}
}
if (h != null){
groups.push({node:h, block:group.slice()});
}
var c = 1;
for ({node:n, block:b} of groups){
if (n.tagName === 'H1'){
n.textContent = `${n.textContent} ${h1}`;
h1 ;
}
else{
n.textContent = `${(p === null ? c.toString() : p '.' c.toString())} ${n.textContent}`;
}
label_headings(b, n.tagName === 'H1' ? null : (p === null ? '':p '.') c.toString());
for (var k of b.filter(function(x){return x.nodeType === 1 && x.tagName[0] != "H"})){
label_headings(Array.from(k.childNodes).filter(function(x){return x.nodeType === 1}), null);
}
c ;
}
}
label_headings(Array.from(root.childNodes).filter(function(x){return x.nodeType === 1}))
}
number_headings(document.querySelector('#text_output'), null)