Repository: mkdocs/mkdocs

Issue:
Not all headers are automatically linked
I have an API reference site for a project that's hosted on ReadTheDocs using mkdocs as the documentation engine. Headers that contain things like `<code>` blocks aren't linked, while all others seem to be.

I can reproduce this locally with a plain mkdocs install using the RTD theme.

Here's an example:
http://carbon.lpghatguy.com/en/latest/Classes/Collections.Tuple/

All three of the methods in that page should be automatically linked in the sidebar navigation, but only the one without any fancy decoration is. All of them have been given valid HTML ids, so they're possible to link, they just aren't.

The markdown for that page, which works around a couple RTD bugs and doesn't look that great, is here:
https://raw.githubusercontent.com/lua-carbon/carbon/master/docs/Classes/Collections.Tuple.md



Discussion:
I think I've seen this before and I think it happens when a page doesn't contain a top level `# title` as yours doesn't - you add it with HTML directly.

I can't explain why it chooses the titles it does, that is strange.

Just having a look at this - I think you managed to work around the problem? The current link looks great.

If this still needs looking into, can you comment with a link to a problem area or provide some markdown with the issue. Thanks.

I ended up just redoing the entire website using HTML links instead; this way, nothing gets linked at all, but I needed it for other requirements.

However, it's still possible to reproduce:

``` markdown
# Hello World (linked)
# Hello <code>World</code> (unlinked)
```

Thanks - I've got a failing test case. The problem is using regular expressions to parse HTML which is never a good idea. Working on a fix.


Resolving patch:
diff --git a/mkdocs/compat.py b/mkdocs/compat.py
--- a/mkdocs/compat.py
+++ b/mkdocs/compat.py
@@ -13,6 +13,7 @@
     httpserver = httpserver
     import SocketServer
     socketserver = SocketServer
+    from HTMLParser import HTMLParser
 
     import itertools
     zip = itertools.izip
@@ -30,6 +31,7 @@
     httpserver = httpserver
     import socketserver
     socketserver = socketserver
+    from html.parser import HTMLParser
 
     zip = zip
 
diff --git a/mkdocs/toc.py b/mkdocs/toc.py
--- a/mkdocs/toc.py
+++ b/mkdocs/toc.py
@@ -14,9 +14,7 @@
 * Parse table of contents HTML into the underlying data structure.
 """
 
-import re
-
-TOC_LINK_REGEX = re.compile('<a href=["]([^"]*)["]>([^<]*)</a>')
+from mkdocs.compat import HTMLParser
 
 
 class TableOfContents(object):
@@ -52,6 +50,32 @@ def _indent_print(self, depth=0):
         return ret
 
 
+class TOCParser(HTMLParser):
+
+    def __init__(self):
+        HTMLParser.__init__(self)
+        self.links = []
+
+        self.in_anchor = True
+        self.attrs = None
+        self.title = ''
+
+    def handle_starttag(self, tag, attrs):
+
+        if tag == 'a':
+            self.in_anchor = True
+            self.attrs = dict(attrs)
+
+    def handle_endtag(self, tag):
+        if tag == 'a':
+            self.in_anchor = False
+
+    def handle_data(self, data):
+
+        if self.in_anchor:
+            self.title += data
+
+
 def _parse_html_table_of_contents(html):
     """
     Given a table of contents string that has been automatically generated by
@@ -63,9 +87,11 @@ def _parse_html_table_of_contents(html):
     parents = []
     ret = []
     for line in lines:
-        match = TOC_LINK_REGEX.search(line)
-        if match:
-            href, title = match.groups()
+        parser = TOCParser()
+        parser.feed(line)
+        if parser.title:
+            href = parser.attrs['href']
+            title = parser.title
             nav = AnchorLink(title, href)
             # Add the item to its parent if required.  If it is a topmost
             # item then instead append it to our return value.


Test patch:
diff --git a/mkdocs/tests/toc_tests.py b/mkdocs/tests/toc_tests.py
--- a/mkdocs/tests/toc_tests.py
+++ b/mkdocs/tests/toc_tests.py
@@ -29,6 +29,20 @@ def test_indented_toc(self):
         toc = self.markdown_to_toc(md)
         self.assertEqual(str(toc).strip(), expected)
 
+    def test_indented_toc_html(self):
+        md = dedent("""
+        # Heading 1
+        ## <code>Heading</code> 2
+        ## Heading 3
+        """)
+        expected = dedent("""
+        Heading 1 - #heading-1
+            Heading 2 - #heading-2
+            Heading 3 - #heading-3
+        """)
+        toc = self.markdown_to_toc(md)
+        self.assertEqual(str(toc).strip(), expected)
+
     def test_flat_toc(self):
         md = dedent("""
         # Heading 1