Jeremy wrote:
> Does anyone have a clever algorithm for generating an outline of the
> current document from (client-side) javascript using DOM methods?
> For example, let's say I predictably have a document structured
> hierarchically with <h1>...<h6> tags. I want to generate an outline of
> the document wherein I have nested lists of the contents of the headers.
> Take for example the following snippet of a fictional legal document:
> ------------
> <h1>Main Title</h1>
> <h2>Section One</h2>
> <h3>Paragraph A</h3>
> <p>Congress shall make no law regarding the production of baggy clown
> pants.</p>
> <h3>Paragraph B</h3>
> <p>Congress shall make now law restricting the use of said clown pants,
> for any purpose otherwise legal.</p>
> <h2>Section Two</h2>
> <h3>Paragraph A</h3>
> <p>Etc, etc.</p>
> ----------
> From this I would want to generate
> ------
> <ul>
> <li>Main Title
> <ul>
> <li>Section One
> <ul>
> <li>Paragraph A</li>
> <li>Paragraph B</li>
> </ul>
> </li>
> <li>Section Two
> <ul>
> <li>Paragraph A</li>
> </ul>
> </li>
> </ul>
> ------
> using DOM methods. There are two ways I can think of to do this
Just wander down the DOM and create a series of nested ULs with the
heading text in LIs. It's a tad easier using a bit of innerHTML, but not
much. Here's a pure DOM method:
<title>Outline</title>
<script type="text/javascript">
var genTOC = (function () {
var tocHTML = '';
var level = 1;
var tagRE = /^h\d+/;
var toc = genNode('ul');
var currentEl = toc;
function getText (el) {
if (el.textContent) {return el.textContent;}
if (el.innerText) {return el.innerText;}
if (typeof el.innerHTML == 'string') {
return el.innerHTML.replace(/<[^<>]+>/g,'');
}
}
function genNode(t) { return document.createElement(t);}
function genText(s) { return document.createTextNode(s);}
function previous(el, t){
el = el.parentNode;
while(el.tagName.toLowerCase() != t) {
el = el.parentNode;
}
return el;
}
return {
start: function (tocEl, startEl) {
if (!document.getElementById)
return;
if (typeof tocEl == 'string')
tocEl = document.getElementById(tocEl);
if (typeof startEl == 'string')
startEl = document.getElementById(startEl);
startEl = startEl || document.body;
this.run(startEl);
tocEl.appendChild(toc);
},
run: function(el){
var kid, kids = el.childNodes;
var t, thisLevel;
for (var i=0, len=kids.length; i<len; i++) {
kid = kids[i];
if (kid.tagName && tagRE.test(kid.tagName.toLowerCase())) {
thisLevel = kid.tagName.substring(1);
if (thisLevel > level) {
currentEl.appendChild(genNode('ul'));
currentEl = currentEl.lastChild;
level++;
} else {
while (thisLevel < level) {
currentEl = previous(currentEl, 'ul');
level--;
}
}
t = genNode('li');
t.appendChild(genText(getText(kid)))
currentEl.appendChild(t);
}
if (kid.childNodes) {this.run(kid);}
}
}
}
})();
window.onload = function(){genTOC.start('tocDiv');}
</script>
<body>
<div id="tocDiv"></div>
<div>
<h1>Heading 1</h1>
<p>Lorem Ipsum</p>
<h2>Heading 1.1</h2>
<p>Lorem Ipsum</p>
<h2>Heading 1.2</h2>
<p>Lorem Ipsum</p>
<h3>Heading 1.2.1</h3>
<p>Lorem Ipsum</p>
<h3>Heading 1.2.2</h3>
<p>Lorem Ipsum</p>
<h3>Heading 1.2.3</h3>
<p>Lorem Ipsum</p>
<h2>Heading 1.3</h2>
<div>
<p>Lorem Ipsum</p>
<h3>Heading 1.3.1</h3>
<p>Lorem Ipsum</p>
<h3>Heading 1.3.2</h3>
<p>Lorem Ipsum</p>
</div>
<div>
<h1>Heading 2</h1>
<p>Lorem Ipsum</p>
<h2>Heading 2.1</h2>
<p>Lorem Ipsum</p>
<h3>Heading 2.1.1</h3>
<p>Lorem Ipsum</p>
<h3>Heading 2.1.2</h3>
</div>
</div>
</body>
Lightly tested.
--
Rob