Going to use KNPMenu bundle to help facilitate multi-level lists

Этот коммит содержится в:
Casey McLaughlin 2014-12-30 15:04:25 -05:00
родитель 7ea43a97fc
Коммит 965c254b5c
8 изменённых файлов: 180 добавлений и 49 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -12,7 +12,7 @@ tags. It can also automatically add appropriate "id" anchor links to content.
Features: Features:
* Generates arrays or HTML <li*gt; tags * 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 * Specify which *H1*...*H6* heading tags to use at runtime
* Includes Twig Extension for generating TOC lists and compatible markup directly from templates * Includes Twig Extension for generating TOC lists and compatible markup directly from templates
* PSR-0 thru PSR-2 Compliant * 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 1. The hierarchy of your content is defined solely by the header (*H1*...*H6*) tags. All other tags
are ignored. 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. or if there is no `title`, the plaintext body of the header tag.
Installation Options Installation Options
@ -41,12 +43,10 @@ Or, drop the `src` folder into your application and use a PSR-0 autoloader to in
Usage 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 1. Adds `id` anchor tags to any *H1*..*H6* tags that do not already have any.
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.
2. Generates HTML (or an associative array) of anchor links that can be rendered in your
template.
Basic Example: Basic Example:
@ -59,37 +59,53 @@ $myHtmlContent = <<<END
<h3 id='bar'>This is a header tag with an anchor id</h3> <h3 id='bar'>This is a header tag with an anchor id</h3>
END; END;
$tocContent = new \TOC\TocContent($myHtmlContent); $markupFixer = new TOC\MarkupFixer();
$tocGenerator = new TOC\TocGenerator();
// Generate HTML list of links // This ensures that all header tags have `id` attributes so they can be used as anchors
echo "<ul>" . $tocContent->getTocListItems('h1', 'h2') . "</ul>"; $htmlOut = "<div class='content'>" . $markupFixer->fix($myHtmlContent) . "</div>";
$htmlOut .= "<div class='toc'><ul>" . $tocGenerator->getHtmlItems($myHtmlContent) . "</ul></div>";
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 Twig Integration
---------------- ----------------
This library includes a [Twig](http://twig.sensiolabs.org) extension that enables you to load 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 ```twig
{# Generates HTML markup for given htmlContent #} {# 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) #}
<ul>{{ toc(htmlContent, 'h1', 'h3') }}</ul> <ul>{{ toc(htmlContent, 'h1', 'h3') }}</ul>
{# Generates an array of anchor links for given htmlContent #} {# 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) #}
<ul> <ul>
{% for anchor, title in toc_items(htmlContent 'h1', 'h3'): %} {% for anchor, title in toc_items(htmlContent, 'h1', 'h3'): %}
<li class='whatever'><a href='{{ anchor }}'>{{ title }}</a></li> <li class='whatever'><a href='{{ anchor }}'>{{ title }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
```
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 #} {# 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 #}
<div class='my_content'> <div class='my_content'>
{{ toc_content(htmlContent, 'h1', 'h3') }} {{ htmlContent | add_anchors('h1', 'h3') }}
</div> </div>
``` ```
@ -103,15 +119,18 @@ For example:
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block page_content %} {% block page_content %}
<div class='page_sidebar'> <div class='page_sidebar'>
{{ toc(block('my_writeup'), 'h1', 'h2') }} {{ toc(add_anchors(block('my_writeup'), 'h1', 'h2'), 'h1', 'h2') }}
</div> </div>
<div class='page_content'> <div class='page_content'>
{{ toc_content(block('my_writeup'), 'h1', 'h2') }} {{ add_anchors(block('my_writeup'), 'h1', 'h2') }}
</div> </div>
{% endblock %} {% endblock %}
{% block my_writeup %} {% block my_writeup %}
<h1> <h1>Hi There</h1>
<p>Lorum ipsum baz biz etecetra</p>
<h2>This is some content</h2>
<p>More content here. Blah blah</p>
{% endblock %} {% endblock %}
``` ```

Просмотреть файл

@ -22,8 +22,10 @@
}, },
"require": { "require": {
"php": ">=5.4", "php": ">=5.4",
"sunra/php-simple-html-dom-parser": "~1.5", "sunra/php-simple-html-dom-parser": "~1.5",
"cocur/slugify": "~1.0" "cocur/slugify": "~1.0",
"knplabs/knp-menu": "~2.0"
}, },
"require-dev": { "require-dev": {
"twig/twig": "~1.13", "twig/twig": "~1.13",

66
composer.lock сгенерированный
Просмотреть файл

@ -4,7 +4,7 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"hash": "93020f0823d28448cae16c110561fe2d", "hash": "adfeb8aba6a2ded99b1e8b37508b4145",
"packages": [ "packages": [
{ {
"name": "cocur/slugify", "name": "cocur/slugify",
@ -66,6 +66,70 @@
], ],
"time": "2014-11-26 22:45:14" "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", "name": "sunra/php-simple-html-dom-parser",
"version": "v1.5.0", "version": "v1.5.0",

Просмотреть файл

@ -17,7 +17,7 @@ use Sunra\PhpSimple\HtmlDomParser;
* *
* @author Casey McLaughlin <caseyamcl@gmail.com> * @author Casey McLaughlin <caseyamcl@gmail.com>
*/ */
class TocMarkupFixer class MarkupFixer
{ {
use HeaderTagInterpreter; use HeaderTagInterpreter;
@ -51,7 +51,7 @@ class TocMarkupFixer
* @return string Markup with added IDs * @return string Markup with added IDs
* @throws RuntimeException * @throws RuntimeException
*/ */
public function fix($markup, $topLevel = 1, $depth = 2) public function fix($markup, $topLevel = 1, $depth = 6)
{ {
$sluggifier = new Sluggifier(); $sluggifier = new Sluggifier();

Просмотреть файл

@ -5,6 +5,8 @@
namespace TOC; namespace TOC;
use Knp\Menu\MenuFactory;
use Knp\Menu\MenuItem;
use Sunra\PhpSimple\HtmlDomParser; use Sunra\PhpSimple\HtmlDomParser;
use RuntimeException; use RuntimeException;
@ -24,16 +26,23 @@ class TocGenerator
*/ */
private $domParser; private $domParser;
/**
* @var \Knp\Menu\MenuFactory
*/
private $menuFactory;
// --------------------------------------------------------------- // ---------------------------------------------------------------
/** /**
* Constructor * Constructor
* *
* @param \Knp\Menu\MenuFactory $menuFactory
* @param \Sunra\PhpSimple\HtmlDomParser $domParser * @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 * 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 string $markup Content to get items from
* @param int $topLevel Top Header (1 through 6) * @param int $topLevel Top Header (1 through 6)
* @param int $depth Depth (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. // Empty? Do nothing.
if (trim($markup) == '') { if (trim($markup) == '') {
return []; return [];
} }
// Parse HTML // Parse HTML
$items = []; $tagsToMatch = $this->determineHeaderTags($topLevel, $depth);
$tags = $this->determineHeaderTags($topLevel, $depth);
$parsed = $this->domParser->str_get_html($markup); $parsed = $this->domParser->str_get_html($markup);
// Runtime exception for bad code // Runtime exception for bad code
@ -64,17 +79,42 @@ class TocGenerator
} }
// Extract items // 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; continue;
} }
$dispText = $tag->title ?: $tag->plaintext; // Get the tagname and the level
$items[$tag->id] = $dispText; $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('');
}
} }
return $items; $parent->addChild($element->title ?: $element->plaintext, ['uri' => '#' . $element->id]);
$lastLevel = $level;
}
return $menu;
} }
// --------------------------------------------------------------- // ---------------------------------------------------------------
@ -87,7 +127,7 @@ class TocGenerator
* @param int $depth Depth (1 through 6) * @param int $depth Depth (1 through 6)
* @return string HTML <LI> items * @return string HTML <LI> 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 = []; $arr = [];

Просмотреть файл

@ -17,7 +17,7 @@ class TocTwigExtension extends Twig_Extension
private $generator; private $generator;
/** /**
* @var \TOC\TocMarkupFixer * @var \TOC\MarkupFixer
*/ */
private $fixer; private $fixer;
@ -27,12 +27,12 @@ class TocTwigExtension extends Twig_Extension
* Constructor * Constructor
* *
* @param \TOC\TocGenerator $generator * @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->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) { $filters[] = new \Twig_SimpleFilter('add_anchors', function($str, $top = 1, $depth = 2) {
return $this->fixer->fix($str, $top, $depth); return $this->fixer->fix($str, $top, $depth);
}); }, ['is_safe' => ['html']]);
return $filters; return $filters;
} }
@ -60,7 +60,7 @@ class TocTwigExtension extends Twig_Extension
return ($titleTemplate) return ($titleTemplate)
? $this->generator->getHtmlItems($markup, $top, $depth, $titleTemplate) ? $this->generator->getHtmlItems($markup, $top, $depth, $titleTemplate)
: $this->generator->getHtmlItems($markup, $top, $depth); : $this->generator->getHtmlItems($markup, $top, $depth);
}); }, ['is_safe' => ['html']]);
// ~~~ // ~~~

Просмотреть файл

@ -1,5 +1,5 @@
<?php <?php
use TOC\TocMarkupFixer; use TOC\MarkupFixer;
/** /**
* Created by PhpStorm. * Created by PhpStorm.
@ -8,19 +8,19 @@ use TOC\TocMarkupFixer;
* Time: 1:24 PM * Time: 1:24 PM
*/ */
class TocMarkupFixerTest extends PHPUnit_Framework_TestCase class MarkupFixerTest extends PHPUnit_Framework_TestCase
{ {
public function testInstantiateSucceeds() public function testInstantiateSucceeds()
{ {
$obj = new TocMarkupFixer(); $obj = new MarkupFixer();
$this->assertInstanceOf('\TOC\TocMarkupFixer', $obj); $this->assertInstanceOf('\TOC\MarkupFixer', $obj);
} }
// --------------------------------------------------------------- // ---------------------------------------------------------------
public function testFixAddsIdsOnlyToElementsWithoutThem() public function testFixAddsIdsOnlyToElementsWithoutThem()
{ {
$obj = new TocMarkupFixer(); $obj = new MarkupFixer();
$html = "<h1>No ID</h1><h2>Existing ID</h2><h3>Ignored</h3>"; $html = "<h1>No ID</h1><h2>Existing ID</h2><h3>Ignored</h3>";
@ -34,7 +34,7 @@ class TocMarkupFixerTest extends PHPUnit_Framework_TestCase
public function testFixDoesNotDuplicateIdsWhenFixing() public function testFixDoesNotDuplicateIdsWhenFixing()
{ {
$obj = new TocMarkupFixer(); $obj = new MarkupFixer();
$html = "<h1>FooBar</h1><h2>FooBar</h2><h3>FooBar</h3>"; $html = "<h1>FooBar</h1><h2>FooBar</h2><h3>FooBar</h3>";
@ -48,7 +48,7 @@ class TocMarkupFixerTest extends PHPUnit_Framework_TestCase
public function testFixUsesTitleAttributeWhenAvailable() public function testFixUsesTitleAttributeWhenAvailable()
{ {
$obj = new TocMarkupFixer(); $obj = new MarkupFixer();
$html = "<h1>No ID</h1><h2 title='b'>Existing ID</h2><h3>Ignored</h3>"; $html = "<h1>No ID</h1><h2 title='b'>Existing ID</h2><h3>Ignored</h3>";