1<?php 2 3/** 4 * Plugin RefNotes: Namespace heplers 5 * 6 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 7 * @author Mykola Ostrovskyy <dwpforge@gmail.com> 8 */ 9 10//////////////////////////////////////////////////////////////////////////////////////////////////// 11abstract class refnotes_namespace_data_stash { 12 13 protected $index; 14 15 /** 16 * Constructor 17 */ 18 public function __construct() { 19 $this->index = array(); 20 } 21 22 /** 23 * 24 */ 25 abstract public function add($namespace, $data); 26 27 /** 28 * 29 */ 30 public function getCount() { 31 return count($this->index); 32 } 33 34 /** 35 * 36 */ 37 public function getIndex() { 38 return array_keys($this->index); 39 } 40 41 /** 42 * 43 */ 44 public function getAt($index) { 45 return array_key_exists($index, $this->index) ? $this->index[$index] : array(); 46 } 47 48 /** 49 * 50 */ 51 public function sort() { 52 ksort($this->index); 53 } 54} 55 56//////////////////////////////////////////////////////////////////////////////////////////////////// 57class refnotes_namespace_data { 58 59 protected $namespace; 60 protected $data; 61 62 /** 63 * Constructor 64 */ 65 public function __construct($namespace, $data) { 66 $this->namespace = $namespace; 67 $this->data = $data; 68 } 69 70 /** 71 * 72 */ 73 public function getNamespace() { 74 return $this->namespace->getName(); 75 } 76 77 /** 78 * 79 */ 80 public function getData() { 81 return $this->data; 82 } 83} 84 85//////////////////////////////////////////////////////////////////////////////////////////////////// 86class refnotes_namespace_style_stash extends refnotes_namespace_data_stash { 87 88 private $page; 89 90 /** 91 * Constructor 92 */ 93 public function __construct($page) { 94 parent::__construct(); 95 96 $this->page = $page; 97 } 98 99 /** 100 * 101 */ 102 public function add($namespace, $data) { 103 $style = new refnotes_namespace_style_info($namespace, $data); 104 $parent = $style->getInheritedNamespace(); 105 106 if (($parent == '') && ($namespace->getScopesCount() == 1)) { 107 /* Default inheritance for the first scope */ 108 $parent = refnotes_namespace::getParentName($namespace->getName()); 109 } 110 111 $index = $namespace->getStyleIndex($this->page->findParentNamespace($parent)); 112 113 $this->index[$index][] = $style; 114 } 115 /** 116 * Sort the style blocks so that the namespaces with inherited style go after 117 * the namespaces they inherit from. 118 */ 119 public function sort() { 120 parent::sort(); 121 122 $this->sortByDefaultInheritance(); 123 $this->sortByExplicitInheritance(); 124 } 125 126 /** 127 * 128 */ 129 private function sortByDefaultInheritance() { 130 foreach ($this->index as &$index) { 131 $namespace = array(); 132 133 foreach ($index as $style) { 134 $namespace[] = $style->getNamespace(); 135 } 136 137 array_multisort($namespace, SORT_ASC, $index); 138 } 139 } 140 141 /** 142 * 143 */ 144 private function sortByExplicitInheritance() { 145 foreach ($this->index as &$index) { 146 $derived = array(); 147 $sorted = array(); 148 149 foreach ($index as $style) { 150 if ($style->isDerived()) { 151 $derived[] = $style; 152 } 153 else { 154 $sorted[] = $style; 155 } 156 } 157 158 $derivedCount = count($derived); 159 160 if ($derivedCount > 0) { 161 if ($derivedCount == 1) { 162 $sorted[] = $derived[0]; 163 } 164 else { 165 /* Perform simplified topological sorting */ 166 $target = array(); 167 $source = array(); 168 169 for ($i = 0; $i < $derivedCount; $i++) { 170 $target[$i] = $derived[$i]->getNamespace(); 171 $source[$i] = $derived[$i]->getInheritedNamespace(); 172 } 173 174 for ($j = 0; $j < $derivedCount; $j++) { 175 foreach ($source as $i => $s) { 176 if (!in_array($s, $target)) { 177 break; 178 } 179 } 180 181 $sorted[] = $derived[$i]; 182 183 unset($target[$i]); 184 unset($source[$i]); 185 } 186 } 187 } 188 189 $index = $sorted; 190 } 191 } 192} 193 194//////////////////////////////////////////////////////////////////////////////////////////////////// 195class refnotes_namespace_style_info extends refnotes_namespace_data { 196 197 /** 198 * 199 */ 200 public function isDerived() { 201 return array_key_exists('inherit', $this->data); 202 } 203 204 /** 205 * 206 */ 207 public function getInheritedNamespace() { 208 return $this->isDerived() ? $this->data['inherit'] : ''; 209 } 210} 211 212//////////////////////////////////////////////////////////////////////////////////////////////////// 213class refnotes_namespace_mapping_stash extends refnotes_namespace_data_stash { 214 215 /** 216 * 217 */ 218 public function add($namespace, $data) { 219 $this->index[$namespace->getMappingIndex()][] = new refnotes_namespace_data($namespace, $data); 220 } 221} 222 223//////////////////////////////////////////////////////////////////////////////////////////////////// 224class refnotes_namespace { 225 226 private $name; 227 private $style; 228 private $renderer; 229 private $scope; 230 private $newScope; 231 232 /** 233 * 234 */ 235 public static function getNamePattern($type) { 236 $result = '(?:(?:' . refnotes_note::getNamePattern('strict') . ')?:)*'; 237 238 if ($type == 'required') { 239 $result .= '(?::|' . refnotes_note::getNamePattern('strict') . '):*'; 240 } 241 242 return $result; 243 } 244 245 /** 246 * Returns canonic name for a namespace 247 */ 248 public static function canonizeName($name) { 249 return preg_replace('/:{2,}/', ':', ':' . $name . ':'); 250 } 251 252 /** 253 * Returns name of the parent namespace 254 */ 255 public static function getParentName($name) { 256 return preg_replace('/\w*:$/', '', $name); 257 } 258 259 /** 260 * Splits full note name into namespace and name components 261 */ 262 public static function parseName($name) { 263 $pos = strrpos($name, ':'); 264 if ($pos !== false) { 265 $namespace = self::canonizeName(substr($name, 0, $pos)); 266 $name = substr($name, $pos + 1); 267 } 268 else { 269 $namespace = ':'; 270 } 271 272 return array($namespace, $name); 273 } 274 275 /** 276 * Constructor 277 */ 278 public function __construct($name, $parent = NULL) { 279 $this->name = $name; 280 $this->style = array(); 281 $this->renderer = NULL; 282 $this->scope = array(); 283 $this->newScope = true; 284 285 if ($parent != NULL) { 286 $this->style = $parent->style; 287 } 288 } 289 290 /** 291 * 292 */ 293 public function getName() { 294 return $this->name; 295 } 296 297 /** 298 * 299 */ 300 public function getScopesCount() { 301 return count($this->scope); 302 } 303 304 /** 305 * 306 */ 307 public function inheritStyle($source) { 308 $this->style = $source->style; 309 $this->renderer = NULL; 310 } 311 312 /** 313 * 314 */ 315 public function setStyle($style) { 316 $this->style = array_merge($this->style, $style); 317 $this->renderer = NULL; 318 } 319 320 /** 321 * 322 */ 323 public function getStyle($name) { 324 return array_key_exists($name, $this->style) ? $this->style[$name] : ''; 325 } 326 327 /** 328 * Defer creation of renderer until namespace style is set. 329 */ 330 public function getRenderer() { 331 if ($this->renderer == NULL) { 332 $this->renderer = new refnotes_renderer($this); 333 } 334 335 return $this->renderer; 336 } 337 338 /** 339 * 340 */ 341 private function getScope($index) { 342 $index = count($this->scope) + $index; 343 344 return ($index >= 0) ? $this->scope[$index] : new refnotes_scope_mock(); 345 } 346 347 /** 348 * 349 */ 350 private function getPreviousScope() { 351 return $this->getScope(-2); 352 } 353 354 /** 355 * 356 */ 357 private function getCurrentScope() { 358 return $this->getScope(-1); 359 } 360 361 /** 362 * 363 */ 364 public function getActiveScope() { 365 if ($this->newScope) { 366 $this->scope[] = new refnotes_scope($this, count($this->scope) + 1); 367 $this->newScope = false; 368 } 369 370 return $this->getCurrentScope(); 371 } 372 373 /** 374 * 375 */ 376 public function markScopeStart($callIndex) { 377 if (!$this->getCurrentScope()->isOpen()) { 378 $this->scope[] = new refnotes_scope(NULL, 0, $callIndex); 379 } 380 } 381 382 /** 383 * 384 */ 385 public function markScopeEnd($callIndex) { 386 /* Create an empty scope if there is no open one */ 387 $this->markScopeStart($callIndex - 1); 388 $this->getCurrentScope()->getLimits()->end = $callIndex; 389 } 390 391 392 /** 393 * Find last scope end within specified range 394 */ 395 private function findScopeEnd($start, $end) { 396 for ($i = count($this->scope) - 1; $i >= 0; $i--) { 397 $scopeEnd = $this->scope[$i]->getLimits()->end; 398 399 if (($scopeEnd > $start) && ($scopeEnd < $end)) { 400 return $scopeEnd; 401 } 402 } 403 404 return -1; 405 } 406 407 /** 408 * 409 */ 410 public function getStyleIndex($parent) { 411 $previousEnd = $this->getPreviousScope()->getLimits()->end; 412 $currentStart = $this->getCurrentScope()->getLimits()->start; 413 $parentEnd = ($parent != NULL) ? $parent->findScopeEnd($previousEnd, $currentStart) : -1; 414 415 return max($parentEnd, $previousEnd) + 1; 416 } 417 418 /** 419 * 420 */ 421 public function getMappingIndex() { 422 return $this->getPreviousScope()->getLimits()->end + 1; 423 } 424 425 /** 426 * 427 */ 428 public function rewriteReferences($limit = '') { 429 $this->resetScope(); 430 431 if (count($this->scope) > 0) { 432 $html = $this->getCurrentScope()->rewriteReferences($limit); 433 } 434 } 435 436 /** 437 * 438 */ 439 public function renderNotes($mode, $limit = '') { 440 $this->resetScope(); 441 $doc = ''; 442 443 if (count($this->scope) > 0) { 444 $doc = $this->getCurrentScope()->renderNotes($mode, $limit); 445 } 446 447 return $doc; 448 } 449 450 /** 451 * 452 */ 453 private function resetScope() { 454 switch ($this->getStyle('scoping')) { 455 case 'single': 456 break; 457 458 default: 459 $this->newScope = true; 460 break; 461 } 462 } 463} 464