ただ形を変えただけ

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>