1<?php
2if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
3if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
4require_once(DOKU_PLUGIN.'syntax.php');
5
6class syntax_plugin_complex_lists extends DokuWiki_Syntax_Plugin {
7
8    function getInfo(){
9        return array(
10            'author' => 'Troy Rollo',
11            'email'  => 'dokuwiki@troy.rollo.name',
12            'date'   => '2008-06-10',
13            'name'   => 'Complex Lists Plugin',
14            'desc'   => 'Add complex multi-level lists',
15            'url'    => 'http://wiki.splitbrain.org/plugin:complex_lists'
16        );
17    }
18
19    function syntax_plugin_complex_lists(){
20    }
21
22    function getAllowedTypes()
23    {
24        return array('container', 'paragraphs', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs');
25    }
26
27    function getType(){
28        return 'container';
29    }
30
31    function getPType(){
32        return 'block';
33    }
34
35    function getSort(){
36        return 15;
37    }
38
39    function connectTo($mode) {
40        $this->Lexer->addEntryPattern('\n#[^\n#]+\|[^\n#]+#\n', $mode, 'plugin_complex_lists');
41    }
42    function postConnect() {
43	$this->Lexer->addPattern('\n#\n', 'plugin_complex_lists');
44	$this->Lexer->addPattern('\n#\+#\n', 'plugin_complex_lists');
45	$this->Lexer->addPattern('\n#-#\n', 'plugin_complex_lists');
46	$this->Lexer->addPattern('#:[^#]+#', 'plugin_complex_lists');
47        $this->Lexer->addExitPattern('\n##\n','plugin_complex_lists');
48    }
49
50    function handle($match, $state, $pos, &$handler){
51        switch ($state)
52	{
53        case DOKU_LEXER_ENTER:
54		return array($state, $this->list_styles = explode('|', trim($match, "#\r\n")));
55
56	case DOKU_LEXER_EDIT:
57		return array($state);
58
59	case DOKU_LEXER_UNMATCHED:
60		$handler->base($match, $state, $pos);
61		return array($state);
62
63	case DOKU_LEXER_MATCHED:
64		$match = trim($match, "\n");
65		switch ($match)
66		{
67		case '#':
68		    return array($state, 0);
69
70		case '#+#':
71		    return array($state, 1);
72
73		case '#-#':
74		    return array($state, 2);
75
76		default: /* Bookmark */
77		    return array($state, 3, substr(trim($match, '#'), 1));
78
79		}
80        }
81        return array($state);
82    }
83
84    function romanNum($n)
85    {
86	$r = '';
87
88	while ($n >= 1000)
89	{
90		$n -= 1000;
91		$r = $r . 'm';
92	}
93	if ($n >= 900)
94	{
95		$r = $r . 'cm';
96		$n -= 900;
97	}
98	if ($n >= 500)
99	{
100		$r = $r . 'd';
101		$n -= 500;
102	}
103	if ($n > 400)
104	{
105		$r = $r . 'cd';
106		$n -= 400;
107	}
108	while ($n >= 100)
109	{
110		$r = $r . 'c';
111		$n -= 100;
112	}
113	if ($n >= 90)
114	{
115		$r = $r . 'xc';
116		$n -= 90;
117	}
118	if ($n >= '50')
119	{
120		$r = $r . 'l';
121		$n -= 50;
122	}
123	if ($n >= '40')
124	{
125		$r = $r . 'xl';
126		$n -= 40;
127	}
128	while ($n >= 10)
129	{
130		$r = $r . 'x';
131		$n -= 10;
132	}
133	if ($n == 9)
134	{
135		$r = $r . 'ix';
136		$n = 0;
137	}
138	if ($n >= 5)
139	{
140		$r = $r .'v';
141		$n -= 5;
142	}
143	if ($n == 4)
144	{
145		$r = $r . 'iv';
146		$n = 0;
147	}
148	while ($n > 0)
149	{
150		$r = $r . 'i';
151		--$n;
152	}
153	return $r;
154    }
155
156    function latinNum($n)
157    {
158	$r = '';
159        $j=0;
160	while ($n > 0 && $j < 20)
161	{
162	    $d = $n % 27;
163	    $r = chr(0x60 + $d);
164	    $n = ($n - $d) / 27;
165	    if ($n > 0)
166		++$n;
167	    ++$j;
168	}
169	return $r;
170    }
171
172    function getLevelText($counters, $level)
173    {
174	$str = $this->list_styles[$level - 1];
175	$len = strlen($str);
176	$type = 0;
177	$n = $counters[$level];
178
179	for ($loc = 0; $loc < $len; ++$loc)
180	{
181	   $type = strpos("01aAiI", substr($str, $loc, 1));
182	   if ($type)
183		break;
184	}
185	switch ($type)
186	{
187	default:
188	    return '???';
189
190	case 1:
191	    $numtext = $n;
192	    break;
193
194	case 2:
195	    $numtext = $this->latinNum($n);
196	    break;
197
198	case 3:
199	    $numtext = strtoupper($this->latinNum($n));
200	    break;
201
202	case 4:
203	    $numtext = $this->romanNum($n);
204	    break;
205
206	case 5:
207	    $numtext = strtoupper($this->romanNum($n));
208	    break;
209	}
210
211	return htmlspecialchars(substr($str, 0, $loc) . $numtext . substr($str, $loc + 1));
212    }
213
214    function getListText()
215    {
216	return $this->getLevelText($this->list_counters, $this->list_level);
217    }
218
219    function startList() {
220        $this->list_level += 1;
221	$this->list_counters[$this->list_level] = 0;
222	return '<table class="complexlist' . $this->list_level . '">' . "\n";
223    }
224
225    function startListItem() {
226	$this->list_counters[$this->list_level] += 1;
227	return	'<!--CPLX-LIST-ITEM#' . $this->list_level . '#' . $this->list_counters[$this->list_level] .
228		'--><tr><td class="complexlistnum">' .
229		$this->getListText() .
230		'</td><td class="complexlistcont">' . "\n";
231    }
232
233    function endListItem() {
234	if ($this->list_level > 0)
235		return "</td></tr>\n";
236    }
237
238    function endList()
239    {
240	if ($this->list_level > 0)
241	{
242	    $this->list_level -= 1;
243            return "<!--CPLX-LIST-ENDSUB--></table>\n";
244	}
245    }
246
247    function getListLevelNow()
248    {
249        return $this->list_level;
250    }
251    /**
252     * Create output
253     */
254    function render($mode, &$renderer, $data) {
255        if($mode == 'xhtml'){
256	    $first = 1;
257	    switch ($data[0])
258	    {
259	    case DOKU_LEXER_UNMATCHED:
260		//$renderer->doc .= "<pre>[$data[1]]</pre>";
261		return false;
262
263	    case DOKU_LEXER_ENTER:
264		$this->bookmarks = array();
265		$this->list_styles = $data[1];
266
267		$this->list_level = 0;
268		$renderer->doc .= '<!--CPLX-LIST-START-->';
269		$renderer->doc .= $this->startList() .
270				  $this->startListItem();
271		return true;
272
273	    case DOKU_LEXER_MATCHED:
274		switch ($data[1])
275		{
276		case 0:
277			$renderer->doc .= $this->endListItem() .
278					$this->startListItem();
279			break;
280
281		case 1:
282			$renderer->doc .= $this->startList() .
283					$this->startListItem();
284			break;
285
286		case 2:
287			$renderer->doc .= $this->endListItem() .
288					$this->endList();
289			break;
290
291		case 3:
292			$this->bookmarks[$data[2]][0] = $this->list_level;
293			$this->bookmarks[$data[2]][1] = $this->list_counters;
294			break;
295		}
296		return true;
297
298	    case DOKU_LEXER_EXIT:
299		while ($this->list_level > 0)
300		{
301		    $renderer->doc .= $this->endListItem() .
302		    			$this->endList();
303		}
304		$pos = strpos($renderer->doc, '<!--CPLX-LIST-START-->');
305		$listtext = substr($renderer->doc, $pos + 22);
306		$renderer->doc = substr($renderer->doc, 0, $pos);
307
308		$level = 1;
309		$lcounters[1] = 0;
310
311		while ($listtext)
312		{
313		    $pos = strpos($listtext, '<!--CPLX-LIST-');
314		    if (!$pos)
315		    {
316			$renderer->doc .= $listtext;
317			break;
318		    }
319		    $renderer->doc .= substr($listtext, 0, $pos);
320		    $listtext = substr($listtext, $pos+14);
321		    $pos = strpos($listtext, '-->');
322		    if (!$pos)
323		    {
324			$renderer->doc .= 'Catastrophe';
325			break;
326		    }
327		    $command = explode('#', substr($listtext, 0, $pos));
328		    $listtext = substr($listtext, $pos + 3);
329
330		    switch ($command[0])
331		    {
332		    case 'XREF':
333			$bookmark = $this->bookmarks[$command[2]];
334			if ($bookmark)
335			{
336			    if ($command[1] == 2)
337			    {
338			        for ($i = 1; $i <= $bookmark[0]; ++$i)
339			            $renderer->doc .= $this->getLevelText($bookmark[1], $i);
340			    }
341			    else
342			    {
343			        $last = $bookmark[0];
344			        $first = $last;
345			        if ($first > $level)
346				    $first = $level + 1;
347			        for ($i = 1; $i < $first; ++$i)
348			        {
349			            if ($lcounters[$i] != $bookmark[1][$i])
350				    {
351					$first = $i;
352					break;
353				    }
354				}
355			        for ($i = $first; $i <= $last; ++$i)
356				    $renderer->doc .= $this->getLevelText($bookmark[1], $i);
357			    }
358			}
359			else
360			{
361			       $renderer->doc .= '<span class="cplxlisterror">Bookmark not found!</span>';
362			}
363			break;
364
365		    case 'ENDSUB':
366			--$level;
367			break;
368
369		    case 'ITEM':
370			if ($command[1] > $level)
371			    $level = $command[1];
372			$lcounters[$level] = $command[2];
373			break;
374
375		    default:
376			$renderer->doc .= 'Bad command: ' . $command[0] . '!';
377		    }
378		}
379
380		return true;
381	    }
382        }
383        return false;
384    }
385
386}
387
388//Setup VIM: ex: et ts=4 enc=utf-8 :
389?>
390