[from now] 2014/08/06 18:20:14

diff --git a/src/main/groovy/gaiden/markdown/AbstractVisitor.groovy b/src/main/groovy/gaiden/markdown/AbstractVisitor.groovy
new file mode 100644
index 0000000..c713bc1
--- /dev/null
+++ b/src/main/groovy/gaiden/markdown/AbstractVisitor.groovy
@@ -0,0 +1,198 @@
+package gaiden.markdown
+
+import org.pegdown.ast.AbbreviationNode
+import org.pegdown.ast.AutoLinkNode
+import org.pegdown.ast.BlockQuoteNode
+import org.pegdown.ast.BulletListNode
+import org.pegdown.ast.CodeNode
+import org.pegdown.ast.DefinitionListNode
+import org.pegdown.ast.DefinitionNode
+import org.pegdown.ast.DefinitionTermNode
+import org.pegdown.ast.ExpImageNode
+import org.pegdown.ast.ExpLinkNode
+import org.pegdown.ast.HeaderNode
+import org.pegdown.ast.HtmlBlockNode
+import org.pegdown.ast.InlineHtmlNode
+import org.pegdown.ast.ListItemNode
+import org.pegdown.ast.MailLinkNode
+import org.pegdown.ast.Node
+import org.pegdown.ast.OrderedListNode
+import org.pegdown.ast.ParaNode
+import org.pegdown.ast.QuotedNode
+import org.pegdown.ast.RefImageNode
+import org.pegdown.ast.RefLinkNode
+import org.pegdown.ast.ReferenceNode
+import org.pegdown.ast.RootNode
+import org.pegdown.ast.SimpleNode
+import org.pegdown.ast.SpecialTextNode
+import org.pegdown.ast.StrikeNode
+import org.pegdown.ast.StrongEmphSuperNode
+import org.pegdown.ast.SuperNode
+import org.pegdown.ast.TableBodyNode
+import org.pegdown.ast.TableCaptionNode
+import org.pegdown.ast.TableCellNode
+import org.pegdown.ast.TableColumnNode
+import org.pegdown.ast.TableHeaderNode
+import org.pegdown.ast.TableNode
+import org.pegdown.ast.TableRowNode
+import org.pegdown.ast.TextNode
+import org.pegdown.ast.VerbatimNode
+import org.pegdown.ast.Visitor
+import org.pegdown.ast.WikiLinkNode
+
+class AbstractVisitor implements Visitor {
+
+    @Override
+    void visit(AbbreviationNode node) {
+    }
+
+    @Override
+    void visit(AutoLinkNode node) {
+    }
+
+    @Override
+    void visit(BlockQuoteNode node) {
+    }
+
+    @Override
+    void visit(BulletListNode node) {
+    }
+
+    @Override
+    void visit(CodeNode node) {
+    }
+
+    @Override
+    void visit(DefinitionListNode node) {
+    }
+
+    @Override
+    void visit(DefinitionNode node) {
+    }
+
+    @Override
+    void visit(DefinitionTermNode node) {
+    }
+
+    @Override
+    void visit(ExpImageNode node) {
+    }
+
+    @Override
+    void visit(ExpLinkNode node) {
+    }
+
+    @Override
+    void visit(HeaderNode node) {
+    }
+
+    @Override
+    void visit(HtmlBlockNode node) {
+    }
+
+    @Override
+    void visit(InlineHtmlNode node) {
+    }
+
+    @Override
+    void visit(ListItemNode node) {
+
+    }
+
+    @Override
+    void visit(MailLinkNode node) {
+    }
+
+    @Override
+    void visit(OrderedListNode node) {
+    }
+
+    @Override
+    void visit(ParaNode node) {
+    }
+
+    @Override
+    void visit(QuotedNode node) {
+    }
+
+    @Override
+    void visit(ReferenceNode node) {
+    }
+
+    @Override
+    void visit(RefImageNode node) {
+    }
+
+    @Override
+    void visit(RefLinkNode node) {
+    }
+
+    @Override
+    void visit(RootNode node) {
+    }
+
+    @Override
+    void visit(SimpleNode node) {
+    }
+
+    @Override
+    void visit(SpecialTextNode node) {
+    }
+
+    @Override
+    void visit(StrikeNode node) {
+    }
+
+    @Override
+    void visit(StrongEmphSuperNode node) {
+    }
+
+    @Override
+    void visit(TableBodyNode node) {
+
+    }
+
+    @Override
+    void visit(TableCaptionNode node) {
+    }
+
+    @Override
+    void visit(TableCellNode node) {
+    }
+
+    @Override
+    void visit(TableColumnNode node) {
+    }
+
+    @Override
+    void visit(TableHeaderNode node) {
+    }
+
+    @Override
+    void visit(TableNode node) {
+    }
+
+    @Override
+    void visit(TableRowNode node) {
+    }
+
+    @Override
+    void visit(VerbatimNode node) {
+    }
+
+    @Override
+    void visit(WikiLinkNode node) {
+    }
+
+    @Override
+    void visit(TextNode node) {
+    }
+
+    @Override
+    void visit(SuperNode node) {
+    }
+
+    @Override
+    void visit(Node node) {
+    }
+}
diff --git a/src/main/groovy/gaiden/markdown/GaidenToHtmlSerializer.groovy b/src/main/groovy/gaiden/markdown/GaidenToHtmlSerializer.groovy
index 3d0b41c..947a808 100644
--- a/src/main/groovy/gaiden/markdown/GaidenToHtmlSerializer.groovy
+++ b/src/main/groovy/gaiden/markdown/GaidenToHtmlSerializer.groovy
@@ -34,8 +34,6 @@ class GaidenToHtmlSerializer extends ToHtmlSerializer {

     private ImageRenderer imageRenderer

-    private Map headers = []
-
     GaidenToHtmlSerializer(LinkRenderer linkRenderer, ImageRenderer imageRenderer) {
         super(linkRenderer)
         this.imageRenderer = imageRenderer
diff --git a/src/main/groovy/gaiden/markdown/Header.groovy b/src/main/groovy/gaiden/markdown/Header.groovy
new file mode 100644
index 0000000..f232c38
--- /dev/null
+++ b/src/main/groovy/gaiden/markdown/Header.groovy
@@ -0,0 +1,9 @@
+package gaiden.markdown
+
+import groovy.transform.CompileStatic
+
+@CompileStatic
+class Header {
+    String text
+    int level
+}
diff --git a/src/main/groovy/gaiden/markdown/HeaderParser.groovy b/src/main/groovy/gaiden/markdown/HeaderParser.groovy
new file mode 100644
index 0000000..91cb567
--- /dev/null
+++ b/src/main/groovy/gaiden/markdown/HeaderParser.groovy
@@ -0,0 +1,48 @@
+package gaiden.markdown
+
+import org.pegdown.LinkRenderer
+import org.pegdown.ToHtmlSerializer
+import org.pegdown.ast.HeaderNode
+import org.pegdown.ast.Node
+import org.pegdown.ast.RootNode
+
+class HeaderParser extends ToHtmlSerializer {
+
+    private RootNode rootNode
+    private List<Header> headers = []
+
+    HeaderParser(RootNode rootNode) {
+        super(new LinkRenderer())
+        this.rootNode = rootNode
+    }
+
+    List<Header> getHeaders() {
+        rootNode.accept(this)
+        return headers
+    }
+
+    @Override
+    void visit(HeaderNode headerNode) {
+        headers << new Header(
+            text: new HeaderTextSerializer(headerNode).text,
+            level: headerNode.level,
+        )
+    }
+
+    static class HeaderTextSerializer extends ToHtmlSerializer {
+
+        private HeaderNode headerNode
+
+        HeaderTextSerializer(HeaderNode headerNode) {
+            super(new LinkRenderer())
+            this.headerNode = headerNode
+        }
+
+        String getText() {
+            headerNode.children.each { Node node ->
+                node.accept(this)
+            }
+            printer.getString()
+        }
+    }
+}
diff --git a/src/test/groovy/gaiden/markdown/HeaderParserSpec.groovy b/src/test/groovy/gaiden/markdown/HeaderParserSpec.groovy
new file mode 100644
index 0000000..702e0b8
--- /dev/null
+++ b/src/test/groovy/gaiden/markdown/HeaderParserSpec.groovy
@@ -0,0 +1,50 @@
+package gaiden.markdown
+
+import gaiden.GaidenSpec
+import org.pegdown.PegDownProcessor
+
+class HeaderParserSpec extends GaidenSpec {
+
+    def "xxx"() {
+        given:
+            def pegDownProcessor = new PegDownProcessor()
+            def rootNode = pegDownProcessor.parseMarkdown('''
+Header1
+=======
+
+Header2
+-------
+
+# Header3
+## Header4
+### Header5
+#### Header6
+##### Header7
+###### Header8
+'''.toCharArray())
+            def headerParser = new HeaderParser(rootNode)
+
+        when:
+            def headers = headerParser.headers
+
+        then:
+            assertHeaders headers, [
+                [text: "Header1", level: 1],
+                [text: "Header2", level: 2],
+                [text: "Header3", level: 1],
+                [text: "Header4", level: 2],
+                [text: "Header5", level: 3],
+                [text: "Header6", level: 4],
+                [text: "Header7", level: 5],
+                [text: "Header8", level: 6],
+            ]
+    }
+
+    private static assertHeaders(List<Header> actual, List<Map> expected) {
+        def headers = actual.collect { Header header ->
+            [text: header.text, level: header.level]
+        }
+        assert headers == expected
+        return true
+    }
+}