1<?php 2 3/** 4 * Plugin RefNotes: Reference collector/renderer 5 * 6 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 7 * @author Mykola Ostrovskyy <dwpforge@gmail.com> 8 */ 9 10require_once(DOKU_PLUGIN . 'refnotes/core.php'); 11require_once(DOKU_PLUGIN . 'refnotes/bibtex.php'); 12 13//////////////////////////////////////////////////////////////////////////////////////////////////// 14class syntax_plugin_refnotes_references extends DokuWiki_Syntax_Plugin { 15 use refnotes_localization_plugin; 16 17 private $mode; 18 private $entryPattern; 19 private $exitPattern; 20 private $handlePattern; 21 private $noteCapture; 22 23 /** 24 * Constructor 25 */ 26 public function __construct() { 27 refnotes_localization::initialize($this); 28 29 $this->mode = substr(get_class($this), 7); 30 $this->noteCapture = new refnotes_note_capture(); 31 32 $this->initializePatterns(); 33 } 34 35 /** 36 * 37 */ 38 private function initializePatterns() { 39 if (refnotes_configuration::getSetting('replace-footnotes')) { 40 $entry = '(?:\(\(|\[\()'; 41 $exit = '(?:\)\)|\)\])'; 42 $id = '@@FNT\d+|#\d+'; 43 } 44 else { 45 $entry = '\[\('; 46 $exit = '\)\]'; 47 $id = '#\d+'; 48 } 49 50 $strictName = refnotes_note::getNamePattern('strict'); 51 $extendedName = refnotes_note::getNamePattern('extended'); 52 $namespace = refnotes_namespace::getNamePattern('optional'); 53 54 $text = '.*?'; 55 56 $strictName = '(?:' . $id . '|' . $strictName . ')'; 57 $fullName = '\s*(?:' . $namespace . $strictName . '|:' . $namespace . $extendedName . ')\s*'; 58 $lookaheadExit = '(?=' . $exit . ')'; 59 $nameEntry = $fullName . $lookaheadExit; 60 61 $extendedName = '(?:' . $id . '|' . $extendedName . ')'; 62 $optionalFullName = $namespace . $extendedName . '?'; 63 $structuredEntry = '\s*' . $optionalFullName . '\s*>>' . $text . $lookaheadExit; 64 65 $define = '\s*' . $optionalFullName . '\s*>\s*'; 66 $optionalDefine = '(?:' . $define . ')?'; 67 $lookaheadExit = '(?=' . $text . $exit . ')'; 68 $defineEntry = $optionalDefine . $lookaheadExit; 69 70 $this->entryPattern = $entry . '(?:' . $nameEntry . '|' . $structuredEntry . '|' . $defineEntry . ')'; 71 $this->exitPattern = $exit; 72 $this->handlePattern = '/' . $entry . '\s*(' . $optionalFullName . ')\s*(?:>>(.*))?(.*)/s'; 73 } 74 75 /** 76 * What kind of syntax are we? 77 */ 78 public function getType() { 79 return 'formatting'; 80 } 81 82 /** 83 * What modes are allowed within our mode? 84 */ 85 public function getAllowedTypes() { 86 return array ( 87 'formatting', 88 'substition', 89 'protected', 90 'disabled' 91 ); 92 } 93 94 /** 95 * Where to sort in? 96 */ 97 public function getSort() { 98 return 145; 99 } 100 101 public function connectTo($mode) { 102 refnotes_parser_core::getInstance()->registerLexer($this->Lexer); 103 104 $this->Lexer->addEntryPattern($this->entryPattern, $mode, $this->mode); 105 } 106 107 public function postConnect() { 108 $this->Lexer->addExitPattern($this->exitPattern, $this->mode); 109 } 110 111 /** 112 * Handle the match 113 */ 114 public function handle($match, $state, $pos, Doku_Handler $handler) { 115 $result = refnotes_parser_core::getInstance()->canHandle($state); 116 117 if ($result) { 118 switch ($state) { 119 case DOKU_LEXER_ENTER: 120 $result = $this->handleEnter($match); 121 break; 122 123 case DOKU_LEXER_EXIT: 124 $result = $this->handleExit(); 125 break; 126 } 127 } 128 129 if ($result === false) { 130 $handler->addCall('cdata', array($match), $pos); 131 } 132 133 return $result; 134 } 135 136 /** 137 * Create output 138 */ 139 public function render($mode, Doku_Renderer $renderer, $data) { 140 $result = false; 141 142 try { 143 switch ($mode) { 144 case 'xhtml': 145 case 'odt': 146 $result = $this->renderReferences($mode, $renderer, $data); 147 break; 148 149 case 'metadata': 150 $result = $this->renderMetadata($renderer, $data); 151 break; 152 } 153 } 154 catch (Exception $error) { 155 msg($error->getMessage(), -1); 156 } 157 158 return $result; 159 } 160 161 /** 162 * 163 */ 164 private function handleEnter($syntax) { 165 if (preg_match($this->handlePattern, $syntax, $match) == 0) { 166 return false; 167 } 168 169 refnotes_parser_core::getInstance()->enterReference($match[1], $match[2]); 170 171 return array('start'); 172 } 173 174 /** 175 * 176 */ 177 private function handleExit() { 178 $reference = refnotes_parser_core::getInstance()->exitReference(); 179 180 if ($reference->hasData()) { 181 return array('render', $reference->getAttributes(), $reference->getData()); 182 } 183 else { 184 return array('render', $reference->getAttributes()); 185 } 186 } 187 188 /** 189 * 190 */ 191 public function renderReferences($mode, $renderer, $data) { 192 switch ($data[0]) { 193 case 'start': 194 $this->noteCapture->start($renderer); 195 break; 196 197 case 'render': 198 $this->renderReference($mode, $renderer, $data[1], (count($data) > 2) ? $data[2] : array()); 199 break; 200 } 201 202 return true; 203 } 204 205 /** 206 * Stops renderer output capture and renders the reference link 207 */ 208 private function renderReference($mode, $renderer, $attributes, $data) { 209 $reference = refnotes_renderer_core::getInstance()->addReference($attributes, $data); 210 $text = $this->noteCapture->stop(); 211 212 if ($text != '') { 213 $reference->getNote()->setText($text); 214 } 215 216 $renderer->doc .= $reference->render($mode); 217 } 218 219 /** 220 * 221 */ 222 public function renderMetadata($renderer, $data) { 223 if ($data[0] == 'render') { 224 $source = ''; 225 226 if (array_key_exists('source', $data[1])) { 227 $source = $data[1]['source']; 228 } 229 230 if (($source != '') && ($source != '{configuration}')) { 231 $renderer->meta['plugin']['refnotes']['dbref'][wikiFN($source)] = true; 232 } 233 } 234 235 return true; 236 } 237} 238 239//////////////////////////////////////////////////////////////////////////////////////////////////// 240class refnotes_note_capture { 241 242 private $renderer; 243 private $note; 244 private $doc; 245 246 /** 247 * Constructor 248 */ 249 public function __construct() { 250 $this->initialize(); 251 } 252 253 /** 254 * 255 */ 256 private function initialize() { 257 $this->renderer = NULL; 258 $this->doc = ''; 259 } 260 261 /** 262 * 263 */ 264 private function resetCapture() { 265 $this->renderer->doc = ''; 266 } 267 268 /** 269 * 270 */ 271 public function start($renderer) { 272 $this->renderer = $renderer; 273 $this->doc = $renderer->doc; 274 275 $this->resetCapture(); 276 } 277 278 /** 279 * 280 */ 281 public function restart() { 282 $text = trim($this->renderer->doc); 283 284 $this->resetCapture(); 285 286 return $text; 287 } 288 289 /** 290 * 291 */ 292 public function stop() { 293 $text = trim($this->renderer->doc); 294 295 $this->renderer->doc = $this->doc; 296 297 $this->initialize(); 298 299 return $text; 300 } 301} 302 303//////////////////////////////////////////////////////////////////////////////////////////////////// 304class refnotes_nested_call_writer extends \dokuwiki\Parsing\Handler\Nest { 305 306 private $handler; 307 private $callWriterBackup; 308 309 /** 310 * Constructor 311 * 312 * HACK: Fix compatibility with PHP versions before 7.2 by passing handler as second optional 313 * argument. This makes constructor signature compatible with one defined in ReWriterInterface. 314 * Starting from PHP 7.2 this is not needed because arguments without type hint are compatible 315 * with any type since they have a wider type (any type). 316 * https://wiki.php.net/rfc/parameter-no-type-variance 317 */ 318 public function __construct(\dokuwiki\Parsing\Handler\CallWriterInterface $callWriter, $handler = NULL) { 319 $this->handler = $handler; 320 321 parent::__construct($this->handler->getCallWriter()); 322 } 323 324 /** 325 * 326 */ 327 public function connect() { 328 $this->callWriterBackup = $this->handler->getCallWriter(); 329 330 $this->handler->setCallWriter($this); 331 } 332 333 /** 334 * 335 */ 336 public function disconnect() { 337 $this->handler->setCallWriter($this->callWriterBackup); 338 } 339} 340