1<?php 2/* 3 * This plugin extends DokuWiki's list markup syntax to allow definition lists 4 * and list items with multiple paragraphs. The complete syntax is as follows: 5 * 6 * 7 * - ordered list item [<ol><li>] <!-- as standard syntax --> 8 * * unordered list item [<ul><li>] <!-- as standard syntax --> 9 * ? definition list term [<dl><dt>] 10 * : definition list definition [<dl><dd>] 11 * 12 * -- ordered list item w/ multiple paragraphs 13 * ** unordered list item w/ multiple paragraphs 14 * :: definition list definition w/multiple paragraphs 15 * .. new paragraph in --, **, or :: 16 * 17 * 18 * Lists can be nested within lists, just as in the standard DokuWiki syntax. 19 * 20 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 21 * @author Ben Slusky <sluskyb@paranoiacs.org> 22 * 23 */ 24if (!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); 25if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 26require_once(DOKU_PLUGIN.'syntax.php'); 27class syntax_plugin_yalist extends DokuWiki_Syntax_Plugin { 28 var $stack = array(); 29 function getType() { 30 return 'container'; 31 } 32 function getSort() { 33 // just before listblock (10) 34 return 9; 35 } 36 function getPType() { 37 return 'block'; 38 } 39 function getAllowedTypes() { 40 return array('substition', 'protected', 'disabled', 'formatting'); 41 } 42 function connectTo($mode) { 43 $this->Lexer->addEntryPattern('\n {2,}(?:--?|\*\*?|\?|::?)', $mode, 'plugin_yalist'); 44 $this->Lexer->addEntryPattern('\n\t{1,}(?:--?|\*\*?|\?|::?)', $mode, 'plugin_yalist'); 45 $this->Lexer->addPattern('\n {2,}(?:--?|\*\*?|\?|::?|\.\.)', 'plugin_yalist'); 46 $this->Lexer->addPattern('\n\t{1,}(?:--?|\*\*?|\?|::?|\.\.)', 'plugin_yalist'); 47 } 48 function postConnect() { 49 $this->Lexer->addExitPattern('\n', 'plugin_yalist'); 50 } 51 function handle($match, $state, $pos, Doku_Handler $handler) { 52 $output = array(); 53 $level = 0; 54 switch ($state) { 55 case DOKU_LEXER_ENTER: 56 $frame = $this->_interpret_match($match); 57 $level = $frame['level'] = 1; 58 array_push($output, 59 "${frame['list']}_open", 60 "${frame['item']}_open", 61 "${frame['item']}_content_open"); 62 if ($frame['paras']) 63 array_push($output, 'p_open'); 64 array_push($this->stack, $frame); 65 break; 66 case DOKU_LEXER_EXIT: 67 $close_content = true; 68 while ($frame = array_pop($this->stack)) { 69 // for the first frame we pop off the stack, we'll need to 70 // close the content tag; for the rest it will have been 71 // closed already 72 if ($close_content) { 73 if ($frame['paras']) 74 array_push($output, 'p_close'); 75 array_push($output, "${frame['item']}_content_close"); 76 $close_content = false; 77 } 78 array_push($output, 79 "${frame['item']}_close", 80 "${frame['list']}_close"); 81 } 82 break; 83 case DOKU_LEXER_MATCHED: 84 $last_frame = end($this->stack); 85 if (substr($match, -2) == '..') { 86 // new paragraphs cannot be deeper than the current depth, 87 // but they may be shallower 88 $para_depth = count(explode(' ', str_replace("\t", ' ', $match))); 89 $close_content = true; 90 while ($para_depth < $last_frame['depth'] && count($this->stack) > 1) { 91 if ($close_content) { 92 if ($last_frame['paras']) 93 array_push($output, 'p_close'); 94 array_push($output, "${last_frame['item']}_content_close"); 95 $close_content = false; 96 } 97 array_push($output, 98 "${last_frame['item']}_close", 99 "${last_frame['list']}_close"); 100 array_pop($this->stack); 101 $last_frame = end($this->stack); 102 } 103 if ($last_frame['paras']) { 104 if ($close_content) 105 // depth did not change 106 array_push($output, 'p_close', 'p_open'); 107 else 108 array_push($output, 109 "${last_frame['item']}_content_open", 110 'p_open'); 111 } else { 112 // let's just pretend we didn't match... 113 $state = DOKU_LEXER_UNMATCHED; 114 $output = $match; 115 } 116 break; 117 } 118 $curr_frame = $this->_interpret_match($match); 119 if ($curr_frame['depth'] > $last_frame['depth']) { 120 // going one level deeper 121 $level = $last_frame['level'] + 1; 122 if ($last_frame['paras']) 123 array_push($output, 'p_close'); 124 array_push($output, 125 "${last_frame['item']}_content_close", 126 "${curr_frame['list']}_open"); 127 } else { 128 // same depth, or getting shallower 129 $close_content = true; 130 // keep popping frames off the stack until we find a frame 131 // that's at least as deep as this one, or until only the 132 // bottom frame (i.e. the initial list markup) remains 133 while ($curr_frame['depth'] < $last_frame['depth'] && 134 count($this->stack) > 1) 135 { 136 // again, we need to close the content tag only for 137 // the first frame popped off the stack 138 if ($close_content) { 139 if ($last_frame['paras']) 140 array_push($output, 'p_close'); 141 array_push($output, "${last_frame['item']}_content_close"); 142 $close_content = false; 143 } 144 array_push($output, 145 "${last_frame['item']}_close", 146 "${last_frame['list']}_close"); 147 array_pop($this->stack); 148 $last_frame = end($this->stack); 149 } 150 // pull the last frame off the stack; 151 // it will be replaced by the current frame 152 array_pop($this->stack); 153 $level = $last_frame['level']; 154 if ($close_content) { 155 if ($last_frame['paras']) 156 array_push($output, 'p_close'); 157 array_push($output, "${last_frame['item']}_content_close"); 158 $close_content = false; 159 } 160 array_push($output, "${last_frame['item']}_close"); 161 if ($curr_frame['list'] != $last_frame['list']) { 162 // change list types 163 array_push($output, 164 "${last_frame['list']}_close", 165 "${curr_frame['list']}_open"); 166 } 167 } 168 // and finally, open tags for the new list item 169 array_push($output, 170 "${curr_frame['item']}_open", 171 "${curr_frame['item']}_content_open"); 172 if ($curr_frame['paras']) 173 array_push($output, 'p_open'); 174 $curr_frame['level'] = $level; 175 array_push($this->stack, $curr_frame); 176 break; 177 case DOKU_LEXER_UNMATCHED: 178 $output = $match; 179 break; 180 } 181 return array('state' => $state, 'output' => $output, 'level' => $level); 182 } 183 function _interpret_match($match) { 184 $tag_table = array( 185 '*' => 'u_li', 186 '-' => 'o_li', 187 '?' => 'dt', 188 ':' => 'dd', 189 ); 190 $tag = $tag_table[substr($match, -1)]; 191 return array( 192 'depth' => count(explode(' ', str_replace("\t", ' ', $match))), 193 'list' => substr($tag, 0, 1) . 'l', 194 'item' => substr($tag, -2), 195 'paras' => (substr($match, -1) == substr($match, -2, 1)), 196 ); 197 } 198 function render($mode, Doku_Renderer $renderer, $data) { 199 if ($mode != 'xhtml' && $mode != 'latex') 200 return false; 201 if ($data['state'] == DOKU_LEXER_UNMATCHED) { 202 $renderer->doc .= $renderer->_xmlEntities($data['output']); 203 return true; 204 } 205 foreach ($data['output'] as $i) { 206 $markup = ''; 207 if ($mode == 'xhtml') { 208 switch ($i) { 209 case 'ol_open': 210 $markup = "<ol>\n"; 211 break; 212 case 'ol_close': 213 $markup = "</ol>\n"; 214 break; 215 case 'ul_open': 216 $markup = "<ul>\n"; 217 break; 218 case 'ul_close': 219 $markup = "</ul>\n"; 220 break; 221 case 'dl_open': 222 $markup = "<dl>\n"; 223 break; 224 case 'dl_close': 225 $markup = "</dl>\n"; 226 break; 227 case 'li_open': 228 $markup = "<li class=\"level${data['level']}\">"; 229 break; 230 case 'li_content_open': 231 $markup = "<div class=\"li\">\n"; 232 break; 233 case 'li_content_close': 234 $markup = "\n</div>"; 235 break; 236 case 'li_close': 237 $markup = "</li>\n"; 238 break; 239 case 'dt_open': 240 $markup = "<dt class=\"level${data['level']}\">"; 241 break; 242 case 'dt_content_open': 243 $markup = "<span class=\"dt\">"; 244 break; 245 case 'dt_content_close': 246 $markup = "</span>"; 247 break; 248 case 'dt_close': 249 $markup = "</dt>\n"; 250 break; 251 case 'dd_open': 252 $markup = "<dd class=\"level${data['level']}\">"; 253 break; 254 case 'dd_content_open': 255 $markup = "<div class=\"dd\">\n"; 256 break; 257 case 'dd_content_close': 258 $markup = "\n</div>"; 259 break; 260 case 'dd_close': 261 $markup = "</dd>\n"; 262 break; 263 case 'p_open': 264 $markup = "<p>\n"; 265 break; 266 case 'p_close': 267 $markup = "\n</p>"; 268 break; 269 } 270 } else { 271 // $mode == 'latex' 272 switch ($i) { 273 case 'ol_open': 274 $markup = "\\begin{enumerate}\n"; 275 break; 276 case 'ol_close': 277 $markup = "\\end{enumerate}\n"; 278 break; 279 case 'ul_open': 280 $markup = "\\begin{itemize}\n"; 281 break; 282 case 'ul_close': 283 $markup = "\\end{itemize}\n"; 284 break; 285 case 'dl_open': 286 $markup = "\\begin{description}\n"; 287 break; 288 case 'dl_close': 289 $markup = "\\end{description}\n"; 290 break; 291 case 'li_open': 292 $markup = "\item "; 293 break; 294 case 'li_content_open': 295 break; 296 case 'li_content_close': 297 break; 298 case 'li_close': 299 $markup = "\n"; 300 break; 301 case 'dt_open': 302 $markup = "\item["; 303 break; 304 case 'dt_content_open': 305 break; 306 case 'dt_content_close': 307 break; 308 case 'dt_close': 309 $markup = "] "; 310 break; 311 case 'dd_open': 312 break; 313 case 'dd_content_open': 314 break; 315 case 'dd_content_close': 316 break; 317 case 'dd_close': 318 $markup = "\n"; 319 break; 320 case 'p_open': 321 $markup = "\n"; 322 break; 323 case 'p_close': 324 $markup = "\n"; 325 break; 326 } 327 } 328 $renderer->doc .= $markup; 329 } 330 if ($data['state'] == DOKU_LEXER_EXIT) 331 $renderer->doc .= "\n"; 332 return true; 333 } 334}