1<?php
2
3/**
4 * DokuWiki Plugin Numbered Headings: add tiered numbers for hierarchical headings
5 *
6 * Usage:   ===== - Heading Level 2 =====
7 *          ==== - Heading Level 3 ====
8 *          ==== - Heading Level 3 ====
9 *          ...
10 *
11 * =>       1. Heading Level 2
12 *              1.1 Heading Level 3
13 *              1.2 Heading Level 3
14 *          ...
15 *
16 * Config settings
17 *     tier1  : heading level corresponding to the 1st tier
18 *     format : numbering format (used in vsprintf) of each tier, JSON array string
19 *
20 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
21 * @author     Lars J. Metz <dokuwiki@meistermetz.de>
22 * @author     Satoshi Sahara <sahara.satoshi@gmail.com>
23 */
24class syntax_plugin_numberedheadings extends DokuWiki_Syntax_Plugin
25{
26    function getType()
27    {
28        return 'substition';
29    }
30
31    /**
32     * Connect pattern to lexer
33     */
34    protected $mode, $pattern;
35
36    function preConnect()
37    {
38        // syntax mode, drop 'syntax_' from class name
39        $this->mode = substr(get_class($this), 7);
40
41        // syntax pattern
42        $this->pattern[0] = '~~HEADLINE NUMBERING FIRST LEVEL = \d~~';
43        $this->pattern[5] = '^[ \t]*={2,} ?-+(?:[#"][^\n]*)? [^\n]*={2,}[ \t]*(?=\n)';
44    }
45
46    function connectTo($mode)
47    {
48        $this->Lexer->addSpecialPattern($this->pattern[0], $mode, $this->mode);
49        $this->Lexer->addSpecialPattern($this->pattern[5], $mode, $this->mode);
50
51        // backward compatibility, to be obsoleted in future ...
52        $this->Lexer->addSpecialPattern(
53                        '{{header>[1-5]}}', $mode, $this->mode);
54        $this->Lexer->addSpecialPattern(
55                        '{{startlevel>[1-5]}}', $mode, $this->mode);
56    }
57
58    function getSort()
59    {
60        return 45;
61    }
62
63    /**
64     * Handle the match
65     */
66    function handle($match, $state, $pos, Doku_Handler $handler)
67    {
68        // obtain the first tier (Tier1) level from the page if defined
69        $match = trim($match);
70        if ($match[0] !== '=') {
71            // Note: The Tier1 Level may become 0 (auto-detect) in the page
72            $level = (int) substr($match, -3, 1);
73            return $data = compact('level');
74        }
75
76        // obtain the level of the heading
77        $level = 7 - min(strspn($match, '='), 6);
78
79        // separate parameter and title
80        // == -#n title  == ; "#" is a parameter indicates number
81        // == - #n title == ; "#" is a placeholder of numbering label
82
83        $text = trim(trim($match), '='); // drop heading markup
84        $text = ltrim($text);
85        $dash = strspn($text, '-');      // count dash marker to check '-' or '--'
86        $text = substr($text, $dash);
87
88        switch ($text[0]) {
89            case ' ':
90                list($number, $title) = array('', trim($text));
91                if ($title[0] == '#') {
92                    // extra check of title
93                    // == - # title ==     ; "#" is NOT numbering label
94                    // == - #12 title ==   ; "#" is numbering label with number
95                    // == - #12.3 title == ; "#" is NOT numbering label
96                    $part = explode(' ', substr($title, 1), 2);
97                    if (ctype_digit($part[0])) {
98                        $number = $part[0] +0;
99                        $title  = trim($part[1]);
100                    }
101                }
102                break;
103            case '#': // numeric numbering, (integer) $number
104                list($number, $title) = explode(' ', substr($text, 1), 2);
105                $number = ctype_digit($number) ? $number +0 : '';
106                $title  = trim($title);
107                break;
108            case '"': // alpha-numeric numbering, (string) $number
109                $closed = strpos($text, '"', 1); // search closing "
110                if ($closed !== false) {
111                    $number = substr($text, 1, $closed -1);
112                    $title  = trim(substr($text, $closed + 1));
113                } else {
114                    list($number, $title) = explode(' ', substr($text, 1), 2);
115                    $title  = trim($title);
116                }
117                break;
118        }
119
120        // non-visible numbered headings, marked with '--'
121        if ($dash > 1 && $title[0] == '[' && substr($title, -1) == ']') {
122            $format = $title;
123            unset($title);
124        }
125
126        $data = compact('dash', 'level', 'number', 'title', 'format');
127
128        if ($dash == 1) {
129            // do same as parser::handler->header()
130        	if ($this->getSectionState($handler)) $this->addCall($handler, 'section_close', [], $pos);
131
132            // plugin instruction to be rewrited later
133            $handler->addPluginCall(substr(get_class($this), 14), $data, $state, $pos, $match);
134            $this->addCall($handler, 'section_open', [$level], $pos);
135            $this->setSectionState($handler, true);
136        } else {
137            return $data;
138        }
139        return false;
140    }
141
142    /**
143     * Create output
144     */
145    function render($format, Doku_Renderer $renderer, $data)
146    {
147        // nothing to do, should never be called because plugin instructions
148        // are converted to normal headers in PARSER_HANDLER_DONE event handler
149    }
150
151    /* -------------------------------------------------------------- *
152     * Compatibility methods for DokuWiki Hogfather
153     * -------------------------------------------------------------- */
154     // add a new call using CallWriter of the handler object
155     private function addCall(Doku_Handler $handler, $method, $args, $pos)
156     {
157     	 if (method_exists($handler, 'addCall')) {
158     	 	 // applicable since DokuWiki RC3 2020-06-10 Hogfather
159     	 	 $handler->addCall($method, $args, $pos);
160     	 } else {
161     	 	 // until DokuWiki 2018-04-22 Greebo
162     	 	 $handler->_addCall($method, $args, $pos);
163     	 }
164     }
165
166     // get section status of the handler object
167     private function getSectionstate(Doku_Handler $handler)
168     {
169     	 if (method_exists($handler, 'getStatus')) {
170     	     return $handler->getStatus('section');
171     	 } else {
172     	 	 return $handler->status['section'];
173     	 }
174     }
175
176     // set section status of the handler object
177     private function setSectionstate(Doku_Handler $handler, $value)
178     {
179     	 if (method_exists($handler, 'setStatus')) {
180     	     $handler->setStatus('section', $value);
181     	 } else {
182     	 	 $handler->status['section'] = $value;
183     	 }
184     }
185
186}
187