xref: /dokuwiki/inc/parser/parser.php (revision 6d8e3ea1634ecdd794e663fa1f7c802f2fd8914f)
1<?php
2if(!defined('DOKU_INC')) die('meh.');
3require_once DOKU_INC . 'inc/parser/lexer.php';
4require_once DOKU_INC . 'inc/parser/handler.php';
5
6
7/**
8 * Define various types of modes used by the parser - they are used to
9 * populate the list of modes another mode accepts
10 */
11global $PARSER_MODES;
12$PARSER_MODES = array(
13    // containers are complex modes that can contain many other modes
14    // hr breaks the principle but they shouldn't be used in tables / lists
15    // so they are put here
16    'container'    => array('listblock','table','quote','hr'),
17
18    // some mode are allowed inside the base mode only
19    'baseonly'     => array('header'),
20
21    // modes for styling text -- footnote behaves similar to styling
22    'formatting'   => array('strong', 'emphasis', 'underline', 'monospace',
23                            'subscript', 'superscript', 'deleted', 'footnote'),
24
25    // modes where the token is simply replaced - they can not contain any
26    // other modes
27    'substition'   => array('acronym','smiley','wordblock','entity',
28                            'camelcaselink', 'internallink','media',
29                            'externallink','linebreak','emaillink',
30                            'windowssharelink','filelink','notoc',
31                            'nocache','multiplyentity','quotes','rss'),
32
33    // modes which have a start and end token but inside which
34    // no other modes should be applied
35    'protected'    => array('preformatted','code','file','php','html','htmlblock','phpblock'),
36
37    // inside this mode no wiki markup should be applied but lineendings
38    // and whitespace isn't preserved
39    'disabled'     => array('unformatted'),
40
41    // used to mark paragraph boundaries
42    'paragraphs'   => array('eol')
43);
44
45//-------------------------------------------------------------------
46
47/**
48 * Sets up the Lexer with modes and points it to the Handler
49 * For an intro to the Lexer see: wiki:parser
50 */
51class Doku_Parser {
52
53    var $Handler;
54
55    /**
56     * @var Doku_Lexer $Lexer
57     */
58    var $Lexer;
59
60    var $modes = array();
61
62    var $connected = false;
63
64    function addBaseMode(& $BaseMode) {
65        $this->modes['base'] =& $BaseMode;
66        if ( !$this->Lexer ) {
67            $this->Lexer = new Doku_Lexer($this->Handler,'base', true);
68        }
69        $this->modes['base']->Lexer =& $this->Lexer;
70    }
71
72    /**
73     * PHP preserves order of associative elements
74     * Mode sequence is important
75     */
76    function addMode($name, & $Mode) {
77        if ( !isset($this->modes['base']) ) {
78            $this->addBaseMode(new Doku_Parser_Mode_base());
79        }
80        $Mode->Lexer = & $this->Lexer;
81        $this->modes[$name] =& $Mode;
82    }
83
84    function connectModes() {
85
86        if ( $this->connected ) {
87            return;
88        }
89
90        foreach ( array_keys($this->modes) as $mode ) {
91
92            // Base isn't connected to anything
93            if ( $mode == 'base' ) {
94                continue;
95            }
96            $this->modes[$mode]->preConnect();
97
98            foreach ( array_keys($this->modes) as $cm ) {
99
100                if ( $this->modes[$cm]->accepts($mode) ) {
101                    $this->modes[$mode]->connectTo($cm);
102                }
103
104            }
105
106            $this->modes[$mode]->postConnect();
107        }
108
109        $this->connected = true;
110    }
111
112    function parse($doc) {
113        if ( $this->Lexer ) {
114            $this->connectModes();
115            // Normalize CRs and pad doc
116            $doc = "\n".str_replace("\r\n","\n",$doc)."\n";
117            $this->Lexer->parse($doc);
118            $this->Handler->_finalize();
119            return $this->Handler->calls;
120        } else {
121            return false;
122        }
123    }
124
125}
126
127//-------------------------------------------------------------------
128
129/**
130 * Class Doku_Parser_Mode_Interface
131 *
132 * Defines a mode (syntax component) in the Parser
133 */
134interface Doku_Parser_Mode_Interface {
135    /**
136     * returns a number used to determine in which order modes are added
137     */
138    public function getSort();
139
140    /**
141     * Called before any calls to connectTo
142     */
143    function preConnect();
144
145    /**
146     * Connects the mode
147     *
148     * @param string $mode
149     */
150    function connectTo($mode);
151
152    /**
153     * Called after all calls to connectTo
154     */
155    function postConnect();
156
157    /**
158     * Check if given mode is accepted inside this mode
159     *
160     * @param string $mode
161     * @return bool
162     */
163    function accepts($mode);
164}
165
166/**
167 * This class and all the subclasses below are used to reduce the effort required to register
168 * modes with the Lexer.
169 *
170 * @author Harry Fuecks <hfuecks@gmail.com>
171 */
172class Doku_Parser_Mode implements Doku_Parser_Mode_Interface {
173    /**
174     * @var Doku_Lexer $Lexer
175     */
176    var $Lexer;
177    var $allowedModes = array();
178
179    function getSort() {
180        trigger_error('getSort() not implemented in '.get_class($this), E_USER_WARNING);
181    }
182
183    function preConnect() {}
184    function connectTo($mode) {}
185    function postConnect() {}
186    function accepts($mode) {
187        return in_array($mode, (array) $this->allowedModes );
188    }
189}
190
191/**
192 * Basically the same as Doku_Parser_Mode but extends from DokuWiki_Plugin
193 *
194 * Adds additional functions to syntax plugins
195 */
196class Doku_Parser_Mode_Plugin extends DokuWiki_Plugin implements Doku_Parser_Mode_Interface {
197    /**
198     * @var Doku_Lexer $Lexer
199     */
200    var $Lexer;
201    var $allowedModes = array();
202
203    /**
204     * Sort for applying this mode
205     *
206     * @return int
207     */
208    function getSort() {
209        trigger_error('getSort() not implemented in '.get_class($this), E_USER_WARNING);
210    }
211
212    function preConnect() {}
213    function connectTo($mode) {}
214    function postConnect() {}
215    function accepts($mode) {
216        return in_array($mode, (array) $this->allowedModes );
217    }
218}
219
220//-------------------------------------------------------------------
221class Doku_Parser_Mode_base extends Doku_Parser_Mode {
222
223    function Doku_Parser_Mode_base() {
224        global $PARSER_MODES;
225
226        $this->allowedModes = array_merge (
227                $PARSER_MODES['container'],
228                $PARSER_MODES['baseonly'],
229                $PARSER_MODES['paragraphs'],
230                $PARSER_MODES['formatting'],
231                $PARSER_MODES['substition'],
232                $PARSER_MODES['protected'],
233                $PARSER_MODES['disabled']
234            );
235    }
236
237    function getSort() {
238        return 0;
239    }
240}
241
242//-------------------------------------------------------------------
243class Doku_Parser_Mode_footnote extends Doku_Parser_Mode {
244
245    function Doku_Parser_Mode_footnote() {
246        global $PARSER_MODES;
247
248        $this->allowedModes = array_merge (
249                $PARSER_MODES['container'],
250                $PARSER_MODES['formatting'],
251                $PARSER_MODES['substition'],
252                $PARSER_MODES['protected'],
253                $PARSER_MODES['disabled']
254            );
255
256        unset($this->allowedModes[array_search('footnote', $this->allowedModes)]);
257    }
258
259    function connectTo($mode) {
260        $this->Lexer->addEntryPattern(
261            '\x28\x28(?=.*\x29\x29)',$mode,'footnote'
262            );
263    }
264
265    function postConnect() {
266        $this->Lexer->addExitPattern(
267            '\x29\x29','footnote'
268            );
269    }
270
271    function getSort() {
272        return 150;
273    }
274}
275
276//-------------------------------------------------------------------
277class Doku_Parser_Mode_header extends Doku_Parser_Mode {
278
279    function connectTo($mode) {
280        //we're not picky about the closing ones, two are enough
281        $this->Lexer->addSpecialPattern(
282                            '[ \t]*={2,}[^\n]+={2,}[ \t]*(?=\n)',
283                            $mode,
284                            'header'
285                        );
286    }
287
288    function getSort() {
289        return 50;
290    }
291}
292
293//-------------------------------------------------------------------
294class Doku_Parser_Mode_notoc extends Doku_Parser_Mode {
295
296    function connectTo($mode) {
297        $this->Lexer->addSpecialPattern('~~NOTOC~~',$mode,'notoc');
298    }
299
300    function getSort() {
301        return 30;
302    }
303}
304
305//-------------------------------------------------------------------
306class Doku_Parser_Mode_nocache extends Doku_Parser_Mode {
307
308    function connectTo($mode) {
309        $this->Lexer->addSpecialPattern('~~NOCACHE~~',$mode,'nocache');
310    }
311
312    function getSort() {
313        return 40;
314    }
315}
316
317//-------------------------------------------------------------------
318class Doku_Parser_Mode_linebreak extends Doku_Parser_Mode {
319
320    function connectTo($mode) {
321        $this->Lexer->addSpecialPattern('\x5C{2}(?:[ \t]|(?=\n))',$mode,'linebreak');
322    }
323
324    function getSort() {
325        return 140;
326    }
327}
328
329//-------------------------------------------------------------------
330class Doku_Parser_Mode_eol extends Doku_Parser_Mode {
331
332    function connectTo($mode) {
333        $badModes = array('listblock','table');
334        if ( in_array($mode, $badModes) ) {
335            return;
336        }
337        // see FS#1652, pattern extended to swallow preceding whitespace to avoid issues with lines that only contain whitespace
338        $this->Lexer->addSpecialPattern('(?:^[ \t]*)?\n',$mode,'eol');
339    }
340
341    function getSort() {
342        return 370;
343    }
344}
345
346//-------------------------------------------------------------------
347class Doku_Parser_Mode_hr extends Doku_Parser_Mode {
348
349    function connectTo($mode) {
350        $this->Lexer->addSpecialPattern('\n[ \t]*-{4,}[ \t]*(?=\n)',$mode,'hr');
351    }
352
353    function getSort() {
354        return 160;
355    }
356}
357
358//-------------------------------------------------------------------
359/**
360 * This class sets the markup for bold (=strong),
361 * italic (=emphasis), underline etc.
362 */
363class Doku_Parser_Mode_formatting extends Doku_Parser_Mode {
364    var $type;
365
366    var $formatting = array (
367        'strong' => array (
368            'entry'=>'\*\*(?=.*\*\*)',
369            'exit'=>'\*\*',
370            'sort'=>70
371            ),
372
373        'emphasis'=> array (
374            'entry'=>'//(?=[^\x00]*[^:])', //hack for bugs #384 #763 #1468
375            'exit'=>'//',
376            'sort'=>80
377            ),
378
379        'underline'=> array (
380            'entry'=>'__(?=.*__)',
381            'exit'=>'__',
382            'sort'=>90
383            ),
384
385        'monospace'=> array (
386            'entry'=>'\x27\x27(?=.*\x27\x27)',
387            'exit'=>'\x27\x27',
388            'sort'=>100
389            ),
390
391        'subscript'=> array (
392            'entry'=>'<sub>(?=.*</sub>)',
393            'exit'=>'</sub>',
394            'sort'=>110
395            ),
396
397        'superscript'=> array (
398            'entry'=>'<sup>(?=.*</sup>)',
399            'exit'=>'</sup>',
400            'sort'=>120
401            ),
402
403        'deleted'=> array (
404            'entry'=>'<del>(?=.*</del>)',
405            'exit'=>'</del>',
406            'sort'=>130
407            ),
408        );
409
410    function Doku_Parser_Mode_formatting($type) {
411        global $PARSER_MODES;
412
413        if ( !array_key_exists($type, $this->formatting) ) {
414            trigger_error('Invalid formatting type '.$type, E_USER_WARNING);
415        }
416
417        $this->type = $type;
418
419        // formatting may contain other formatting but not it self
420        $modes = $PARSER_MODES['formatting'];
421        $key = array_search($type, $modes);
422        if ( is_int($key) ) {
423            unset($modes[$key]);
424        }
425
426        $this->allowedModes = array_merge (
427                $modes,
428                $PARSER_MODES['substition'],
429                $PARSER_MODES['disabled']
430            );
431    }
432
433    function connectTo($mode) {
434
435        // Can't nest formatting in itself
436        if ( $mode == $this->type ) {
437            return;
438        }
439
440        $this->Lexer->addEntryPattern(
441                $this->formatting[$this->type]['entry'],
442                $mode,
443                $this->type
444            );
445    }
446
447    function postConnect() {
448
449        $this->Lexer->addExitPattern(
450            $this->formatting[$this->type]['exit'],
451            $this->type
452            );
453
454    }
455
456    function getSort() {
457        return $this->formatting[$this->type]['sort'];
458    }
459}
460
461//-------------------------------------------------------------------
462class Doku_Parser_Mode_listblock extends Doku_Parser_Mode {
463
464    function Doku_Parser_Mode_listblock() {
465        global $PARSER_MODES;
466
467        $this->allowedModes = array_merge (
468                $PARSER_MODES['formatting'],
469                $PARSER_MODES['substition'],
470                $PARSER_MODES['disabled'],
471                $PARSER_MODES['protected'] #XXX new
472            );
473
474    //    $this->allowedModes[] = 'footnote';
475    }
476
477    function connectTo($mode) {
478        $this->Lexer->addEntryPattern('[ \t]*\n {2,}[\-\*]',$mode,'listblock');
479        $this->Lexer->addEntryPattern('[ \t]*\n\t{1,}[\-\*]',$mode,'listblock');
480
481        $this->Lexer->addPattern('\n {2,}[\-\*]','listblock');
482        $this->Lexer->addPattern('\n\t{1,}[\-\*]','listblock');
483
484    }
485
486    function postConnect() {
487        $this->Lexer->addExitPattern('\n','listblock');
488    }
489
490    function getSort() {
491        return 10;
492    }
493}
494
495//-------------------------------------------------------------------
496class Doku_Parser_Mode_table extends Doku_Parser_Mode {
497
498    function Doku_Parser_Mode_table() {
499        global $PARSER_MODES;
500
501        $this->allowedModes = array_merge (
502                $PARSER_MODES['formatting'],
503                $PARSER_MODES['substition'],
504                $PARSER_MODES['disabled'],
505                $PARSER_MODES['protected']
506            );
507    }
508
509    function connectTo($mode) {
510        $this->Lexer->addEntryPattern('[\t ]*\n\^',$mode,'table');
511        $this->Lexer->addEntryPattern('[\t ]*\n\|',$mode,'table');
512    }
513
514    function postConnect() {
515        $this->Lexer->addPattern('\n\^','table');
516        $this->Lexer->addPattern('\n\|','table');
517        $this->Lexer->addPattern('[\t ]*:::[\t ]*(?=[\|\^])','table');
518        $this->Lexer->addPattern('[\t ]+','table');
519        $this->Lexer->addPattern('\^','table');
520        $this->Lexer->addPattern('\|','table');
521        $this->Lexer->addExitPattern('\n','table');
522    }
523
524    function getSort() {
525        return 60;
526    }
527}
528
529//-------------------------------------------------------------------
530class Doku_Parser_Mode_unformatted extends Doku_Parser_Mode {
531
532    function connectTo($mode) {
533        $this->Lexer->addEntryPattern('<nowiki>(?=.*</nowiki>)',$mode,'unformatted');
534        $this->Lexer->addEntryPattern('%%(?=.*%%)',$mode,'unformattedalt');
535    }
536
537    function postConnect() {
538        $this->Lexer->addExitPattern('</nowiki>','unformatted');
539        $this->Lexer->addExitPattern('%%','unformattedalt');
540        $this->Lexer->mapHandler('unformattedalt','unformatted');
541    }
542
543    function getSort() {
544        return 170;
545    }
546}
547
548//-------------------------------------------------------------------
549class Doku_Parser_Mode_php extends Doku_Parser_Mode {
550
551    function connectTo($mode) {
552        $this->Lexer->addEntryPattern('<php>(?=.*</php>)',$mode,'php');
553        $this->Lexer->addEntryPattern('<PHP>(?=.*</PHP>)',$mode,'phpblock');
554    }
555
556    function postConnect() {
557        $this->Lexer->addExitPattern('</php>','php');
558        $this->Lexer->addExitPattern('</PHP>','phpblock');
559    }
560
561    function getSort() {
562        return 180;
563    }
564}
565
566//-------------------------------------------------------------------
567class Doku_Parser_Mode_html extends Doku_Parser_Mode {
568
569    function connectTo($mode) {
570        $this->Lexer->addEntryPattern('<html>(?=.*</html>)',$mode,'html');
571        $this->Lexer->addEntryPattern('<HTML>(?=.*</HTML>)',$mode,'htmlblock');
572    }
573
574    function postConnect() {
575        $this->Lexer->addExitPattern('</html>','html');
576        $this->Lexer->addExitPattern('</HTML>','htmlblock');
577    }
578
579    function getSort() {
580        return 190;
581    }
582}
583
584//-------------------------------------------------------------------
585class Doku_Parser_Mode_preformatted extends Doku_Parser_Mode {
586
587    function connectTo($mode) {
588        // Has hard coded awareness of lists...
589        $this->Lexer->addEntryPattern('\n  (?![\*\-])',$mode,'preformatted');
590        $this->Lexer->addEntryPattern('\n\t(?![\*\-])',$mode,'preformatted');
591
592        // How to effect a sub pattern with the Lexer!
593        $this->Lexer->addPattern('\n  ','preformatted');
594        $this->Lexer->addPattern('\n\t','preformatted');
595
596    }
597
598    function postConnect() {
599        $this->Lexer->addExitPattern('\n','preformatted');
600    }
601
602    function getSort() {
603        return 20;
604    }
605}
606
607//-------------------------------------------------------------------
608class Doku_Parser_Mode_code extends Doku_Parser_Mode {
609
610    function connectTo($mode) {
611        $this->Lexer->addEntryPattern('<code\b(?=.*</code>)',$mode,'code');
612    }
613
614    function postConnect() {
615        $this->Lexer->addExitPattern('</code>','code');
616    }
617
618    function getSort() {
619        return 200;
620    }
621}
622
623//-------------------------------------------------------------------
624class Doku_Parser_Mode_file extends Doku_Parser_Mode {
625
626    function connectTo($mode) {
627        $this->Lexer->addEntryPattern('<file\b(?=.*</file>)',$mode,'file');
628    }
629
630    function postConnect() {
631        $this->Lexer->addExitPattern('</file>','file');
632    }
633
634    function getSort() {
635        return 210;
636    }
637}
638
639//-------------------------------------------------------------------
640class Doku_Parser_Mode_quote extends Doku_Parser_Mode {
641
642    function Doku_Parser_Mode_quote() {
643        global $PARSER_MODES;
644
645        $this->allowedModes = array_merge (
646                $PARSER_MODES['formatting'],
647                $PARSER_MODES['substition'],
648                $PARSER_MODES['disabled'],
649                $PARSER_MODES['protected'] #XXX new
650            );
651            #$this->allowedModes[] = 'footnote';
652            #$this->allowedModes[] = 'preformatted';
653            #$this->allowedModes[] = 'unformatted';
654    }
655
656    function connectTo($mode) {
657        $this->Lexer->addEntryPattern('\n>{1,}',$mode,'quote');
658    }
659
660    function postConnect() {
661        $this->Lexer->addPattern('\n>{1,}','quote');
662        $this->Lexer->addExitPattern('\n','quote');
663    }
664
665    function getSort() {
666        return 220;
667    }
668}
669
670//-------------------------------------------------------------------
671class Doku_Parser_Mode_acronym extends Doku_Parser_Mode {
672    // A list
673    var $acronyms = array();
674    var $pattern = '';
675
676    function Doku_Parser_Mode_acronym($acronyms) {
677        usort($acronyms,array($this,'_compare'));
678        $this->acronyms = $acronyms;
679    }
680
681    function preConnect() {
682        if(!count($this->acronyms)) return;
683
684        $bound = '[\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]';
685        $acronyms = array_map('Doku_Lexer_Escape',$this->acronyms);
686        $this->pattern = '(?<=^|'.$bound.')(?:'.join('|',$acronyms).')(?='.$bound.')';
687    }
688
689    function connectTo($mode) {
690        if(!count($this->acronyms)) return;
691
692        if ( strlen($this->pattern) > 0 ) {
693            $this->Lexer->addSpecialPattern($this->pattern,$mode,'acronym');
694        }
695    }
696
697    function getSort() {
698        return 240;
699    }
700
701    /**
702     * sort callback to order by string length descending
703     */
704    function _compare($a,$b) {
705        $a_len = strlen($a);
706        $b_len = strlen($b);
707        if ($a_len > $b_len) {
708            return -1;
709        } else if ($a_len < $b_len) {
710            return 1;
711        }
712
713        return 0;
714    }
715}
716
717//-------------------------------------------------------------------
718class Doku_Parser_Mode_smiley extends Doku_Parser_Mode {
719    // A list
720    var $smileys = array();
721    var $pattern = '';
722
723    function Doku_Parser_Mode_smiley($smileys) {
724        $this->smileys = $smileys;
725    }
726
727    function preConnect() {
728        if(!count($this->smileys) || $this->pattern != '') return;
729
730        $sep = '';
731        foreach ( $this->smileys as $smiley ) {
732            $this->pattern .= $sep.'(?<=\W|^)'.Doku_Lexer_Escape($smiley).'(?=\W|$)';
733            $sep = '|';
734        }
735    }
736
737    function connectTo($mode) {
738        if(!count($this->smileys)) return;
739
740        if ( strlen($this->pattern) > 0 ) {
741            $this->Lexer->addSpecialPattern($this->pattern,$mode,'smiley');
742        }
743    }
744
745    function getSort() {
746        return 230;
747    }
748}
749
750//-------------------------------------------------------------------
751class Doku_Parser_Mode_wordblock extends Doku_Parser_Mode {
752    // A list
753    var $badwords = array();
754    var $pattern = '';
755
756    function Doku_Parser_Mode_wordblock($badwords) {
757        $this->badwords = $badwords;
758    }
759
760    function preConnect() {
761
762        if ( count($this->badwords) == 0 || $this->pattern != '') {
763            return;
764        }
765
766        $sep = '';
767        foreach ( $this->badwords as $badword ) {
768            $this->pattern .= $sep.'(?<=\b)(?i)'.Doku_Lexer_Escape($badword).'(?-i)(?=\b)';
769            $sep = '|';
770        }
771
772    }
773
774    function connectTo($mode) {
775        if ( strlen($this->pattern) > 0 ) {
776            $this->Lexer->addSpecialPattern($this->pattern,$mode,'wordblock');
777        }
778    }
779
780    function getSort() {
781        return 250;
782    }
783}
784
785//-------------------------------------------------------------------
786class Doku_Parser_Mode_entity extends Doku_Parser_Mode {
787    // A list
788    var $entities = array();
789    var $pattern = '';
790
791    function Doku_Parser_Mode_entity($entities) {
792        $this->entities = $entities;
793    }
794
795    function preConnect() {
796        if(!count($this->entities) || $this->pattern != '') return;
797
798        $sep = '';
799        foreach ( $this->entities as $entity ) {
800            $this->pattern .= $sep.Doku_Lexer_Escape($entity);
801            $sep = '|';
802        }
803    }
804
805    function connectTo($mode) {
806        if(!count($this->entities)) return;
807
808        if ( strlen($this->pattern) > 0 ) {
809            $this->Lexer->addSpecialPattern($this->pattern,$mode,'entity');
810        }
811    }
812
813    function getSort() {
814        return 260;
815    }
816}
817
818//-------------------------------------------------------------------
819// Implements the 640x480 replacement
820class Doku_Parser_Mode_multiplyentity extends Doku_Parser_Mode {
821
822    function connectTo($mode) {
823
824        $this->Lexer->addSpecialPattern(
825                    '(?<=\b)(?:[1-9]|\d{2,})[xX]\d+(?=\b)',$mode,'multiplyentity'
826                );
827
828    }
829
830    function getSort() {
831        return 270;
832    }
833}
834
835//-------------------------------------------------------------------
836class Doku_Parser_Mode_quotes extends Doku_Parser_Mode {
837
838    function connectTo($mode) {
839        global $conf;
840
841        $ws   =  '\s/\#~:+=&%@\-\x28\x29\]\[{}><"\'';   // whitespace
842        $punc =  ';,\.?!';
843
844        if($conf['typography'] == 2){
845            $this->Lexer->addSpecialPattern(
846                        "(?<=^|[$ws])'(?=[^$ws$punc])",$mode,'singlequoteopening'
847                    );
848            $this->Lexer->addSpecialPattern(
849                        "(?<=^|[^$ws]|[$punc])'(?=$|[$ws$punc])",$mode,'singlequoteclosing'
850                    );
851            $this->Lexer->addSpecialPattern(
852                        "(?<=^|[^$ws$punc])'(?=$|[^$ws$punc])",$mode,'apostrophe'
853                    );
854        }
855
856        $this->Lexer->addSpecialPattern(
857                    "(?<=^|[$ws])\"(?=[^$ws$punc])",$mode,'doublequoteopening'
858                );
859        $this->Lexer->addSpecialPattern(
860                    "\"",$mode,'doublequoteclosing'
861                );
862
863    }
864
865    function getSort() {
866        return 280;
867    }
868}
869
870//-------------------------------------------------------------------
871class Doku_Parser_Mode_camelcaselink extends Doku_Parser_Mode {
872
873    function connectTo($mode) {
874        $this->Lexer->addSpecialPattern(
875                '\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b',$mode,'camelcaselink'
876            );
877    }
878
879    function getSort() {
880        return 290;
881    }
882}
883
884//-------------------------------------------------------------------
885class Doku_Parser_Mode_internallink extends Doku_Parser_Mode {
886
887    function connectTo($mode) {
888        // Word boundaries?
889        $this->Lexer->addSpecialPattern("\[\[(?:(?:[^[\]]*?\[.*?\])|.*?)\]\]",$mode,'internallink');
890    }
891
892    function getSort() {
893        return 300;
894    }
895}
896
897//-------------------------------------------------------------------
898class Doku_Parser_Mode_media extends Doku_Parser_Mode {
899
900    function connectTo($mode) {
901        // Word boundaries?
902        $this->Lexer->addSpecialPattern("\{\{[^\}]+\}\}",$mode,'media');
903    }
904
905    function getSort() {
906        return 320;
907    }
908}
909
910//-------------------------------------------------------------------
911class Doku_Parser_Mode_rss extends Doku_Parser_Mode {
912
913    function connectTo($mode) {
914        $this->Lexer->addSpecialPattern("\{\{rss>[^\}]+\}\}",$mode,'rss');
915    }
916
917    function getSort() {
918        return 310;
919    }
920}
921
922//-------------------------------------------------------------------
923class Doku_Parser_Mode_externallink extends Doku_Parser_Mode {
924    var $schemes = array();
925    var $patterns = array();
926
927    function preConnect() {
928        if(count($this->patterns)) return;
929
930        $ltrs = '\w';
931        $gunk = '/\#~:.?+=&%@!\-\[\]';
932        $punc = '.:?\-;,';
933        $host = $ltrs.$punc;
934        $any  = $ltrs.$gunk.$punc;
935
936        $this->schemes = getSchemes();
937        foreach ( $this->schemes as $scheme ) {
938            $this->patterns[] = '\b(?i)'.$scheme.'(?-i)://['.$any.']+?(?=['.$punc.']*[^'.$any.'])';
939        }
940
941        $this->patterns[] = '\b(?i)www?(?-i)\.['.$host.']+?\.['.$host.']+?['.$any.']+?(?=['.$punc.']*[^'.$any.'])';
942        $this->patterns[] = '\b(?i)ftp?(?-i)\.['.$host.']+?\.['.$host.']+?['.$any.']+?(?=['.$punc.']*[^'.$any.'])';
943    }
944
945    function connectTo($mode) {
946
947        foreach ( $this->patterns as $pattern ) {
948            $this->Lexer->addSpecialPattern($pattern,$mode,'externallink');
949        }
950    }
951
952    function getSort() {
953        return 330;
954    }
955}
956
957//-------------------------------------------------------------------
958class Doku_Parser_Mode_filelink extends Doku_Parser_Mode {
959
960    var $pattern;
961
962    function preConnect() {
963
964        $ltrs = '\w';
965        $gunk = '/\#~:.?+=&%@!\-';
966        $punc = '.:?\-;,';
967        $host = $ltrs.$punc;
968        $any  = $ltrs.$gunk.$punc;
969
970        $this->pattern = '\b(?i)file(?-i)://['.$any.']+?['.
971            $punc.']*[^'.$any.']';
972    }
973
974    function connectTo($mode) {
975        $this->Lexer->addSpecialPattern(
976            $this->pattern,$mode,'filelink');
977    }
978
979    function getSort() {
980        return 360;
981    }
982}
983
984//-------------------------------------------------------------------
985class Doku_Parser_Mode_windowssharelink extends Doku_Parser_Mode {
986
987    var $pattern;
988
989    function preConnect() {
990        $this->pattern = "\\\\\\\\\w+?(?:\\\\[\w-$]+)+";
991    }
992
993    function connectTo($mode) {
994        $this->Lexer->addSpecialPattern(
995            $this->pattern,$mode,'windowssharelink');
996    }
997
998    function getSort() {
999        return 350;
1000    }
1001}
1002
1003//-------------------------------------------------------------------
1004class Doku_Parser_Mode_emaillink extends Doku_Parser_Mode {
1005
1006    function connectTo($mode) {
1007        // pattern below is defined in inc/mail.php
1008        $this->Lexer->addSpecialPattern('<'.PREG_PATTERN_VALID_EMAIL.'>',$mode,'emaillink');
1009    }
1010
1011    function getSort() {
1012        return 340;
1013    }
1014}
1015
1016
1017//Setup VIM: ex: et ts=4 :
1018