1<?php
2/**
3 * DokuWiki Plugin autonumbering (Syntax Component)
4 *
5 * @description         :   This plugin allows the use of multiples counters
6 *                          with multiples levels, within the same page.
7 *
8 * @syntax (Base)       :   ~~#~~
9 *                              --> Where ~~#~~ will be replaced by a number,
10 *                                  auto incremented, and saved in a common
11 *                                  counter.
12 * @syntax (ID)         :   ~~#@COUNTERID~~
13 *                              --> Where COUNTERID is an alphanumeric
14 *                                  identificator, including unserscore,
15 *                                  and starting with a @. This allows
16 *                                  the use of multiple counters.
17 * @syntax (forced)     :   ~~#NUM~~
18 *                              --> Where NUM is a positive number that will
19 *                                  be the begining of the auto incrementation
20 *                                  from there.
21 * @syntax (multilevel) :   ~~#.#~~
22 *                              --> Where .# represent a sublevel and can be
23 *                                  repeated as much as needed.
24 *
25 * @syntax (text)       :   ~~REPORT.EXAMPLE.#~~
26 *                              --> Where only the third level will be an auto
27 *                                  incremented number. The first level will
28 *                                  be a repeated text. Here it will be REPORT.
29 *                                  Samething for the second level with EXAMPLE.
30 *                                  When using text in a level, it will be
31 *                                  implicitly used as counter ID if no counter
32 *                                  ID have been set with @COUNTERID.
33 *
34 * @example             :   ~~Test.#4.#6@CTR_ONE~~
35 *                              --> Where the number will have three levels.
36 *                                  First level will be the text « Test ».
37 *                                  Second level will be an auto incremented
38 *                                  number starting at 4. Third level will be
39 *                                  an auto incremented number starting at 6.
40 *                                  All this will be save in the counter
41 *                                  « CTR_ONE ».
42 *
43 * @license             :   GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
44 */
45
46
47// must be run within DokuWiki
48if (!defined('DOKU_INC')) die();
49
50if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
51
52require_once(DOKU_PLUGIN.'syntax.php');
53
54class syntax_plugin_autonumbering extends DokuWiki_Syntax_Plugin {
55
56    var $PLUGIN_PATTERN = "~~[a-zA-Z0-9_\.#@]*#[a-zA-Z0-9_\.#@]*~~";
57    var $NUMBER_PATTERN = "[0-9]+";
58    var $COUNTER_ID_PATTERN = "[a-zA-Z0-9_]+";
59
60
61    public function getType() {
62        return 'substition';
63    }
64
65    public function getPType() {
66        return 'normal';
67    }
68
69    public function getSort() {
70        return 45;
71    }
72
73    public function connectTo($mode) {
74        $this->Lexer->addSpecialPattern($this->PLUGIN_PATTERN, $mode, 'plugin_autonumbering');
75    }
76
77    public function handle($match, $state, $pos, Doku_Handler $handler){
78        global $COUNTER;
79        $counterID = '';
80        switch ($state) {
81            case DOKU_LEXER_SPECIAL :
82
83                if (preg_match('/~~(.*?)~~/', $match, $matches)) {
84                    $data = $matches[1];
85
86                    if (!empty($data)) {
87                        // Search for EXPLICIT counter ID
88                        if (preg_match('/@(' . $this->COUNTER_ID_PATTERN . ')/', $data, $matches)) {
89                            $counterID = $matches[1];
90                            // Remove counter ID from $data
91                            $data = str_replace('@' . $counterID, '', $data);
92                        } else {    // Search for IMPLICIT counter ID
93                            $alpha = preg_replace('/[^a-zA-Z]/', '', $data);
94                            if (!empty($alpha))
95                                $counterID = $alpha;
96                        }
97
98                        // Separate levels
99                        $dataTab = explode('.', $data);
100
101                        // Get levels quantity
102                        $levelsQty = count($dataTab);
103                        $currentLevel = $levelsQty - 1;
104
105                        // Check if parent level exist
106                        for ($i = 0; $i < $levelsQty; ++$i) {
107                            // Check if level contain text
108                            if (ctype_alpha($dataTab[$i]))
109                                $COUNTER[$counterID][$i] = $dataTab[$i];
110                            // Search for a forced value
111                            else if (preg_match('/(' . $this->NUMBER_PATTERN . ')/', $dataTab[$i], $matches))
112                                if ($i == $currentLevel)
113                                    $COUNTER[$counterID][$i] = $matches[1]-1;
114                                else
115                                    $COUNTER[$counterID][$i] = $matches[1];
116                            // initialize if needed
117                            else if ((!isset($COUNTER[$counterID][$i])) || ($COUNTER[$counterID][$i] == 0))
118                                if ($i == $currentLevel)
119                                    $COUNTER[$counterID][$i] = 0;
120                                else
121                                    $COUNTER[$counterID][$i] = 1;
122                        }
123
124                        // Check if child level exist, and initialize
125                        $counter_levelsQty = count($COUNTER[$counterID]);
126                        for ($i = $currentLevel+1; $i < $counter_levelsQty; ++$i)
127                            $COUNTER[$counterID][$i] = 0;
128
129                        // Increment current level
130                        ++$COUNTER[$counterID][$currentLevel];
131
132                        // Return the number, according the level asked
133                        $number = '';
134                        $period = '';
135                        for ($i = 0; $i < $levelsQty; ++$i) {
136                            $number .= $period . $COUNTER[$counterID][$i];
137                            $period = '.';
138                        }
139                        return array($number, NULL);
140                    } else {
141                        return array($match, NULL);
142                    }
143                }
144            break;
145        }
146        return array();
147    }
148
149    public function render($mode, Doku_Renderer $renderer, $data) {
150        if(($mode == 'xhtml') && (!empty($data))) {
151            list($number, $null) = $data;
152            $renderer->doc .= $number;
153            return true;
154        }
155        return false;
156    }
157
158
159    // To work with the plugin « reproduce », who needs to do
160    // the numbering prior to reproducing the code.
161    public function doNumbering($pageContent){
162        $qtyOccurrences = preg_match_all('/~~(.*?)~~/', $pageContent, $matches);
163        if ($qtyOccurrences > 0) {
164            for ($i = 0; $i < $qtyOccurrences; $i++) {
165                list($number, $null) = $this->handle($matches[0][$i], DOKU_LEXER_SPECIAL, NULL, $handler);
166                $pageContent = preg_replace('(' . $matches[0][$i] . ')', $number, $pageContent, 1);
167            }
168        }
169        return $pageContent;
170    }
171}
172