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 ////////////////////////////////////////////////////////////////////////////////////////////////////
11 abstract 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 ////////////////////////////////////////////////////////////////////////////////////////////////////
57 class 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 ////////////////////////////////////////////////////////////////////////////////////////////////////
86 class 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 ////////////////////////////////////////////////////////////////////////////////////////////////////
195 class 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 ////////////////////////////////////////////////////////////////////////////////////////////////////
213 class 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 ////////////////////////////////////////////////////////////////////////////////////////////////////
224 class 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