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