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