The TOC is an opinionated table of contents generator.

Maintainers

Package info

github.com/cable8mm/toc

pkg:composer/cable8mm/toc

Transparency log

Statistics

Installs: 6

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.3.1 2026-07-13 16:55 UTC

This package is auto-updated.

Last update: 2026-07-13 16:57:25 UTC


README

code-style run-tests Packagist Version Packagist Downloads Packagist Dependency Version Packagist Stars Packagist License

TOC parses Markdown documents and extracts table of contents (navigation) structures. It supports multiple documentation formats including Laravel, Samsung Tizen, Naver Clova AI, and Rhymix.

Originally built for the document2 project.

Installation

composer require cable8mm/toc

Quick Start

Parse a Markdown TOC and iterate over its items:

use Cable8mm\Toc\Toc;

$markdown = '
- ## Prologue
    - [Release Notes](/docs/{{version}}/releases)
    - [Upgrade Guide](/docs/{{version}}/upgrade)
    - [Contribution Guide](/docs/{{version}}/contributions)
- ## Getting Started
    - [Installation](/docs/{{version}}/installation)
    - [Configuration](/docs/{{version}}/configuration)
';

$lines = Toc::of($markdown)->getLines();

foreach ($lines as $line) {
    echo $line->getTitle().PHP_EOL;      // "Prologue", "Release Notes", ...
    echo $line->getLink().PHP_EOL;       // null, "/docs/{{version}}/releases", ...
    echo $line->getType()->name.PHP_EOL; // "section", "page", ...
    echo $line->getDepth().PHP_EOL;      // 1, 2, ...
}

Supported Document Styles

Laravel Style (- ## Section, indented - [Page](link))

- ## Prologue
  - [Release Notes](/docs/{{version}}/releases)
  - [Upgrade Guide](/docs/{{version}}/upgrade)

Samsung Tizen Style (# Section, ## [Page](link))

# What is Tizen?

## Versions

## [Overview](/platform/what-is-tizen/overview.md)

### [TV](/platform/what-is-tizen/profiles/tv.md)

Naver Clova AI Style (# Section, * [Page](link) with 2-space indent)

# Summary

## Clova Face Recognition

- [CFR API란?](/CFR/API_Guide.md#Overview)
  - [유명인 얼굴 인식 API](/CFR/API_Guide.md#CelebrityAPI)

Rhymix Style (### Section, - [Page](link))

### 개요

- [설치 환경](./ko/introduction/requirements.md)
- [라이믹스 설치](./ko/introduction/install.md)

API Reference

Toc — Main parser class

Method Description Returns
Toc::of(string $markdown) Create and parse markdown in one call (factory) Toc
new Toc(string $markdown) Create instance (call ->of() to parse) Toc
getLines() Get all parsed line items Item[]
getLine(int $n) Get the nth line item (0-based) Item
getSectionTitle(string $pageTitle) Find the section title containing a page ?string
toArray() Get grouped sections with their pages array
addConverters(ConverterInterface[]) Add custom preprocessors static

Example — find which section a page belongs to:

$toc = Toc::of($markdown);
echo $toc->getSectionTitle('Release Notes'); // "Prologue"
echo $toc->getSectionTitle('Installation');  // "Getting Started"

Example — grouped navigation structure:

$sections = Toc::of($markdown)->toArray();

foreach ($sections as $section) {
    echo $section['section']->getTitle().PHP_EOL; // Section heading
    foreach ($section['pages'] as $page) {
        echo '  - '.$page->getTitle().PHP_EOL;    // Page under this section
    }
}

Item — A single TOC line

Method Description Returns
getTitle() Extract the display title string
getLink() Extract the URL (if it's a link) ?string
getType() Whether it's a section or page ItemEnum
getDepth() Nesting level (1-based) int
toHtml() Render as <li> HTML tag string

Example — render as HTML:

$lines = Toc::of($markdown)->getLines();

foreach ($lines as $line) {
    echo $line->toHtml().PHP_EOL;
    // <li><h2>Prologue</h2></li>
    // <li><a href="/docs/{{version}}/releases">Release Notes</a></li>
}

ItemEnum — Item type enum

Case Meaning
ItemEnum::section A section heading (no link)
ItemEnum::page A page entry (has a link)

Advanced Usage

Custom depth calculation

Different documentation styles use different indent patterns. Pass style-specific parameters to getDepth():

// Tizen style — uses '#' headings with '#' symbol
Item::of('## Versions')->getDepth(indent: '#', symbol: '#', initialHCount: 1, depth: 1);
// => 2

// Naver Clova style — uses '*' symbol with 2-space indent
Item::of('* [CFR API란?](/CFR/API_Guide.md#Overview)')
    ->getDepth(indent: '  ', symbol: '*', initialHCount: 1, depth: 2);
// => 3

// Rhymix style — starts from h3
Item::of('- [설치 환경](./ko/introduction/requirements.md)')
    ->getDepth(symbol: '-', initialHCount: 3, depth: 1);
// => 2

Custom converters

Add preprocessors to clean or transform markdown before parsing:

use Cable8mm\Toc\Converters\CleanJustTopHConverter;

$toc = new Toc($markdown);
$toc->addConverters([
    new CleanJustTopHConverter, // Removes standalone top-level # headings
]);

// Now parse
$reflection = new ReflectionMethod(Toc::class, 'normalize');
$reflection->setAccessible(true);
$reflection->invoke($toc);

You can also implement your own converter by implementing ConverterInterface:

use Cable8mm\Toc\Contracts\ConverterInterface;
use Cable8mm\Toc\Types\MarkdownString;

class MyConverter implements ConverterInterface
{
    public function do(MarkdownString $markdown): MarkdownString
    {
        // Transform $markdown, then return a new MarkdownString
        $cleaned = some_transformation((string) $markdown);
        return new MarkdownString($cleaned);
    }
}

Testing

composer test

Code Style

composer lint    # Auto-fix PSR-12 issues
composer inspect # Check PSR-12 compliance

License

The MIT License (MIT). See LICENSE.