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