ただ形を変えただけ
entry と exit の動きを理解するまでには、時間が必要だな。
ああぁ〜果てしない〜。
<!DOCTYPE html> <title>TEST</title> <h1>TEST</h1> <p><textarea rows="30" cols="100" id="outlines"></textarea></p> <hgroup> <h1 id="sg1">1. HTML 4.01 仕様書</h1> <h2>subtitle</h2> </hgroup> <h2 id="s1-1"><span>1.1.</span> <img alt="SGML と HTML の関係" src="dummy"></h2> <h3 id="s1-1-1"><span>1.1.1.</span> SGML の概説</h3> <h3 id="s1-2-2"><span>1.1.2.</span> HTML で用いられる SGML 構成素</h3> <blockquote> <h1>in-blockquote section</h1> <h6>in-blockquote subsubsection6</h6> <h2>in-blockquote subsection</h2> <h3>in-blockquote subsubsection</h3> <h6>in-blockquote subsubsection6</h6> </blockquote> <h4><a name="name-s1-1-2-1"><span>1.1.2.1.</span> 要素</a></h4> <h4><span>1.1.2.2.</span> 属性</h4> <h4><span>1.1.2.3.</span> 文字参照</h4> <h4><span>1.1.2.4.</span> コメント</h4> <h3 id="s1-1-3"><span>1.1.3.</span> HTML DTD の読み方</h3> <h4><span>1.1.3.1.</span> DTD コメント</h4> <h4><span>1.1.3.2.</span> パラメータ実体定義</h4> <h4><span>1.1.3.3.</span> 要素宣言</h4> <h5><span>1.1.3.3.1.</span> 内容モデル定義</h5> <h6><span>1.1.3.3.1.1.</span> シンタクス</h6> <h4><span>1.1.3.4.</span> 属性定義</h4> <h5><span>1.1.3.4.1.</span> 属性定義中の <strong>DTD 実体</strong></h5> <h5><span>1.1.3.4.2.</span> 論理型属性</h5> <h2 id="s1-1"><span>1.1.</span> <img alt="SGML と HTML の関係" src="dummy"></h2> <h3 id="s1-1-1"><span>1.1.1.</span> SGML の概説</h3> <h3 id="s1-2-2"><span>1.1.2.</span> HTML で用いられる SGML 構成素</h3> <section> <nav> <h3>レベル 3 見出し</h3> <p><a href="/">Home</a></p> </nav> <p>Hello world.</p> <aside> <p>My cat is cute.</p> </aside> <h1>レベル 1 見出し</h1> </section> <script> (function() { function OutLiner ( root ) { this.root = root; this.outline = extract( root ); } //______________ var outLine, section, stack, hash = { H1 : { type: 'heading', value: 1 }, H2 : { type: 'heading', value: 2 }, H3 : { type: 'heading', value: 3 }, H4 : { type: 'heading', value: 4 }, H5 : { type: 'heading', value: 5 }, H6 : { type: 'heading', value: 6 }, HGROUP : { type: 'heading', value: 99 }, ARTICLE : { type: 'content' }, ASIDE : { type: 'content' }, NAV : { type: 'content' }, SECTION : { type: 'content' }, BLOCKQUOTE : { type: 'root' }, BODY : { type: 'root' }, FIGURE : { type: 'root' }, TD : { type: 'root' } }, // 見出しのレベルを返す getHeadingLevel = function (n) { var r, o = hash[ n.tagName ] || { }; if (99 == o.value) { for (r = 1; r < 7; r++) if (n.getElementsByTagName ('h' + r).length) return r; return 1; } return o.value || 0; }, // 前後の不正な文字を削除 trim = (function ( reg ) { return function ( text ) { return text.replace( reg, '' ); }; })(/^\s+|\s+$/g), // textContent(innerText) 画像、フォームの要素からも拾う getTextContent = function ( node ) { var result = [ ]; var root = node; var tmp; while (true) { if (3 == node.nodeType) result.push (trim (node.nodeValue)); else if ( 'IMG' == node.tagName ) result.push (trim (node.alt)); else if ( 'INPUT' == node.tagName ) result.push (trim (node.value)); if (node.hasChildNodes()) node = node.firstChild; else while (true) if (tmp = node.nextSibling) { node = tmp; break; } else { node = node.parentNode; if( node == root ) return result.join('') || ''; } } }, // 入口 enter = function ( node ) { var top = stack[ stack.length - 1 ]; var np = hash[ node.tagName ] || { }; var tp = top ? hash[ top.tagName ] || { }: { }; if( np.type == 'content' || np.type == 'root' ) { // 章節構造のタグならば if (outLine !== null && section.heading === null) section.heading = undefined; if (outLine !== null) stack.push(outLine); outLine = { element: node, children: [ ] }; section = { heading: null, children: [ ], associated: [ ], parent: outLine }; outLine.children.push(section); } else if (outLine === null); // NOP else if ('heading' == np.type && tp.value != 99) { if (section.heading == null) section.heading = node; else if (outLine.children[outLine.children.length - 1].heading == null || getHeadingLevel(node) <= getHeadingLevel(outLine.children[outLine.children.length - 1].heading)) { section = { heading: node, children: [], associated: [], parent: section }; outLine.children.push(section); } else { var candidateSection = section; while (true) { if (getHeadingLevel(node) > getHeadingLevel(candidateSection.heading)) { section = { heading: node, children: [], associated: [], parent: section }; candidateSection.children.push(section); break; } var newcandidateSection = candidateSection.parent; candidateSection = newcandidateSection; } } stack.push(node); } }, // 出口 exit = function ( node ) { var top = stack[ stack.length - 1 ]; var np = hash[ node.tagName ] || { }; var line; if (top === node) stack.pop(); else if ('content' == np.type && stack.length > 0) { line = outLine; outLine = stack.pop(); section = outLine.children[ outLine.children.length - 1]; section.children.push(line); } else if ('root' == np.type && stack.length > 0) { line = outLine; outLine = stack.pop(); section = outLine.children[ outLine.children.length - 1]; while (section.children.length > 0) section = section.children[ section.children.length - 1]; section.children.push( line ) } else if ('content' == np.type || 'root' == np.type) section = outLine.children[0]; if (section !== null) section.associated.push(node); }, // 要素を渡り歩く extract = function ( root ) { var tmp; var node = root; outLine = null; section = null; stack = [ ]; while( true ) { node.tagName && enter(node); // 入口 if ( node.hasChildNodes() ) node = node.firstChild; else while (node) { node.tagName && exit(node); // 出口 if (tmp = node.nextSibling) { node = tmp; break; } node = node.parentNode; if( node == root ) return outLine; } } }, // 文字列化 writeToString = function (division, level) { var result; var subresult; var padding; if (! level) level = 1; padding = new Array(level).join('\u0020\u0020') + '+ '; // section if ('heading' in division) result = division.heading ? [padding + getTextContent(division.heading).replace(/\s{2,}/g, '\u0020')] : (division.heading === null) ? [padding + '(' + division.parent.element.tagName + ')'] : [], subresult = division.children.map(function(division) { return writeToString(division, level + 1); }); // outline else if ('root' == hash[ division.element.tagName ].type) result = [padding + '[' + division.element.tagName + ']'], subresult = division.children.map(function(division) { return writeToString(division, Number(this)); }, level + 1); // anonymous section else result = [], subresult = division.children.map(function(division) { return writeToString(division, level); }); return result.concat(subresult).join('\n'); }; //___________ // outline を文字列化 OutLiner.prototype.toString = function () { return writeToString( this.outline ); }; // グローバル変数化 this.createOutlineView = function (root) { return root ? new OutLiner (root): null; }; })(); var outline = createOutlineView( document.body ); document.getElementById('outlines').value = outline.toString(); </script>