diff --git a/code/DocumentationParser.php b/code/DocumentationParser.php index edf2a9d..93aa6e6 100644 --- a/code/DocumentationParser.php +++ b/code/DocumentationParser.php @@ -14,7 +14,9 @@ class DocumentationParser { * @var String Rewriting of api links in the format "[api:MyClass]" or "[api:MyClass::$my_property]". */ static $api_link_base = 'http://api.silverstripe.org/search/lookup/?q=%s&version=%s&module=%s'; - + + static $heading_counts = array(); + /** * Parse a given path to the documentation for a file. Performs a case insensitive * lookup on the file system. Automatically appends the file extension to one of the markdown @@ -39,6 +41,7 @@ class DocumentationParser { $md = self::rewrite_image_links($md, $page); $md = self::rewrite_relative_links($md, $page, $baselink); $md = self::rewrite_api_links($md, $page); + $md = self::rewrite_heading_anchors($md, $page); // $md = self::rewrite_code_blocks($md, $page); require_once('../sapphiredocs/thirdparty/markdown.php'); @@ -187,6 +190,51 @@ class DocumentationParser { return $md; } + /** + * + */ + static function rewrite_heading_anchors($md, $page) { + $re = '/ + \#+(.*) + /x'; + $md = preg_replace_callback($re, array('DocumentationParser', '_rewrite_heading_anchors_callback'), $md); + + return $md; + } + + static function _rewrite_heading_anchors_callback($matches) { + $heading = $matches[0]; + $headingText = $matches[1]; + + if(preg_match('/\{\#.*\}/', $headingText)) return $heading; + + if(!isset(self::$heading_counts[$headingText])) { + self::$heading_counts[$headingText] = 1; + } + else { + self::$heading_counts[$headingText]++; + $headingText .= "-" . self::$heading_counts[$headingText]; + } + + return sprintf("%s {#%s}", preg_replace('/\n/', '', $heading), self::generate_html_id($headingText)); + } + + /** + * Generate an html element id from a string + * + * @return String + */ + static function generate_html_id($title) { + $t = $title; + $t = str_replace('&','-and-',$t); + $t = str_replace('&','-and-',$t); + $t = ereg_replace('[^A-Za-z0-9]+','-',$t); + $t = ereg_replace('-+','-',$t); + $t = trim($t, '-'); + + return $t; + } + /** * Resolves all relative links within markdown. * @@ -367,4 +415,5 @@ class DocumentationParser { return $output; } + } \ No newline at end of file diff --git a/javascript/DocumentationViewer.js b/javascript/DocumentationViewer.js index 49d5398..1d0e7a5 100644 --- a/javascript/DocumentationViewer.js +++ b/javascript/DocumentationViewer.js @@ -13,7 +13,6 @@ $('#left-column h1, #left-column h2, #left-column h3, #left-column h4').each(function(i) { var current = $(this); - current.attr('id', 'title' + i); toc += '
  • ' + current.html() + '
  • '; }); diff --git a/tests/DocumentationParserTest.php b/tests/DocumentationParserTest.php index 5de98f1..b7c0c3a 100644 --- a/tests/DocumentationParserTest.php +++ b/tests/DocumentationParserTest.php @@ -63,6 +63,44 @@ class DocumentationParserTest extends SapphireTest { $result ); } + + function testHeadlineAnchors() { + $page = new DocumentationPage( + 'test.md', + new DocumentationEntity('mymodule', '2.4', BASE_PATH . '/sapphiredocs/tests/docs/'), + 'en', + '2.4' + ); + + $result = DocumentationParser::rewrite_heading_anchors($page->getMarkdown(), $page); + + /* + # Heading one {#Heading-one} + + # Heading with custom anchor {#custom-anchor} {#Heading-with-custom-anchor-custom-anchor} + + ## Heading two {#Heading-two} + + ### Heading three {#Heading-three} + + ## Heading duplicate {#Heading-duplicate} + + ## Heading duplicate {#Heading-duplicate-2} + + ## Heading duplicate {#Heading-duplicate-3} + + */ + + $this->assertContains('# Heading one {#Heading-one}', $result); + $this->assertContains('# Heading with custom anchor {#custom-anchor}', $result); + $this->assertNotContains('# Heading with custom anchor {#custom-anchor} {#Heading', $result); + $this->assertContains('# Heading two {#Heading-two}', $result); + $this->assertContains('# Heading three {#Heading-three}', $result); + $this->assertContains('## Heading duplicate {#Heading-duplicate}', $result); + $this->assertContains('## Heading duplicate {#Heading-duplicate-2}', $result); + $this->assertContains('## Heading duplicate {#Heading-duplicate-3}', $result); + + } function testRelativeLinks() { // Page on toplevel diff --git a/tests/docs/en/test.md b/tests/docs/en/test.md index 8b8919b..d628eb0 100644 --- a/tests/docs/en/test.md +++ b/tests/docs/en/test.md @@ -19,4 +19,18 @@ test Normal text after code block code block - without formatting prefix \ No newline at end of file + without formatting prefix + +# Heading one + +# Heading with custom anchor {#custom-anchor} + +## Heading two + +### Heading three + +## Heading duplicate + +## Heading duplicate + +## Heading duplicate