1<?php
2
3/**
4 * Plugin RefNotes: Core functionality
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/locale.php');
11require_once(DOKU_PLUGIN . 'refnotes/config.php');
12require_once(DOKU_PLUGIN . 'refnotes/refnote.php');
13require_once(DOKU_PLUGIN . 'refnotes/reference.php');
14require_once(DOKU_PLUGIN . 'refnotes/note.php');
15require_once(DOKU_PLUGIN . 'refnotes/namespace.php');
16require_once(DOKU_PLUGIN . 'refnotes/scope.php');
17require_once(DOKU_PLUGIN . 'refnotes/rendering.php');
18require_once(DOKU_PLUGIN . 'refnotes/database.php');
19
20////////////////////////////////////////////////////////////////////////////////////////////////////
21class refnotes_parser_core {
22
23    private static $instance = NULL;
24
25    private $context;
26    private $lexer;
27    private $handler;
28
29    /**
30     *
31     */
32    public static function getInstance() {
33        if (self::$instance == NULL) {
34            self::$instance = new refnotes_parser_core();
35        }
36
37        return self::$instance;
38    }
39
40    /**
41     * Constructor
42     */
43    public function __construct() {
44        /* Default context. Should never be used, but just in case... */
45        $this->context = array(new refnotes_parsing_context());
46        $this->lexer = NULL;
47        $this->handler = NULL;
48    }
49
50    /**
51     *
52     */
53    public function registerLexer($lexer) {
54        $this->lexer = $lexer;
55    }
56
57    /**
58     *
59     */
60    public function enterParsingContext() {
61        $this->context[] = new refnotes_parsing_context();
62    }
63
64    /**
65     *
66     */
67    public function exitParsingContext($handler) {
68        $this->handler = $handler;
69
70        unset($this->context[count($this->context) - 1]);
71    }
72
73    /**
74     *
75     */
76    public function getInstructions($text) {
77        $this->callWriter = new refnotes_nested_call_writer($this->handler->getCallWriter(), $this->handler);
78
79        $this->callWriter->connect();
80        $this->lexer->parse($text);
81        $this->callWriter->disconnect();
82
83        return $this->callWriter->calls;
84    }
85
86    /**
87     *
88     */
89    private function getCurrentContext() {
90        return end($this->context);
91    }
92
93    /**
94     *
95     */
96    public function canHandle($state) {
97        return $this->getCurrentContext()->canHandle($state);
98    }
99
100    /**
101     *
102     */
103    public function enterReference($name, $data) {
104        $this->getCurrentContext()->enterReference($name, $data);
105    }
106
107    /**
108     *
109     */
110    public function exitReference() {
111        return $this->getCurrentContext()->exitReference();
112    }
113}
114
115////////////////////////////////////////////////////////////////////////////////////////////////////
116class refnotes_parsing_context {
117
118    private $handling;
119    private $reference;
120
121    /**
122     * Constructor
123     */
124    public function __construct() {
125        $this->reset();
126    }
127
128    /**
129     *
130     */
131    private function reset() {
132        $this->handling = false;
133        $this->reference = NULL;
134    }
135
136    /**
137     *
138     */
139    public function canHandle($state) {
140        switch ($state) {
141            case DOKU_LEXER_ENTER:
142                $result = !$this->handling;
143                break;
144
145            case DOKU_LEXER_EXIT:
146                $result = $this->handling;
147                break;
148
149            default:
150                $result = false;
151                break;
152        }
153
154        return $result;
155    }
156
157    /**
158     *
159     */
160    public function enterReference($name, $data) {
161        $this->handling = true;
162        $this->reference = new refnotes_parser_reference($name, $data);
163    }
164
165    /**
166     *
167     */
168    public function exitReference() {
169        $reference = $this->reference;
170
171        $this->reset();
172
173        return $reference;
174    }
175}
176
177////////////////////////////////////////////////////////////////////////////////////////////////////
178abstract class refnotes_core {
179
180    protected $presetStyle;
181    protected $namespace;
182    protected $mapping;
183
184    /**
185     * Constructor
186     */
187    public function __construct() {
188        $this->presetStyle = refnotes_configuration::load('namespaces');
189        $this->namespace = array();
190        $this->mapping = array();
191    }
192
193    /**
194     *
195     */
196    public function getNamespaceCount() {
197        return count($this->namespace);
198    }
199
200    /**
201     * Returns a namespace given it's name. The namespace is created if it doesn't exist yet.
202     */
203    public function getNamespace($name) {
204        $result = $this->findNamespace($name);
205
206        if ($result == NULL) {
207            $result = $this->createNamespace($name);
208        }
209
210        return $result;
211    }
212
213    /**
214     * Finds a namespace given it's name
215     */
216    protected function findNamespace($name) {
217        $result = NULL;
218
219        if (array_key_exists($name, $this->namespace)) {
220            $result = $this->namespace[$name];
221        }
222
223        return $result;
224    }
225
226    /**
227     *  Finds a namespace or it's parent
228     */
229    public function findParentNamespace($name) {
230        while (($name != '') && !array_key_exists($name, $this->namespace)) {
231            $name = refnotes_namespace::getParentName($name);
232        }
233
234        return ($name != '') ? $this->namespace[$name] : NULL;
235    }
236
237    /**
238     *
239     */
240    public function styleNamespace($namespaceName, $style) {
241        $namespace = $this->getNamespace($namespaceName);
242
243        if (array_key_exists('inherit', $style)) {
244            $source = $this->getNamespace($style['inherit']);
245            $namespace->inheritStyle($source);
246        }
247
248        $namespace->setStyle($style);
249    }
250
251    /**
252     *
253     */
254    public function setNamespaceMapping($namespaceName, $map) {
255        foreach ($map as $ns) {
256            $this->mapping[$ns] = $namespaceName;
257        }
258    }
259
260    /**
261     *
262     */
263    protected function clearNamespaceMapping($namespaceName) {
264        $this->mapping = array_diff($this->mapping, array($namespaceName));
265    }
266
267    /**
268     *
269     */
270    protected function createNamespace($name) {
271        if ($name != ':') {
272            $parentName = refnotes_namespace::getParentName($name);
273            $parent = $this->getNamespace($parentName);
274            $this->namespace[$name] = new refnotes_namespace($name, $parent);
275        }
276        else {
277            $this->namespace[$name] = new refnotes_namespace($name);
278        }
279
280        if (array_key_exists($name, $this->presetStyle)) {
281            $this->namespace[$name]->setStyle($this->presetStyle[$name]);
282        }
283
284        return $this->namespace[$name];
285    }
286
287    /**
288     *
289     */
290    protected function getNote($namespaceName, $noteName) {
291        $scope = $this->getNamespace($namespaceName)->getActiveScope();
292        $note = $scope->findNote($namespaceName, $noteName);
293
294        if (($note == NULL) && array_key_exists($namespaceName, $this->mapping)) {
295            $scope = $this->getNamespace($this->mapping[$namespaceName])->getActiveScope();
296            $note = $scope->findNote($namespaceName, $noteName);
297        }
298
299        if ($note == NULL) {
300            if (!is_int($noteName)) {
301                $note = $this->createNote($scope, $namespaceName, $noteName);
302
303                $scope->addNote($note);
304            }
305            else {
306                $note = new refnotes_note_mock();
307            }
308        }
309
310        return $note;
311    }
312
313    /**
314     *
315     */
316    abstract protected function createNote($scope, $namespaceName, $noteName);
317}
318
319////////////////////////////////////////////////////////////////////////////////////////////////////
320class refnotes_renderer_core extends refnotes_core {
321
322    private static $instance = NULL;
323
324    /**
325     * Renderer core is used by both references and notes syntax plugins during the rendering
326     * stage. The instance has to be shared between the plugins, and since there should be no
327     * more than one rendering pass during a DW page request, a single instance of the syntax
328     * core should be enough.
329     */
330    public static function getInstance() {
331        if (self::$instance == NULL) {
332            self::$instance = new refnotes_renderer_core();
333        }
334
335        return self::$instance;
336    }
337
338    /**
339     *
340     */
341    public function addReference($attributes, $data) {
342        $note = $this->getNote($attributes['ns'], $attributes['name']);
343        $reference = new refnotes_renderer_reference($note, $attributes, $data);
344
345        $note->addReference($reference);
346
347        return $reference;
348    }
349
350    /**
351     *
352     */
353    public function renderNotes($mode, $namespaceName, $limit) {
354        $this->clearNamespaceMapping($namespaceName);
355
356        $html = '';
357
358        if ($namespaceName == '*') {
359            foreach ($this->namespace as $namespace) {
360                $html .= $namespace->renderNotes($mode);
361            }
362        }
363        else {
364            $namespace = $this->findNamespace($namespaceName);
365            if ($namespace != NULL) {
366                $html = $namespace->renderNotes($mode, $limit);
367            }
368        }
369
370        return $html;
371    }
372
373    /**
374     *
375     */
376    protected function createNote($scope, $namespaceName, $noteName) {
377        return new refnotes_renderer_note($scope, $namespaceName, $noteName);
378    }
379}
380
381////////////////////////////////////////////////////////////////////////////////////////////////////
382class refnotes_action_core extends refnotes_core {
383
384    private $styleStash;
385    private $mappingStash;
386
387    /**
388     * Constructor
389     */
390    public function __construct() {
391        parent::__construct();
392
393        $this->styleStash = new refnotes_namespace_style_stash($this);
394        $this->mappingStash = new refnotes_namespace_mapping_stash();
395    }
396
397    /**
398     *
399     */
400    public function markScopeStart($namespaceName, $callIndex) {
401        $this->getNamespace($namespaceName)->markScopeStart($callIndex);
402    }
403
404    /**
405     *
406     */
407    public function markScopeEnd($namespaceName, $callIndex) {
408        $this->getNamespace($namespaceName)->markScopeEnd($callIndex);
409    }
410
411    /**
412     * Collect styling information from the page
413     */
414    public function addStyle($namespaceName, $style) {
415        $this->styleStash->add($this->getNamespace($namespaceName), $style);
416    }
417
418    /**
419     *
420     */
421    public function getStyles() {
422        return $this->styleStash;
423    }
424
425    /**
426     * Collect mapping information from the page
427     */
428    public function addMapping($namespaceName, $map) {
429        $this->mappingStash->add($this->getNamespace($namespaceName), $map);
430    }
431
432    /**
433     *
434     */
435    public function getMappings() {
436        return $this->mappingStash;
437    }
438
439    /**
440     *
441     */
442    public function reset() {
443        $this->namespace = array();
444    }
445
446    /**
447     *
448     */
449    public function addReference($attributes, $data, $call) {
450        $note = $this->getNote($attributes['ns'], $attributes['name']);
451        $reference = new refnotes_action_reference($note, $attributes, $data, $call);
452
453        $note->addReference($reference);
454
455        return $reference;
456    }
457
458    /**
459     *
460     */
461    public function rewriteReferences($namespaceName, $limit) {
462        $this->clearNamespaceMapping($namespaceName);
463
464        if ($namespaceName == '*') {
465            foreach ($this->namespace as $namespace) {
466                $namespace->rewriteReferences();
467            }
468        }
469        else {
470            $namespace = $this->findNamespace($namespaceName);
471            if ($namespace != NULL) {
472                $namespace->rewriteReferences($limit);
473            }
474        }
475    }
476
477    /**
478     *
479     */
480    protected function createNote($scope, $namespaceName, $noteName) {
481        return new refnotes_action_note($scope, $namespaceName, $noteName);
482    }
483}
484