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