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