diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29..e86220c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +# ChangeLog - PHP TOC +All notable changes to this project are documented in this file. + +## 1.0 - 2014-12-30 +### Added +- Initial Version \ No newline at end of file diff --git a/README.md b/README.md index cf672fc..7b31ea5 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ tags. It can also automatically add appropriate "id" anchor links to content. Features: * Generates arrays or HTML <li*gt; tags -* Adds anchor IDs to content +* Adds anchor IDs to content where they do not exist * Specify which *H1*...*H6* heading tags to use at runtime * Includes Twig Extension for generating TOC lists and compatible markup directly from templates * PSR-0 thru PSR-2 Compliant @@ -23,7 +23,9 @@ In the spirit of KISS philosophy, this library assumes a few things: 1. The hierarchy of your content is defined solely by the header (*H1*...*H6*) tags. All other tags are ignored. -2. The link titles in the Table of Contents should match either the `title` attribute of the header tag, +2. The hierarchy is well-formed, meaning that you don't randomly distribute header tags around. They occur in a + predictable order (*H2*s are children of *H1*s, *H3*s are children of *H2*s, etc). +3. The link titles in the Table of Contents should match either the `title` attribute of the header tag, or if there is no `title`, the plaintext body of the header tag. Installation Options @@ -41,12 +43,10 @@ Or, drop the `src` folder into your application and use a PSR-0 autoloader to in Usage ----- -This library does two things with any HTML content passed in: +This library does two things: -1. Adds `id` tags to any H1..H6 tags (or header-levels specified at runtime) that do - not already have any. This is to enable anchor links in the content. -2. Generates HTML (or an associative array) of anchor links that can be rendered in your - template. +1. Adds `id` anchor tags to any *H1*..*H6* tags that do not already have any. +2. Generates HTML (or an associative array) of anchor links that can be rendered in your template. Basic Example: @@ -59,37 +59,53 @@ $myHtmlContent = <<This is a header tag with an anchor id END; -$tocContent = new \TOC\TocContent($myHtmlContent); +$markupFixer = new TOC\MarkupFixer(); +$tocGenerator = new TOC\TocGenerator(); -// Generate HTML list of links -echo ""; +// This ensures that all header tags have `id` attributes so they can be used as anchors +$htmlOut = "
" . $markupFixer->fix($myHtmlContent) . "
"; +$htmlOut .= "
"; +echo $htmlOut; ``` +There are two service classes: `TOC\TocGenerator` and `TOC\MarkupFixer`: + +The `TocGenerator` class accepts HTML markup and generates a list of anchor links + + Twig Integration ---------------- This library includes a [Twig](http://twig.sensiolabs.org) extension that enables you to load -TOC lists and compatible markup from your Twig templates. +TOC lists and add anchors to markup from your Twig templates. -Specifically, the extension adds three Twig functions: +Specifically, the extension adds two Twig functions for generating Table of Contents lists items: ```twig {# Generates HTML markup for given htmlContent #} -{# The second two parameters are optional (defaults are h1, h3) #} +{# The second two parameters are optional (defaults are h1, h6) #} {# Generates an array of anchor links for given htmlContent #} -{# The second two parameters are optional (defaults are h1, h3) #} +{# The second two parameters are optional (defaults are h1, h6) #} +``` +It also adds one function and one filter for ensuring that your content includes anchors for +all HTML tags: + +```twig {# Adds anchor links (id tags) for given htmlContent #} -{# The second two parameters are optional (defaults are h1, h3) #} +{# The second two parameters are optional (defaults are h1, h6) #} +{{ add_anchors(htmlContent, 'h1', 'h2') + +{# You can also use it as a filter #}
- {{ toc_content(htmlContent, 'h1', 'h3') }} + {{ htmlContent | add_anchors('h1', 'h3') }}
``` @@ -103,15 +119,18 @@ For example: {% extends 'base.html.twig' %} {% block page_content %}
- {{ toc(block('my_writeup'), 'h1', 'h2') }} + {{ toc(add_anchors(block('my_writeup'), 'h1', 'h2'), 'h1', 'h2') }}
- {{ toc_content(block('my_writeup'), 'h1', 'h2') }} + {{ add_anchors(block('my_writeup'), 'h1', 'h2') }}
{% endblock %} {% block my_writeup %} -

+

Hi There

+

Lorum ipsum baz biz etecetra

+

This is some content

+

More content here. Blah blah

{% endblock %} ``` diff --git a/composer.json b/composer.json index 25c1dac..596ab35 100644 --- a/composer.json +++ b/composer.json @@ -22,8 +22,10 @@ }, "require": { "php": ">=5.4", + "sunra/php-simple-html-dom-parser": "~1.5", - "cocur/slugify": "~1.0" + "cocur/slugify": "~1.0", + "knplabs/knp-menu": "~2.0" }, "require-dev": { "twig/twig": "~1.13", diff --git a/composer.lock b/composer.lock index 8e0c7f2..ed5bed9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "93020f0823d28448cae16c110561fe2d", + "hash": "adfeb8aba6a2ded99b1e8b37508b4145", "packages": [ { "name": "cocur/slugify", @@ -66,6 +66,70 @@ ], "time": "2014-11-26 22:45:14" }, + { + "name": "knplabs/knp-menu", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/KnpLabs/KnpMenu.git", + "reference": "5758d0026d7ed00c8dd4727e413918cf2dc74c1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/KnpLabs/KnpMenu/zipball/5758d0026d7ed00c8dd4727e413918cf2dc74c1a", + "reference": "5758d0026d7ed00c8dd4727e413918cf2dc74c1a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "pimple/pimple": "1.0.*", + "silex/silex": "1.0.*", + "twig/twig": ">=1.2,<2.0-dev" + }, + "suggest": { + "pimple/pimple": "for the built-in implementations of the menu provider and renderer provider", + "silex/silex": "for the integration with your silex application", + "twig/twig": "for the TwigRenderer and the integration with your templates" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Knp\\Menu\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christophe Coevoet", + "email": "stof@notk.org" + }, + { + "name": "KnpLabs", + "homepage": "http://knplabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://github.com/KnpLabs/KnpMenu/contributors" + } + ], + "description": "An object oriented menu library", + "homepage": "http://knplabs.com", + "keywords": [ + "menu", + "tree" + ], + "time": "2014-08-01 09:50:16" + }, { "name": "sunra/php-simple-html-dom-parser", "version": "v1.5.0", diff --git a/src/TocMarkupFixer.php b/src/MarkupFixer.php similarity index 95% rename from src/TocMarkupFixer.php rename to src/MarkupFixer.php index 054fd21..152ea3a 100644 --- a/src/TocMarkupFixer.php +++ b/src/MarkupFixer.php @@ -17,7 +17,7 @@ use Sunra\PhpSimple\HtmlDomParser; * * @author Casey McLaughlin */ -class TocMarkupFixer +class MarkupFixer { use HeaderTagInterpreter; @@ -51,7 +51,7 @@ class TocMarkupFixer * @return string Markup with added IDs * @throws RuntimeException */ - public function fix($markup, $topLevel = 1, $depth = 2) + public function fix($markup, $topLevel = 1, $depth = 6) { $sluggifier = new Sluggifier(); diff --git a/src/TocGenerator.php b/src/TocGenerator.php index 878785e..c145d1f 100644 --- a/src/TocGenerator.php +++ b/src/TocGenerator.php @@ -5,6 +5,8 @@ namespace TOC; +use Knp\Menu\MenuFactory; +use Knp\Menu\MenuItem; use Sunra\PhpSimple\HtmlDomParser; use RuntimeException; @@ -24,16 +26,23 @@ class TocGenerator */ private $domParser; + /** + * @var \Knp\Menu\MenuFactory + */ + private $menuFactory; + // --------------------------------------------------------------- /** * Constructor * + * @param \Knp\Menu\MenuFactory $menuFactory * @param \Sunra\PhpSimple\HtmlDomParser $domParser */ - public function __construct(HtmlDomParser $domParser = null) + public function __construct(MenuFactory $menuFactory = null, HtmlDomParser $domParser = null) { - $this->domParser = $domParser ?: new HtmlDomParser(); + $this->domParser = $domParser ?: new HtmlDomParser(); + $this->menuFactory = $menuFactory ?: new MenuFactory(); } // --------------------------------------------------------------- @@ -41,21 +50,27 @@ class TocGenerator /** * Get Link Items * + * Returns a multi-level associative array of items + * + * @TODO: TEST THIS OUT >> And then refactor the getHtmlItems method to use the built-in KNP List library to do so... + * * @param string $markup Content to get items from * @param int $topLevel Top Header (1 through 6) * @param int $depth Depth (1 through 6) - * @return array Array of items ['anchor' => 'display text', ...] + * @return \Traversable Menu items */ - public function getItems($markup, $topLevel = 1, $depth = 2) + public function getItems($markup, $topLevel = 1, $depth = 6) { + // Setup an empty menu object + $menu = $this->menuFactory->createItem('TOC'); + // Empty? Do nothing. if (trim($markup) == '') { return []; } // Parse HTML - $items = []; - $tags = $this->determineHeaderTags($topLevel, $depth); + $tagsToMatch = $this->determineHeaderTags($topLevel, $depth); $parsed = $this->domParser->str_get_html($markup); // Runtime exception for bad code @@ -64,17 +79,42 @@ class TocGenerator } // Extract items - foreach ($parsed->find(implode(', ', $tags)) as $tag) { - if ( ! $tag->id) { + // Initial settings + $lastLevel = 0; + $parent = $menu; + + // Do it... + foreach ($parsed->find(implode(', ', $tagsToMatch)) as $element) { + + if ( ! $element->id) { continue; } - $dispText = $tag->title ?: $tag->plaintext; - $items[$tag->id] = $dispText; + // Get the tagname and the level + $tagName = $element->tag; + $level = array_search(strtolower($tagName), $tagsToMatch); + + // Determine parent item to which to add child + if ($level == 0) { + $parent = $menu; + } + elseif ($level < $lastLevel) { // traverse up parents until difference between is 1 + for ($l = $lastLevel; $l > $level; $l--) { + $parent = $parent->getParent(); + } + } + elseif ($level > $lastLevel) { // add children until difference between is 1 + for ($l = $lastLevel; $l < ($level-1); $l++) { + $parent = $parent->addChild(''); + } + } + + $parent->addChild($element->title ?: $element->plaintext, ['uri' => '#' . $element->id]); + $lastLevel = $level; } - return $items; + return $menu; } // --------------------------------------------------------------- @@ -87,7 +127,7 @@ class TocGenerator * @param int $depth Depth (1 through 6) * @return string HTML
  • items */ - public function getHtmlItems($markup, $topLevel = 1, $depth = 2, $titleTemplate = 'Go to %s') + public function getHtmlItems($markup, $topLevel = 1, $depth = 6, $titleTemplate = 'Go to %s') { $arr = []; diff --git a/src/TocTwigExtension.php b/src/TocTwigExtension.php index fad2000..ebfb60c 100644 --- a/src/TocTwigExtension.php +++ b/src/TocTwigExtension.php @@ -17,7 +17,7 @@ class TocTwigExtension extends Twig_Extension private $generator; /** - * @var \TOC\TocMarkupFixer + * @var \TOC\MarkupFixer */ private $fixer; @@ -27,12 +27,12 @@ class TocTwigExtension extends Twig_Extension * Constructor * * @param \TOC\TocGenerator $generator - * @param \TOC\TocMarkupFixer $fixer + * @param \TOC\MarkupFixer $fixer */ - public function __construct(TocGenerator $generator = null, TocMarkupFixer $fixer = null) + public function __construct(TocGenerator $generator = null, MarkupFixer $fixer = null) { $this->generator = $generator ?: new TocGenerator(); - $this->fixer = $fixer ?: new TocMarkupFixer(); + $this->fixer = $fixer ?: new MarkupFixer(); } // --------------------------------------------------------------- @@ -43,7 +43,7 @@ class TocTwigExtension extends Twig_Extension $filters[] = new \Twig_SimpleFilter('add_anchors', function($str, $top = 1, $depth = 2) { return $this->fixer->fix($str, $top, $depth); - }); + }, ['is_safe' => ['html']]); return $filters; } @@ -60,7 +60,7 @@ class TocTwigExtension extends Twig_Extension return ($titleTemplate) ? $this->generator->getHtmlItems($markup, $top, $depth, $titleTemplate) : $this->generator->getHtmlItems($markup, $top, $depth); - }); + }, ['is_safe' => ['html']]); // ~~~ diff --git a/tests/TocMarkupFixerTest.php b/tests/MarkupFixerTest.php similarity index 81% rename from tests/TocMarkupFixerTest.php rename to tests/MarkupFixerTest.php index 1aec5cb..c47c5e1 100644 --- a/tests/TocMarkupFixerTest.php +++ b/tests/MarkupFixerTest.php @@ -1,5 +1,5 @@ assertInstanceOf('\TOC\TocMarkupFixer', $obj); + $obj = new MarkupFixer(); + $this->assertInstanceOf('\TOC\MarkupFixer', $obj); } // --------------------------------------------------------------- public function testFixAddsIdsOnlyToElementsWithoutThem() { - $obj = new TocMarkupFixer(); + $obj = new MarkupFixer(); $html = "

    No ID

    Existing ID

    Ignored

    "; @@ -34,7 +34,7 @@ class TocMarkupFixerTest extends PHPUnit_Framework_TestCase public function testFixDoesNotDuplicateIdsWhenFixing() { - $obj = new TocMarkupFixer(); + $obj = new MarkupFixer(); $html = "

    FooBar

    FooBar

    FooBar

    "; @@ -48,7 +48,7 @@ class TocMarkupFixerTest extends PHPUnit_Framework_TestCase public function testFixUsesTitleAttributeWhenAvailable() { - $obj = new TocMarkupFixer(); + $obj = new MarkupFixer(); $html = "

    No ID

    Existing ID

    Ignored

    ";