Repository: mkdocs/mkdocs
Pull request #644 (2015-06-19 15:52:24)

Generate search index entries for all headings, not just H1 and H2
Currently search index generation is limited to H1 and H2, which is hard-coded. I have found it necessary to extend this to H3 and H4, as I wanted to provide more specific search results. Not sure what a good balance would be (e.g. stop at H4), as the level of headings included in the TOC, for example, is up to the template, so I opted for no limits.


Patch:
diff --git a/mkdocs/search.py b/mkdocs/search.py
index 5f27b42f..f4006b51 100644
--- a/mkdocs/search.py
+++ b/mkdocs/search.py
@@ -11,7 +11,7 @@ except ImportError:                     # pragma: no cover
 
 class SearchIndex(object):
     """
-    Search index is a collection of pages and sections (H1 and H2
+    Search index is a collection of pages and sections (heading
     tags and their following content are sections).
     """
 
@@ -26,9 +26,9 @@ class SearchIndex(object):
         for toc_item in toc:
             if toc_item.url[1:] == id_:
                 return toc_item
-            for toc_sub_item in toc_item.children:
-                if toc_sub_item.url[1:] == id_:
-                    return toc_sub_item
+            toc_item_r = self._find_toc_by_id(toc_item.children, id_)
+            if toc_item_r is not None:
+                return toc_item_r
 
     def _add_entry(self, title, text, loc):
         """
@@ -44,7 +44,7 @@ class SearchIndex(object):
     def add_entry_from_context(self, page, content, toc):
         """
         Create a set of entries in the index for a page. One for
-        the page itself and then one for each of it's H1 and H2
+        the page itself and then one for each of its' heading
         tags.
         """
 
@@ -144,7 +144,7 @@ class ContentSection(object):
 class ContentParser(HTMLParser):
     """
     Given a block of HTML, group the content under the preceding
-    H1 or H2 tags which can then be used for creating an index
+    heading tags which can then be used for creating an index
     for that section.
     """
 
@@ -161,8 +161,8 @@ class ContentParser(HTMLParser):
     def handle_starttag(self, tag, attrs):
         """Called at the start of every HTML tag."""
 
-        # We only care about the opening tag for H1 and H2.
-        if tag not in ("h1", "h2"):
+        # We only care about the opening tag for headings.
+        if tag not in (["h%d" % x for x in range(1, 7)]):
             return
 
         # We are dealing with a new header, create a new section
@@ -178,8 +178,8 @@ class ContentParser(HTMLParser):
     def handle_endtag(self, tag):
         """Called at the end of every HTML tag."""
 
-        # We only care about the opening tag for H1 and H2.
-        if tag not in ("h1", "h2"):
+        # We only care about the opening tag for headings.
+        if tag not in (["h%d" % x for x in range(1, 7)]):
             return
 
         self.is_header_tag = False
@@ -191,7 +191,7 @@ class ContentParser(HTMLParser):
 
         if self.section is None:
             # This means we have some content at the start of the
-            # HTML before we reach a H1 or H2. We don't actually
+            # HTML before we reach a heading tag. We don't actually
             # care about that content as it will be added to the
             # overall page entry in the search. So just skip it.
             return


Test patch:
diff --git a/mkdocs/tests/search_tests.py b/mkdocs/tests/search_tests.py
index 9672b805..f6343134 100644
--- a/mkdocs/tests/search_tests.py
+++ b/mkdocs/tests/search_tests.py
@@ -90,7 +90,8 @@ class SearchTests(unittest.TestCase):
         self.assertEqual(toc_item2.title, "Heading 2")
 
         toc_item3 = index._find_toc_by_id(toc, "heading-3")
-        self.assertEqual(toc_item3, None)
+        self.assertEqual(toc_item3.url, "#heading-3")
+        self.assertEqual(toc_item3.title, "Heading 3")
 
     def test_create_search_index(self):
 
@@ -123,7 +124,7 @@ class SearchTests(unittest.TestCase):
             index = search.SearchIndex()
             index.add_entry_from_context(page, html_content, toc)
 
-            self.assertEqual(len(index._entries), 3)
+            self.assertEqual(len(index._entries), 4)
 
             loc = page.abs_url
 
@@ -136,5 +137,9 @@ class SearchTests(unittest.TestCase):
             self.assertEqual(index._entries[1]['location'], "{0}#heading-1".format(loc))
 
             self.assertEqual(index._entries[2]['title'], "Heading 2")
-            self.assertEqual(strip_whitespace(index._entries[2]['text']), "Content2Heading3Content3")
+            self.assertEqual(strip_whitespace(index._entries[2]['text']), "Content2")
             self.assertEqual(index._entries[2]['location'], "{0}#heading-2".format(loc))
+
+            self.assertEqual(index._entries[3]['title'], "Heading 3")
+            self.assertEqual(strip_whitespace(index._entries[3]['text']), "Content3")
+            self.assertEqual(index._entries[3]['location'], "{0}#heading-3".format(loc))