xref: /plugin/aichat/vendor/erusev/parsedown/Parsedown.php (revision 7017fcea956877cba40d598e4eb414116a75b0a8)
1*7017fceaSAndreas Gohr<?php
2*7017fceaSAndreas Gohr
3*7017fceaSAndreas Gohr#
4*7017fceaSAndreas Gohr#
5*7017fceaSAndreas Gohr# Parsedown
6*7017fceaSAndreas Gohr# http://parsedown.org
7*7017fceaSAndreas Gohr#
8*7017fceaSAndreas Gohr# (c) Emanuil Rusev
9*7017fceaSAndreas Gohr# http://erusev.com
10*7017fceaSAndreas Gohr#
11*7017fceaSAndreas Gohr# For the full license information, view the LICENSE file that was distributed
12*7017fceaSAndreas Gohr# with this source code.
13*7017fceaSAndreas Gohr#
14*7017fceaSAndreas Gohr#
15*7017fceaSAndreas Gohr
16*7017fceaSAndreas Gohrclass Parsedown
17*7017fceaSAndreas Gohr{
18*7017fceaSAndreas Gohr    # ~
19*7017fceaSAndreas Gohr
20*7017fceaSAndreas Gohr    const version = '1.7.4';
21*7017fceaSAndreas Gohr
22*7017fceaSAndreas Gohr    # ~
23*7017fceaSAndreas Gohr
24*7017fceaSAndreas Gohr    function text($text)
25*7017fceaSAndreas Gohr    {
26*7017fceaSAndreas Gohr        # make sure no definitions are set
27*7017fceaSAndreas Gohr        $this->DefinitionData = array();
28*7017fceaSAndreas Gohr
29*7017fceaSAndreas Gohr        # standardize line breaks
30*7017fceaSAndreas Gohr        $text = str_replace(array("\r\n", "\r"), "\n", $text);
31*7017fceaSAndreas Gohr
32*7017fceaSAndreas Gohr        # remove surrounding line breaks
33*7017fceaSAndreas Gohr        $text = trim($text, "\n");
34*7017fceaSAndreas Gohr
35*7017fceaSAndreas Gohr        # split text into lines
36*7017fceaSAndreas Gohr        $lines = explode("\n", $text);
37*7017fceaSAndreas Gohr
38*7017fceaSAndreas Gohr        # iterate through lines to identify blocks
39*7017fceaSAndreas Gohr        $markup = $this->lines($lines);
40*7017fceaSAndreas Gohr
41*7017fceaSAndreas Gohr        # trim line breaks
42*7017fceaSAndreas Gohr        $markup = trim($markup, "\n");
43*7017fceaSAndreas Gohr
44*7017fceaSAndreas Gohr        return $markup;
45*7017fceaSAndreas Gohr    }
46*7017fceaSAndreas Gohr
47*7017fceaSAndreas Gohr    #
48*7017fceaSAndreas Gohr    # Setters
49*7017fceaSAndreas Gohr    #
50*7017fceaSAndreas Gohr
51*7017fceaSAndreas Gohr    function setBreaksEnabled($breaksEnabled)
52*7017fceaSAndreas Gohr    {
53*7017fceaSAndreas Gohr        $this->breaksEnabled = $breaksEnabled;
54*7017fceaSAndreas Gohr
55*7017fceaSAndreas Gohr        return $this;
56*7017fceaSAndreas Gohr    }
57*7017fceaSAndreas Gohr
58*7017fceaSAndreas Gohr    protected $breaksEnabled;
59*7017fceaSAndreas Gohr
60*7017fceaSAndreas Gohr    function setMarkupEscaped($markupEscaped)
61*7017fceaSAndreas Gohr    {
62*7017fceaSAndreas Gohr        $this->markupEscaped = $markupEscaped;
63*7017fceaSAndreas Gohr
64*7017fceaSAndreas Gohr        return $this;
65*7017fceaSAndreas Gohr    }
66*7017fceaSAndreas Gohr
67*7017fceaSAndreas Gohr    protected $markupEscaped;
68*7017fceaSAndreas Gohr
69*7017fceaSAndreas Gohr    function setUrlsLinked($urlsLinked)
70*7017fceaSAndreas Gohr    {
71*7017fceaSAndreas Gohr        $this->urlsLinked = $urlsLinked;
72*7017fceaSAndreas Gohr
73*7017fceaSAndreas Gohr        return $this;
74*7017fceaSAndreas Gohr    }
75*7017fceaSAndreas Gohr
76*7017fceaSAndreas Gohr    protected $urlsLinked = true;
77*7017fceaSAndreas Gohr
78*7017fceaSAndreas Gohr    function setSafeMode($safeMode)
79*7017fceaSAndreas Gohr    {
80*7017fceaSAndreas Gohr        $this->safeMode = (bool) $safeMode;
81*7017fceaSAndreas Gohr
82*7017fceaSAndreas Gohr        return $this;
83*7017fceaSAndreas Gohr    }
84*7017fceaSAndreas Gohr
85*7017fceaSAndreas Gohr    protected $safeMode;
86*7017fceaSAndreas Gohr
87*7017fceaSAndreas Gohr    protected $safeLinksWhitelist = array(
88*7017fceaSAndreas Gohr        'http://',
89*7017fceaSAndreas Gohr        'https://',
90*7017fceaSAndreas Gohr        'ftp://',
91*7017fceaSAndreas Gohr        'ftps://',
92*7017fceaSAndreas Gohr        'mailto:',
93*7017fceaSAndreas Gohr        'data:image/png;base64,',
94*7017fceaSAndreas Gohr        'data:image/gif;base64,',
95*7017fceaSAndreas Gohr        'data:image/jpeg;base64,',
96*7017fceaSAndreas Gohr        'irc:',
97*7017fceaSAndreas Gohr        'ircs:',
98*7017fceaSAndreas Gohr        'git:',
99*7017fceaSAndreas Gohr        'ssh:',
100*7017fceaSAndreas Gohr        'news:',
101*7017fceaSAndreas Gohr        'steam:',
102*7017fceaSAndreas Gohr    );
103*7017fceaSAndreas Gohr
104*7017fceaSAndreas Gohr    #
105*7017fceaSAndreas Gohr    # Lines
106*7017fceaSAndreas Gohr    #
107*7017fceaSAndreas Gohr
108*7017fceaSAndreas Gohr    protected $BlockTypes = array(
109*7017fceaSAndreas Gohr        '#' => array('Header'),
110*7017fceaSAndreas Gohr        '*' => array('Rule', 'List'),
111*7017fceaSAndreas Gohr        '+' => array('List'),
112*7017fceaSAndreas Gohr        '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
113*7017fceaSAndreas Gohr        '0' => array('List'),
114*7017fceaSAndreas Gohr        '1' => array('List'),
115*7017fceaSAndreas Gohr        '2' => array('List'),
116*7017fceaSAndreas Gohr        '3' => array('List'),
117*7017fceaSAndreas Gohr        '4' => array('List'),
118*7017fceaSAndreas Gohr        '5' => array('List'),
119*7017fceaSAndreas Gohr        '6' => array('List'),
120*7017fceaSAndreas Gohr        '7' => array('List'),
121*7017fceaSAndreas Gohr        '8' => array('List'),
122*7017fceaSAndreas Gohr        '9' => array('List'),
123*7017fceaSAndreas Gohr        ':' => array('Table'),
124*7017fceaSAndreas Gohr        '<' => array('Comment', 'Markup'),
125*7017fceaSAndreas Gohr        '=' => array('SetextHeader'),
126*7017fceaSAndreas Gohr        '>' => array('Quote'),
127*7017fceaSAndreas Gohr        '[' => array('Reference'),
128*7017fceaSAndreas Gohr        '_' => array('Rule'),
129*7017fceaSAndreas Gohr        '`' => array('FencedCode'),
130*7017fceaSAndreas Gohr        '|' => array('Table'),
131*7017fceaSAndreas Gohr        '~' => array('FencedCode'),
132*7017fceaSAndreas Gohr    );
133*7017fceaSAndreas Gohr
134*7017fceaSAndreas Gohr    # ~
135*7017fceaSAndreas Gohr
136*7017fceaSAndreas Gohr    protected $unmarkedBlockTypes = array(
137*7017fceaSAndreas Gohr        'Code',
138*7017fceaSAndreas Gohr    );
139*7017fceaSAndreas Gohr
140*7017fceaSAndreas Gohr    #
141*7017fceaSAndreas Gohr    # Blocks
142*7017fceaSAndreas Gohr    #
143*7017fceaSAndreas Gohr
144*7017fceaSAndreas Gohr    protected function lines(array $lines)
145*7017fceaSAndreas Gohr    {
146*7017fceaSAndreas Gohr        $CurrentBlock = null;
147*7017fceaSAndreas Gohr
148*7017fceaSAndreas Gohr        foreach ($lines as $line)
149*7017fceaSAndreas Gohr        {
150*7017fceaSAndreas Gohr            if (chop($line) === '')
151*7017fceaSAndreas Gohr            {
152*7017fceaSAndreas Gohr                if (isset($CurrentBlock))
153*7017fceaSAndreas Gohr                {
154*7017fceaSAndreas Gohr                    $CurrentBlock['interrupted'] = true;
155*7017fceaSAndreas Gohr                }
156*7017fceaSAndreas Gohr
157*7017fceaSAndreas Gohr                continue;
158*7017fceaSAndreas Gohr            }
159*7017fceaSAndreas Gohr
160*7017fceaSAndreas Gohr            if (strpos($line, "\t") !== false)
161*7017fceaSAndreas Gohr            {
162*7017fceaSAndreas Gohr                $parts = explode("\t", $line);
163*7017fceaSAndreas Gohr
164*7017fceaSAndreas Gohr                $line = $parts[0];
165*7017fceaSAndreas Gohr
166*7017fceaSAndreas Gohr                unset($parts[0]);
167*7017fceaSAndreas Gohr
168*7017fceaSAndreas Gohr                foreach ($parts as $part)
169*7017fceaSAndreas Gohr                {
170*7017fceaSAndreas Gohr                    $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
171*7017fceaSAndreas Gohr
172*7017fceaSAndreas Gohr                    $line .= str_repeat(' ', $shortage);
173*7017fceaSAndreas Gohr                    $line .= $part;
174*7017fceaSAndreas Gohr                }
175*7017fceaSAndreas Gohr            }
176*7017fceaSAndreas Gohr
177*7017fceaSAndreas Gohr            $indent = 0;
178*7017fceaSAndreas Gohr
179*7017fceaSAndreas Gohr            while (isset($line[$indent]) and $line[$indent] === ' ')
180*7017fceaSAndreas Gohr            {
181*7017fceaSAndreas Gohr                $indent ++;
182*7017fceaSAndreas Gohr            }
183*7017fceaSAndreas Gohr
184*7017fceaSAndreas Gohr            $text = $indent > 0 ? substr($line, $indent) : $line;
185*7017fceaSAndreas Gohr
186*7017fceaSAndreas Gohr            # ~
187*7017fceaSAndreas Gohr
188*7017fceaSAndreas Gohr            $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
189*7017fceaSAndreas Gohr
190*7017fceaSAndreas Gohr            # ~
191*7017fceaSAndreas Gohr
192*7017fceaSAndreas Gohr            if (isset($CurrentBlock['continuable']))
193*7017fceaSAndreas Gohr            {
194*7017fceaSAndreas Gohr                $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
195*7017fceaSAndreas Gohr
196*7017fceaSAndreas Gohr                if (isset($Block))
197*7017fceaSAndreas Gohr                {
198*7017fceaSAndreas Gohr                    $CurrentBlock = $Block;
199*7017fceaSAndreas Gohr
200*7017fceaSAndreas Gohr                    continue;
201*7017fceaSAndreas Gohr                }
202*7017fceaSAndreas Gohr                else
203*7017fceaSAndreas Gohr                {
204*7017fceaSAndreas Gohr                    if ($this->isBlockCompletable($CurrentBlock['type']))
205*7017fceaSAndreas Gohr                    {
206*7017fceaSAndreas Gohr                        $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
207*7017fceaSAndreas Gohr                    }
208*7017fceaSAndreas Gohr                }
209*7017fceaSAndreas Gohr            }
210*7017fceaSAndreas Gohr
211*7017fceaSAndreas Gohr            # ~
212*7017fceaSAndreas Gohr
213*7017fceaSAndreas Gohr            $marker = $text[0];
214*7017fceaSAndreas Gohr
215*7017fceaSAndreas Gohr            # ~
216*7017fceaSAndreas Gohr
217*7017fceaSAndreas Gohr            $blockTypes = $this->unmarkedBlockTypes;
218*7017fceaSAndreas Gohr
219*7017fceaSAndreas Gohr            if (isset($this->BlockTypes[$marker]))
220*7017fceaSAndreas Gohr            {
221*7017fceaSAndreas Gohr                foreach ($this->BlockTypes[$marker] as $blockType)
222*7017fceaSAndreas Gohr                {
223*7017fceaSAndreas Gohr                    $blockTypes []= $blockType;
224*7017fceaSAndreas Gohr                }
225*7017fceaSAndreas Gohr            }
226*7017fceaSAndreas Gohr
227*7017fceaSAndreas Gohr            #
228*7017fceaSAndreas Gohr            # ~
229*7017fceaSAndreas Gohr
230*7017fceaSAndreas Gohr            foreach ($blockTypes as $blockType)
231*7017fceaSAndreas Gohr            {
232*7017fceaSAndreas Gohr                $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
233*7017fceaSAndreas Gohr
234*7017fceaSAndreas Gohr                if (isset($Block))
235*7017fceaSAndreas Gohr                {
236*7017fceaSAndreas Gohr                    $Block['type'] = $blockType;
237*7017fceaSAndreas Gohr
238*7017fceaSAndreas Gohr                    if ( ! isset($Block['identified']))
239*7017fceaSAndreas Gohr                    {
240*7017fceaSAndreas Gohr                        $Blocks []= $CurrentBlock;
241*7017fceaSAndreas Gohr
242*7017fceaSAndreas Gohr                        $Block['identified'] = true;
243*7017fceaSAndreas Gohr                    }
244*7017fceaSAndreas Gohr
245*7017fceaSAndreas Gohr                    if ($this->isBlockContinuable($blockType))
246*7017fceaSAndreas Gohr                    {
247*7017fceaSAndreas Gohr                        $Block['continuable'] = true;
248*7017fceaSAndreas Gohr                    }
249*7017fceaSAndreas Gohr
250*7017fceaSAndreas Gohr                    $CurrentBlock = $Block;
251*7017fceaSAndreas Gohr
252*7017fceaSAndreas Gohr                    continue 2;
253*7017fceaSAndreas Gohr                }
254*7017fceaSAndreas Gohr            }
255*7017fceaSAndreas Gohr
256*7017fceaSAndreas Gohr            # ~
257*7017fceaSAndreas Gohr
258*7017fceaSAndreas Gohr            if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
259*7017fceaSAndreas Gohr            {
260*7017fceaSAndreas Gohr                $CurrentBlock['element']['text'] .= "\n".$text;
261*7017fceaSAndreas Gohr            }
262*7017fceaSAndreas Gohr            else
263*7017fceaSAndreas Gohr            {
264*7017fceaSAndreas Gohr                $Blocks []= $CurrentBlock;
265*7017fceaSAndreas Gohr
266*7017fceaSAndreas Gohr                $CurrentBlock = $this->paragraph($Line);
267*7017fceaSAndreas Gohr
268*7017fceaSAndreas Gohr                $CurrentBlock['identified'] = true;
269*7017fceaSAndreas Gohr            }
270*7017fceaSAndreas Gohr        }
271*7017fceaSAndreas Gohr
272*7017fceaSAndreas Gohr        # ~
273*7017fceaSAndreas Gohr
274*7017fceaSAndreas Gohr        if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
275*7017fceaSAndreas Gohr        {
276*7017fceaSAndreas Gohr            $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
277*7017fceaSAndreas Gohr        }
278*7017fceaSAndreas Gohr
279*7017fceaSAndreas Gohr        # ~
280*7017fceaSAndreas Gohr
281*7017fceaSAndreas Gohr        $Blocks []= $CurrentBlock;
282*7017fceaSAndreas Gohr
283*7017fceaSAndreas Gohr        unset($Blocks[0]);
284*7017fceaSAndreas Gohr
285*7017fceaSAndreas Gohr        # ~
286*7017fceaSAndreas Gohr
287*7017fceaSAndreas Gohr        $markup = '';
288*7017fceaSAndreas Gohr
289*7017fceaSAndreas Gohr        foreach ($Blocks as $Block)
290*7017fceaSAndreas Gohr        {
291*7017fceaSAndreas Gohr            if (isset($Block['hidden']))
292*7017fceaSAndreas Gohr            {
293*7017fceaSAndreas Gohr                continue;
294*7017fceaSAndreas Gohr            }
295*7017fceaSAndreas Gohr
296*7017fceaSAndreas Gohr            $markup .= "\n";
297*7017fceaSAndreas Gohr            $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
298*7017fceaSAndreas Gohr        }
299*7017fceaSAndreas Gohr
300*7017fceaSAndreas Gohr        $markup .= "\n";
301*7017fceaSAndreas Gohr
302*7017fceaSAndreas Gohr        # ~
303*7017fceaSAndreas Gohr
304*7017fceaSAndreas Gohr        return $markup;
305*7017fceaSAndreas Gohr    }
306*7017fceaSAndreas Gohr
307*7017fceaSAndreas Gohr    protected function isBlockContinuable($Type)
308*7017fceaSAndreas Gohr    {
309*7017fceaSAndreas Gohr        return method_exists($this, 'block'.$Type.'Continue');
310*7017fceaSAndreas Gohr    }
311*7017fceaSAndreas Gohr
312*7017fceaSAndreas Gohr    protected function isBlockCompletable($Type)
313*7017fceaSAndreas Gohr    {
314*7017fceaSAndreas Gohr        return method_exists($this, 'block'.$Type.'Complete');
315*7017fceaSAndreas Gohr    }
316*7017fceaSAndreas Gohr
317*7017fceaSAndreas Gohr    #
318*7017fceaSAndreas Gohr    # Code
319*7017fceaSAndreas Gohr
320*7017fceaSAndreas Gohr    protected function blockCode($Line, $Block = null)
321*7017fceaSAndreas Gohr    {
322*7017fceaSAndreas Gohr        if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
323*7017fceaSAndreas Gohr        {
324*7017fceaSAndreas Gohr            return;
325*7017fceaSAndreas Gohr        }
326*7017fceaSAndreas Gohr
327*7017fceaSAndreas Gohr        if ($Line['indent'] >= 4)
328*7017fceaSAndreas Gohr        {
329*7017fceaSAndreas Gohr            $text = substr($Line['body'], 4);
330*7017fceaSAndreas Gohr
331*7017fceaSAndreas Gohr            $Block = array(
332*7017fceaSAndreas Gohr                'element' => array(
333*7017fceaSAndreas Gohr                    'name' => 'pre',
334*7017fceaSAndreas Gohr                    'handler' => 'element',
335*7017fceaSAndreas Gohr                    'text' => array(
336*7017fceaSAndreas Gohr                        'name' => 'code',
337*7017fceaSAndreas Gohr                        'text' => $text,
338*7017fceaSAndreas Gohr                    ),
339*7017fceaSAndreas Gohr                ),
340*7017fceaSAndreas Gohr            );
341*7017fceaSAndreas Gohr
342*7017fceaSAndreas Gohr            return $Block;
343*7017fceaSAndreas Gohr        }
344*7017fceaSAndreas Gohr    }
345*7017fceaSAndreas Gohr
346*7017fceaSAndreas Gohr    protected function blockCodeContinue($Line, $Block)
347*7017fceaSAndreas Gohr    {
348*7017fceaSAndreas Gohr        if ($Line['indent'] >= 4)
349*7017fceaSAndreas Gohr        {
350*7017fceaSAndreas Gohr            if (isset($Block['interrupted']))
351*7017fceaSAndreas Gohr            {
352*7017fceaSAndreas Gohr                $Block['element']['text']['text'] .= "\n";
353*7017fceaSAndreas Gohr
354*7017fceaSAndreas Gohr                unset($Block['interrupted']);
355*7017fceaSAndreas Gohr            }
356*7017fceaSAndreas Gohr
357*7017fceaSAndreas Gohr            $Block['element']['text']['text'] .= "\n";
358*7017fceaSAndreas Gohr
359*7017fceaSAndreas Gohr            $text = substr($Line['body'], 4);
360*7017fceaSAndreas Gohr
361*7017fceaSAndreas Gohr            $Block['element']['text']['text'] .= $text;
362*7017fceaSAndreas Gohr
363*7017fceaSAndreas Gohr            return $Block;
364*7017fceaSAndreas Gohr        }
365*7017fceaSAndreas Gohr    }
366*7017fceaSAndreas Gohr
367*7017fceaSAndreas Gohr    protected function blockCodeComplete($Block)
368*7017fceaSAndreas Gohr    {
369*7017fceaSAndreas Gohr        $text = $Block['element']['text']['text'];
370*7017fceaSAndreas Gohr
371*7017fceaSAndreas Gohr        $Block['element']['text']['text'] = $text;
372*7017fceaSAndreas Gohr
373*7017fceaSAndreas Gohr        return $Block;
374*7017fceaSAndreas Gohr    }
375*7017fceaSAndreas Gohr
376*7017fceaSAndreas Gohr    #
377*7017fceaSAndreas Gohr    # Comment
378*7017fceaSAndreas Gohr
379*7017fceaSAndreas Gohr    protected function blockComment($Line)
380*7017fceaSAndreas Gohr    {
381*7017fceaSAndreas Gohr        if ($this->markupEscaped or $this->safeMode)
382*7017fceaSAndreas Gohr        {
383*7017fceaSAndreas Gohr            return;
384*7017fceaSAndreas Gohr        }
385*7017fceaSAndreas Gohr
386*7017fceaSAndreas Gohr        if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
387*7017fceaSAndreas Gohr        {
388*7017fceaSAndreas Gohr            $Block = array(
389*7017fceaSAndreas Gohr                'markup' => $Line['body'],
390*7017fceaSAndreas Gohr            );
391*7017fceaSAndreas Gohr
392*7017fceaSAndreas Gohr            if (preg_match('/-->$/', $Line['text']))
393*7017fceaSAndreas Gohr            {
394*7017fceaSAndreas Gohr                $Block['closed'] = true;
395*7017fceaSAndreas Gohr            }
396*7017fceaSAndreas Gohr
397*7017fceaSAndreas Gohr            return $Block;
398*7017fceaSAndreas Gohr        }
399*7017fceaSAndreas Gohr    }
400*7017fceaSAndreas Gohr
401*7017fceaSAndreas Gohr    protected function blockCommentContinue($Line, array $Block)
402*7017fceaSAndreas Gohr    {
403*7017fceaSAndreas Gohr        if (isset($Block['closed']))
404*7017fceaSAndreas Gohr        {
405*7017fceaSAndreas Gohr            return;
406*7017fceaSAndreas Gohr        }
407*7017fceaSAndreas Gohr
408*7017fceaSAndreas Gohr        $Block['markup'] .= "\n" . $Line['body'];
409*7017fceaSAndreas Gohr
410*7017fceaSAndreas Gohr        if (preg_match('/-->$/', $Line['text']))
411*7017fceaSAndreas Gohr        {
412*7017fceaSAndreas Gohr            $Block['closed'] = true;
413*7017fceaSAndreas Gohr        }
414*7017fceaSAndreas Gohr
415*7017fceaSAndreas Gohr        return $Block;
416*7017fceaSAndreas Gohr    }
417*7017fceaSAndreas Gohr
418*7017fceaSAndreas Gohr    #
419*7017fceaSAndreas Gohr    # Fenced Code
420*7017fceaSAndreas Gohr
421*7017fceaSAndreas Gohr    protected function blockFencedCode($Line)
422*7017fceaSAndreas Gohr    {
423*7017fceaSAndreas Gohr        if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches))
424*7017fceaSAndreas Gohr        {
425*7017fceaSAndreas Gohr            $Element = array(
426*7017fceaSAndreas Gohr                'name' => 'code',
427*7017fceaSAndreas Gohr                'text' => '',
428*7017fceaSAndreas Gohr            );
429*7017fceaSAndreas Gohr
430*7017fceaSAndreas Gohr            if (isset($matches[1]))
431*7017fceaSAndreas Gohr            {
432*7017fceaSAndreas Gohr                /**
433*7017fceaSAndreas Gohr                 * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
434*7017fceaSAndreas Gohr                 * Every HTML element may have a class attribute specified.
435*7017fceaSAndreas Gohr                 * The attribute, if specified, must have a value that is a set
436*7017fceaSAndreas Gohr                 * of space-separated tokens representing the various classes
437*7017fceaSAndreas Gohr                 * that the element belongs to.
438*7017fceaSAndreas Gohr                 * [...]
439*7017fceaSAndreas Gohr                 * The space characters, for the purposes of this specification,
440*7017fceaSAndreas Gohr                 * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
441*7017fceaSAndreas Gohr                 * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
442*7017fceaSAndreas Gohr                 * U+000D CARRIAGE RETURN (CR).
443*7017fceaSAndreas Gohr                 */
444*7017fceaSAndreas Gohr                $language = substr($matches[1], 0, strcspn($matches[1], " \t\n\f\r"));
445*7017fceaSAndreas Gohr
446*7017fceaSAndreas Gohr                $class = 'language-'.$language;
447*7017fceaSAndreas Gohr
448*7017fceaSAndreas Gohr                $Element['attributes'] = array(
449*7017fceaSAndreas Gohr                    'class' => $class,
450*7017fceaSAndreas Gohr                );
451*7017fceaSAndreas Gohr            }
452*7017fceaSAndreas Gohr
453*7017fceaSAndreas Gohr            $Block = array(
454*7017fceaSAndreas Gohr                'char' => $Line['text'][0],
455*7017fceaSAndreas Gohr                'element' => array(
456*7017fceaSAndreas Gohr                    'name' => 'pre',
457*7017fceaSAndreas Gohr                    'handler' => 'element',
458*7017fceaSAndreas Gohr                    'text' => $Element,
459*7017fceaSAndreas Gohr                ),
460*7017fceaSAndreas Gohr            );
461*7017fceaSAndreas Gohr
462*7017fceaSAndreas Gohr            return $Block;
463*7017fceaSAndreas Gohr        }
464*7017fceaSAndreas Gohr    }
465*7017fceaSAndreas Gohr
466*7017fceaSAndreas Gohr    protected function blockFencedCodeContinue($Line, $Block)
467*7017fceaSAndreas Gohr    {
468*7017fceaSAndreas Gohr        if (isset($Block['complete']))
469*7017fceaSAndreas Gohr        {
470*7017fceaSAndreas Gohr            return;
471*7017fceaSAndreas Gohr        }
472*7017fceaSAndreas Gohr
473*7017fceaSAndreas Gohr        if (isset($Block['interrupted']))
474*7017fceaSAndreas Gohr        {
475*7017fceaSAndreas Gohr            $Block['element']['text']['text'] .= "\n";
476*7017fceaSAndreas Gohr
477*7017fceaSAndreas Gohr            unset($Block['interrupted']);
478*7017fceaSAndreas Gohr        }
479*7017fceaSAndreas Gohr
480*7017fceaSAndreas Gohr        if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
481*7017fceaSAndreas Gohr        {
482*7017fceaSAndreas Gohr            $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
483*7017fceaSAndreas Gohr
484*7017fceaSAndreas Gohr            $Block['complete'] = true;
485*7017fceaSAndreas Gohr
486*7017fceaSAndreas Gohr            return $Block;
487*7017fceaSAndreas Gohr        }
488*7017fceaSAndreas Gohr
489*7017fceaSAndreas Gohr        $Block['element']['text']['text'] .= "\n".$Line['body'];
490*7017fceaSAndreas Gohr
491*7017fceaSAndreas Gohr        return $Block;
492*7017fceaSAndreas Gohr    }
493*7017fceaSAndreas Gohr
494*7017fceaSAndreas Gohr    protected function blockFencedCodeComplete($Block)
495*7017fceaSAndreas Gohr    {
496*7017fceaSAndreas Gohr        $text = $Block['element']['text']['text'];
497*7017fceaSAndreas Gohr
498*7017fceaSAndreas Gohr        $Block['element']['text']['text'] = $text;
499*7017fceaSAndreas Gohr
500*7017fceaSAndreas Gohr        return $Block;
501*7017fceaSAndreas Gohr    }
502*7017fceaSAndreas Gohr
503*7017fceaSAndreas Gohr    #
504*7017fceaSAndreas Gohr    # Header
505*7017fceaSAndreas Gohr
506*7017fceaSAndreas Gohr    protected function blockHeader($Line)
507*7017fceaSAndreas Gohr    {
508*7017fceaSAndreas Gohr        if (isset($Line['text'][1]))
509*7017fceaSAndreas Gohr        {
510*7017fceaSAndreas Gohr            $level = 1;
511*7017fceaSAndreas Gohr
512*7017fceaSAndreas Gohr            while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
513*7017fceaSAndreas Gohr            {
514*7017fceaSAndreas Gohr                $level ++;
515*7017fceaSAndreas Gohr            }
516*7017fceaSAndreas Gohr
517*7017fceaSAndreas Gohr            if ($level > 6)
518*7017fceaSAndreas Gohr            {
519*7017fceaSAndreas Gohr                return;
520*7017fceaSAndreas Gohr            }
521*7017fceaSAndreas Gohr
522*7017fceaSAndreas Gohr            $text = trim($Line['text'], '# ');
523*7017fceaSAndreas Gohr
524*7017fceaSAndreas Gohr            $Block = array(
525*7017fceaSAndreas Gohr                'element' => array(
526*7017fceaSAndreas Gohr                    'name' => 'h' . min(6, $level),
527*7017fceaSAndreas Gohr                    'text' => $text,
528*7017fceaSAndreas Gohr                    'handler' => 'line',
529*7017fceaSAndreas Gohr                ),
530*7017fceaSAndreas Gohr            );
531*7017fceaSAndreas Gohr
532*7017fceaSAndreas Gohr            return $Block;
533*7017fceaSAndreas Gohr        }
534*7017fceaSAndreas Gohr    }
535*7017fceaSAndreas Gohr
536*7017fceaSAndreas Gohr    #
537*7017fceaSAndreas Gohr    # List
538*7017fceaSAndreas Gohr
539*7017fceaSAndreas Gohr    protected function blockList($Line)
540*7017fceaSAndreas Gohr    {
541*7017fceaSAndreas Gohr        list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
542*7017fceaSAndreas Gohr
543*7017fceaSAndreas Gohr        if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
544*7017fceaSAndreas Gohr        {
545*7017fceaSAndreas Gohr            $Block = array(
546*7017fceaSAndreas Gohr                'indent' => $Line['indent'],
547*7017fceaSAndreas Gohr                'pattern' => $pattern,
548*7017fceaSAndreas Gohr                'element' => array(
549*7017fceaSAndreas Gohr                    'name' => $name,
550*7017fceaSAndreas Gohr                    'handler' => 'elements',
551*7017fceaSAndreas Gohr                ),
552*7017fceaSAndreas Gohr            );
553*7017fceaSAndreas Gohr
554*7017fceaSAndreas Gohr            if($name === 'ol')
555*7017fceaSAndreas Gohr            {
556*7017fceaSAndreas Gohr                $listStart = stristr($matches[0], '.', true);
557*7017fceaSAndreas Gohr
558*7017fceaSAndreas Gohr                if($listStart !== '1')
559*7017fceaSAndreas Gohr                {
560*7017fceaSAndreas Gohr                    $Block['element']['attributes'] = array('start' => $listStart);
561*7017fceaSAndreas Gohr                }
562*7017fceaSAndreas Gohr            }
563*7017fceaSAndreas Gohr
564*7017fceaSAndreas Gohr            $Block['li'] = array(
565*7017fceaSAndreas Gohr                'name' => 'li',
566*7017fceaSAndreas Gohr                'handler' => 'li',
567*7017fceaSAndreas Gohr                'text' => array(
568*7017fceaSAndreas Gohr                    $matches[2],
569*7017fceaSAndreas Gohr                ),
570*7017fceaSAndreas Gohr            );
571*7017fceaSAndreas Gohr
572*7017fceaSAndreas Gohr            $Block['element']['text'] []= & $Block['li'];
573*7017fceaSAndreas Gohr
574*7017fceaSAndreas Gohr            return $Block;
575*7017fceaSAndreas Gohr        }
576*7017fceaSAndreas Gohr    }
577*7017fceaSAndreas Gohr
578*7017fceaSAndreas Gohr    protected function blockListContinue($Line, array $Block)
579*7017fceaSAndreas Gohr    {
580*7017fceaSAndreas Gohr        if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
581*7017fceaSAndreas Gohr        {
582*7017fceaSAndreas Gohr            if (isset($Block['interrupted']))
583*7017fceaSAndreas Gohr            {
584*7017fceaSAndreas Gohr                $Block['li']['text'] []= '';
585*7017fceaSAndreas Gohr
586*7017fceaSAndreas Gohr                $Block['loose'] = true;
587*7017fceaSAndreas Gohr
588*7017fceaSAndreas Gohr                unset($Block['interrupted']);
589*7017fceaSAndreas Gohr            }
590*7017fceaSAndreas Gohr
591*7017fceaSAndreas Gohr            unset($Block['li']);
592*7017fceaSAndreas Gohr
593*7017fceaSAndreas Gohr            $text = isset($matches[1]) ? $matches[1] : '';
594*7017fceaSAndreas Gohr
595*7017fceaSAndreas Gohr            $Block['li'] = array(
596*7017fceaSAndreas Gohr                'name' => 'li',
597*7017fceaSAndreas Gohr                'handler' => 'li',
598*7017fceaSAndreas Gohr                'text' => array(
599*7017fceaSAndreas Gohr                    $text,
600*7017fceaSAndreas Gohr                ),
601*7017fceaSAndreas Gohr            );
602*7017fceaSAndreas Gohr
603*7017fceaSAndreas Gohr            $Block['element']['text'] []= & $Block['li'];
604*7017fceaSAndreas Gohr
605*7017fceaSAndreas Gohr            return $Block;
606*7017fceaSAndreas Gohr        }
607*7017fceaSAndreas Gohr
608*7017fceaSAndreas Gohr        if ($Line['text'][0] === '[' and $this->blockReference($Line))
609*7017fceaSAndreas Gohr        {
610*7017fceaSAndreas Gohr            return $Block;
611*7017fceaSAndreas Gohr        }
612*7017fceaSAndreas Gohr
613*7017fceaSAndreas Gohr        if ( ! isset($Block['interrupted']))
614*7017fceaSAndreas Gohr        {
615*7017fceaSAndreas Gohr            $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
616*7017fceaSAndreas Gohr
617*7017fceaSAndreas Gohr            $Block['li']['text'] []= $text;
618*7017fceaSAndreas Gohr
619*7017fceaSAndreas Gohr            return $Block;
620*7017fceaSAndreas Gohr        }
621*7017fceaSAndreas Gohr
622*7017fceaSAndreas Gohr        if ($Line['indent'] > 0)
623*7017fceaSAndreas Gohr        {
624*7017fceaSAndreas Gohr            $Block['li']['text'] []= '';
625*7017fceaSAndreas Gohr
626*7017fceaSAndreas Gohr            $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
627*7017fceaSAndreas Gohr
628*7017fceaSAndreas Gohr            $Block['li']['text'] []= $text;
629*7017fceaSAndreas Gohr
630*7017fceaSAndreas Gohr            unset($Block['interrupted']);
631*7017fceaSAndreas Gohr
632*7017fceaSAndreas Gohr            return $Block;
633*7017fceaSAndreas Gohr        }
634*7017fceaSAndreas Gohr    }
635*7017fceaSAndreas Gohr
636*7017fceaSAndreas Gohr    protected function blockListComplete(array $Block)
637*7017fceaSAndreas Gohr    {
638*7017fceaSAndreas Gohr        if (isset($Block['loose']))
639*7017fceaSAndreas Gohr        {
640*7017fceaSAndreas Gohr            foreach ($Block['element']['text'] as &$li)
641*7017fceaSAndreas Gohr            {
642*7017fceaSAndreas Gohr                if (end($li['text']) !== '')
643*7017fceaSAndreas Gohr                {
644*7017fceaSAndreas Gohr                    $li['text'] []= '';
645*7017fceaSAndreas Gohr                }
646*7017fceaSAndreas Gohr            }
647*7017fceaSAndreas Gohr        }
648*7017fceaSAndreas Gohr
649*7017fceaSAndreas Gohr        return $Block;
650*7017fceaSAndreas Gohr    }
651*7017fceaSAndreas Gohr
652*7017fceaSAndreas Gohr    #
653*7017fceaSAndreas Gohr    # Quote
654*7017fceaSAndreas Gohr
655*7017fceaSAndreas Gohr    protected function blockQuote($Line)
656*7017fceaSAndreas Gohr    {
657*7017fceaSAndreas Gohr        if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
658*7017fceaSAndreas Gohr        {
659*7017fceaSAndreas Gohr            $Block = array(
660*7017fceaSAndreas Gohr                'element' => array(
661*7017fceaSAndreas Gohr                    'name' => 'blockquote',
662*7017fceaSAndreas Gohr                    'handler' => 'lines',
663*7017fceaSAndreas Gohr                    'text' => (array) $matches[1],
664*7017fceaSAndreas Gohr                ),
665*7017fceaSAndreas Gohr            );
666*7017fceaSAndreas Gohr
667*7017fceaSAndreas Gohr            return $Block;
668*7017fceaSAndreas Gohr        }
669*7017fceaSAndreas Gohr    }
670*7017fceaSAndreas Gohr
671*7017fceaSAndreas Gohr    protected function blockQuoteContinue($Line, array $Block)
672*7017fceaSAndreas Gohr    {
673*7017fceaSAndreas Gohr        if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
674*7017fceaSAndreas Gohr        {
675*7017fceaSAndreas Gohr            if (isset($Block['interrupted']))
676*7017fceaSAndreas Gohr            {
677*7017fceaSAndreas Gohr                $Block['element']['text'] []= '';
678*7017fceaSAndreas Gohr
679*7017fceaSAndreas Gohr                unset($Block['interrupted']);
680*7017fceaSAndreas Gohr            }
681*7017fceaSAndreas Gohr
682*7017fceaSAndreas Gohr            $Block['element']['text'] []= $matches[1];
683*7017fceaSAndreas Gohr
684*7017fceaSAndreas Gohr            return $Block;
685*7017fceaSAndreas Gohr        }
686*7017fceaSAndreas Gohr
687*7017fceaSAndreas Gohr        if ( ! isset($Block['interrupted']))
688*7017fceaSAndreas Gohr        {
689*7017fceaSAndreas Gohr            $Block['element']['text'] []= $Line['text'];
690*7017fceaSAndreas Gohr
691*7017fceaSAndreas Gohr            return $Block;
692*7017fceaSAndreas Gohr        }
693*7017fceaSAndreas Gohr    }
694*7017fceaSAndreas Gohr
695*7017fceaSAndreas Gohr    #
696*7017fceaSAndreas Gohr    # Rule
697*7017fceaSAndreas Gohr
698*7017fceaSAndreas Gohr    protected function blockRule($Line)
699*7017fceaSAndreas Gohr    {
700*7017fceaSAndreas Gohr        if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
701*7017fceaSAndreas Gohr        {
702*7017fceaSAndreas Gohr            $Block = array(
703*7017fceaSAndreas Gohr                'element' => array(
704*7017fceaSAndreas Gohr                    'name' => 'hr'
705*7017fceaSAndreas Gohr                ),
706*7017fceaSAndreas Gohr            );
707*7017fceaSAndreas Gohr
708*7017fceaSAndreas Gohr            return $Block;
709*7017fceaSAndreas Gohr        }
710*7017fceaSAndreas Gohr    }
711*7017fceaSAndreas Gohr
712*7017fceaSAndreas Gohr    #
713*7017fceaSAndreas Gohr    # Setext
714*7017fceaSAndreas Gohr
715*7017fceaSAndreas Gohr    protected function blockSetextHeader($Line, array $Block = null)
716*7017fceaSAndreas Gohr    {
717*7017fceaSAndreas Gohr        if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
718*7017fceaSAndreas Gohr        {
719*7017fceaSAndreas Gohr            return;
720*7017fceaSAndreas Gohr        }
721*7017fceaSAndreas Gohr
722*7017fceaSAndreas Gohr        if (chop($Line['text'], $Line['text'][0]) === '')
723*7017fceaSAndreas Gohr        {
724*7017fceaSAndreas Gohr            $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
725*7017fceaSAndreas Gohr
726*7017fceaSAndreas Gohr            return $Block;
727*7017fceaSAndreas Gohr        }
728*7017fceaSAndreas Gohr    }
729*7017fceaSAndreas Gohr
730*7017fceaSAndreas Gohr    #
731*7017fceaSAndreas Gohr    # Markup
732*7017fceaSAndreas Gohr
733*7017fceaSAndreas Gohr    protected function blockMarkup($Line)
734*7017fceaSAndreas Gohr    {
735*7017fceaSAndreas Gohr        if ($this->markupEscaped or $this->safeMode)
736*7017fceaSAndreas Gohr        {
737*7017fceaSAndreas Gohr            return;
738*7017fceaSAndreas Gohr        }
739*7017fceaSAndreas Gohr
740*7017fceaSAndreas Gohr        if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
741*7017fceaSAndreas Gohr        {
742*7017fceaSAndreas Gohr            $element = strtolower($matches[1]);
743*7017fceaSAndreas Gohr
744*7017fceaSAndreas Gohr            if (in_array($element, $this->textLevelElements))
745*7017fceaSAndreas Gohr            {
746*7017fceaSAndreas Gohr                return;
747*7017fceaSAndreas Gohr            }
748*7017fceaSAndreas Gohr
749*7017fceaSAndreas Gohr            $Block = array(
750*7017fceaSAndreas Gohr                'name' => $matches[1],
751*7017fceaSAndreas Gohr                'depth' => 0,
752*7017fceaSAndreas Gohr                'markup' => $Line['text'],
753*7017fceaSAndreas Gohr            );
754*7017fceaSAndreas Gohr
755*7017fceaSAndreas Gohr            $length = strlen($matches[0]);
756*7017fceaSAndreas Gohr
757*7017fceaSAndreas Gohr            $remainder = substr($Line['text'], $length);
758*7017fceaSAndreas Gohr
759*7017fceaSAndreas Gohr            if (trim($remainder) === '')
760*7017fceaSAndreas Gohr            {
761*7017fceaSAndreas Gohr                if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
762*7017fceaSAndreas Gohr                {
763*7017fceaSAndreas Gohr                    $Block['closed'] = true;
764*7017fceaSAndreas Gohr
765*7017fceaSAndreas Gohr                    $Block['void'] = true;
766*7017fceaSAndreas Gohr                }
767*7017fceaSAndreas Gohr            }
768*7017fceaSAndreas Gohr            else
769*7017fceaSAndreas Gohr            {
770*7017fceaSAndreas Gohr                if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
771*7017fceaSAndreas Gohr                {
772*7017fceaSAndreas Gohr                    return;
773*7017fceaSAndreas Gohr                }
774*7017fceaSAndreas Gohr
775*7017fceaSAndreas Gohr                if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
776*7017fceaSAndreas Gohr                {
777*7017fceaSAndreas Gohr                    $Block['closed'] = true;
778*7017fceaSAndreas Gohr                }
779*7017fceaSAndreas Gohr            }
780*7017fceaSAndreas Gohr
781*7017fceaSAndreas Gohr            return $Block;
782*7017fceaSAndreas Gohr        }
783*7017fceaSAndreas Gohr    }
784*7017fceaSAndreas Gohr
785*7017fceaSAndreas Gohr    protected function blockMarkupContinue($Line, array $Block)
786*7017fceaSAndreas Gohr    {
787*7017fceaSAndreas Gohr        if (isset($Block['closed']))
788*7017fceaSAndreas Gohr        {
789*7017fceaSAndreas Gohr            return;
790*7017fceaSAndreas Gohr        }
791*7017fceaSAndreas Gohr
792*7017fceaSAndreas Gohr        if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
793*7017fceaSAndreas Gohr        {
794*7017fceaSAndreas Gohr            $Block['depth'] ++;
795*7017fceaSAndreas Gohr        }
796*7017fceaSAndreas Gohr
797*7017fceaSAndreas Gohr        if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
798*7017fceaSAndreas Gohr        {
799*7017fceaSAndreas Gohr            if ($Block['depth'] > 0)
800*7017fceaSAndreas Gohr            {
801*7017fceaSAndreas Gohr                $Block['depth'] --;
802*7017fceaSAndreas Gohr            }
803*7017fceaSAndreas Gohr            else
804*7017fceaSAndreas Gohr            {
805*7017fceaSAndreas Gohr                $Block['closed'] = true;
806*7017fceaSAndreas Gohr            }
807*7017fceaSAndreas Gohr        }
808*7017fceaSAndreas Gohr
809*7017fceaSAndreas Gohr        if (isset($Block['interrupted']))
810*7017fceaSAndreas Gohr        {
811*7017fceaSAndreas Gohr            $Block['markup'] .= "\n";
812*7017fceaSAndreas Gohr
813*7017fceaSAndreas Gohr            unset($Block['interrupted']);
814*7017fceaSAndreas Gohr        }
815*7017fceaSAndreas Gohr
816*7017fceaSAndreas Gohr        $Block['markup'] .= "\n".$Line['body'];
817*7017fceaSAndreas Gohr
818*7017fceaSAndreas Gohr        return $Block;
819*7017fceaSAndreas Gohr    }
820*7017fceaSAndreas Gohr
821*7017fceaSAndreas Gohr    #
822*7017fceaSAndreas Gohr    # Reference
823*7017fceaSAndreas Gohr
824*7017fceaSAndreas Gohr    protected function blockReference($Line)
825*7017fceaSAndreas Gohr    {
826*7017fceaSAndreas Gohr        if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
827*7017fceaSAndreas Gohr        {
828*7017fceaSAndreas Gohr            $id = strtolower($matches[1]);
829*7017fceaSAndreas Gohr
830*7017fceaSAndreas Gohr            $Data = array(
831*7017fceaSAndreas Gohr                'url' => $matches[2],
832*7017fceaSAndreas Gohr                'title' => null,
833*7017fceaSAndreas Gohr            );
834*7017fceaSAndreas Gohr
835*7017fceaSAndreas Gohr            if (isset($matches[3]))
836*7017fceaSAndreas Gohr            {
837*7017fceaSAndreas Gohr                $Data['title'] = $matches[3];
838*7017fceaSAndreas Gohr            }
839*7017fceaSAndreas Gohr
840*7017fceaSAndreas Gohr            $this->DefinitionData['Reference'][$id] = $Data;
841*7017fceaSAndreas Gohr
842*7017fceaSAndreas Gohr            $Block = array(
843*7017fceaSAndreas Gohr                'hidden' => true,
844*7017fceaSAndreas Gohr            );
845*7017fceaSAndreas Gohr
846*7017fceaSAndreas Gohr            return $Block;
847*7017fceaSAndreas Gohr        }
848*7017fceaSAndreas Gohr    }
849*7017fceaSAndreas Gohr
850*7017fceaSAndreas Gohr    #
851*7017fceaSAndreas Gohr    # Table
852*7017fceaSAndreas Gohr
853*7017fceaSAndreas Gohr    protected function blockTable($Line, array $Block = null)
854*7017fceaSAndreas Gohr    {
855*7017fceaSAndreas Gohr        if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
856*7017fceaSAndreas Gohr        {
857*7017fceaSAndreas Gohr            return;
858*7017fceaSAndreas Gohr        }
859*7017fceaSAndreas Gohr
860*7017fceaSAndreas Gohr        if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
861*7017fceaSAndreas Gohr        {
862*7017fceaSAndreas Gohr            $alignments = array();
863*7017fceaSAndreas Gohr
864*7017fceaSAndreas Gohr            $divider = $Line['text'];
865*7017fceaSAndreas Gohr
866*7017fceaSAndreas Gohr            $divider = trim($divider);
867*7017fceaSAndreas Gohr            $divider = trim($divider, '|');
868*7017fceaSAndreas Gohr
869*7017fceaSAndreas Gohr            $dividerCells = explode('|', $divider);
870*7017fceaSAndreas Gohr
871*7017fceaSAndreas Gohr            foreach ($dividerCells as $dividerCell)
872*7017fceaSAndreas Gohr            {
873*7017fceaSAndreas Gohr                $dividerCell = trim($dividerCell);
874*7017fceaSAndreas Gohr
875*7017fceaSAndreas Gohr                if ($dividerCell === '')
876*7017fceaSAndreas Gohr                {
877*7017fceaSAndreas Gohr                    continue;
878*7017fceaSAndreas Gohr                }
879*7017fceaSAndreas Gohr
880*7017fceaSAndreas Gohr                $alignment = null;
881*7017fceaSAndreas Gohr
882*7017fceaSAndreas Gohr                if ($dividerCell[0] === ':')
883*7017fceaSAndreas Gohr                {
884*7017fceaSAndreas Gohr                    $alignment = 'left';
885*7017fceaSAndreas Gohr                }
886*7017fceaSAndreas Gohr
887*7017fceaSAndreas Gohr                if (substr($dividerCell, - 1) === ':')
888*7017fceaSAndreas Gohr                {
889*7017fceaSAndreas Gohr                    $alignment = $alignment === 'left' ? 'center' : 'right';
890*7017fceaSAndreas Gohr                }
891*7017fceaSAndreas Gohr
892*7017fceaSAndreas Gohr                $alignments []= $alignment;
893*7017fceaSAndreas Gohr            }
894*7017fceaSAndreas Gohr
895*7017fceaSAndreas Gohr            # ~
896*7017fceaSAndreas Gohr
897*7017fceaSAndreas Gohr            $HeaderElements = array();
898*7017fceaSAndreas Gohr
899*7017fceaSAndreas Gohr            $header = $Block['element']['text'];
900*7017fceaSAndreas Gohr
901*7017fceaSAndreas Gohr            $header = trim($header);
902*7017fceaSAndreas Gohr            $header = trim($header, '|');
903*7017fceaSAndreas Gohr
904*7017fceaSAndreas Gohr            $headerCells = explode('|', $header);
905*7017fceaSAndreas Gohr
906*7017fceaSAndreas Gohr            foreach ($headerCells as $index => $headerCell)
907*7017fceaSAndreas Gohr            {
908*7017fceaSAndreas Gohr                $headerCell = trim($headerCell);
909*7017fceaSAndreas Gohr
910*7017fceaSAndreas Gohr                $HeaderElement = array(
911*7017fceaSAndreas Gohr                    'name' => 'th',
912*7017fceaSAndreas Gohr                    'text' => $headerCell,
913*7017fceaSAndreas Gohr                    'handler' => 'line',
914*7017fceaSAndreas Gohr                );
915*7017fceaSAndreas Gohr
916*7017fceaSAndreas Gohr                if (isset($alignments[$index]))
917*7017fceaSAndreas Gohr                {
918*7017fceaSAndreas Gohr                    $alignment = $alignments[$index];
919*7017fceaSAndreas Gohr
920*7017fceaSAndreas Gohr                    $HeaderElement['attributes'] = array(
921*7017fceaSAndreas Gohr                        'style' => 'text-align: '.$alignment.';',
922*7017fceaSAndreas Gohr                    );
923*7017fceaSAndreas Gohr                }
924*7017fceaSAndreas Gohr
925*7017fceaSAndreas Gohr                $HeaderElements []= $HeaderElement;
926*7017fceaSAndreas Gohr            }
927*7017fceaSAndreas Gohr
928*7017fceaSAndreas Gohr            # ~
929*7017fceaSAndreas Gohr
930*7017fceaSAndreas Gohr            $Block = array(
931*7017fceaSAndreas Gohr                'alignments' => $alignments,
932*7017fceaSAndreas Gohr                'identified' => true,
933*7017fceaSAndreas Gohr                'element' => array(
934*7017fceaSAndreas Gohr                    'name' => 'table',
935*7017fceaSAndreas Gohr                    'handler' => 'elements',
936*7017fceaSAndreas Gohr                ),
937*7017fceaSAndreas Gohr            );
938*7017fceaSAndreas Gohr
939*7017fceaSAndreas Gohr            $Block['element']['text'] []= array(
940*7017fceaSAndreas Gohr                'name' => 'thead',
941*7017fceaSAndreas Gohr                'handler' => 'elements',
942*7017fceaSAndreas Gohr            );
943*7017fceaSAndreas Gohr
944*7017fceaSAndreas Gohr            $Block['element']['text'] []= array(
945*7017fceaSAndreas Gohr                'name' => 'tbody',
946*7017fceaSAndreas Gohr                'handler' => 'elements',
947*7017fceaSAndreas Gohr                'text' => array(),
948*7017fceaSAndreas Gohr            );
949*7017fceaSAndreas Gohr
950*7017fceaSAndreas Gohr            $Block['element']['text'][0]['text'] []= array(
951*7017fceaSAndreas Gohr                'name' => 'tr',
952*7017fceaSAndreas Gohr                'handler' => 'elements',
953*7017fceaSAndreas Gohr                'text' => $HeaderElements,
954*7017fceaSAndreas Gohr            );
955*7017fceaSAndreas Gohr
956*7017fceaSAndreas Gohr            return $Block;
957*7017fceaSAndreas Gohr        }
958*7017fceaSAndreas Gohr    }
959*7017fceaSAndreas Gohr
960*7017fceaSAndreas Gohr    protected function blockTableContinue($Line, array $Block)
961*7017fceaSAndreas Gohr    {
962*7017fceaSAndreas Gohr        if (isset($Block['interrupted']))
963*7017fceaSAndreas Gohr        {
964*7017fceaSAndreas Gohr            return;
965*7017fceaSAndreas Gohr        }
966*7017fceaSAndreas Gohr
967*7017fceaSAndreas Gohr        if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
968*7017fceaSAndreas Gohr        {
969*7017fceaSAndreas Gohr            $Elements = array();
970*7017fceaSAndreas Gohr
971*7017fceaSAndreas Gohr            $row = $Line['text'];
972*7017fceaSAndreas Gohr
973*7017fceaSAndreas Gohr            $row = trim($row);
974*7017fceaSAndreas Gohr            $row = trim($row, '|');
975*7017fceaSAndreas Gohr
976*7017fceaSAndreas Gohr            preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
977*7017fceaSAndreas Gohr
978*7017fceaSAndreas Gohr            foreach ($matches[0] as $index => $cell)
979*7017fceaSAndreas Gohr            {
980*7017fceaSAndreas Gohr                $cell = trim($cell);
981*7017fceaSAndreas Gohr
982*7017fceaSAndreas Gohr                $Element = array(
983*7017fceaSAndreas Gohr                    'name' => 'td',
984*7017fceaSAndreas Gohr                    'handler' => 'line',
985*7017fceaSAndreas Gohr                    'text' => $cell,
986*7017fceaSAndreas Gohr                );
987*7017fceaSAndreas Gohr
988*7017fceaSAndreas Gohr                if (isset($Block['alignments'][$index]))
989*7017fceaSAndreas Gohr                {
990*7017fceaSAndreas Gohr                    $Element['attributes'] = array(
991*7017fceaSAndreas Gohr                        'style' => 'text-align: '.$Block['alignments'][$index].';',
992*7017fceaSAndreas Gohr                    );
993*7017fceaSAndreas Gohr                }
994*7017fceaSAndreas Gohr
995*7017fceaSAndreas Gohr                $Elements []= $Element;
996*7017fceaSAndreas Gohr            }
997*7017fceaSAndreas Gohr
998*7017fceaSAndreas Gohr            $Element = array(
999*7017fceaSAndreas Gohr                'name' => 'tr',
1000*7017fceaSAndreas Gohr                'handler' => 'elements',
1001*7017fceaSAndreas Gohr                'text' => $Elements,
1002*7017fceaSAndreas Gohr            );
1003*7017fceaSAndreas Gohr
1004*7017fceaSAndreas Gohr            $Block['element']['text'][1]['text'] []= $Element;
1005*7017fceaSAndreas Gohr
1006*7017fceaSAndreas Gohr            return $Block;
1007*7017fceaSAndreas Gohr        }
1008*7017fceaSAndreas Gohr    }
1009*7017fceaSAndreas Gohr
1010*7017fceaSAndreas Gohr    #
1011*7017fceaSAndreas Gohr    # ~
1012*7017fceaSAndreas Gohr    #
1013*7017fceaSAndreas Gohr
1014*7017fceaSAndreas Gohr    protected function paragraph($Line)
1015*7017fceaSAndreas Gohr    {
1016*7017fceaSAndreas Gohr        $Block = array(
1017*7017fceaSAndreas Gohr            'element' => array(
1018*7017fceaSAndreas Gohr                'name' => 'p',
1019*7017fceaSAndreas Gohr                'text' => $Line['text'],
1020*7017fceaSAndreas Gohr                'handler' => 'line',
1021*7017fceaSAndreas Gohr            ),
1022*7017fceaSAndreas Gohr        );
1023*7017fceaSAndreas Gohr
1024*7017fceaSAndreas Gohr        return $Block;
1025*7017fceaSAndreas Gohr    }
1026*7017fceaSAndreas Gohr
1027*7017fceaSAndreas Gohr    #
1028*7017fceaSAndreas Gohr    # Inline Elements
1029*7017fceaSAndreas Gohr    #
1030*7017fceaSAndreas Gohr
1031*7017fceaSAndreas Gohr    protected $InlineTypes = array(
1032*7017fceaSAndreas Gohr        '"' => array('SpecialCharacter'),
1033*7017fceaSAndreas Gohr        '!' => array('Image'),
1034*7017fceaSAndreas Gohr        '&' => array('SpecialCharacter'),
1035*7017fceaSAndreas Gohr        '*' => array('Emphasis'),
1036*7017fceaSAndreas Gohr        ':' => array('Url'),
1037*7017fceaSAndreas Gohr        '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
1038*7017fceaSAndreas Gohr        '>' => array('SpecialCharacter'),
1039*7017fceaSAndreas Gohr        '[' => array('Link'),
1040*7017fceaSAndreas Gohr        '_' => array('Emphasis'),
1041*7017fceaSAndreas Gohr        '`' => array('Code'),
1042*7017fceaSAndreas Gohr        '~' => array('Strikethrough'),
1043*7017fceaSAndreas Gohr        '\\' => array('EscapeSequence'),
1044*7017fceaSAndreas Gohr    );
1045*7017fceaSAndreas Gohr
1046*7017fceaSAndreas Gohr    # ~
1047*7017fceaSAndreas Gohr
1048*7017fceaSAndreas Gohr    protected $inlineMarkerList = '!"*_&[:<>`~\\';
1049*7017fceaSAndreas Gohr
1050*7017fceaSAndreas Gohr    #
1051*7017fceaSAndreas Gohr    # ~
1052*7017fceaSAndreas Gohr    #
1053*7017fceaSAndreas Gohr
1054*7017fceaSAndreas Gohr    public function line($text, $nonNestables=array())
1055*7017fceaSAndreas Gohr    {
1056*7017fceaSAndreas Gohr        $markup = '';
1057*7017fceaSAndreas Gohr
1058*7017fceaSAndreas Gohr        # $excerpt is based on the first occurrence of a marker
1059*7017fceaSAndreas Gohr
1060*7017fceaSAndreas Gohr        while ($excerpt = strpbrk($text, $this->inlineMarkerList))
1061*7017fceaSAndreas Gohr        {
1062*7017fceaSAndreas Gohr            $marker = $excerpt[0];
1063*7017fceaSAndreas Gohr
1064*7017fceaSAndreas Gohr            $markerPosition = strpos($text, $marker);
1065*7017fceaSAndreas Gohr
1066*7017fceaSAndreas Gohr            $Excerpt = array('text' => $excerpt, 'context' => $text);
1067*7017fceaSAndreas Gohr
1068*7017fceaSAndreas Gohr            foreach ($this->InlineTypes[$marker] as $inlineType)
1069*7017fceaSAndreas Gohr            {
1070*7017fceaSAndreas Gohr                # check to see if the current inline type is nestable in the current context
1071*7017fceaSAndreas Gohr
1072*7017fceaSAndreas Gohr                if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables))
1073*7017fceaSAndreas Gohr                {
1074*7017fceaSAndreas Gohr                    continue;
1075*7017fceaSAndreas Gohr                }
1076*7017fceaSAndreas Gohr
1077*7017fceaSAndreas Gohr                $Inline = $this->{'inline'.$inlineType}($Excerpt);
1078*7017fceaSAndreas Gohr
1079*7017fceaSAndreas Gohr                if ( ! isset($Inline))
1080*7017fceaSAndreas Gohr                {
1081*7017fceaSAndreas Gohr                    continue;
1082*7017fceaSAndreas Gohr                }
1083*7017fceaSAndreas Gohr
1084*7017fceaSAndreas Gohr                # makes sure that the inline belongs to "our" marker
1085*7017fceaSAndreas Gohr
1086*7017fceaSAndreas Gohr                if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
1087*7017fceaSAndreas Gohr                {
1088*7017fceaSAndreas Gohr                    continue;
1089*7017fceaSAndreas Gohr                }
1090*7017fceaSAndreas Gohr
1091*7017fceaSAndreas Gohr                # sets a default inline position
1092*7017fceaSAndreas Gohr
1093*7017fceaSAndreas Gohr                if ( ! isset($Inline['position']))
1094*7017fceaSAndreas Gohr                {
1095*7017fceaSAndreas Gohr                    $Inline['position'] = $markerPosition;
1096*7017fceaSAndreas Gohr                }
1097*7017fceaSAndreas Gohr
1098*7017fceaSAndreas Gohr                # cause the new element to 'inherit' our non nestables
1099*7017fceaSAndreas Gohr
1100*7017fceaSAndreas Gohr                foreach ($nonNestables as $non_nestable)
1101*7017fceaSAndreas Gohr                {
1102*7017fceaSAndreas Gohr                    $Inline['element']['nonNestables'][] = $non_nestable;
1103*7017fceaSAndreas Gohr                }
1104*7017fceaSAndreas Gohr
1105*7017fceaSAndreas Gohr                # the text that comes before the inline
1106*7017fceaSAndreas Gohr                $unmarkedText = substr($text, 0, $Inline['position']);
1107*7017fceaSAndreas Gohr
1108*7017fceaSAndreas Gohr                # compile the unmarked text
1109*7017fceaSAndreas Gohr                $markup .= $this->unmarkedText($unmarkedText);
1110*7017fceaSAndreas Gohr
1111*7017fceaSAndreas Gohr                # compile the inline
1112*7017fceaSAndreas Gohr                $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
1113*7017fceaSAndreas Gohr
1114*7017fceaSAndreas Gohr                # remove the examined text
1115*7017fceaSAndreas Gohr                $text = substr($text, $Inline['position'] + $Inline['extent']);
1116*7017fceaSAndreas Gohr
1117*7017fceaSAndreas Gohr                continue 2;
1118*7017fceaSAndreas Gohr            }
1119*7017fceaSAndreas Gohr
1120*7017fceaSAndreas Gohr            # the marker does not belong to an inline
1121*7017fceaSAndreas Gohr
1122*7017fceaSAndreas Gohr            $unmarkedText = substr($text, 0, $markerPosition + 1);
1123*7017fceaSAndreas Gohr
1124*7017fceaSAndreas Gohr            $markup .= $this->unmarkedText($unmarkedText);
1125*7017fceaSAndreas Gohr
1126*7017fceaSAndreas Gohr            $text = substr($text, $markerPosition + 1);
1127*7017fceaSAndreas Gohr        }
1128*7017fceaSAndreas Gohr
1129*7017fceaSAndreas Gohr        $markup .= $this->unmarkedText($text);
1130*7017fceaSAndreas Gohr
1131*7017fceaSAndreas Gohr        return $markup;
1132*7017fceaSAndreas Gohr    }
1133*7017fceaSAndreas Gohr
1134*7017fceaSAndreas Gohr    #
1135*7017fceaSAndreas Gohr    # ~
1136*7017fceaSAndreas Gohr    #
1137*7017fceaSAndreas Gohr
1138*7017fceaSAndreas Gohr    protected function inlineCode($Excerpt)
1139*7017fceaSAndreas Gohr    {
1140*7017fceaSAndreas Gohr        $marker = $Excerpt['text'][0];
1141*7017fceaSAndreas Gohr
1142*7017fceaSAndreas Gohr        if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
1143*7017fceaSAndreas Gohr        {
1144*7017fceaSAndreas Gohr            $text = $matches[2];
1145*7017fceaSAndreas Gohr            $text = preg_replace("/[ ]*\n/", ' ', $text);
1146*7017fceaSAndreas Gohr
1147*7017fceaSAndreas Gohr            return array(
1148*7017fceaSAndreas Gohr                'extent' => strlen($matches[0]),
1149*7017fceaSAndreas Gohr                'element' => array(
1150*7017fceaSAndreas Gohr                    'name' => 'code',
1151*7017fceaSAndreas Gohr                    'text' => $text,
1152*7017fceaSAndreas Gohr                ),
1153*7017fceaSAndreas Gohr            );
1154*7017fceaSAndreas Gohr        }
1155*7017fceaSAndreas Gohr    }
1156*7017fceaSAndreas Gohr
1157*7017fceaSAndreas Gohr    protected function inlineEmailTag($Excerpt)
1158*7017fceaSAndreas Gohr    {
1159*7017fceaSAndreas Gohr        if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
1160*7017fceaSAndreas Gohr        {
1161*7017fceaSAndreas Gohr            $url = $matches[1];
1162*7017fceaSAndreas Gohr
1163*7017fceaSAndreas Gohr            if ( ! isset($matches[2]))
1164*7017fceaSAndreas Gohr            {
1165*7017fceaSAndreas Gohr                $url = 'mailto:' . $url;
1166*7017fceaSAndreas Gohr            }
1167*7017fceaSAndreas Gohr
1168*7017fceaSAndreas Gohr            return array(
1169*7017fceaSAndreas Gohr                'extent' => strlen($matches[0]),
1170*7017fceaSAndreas Gohr                'element' => array(
1171*7017fceaSAndreas Gohr                    'name' => 'a',
1172*7017fceaSAndreas Gohr                    'text' => $matches[1],
1173*7017fceaSAndreas Gohr                    'attributes' => array(
1174*7017fceaSAndreas Gohr                        'href' => $url,
1175*7017fceaSAndreas Gohr                    ),
1176*7017fceaSAndreas Gohr                ),
1177*7017fceaSAndreas Gohr            );
1178*7017fceaSAndreas Gohr        }
1179*7017fceaSAndreas Gohr    }
1180*7017fceaSAndreas Gohr
1181*7017fceaSAndreas Gohr    protected function inlineEmphasis($Excerpt)
1182*7017fceaSAndreas Gohr    {
1183*7017fceaSAndreas Gohr        if ( ! isset($Excerpt['text'][1]))
1184*7017fceaSAndreas Gohr        {
1185*7017fceaSAndreas Gohr            return;
1186*7017fceaSAndreas Gohr        }
1187*7017fceaSAndreas Gohr
1188*7017fceaSAndreas Gohr        $marker = $Excerpt['text'][0];
1189*7017fceaSAndreas Gohr
1190*7017fceaSAndreas Gohr        if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
1191*7017fceaSAndreas Gohr        {
1192*7017fceaSAndreas Gohr            $emphasis = 'strong';
1193*7017fceaSAndreas Gohr        }
1194*7017fceaSAndreas Gohr        elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
1195*7017fceaSAndreas Gohr        {
1196*7017fceaSAndreas Gohr            $emphasis = 'em';
1197*7017fceaSAndreas Gohr        }
1198*7017fceaSAndreas Gohr        else
1199*7017fceaSAndreas Gohr        {
1200*7017fceaSAndreas Gohr            return;
1201*7017fceaSAndreas Gohr        }
1202*7017fceaSAndreas Gohr
1203*7017fceaSAndreas Gohr        return array(
1204*7017fceaSAndreas Gohr            'extent' => strlen($matches[0]),
1205*7017fceaSAndreas Gohr            'element' => array(
1206*7017fceaSAndreas Gohr                'name' => $emphasis,
1207*7017fceaSAndreas Gohr                'handler' => 'line',
1208*7017fceaSAndreas Gohr                'text' => $matches[1],
1209*7017fceaSAndreas Gohr            ),
1210*7017fceaSAndreas Gohr        );
1211*7017fceaSAndreas Gohr    }
1212*7017fceaSAndreas Gohr
1213*7017fceaSAndreas Gohr    protected function inlineEscapeSequence($Excerpt)
1214*7017fceaSAndreas Gohr    {
1215*7017fceaSAndreas Gohr        if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
1216*7017fceaSAndreas Gohr        {
1217*7017fceaSAndreas Gohr            return array(
1218*7017fceaSAndreas Gohr                'markup' => $Excerpt['text'][1],
1219*7017fceaSAndreas Gohr                'extent' => 2,
1220*7017fceaSAndreas Gohr            );
1221*7017fceaSAndreas Gohr        }
1222*7017fceaSAndreas Gohr    }
1223*7017fceaSAndreas Gohr
1224*7017fceaSAndreas Gohr    protected function inlineImage($Excerpt)
1225*7017fceaSAndreas Gohr    {
1226*7017fceaSAndreas Gohr        if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
1227*7017fceaSAndreas Gohr        {
1228*7017fceaSAndreas Gohr            return;
1229*7017fceaSAndreas Gohr        }
1230*7017fceaSAndreas Gohr
1231*7017fceaSAndreas Gohr        $Excerpt['text']= substr($Excerpt['text'], 1);
1232*7017fceaSAndreas Gohr
1233*7017fceaSAndreas Gohr        $Link = $this->inlineLink($Excerpt);
1234*7017fceaSAndreas Gohr
1235*7017fceaSAndreas Gohr        if ($Link === null)
1236*7017fceaSAndreas Gohr        {
1237*7017fceaSAndreas Gohr            return;
1238*7017fceaSAndreas Gohr        }
1239*7017fceaSAndreas Gohr
1240*7017fceaSAndreas Gohr        $Inline = array(
1241*7017fceaSAndreas Gohr            'extent' => $Link['extent'] + 1,
1242*7017fceaSAndreas Gohr            'element' => array(
1243*7017fceaSAndreas Gohr                'name' => 'img',
1244*7017fceaSAndreas Gohr                'attributes' => array(
1245*7017fceaSAndreas Gohr                    'src' => $Link['element']['attributes']['href'],
1246*7017fceaSAndreas Gohr                    'alt' => $Link['element']['text'],
1247*7017fceaSAndreas Gohr                ),
1248*7017fceaSAndreas Gohr            ),
1249*7017fceaSAndreas Gohr        );
1250*7017fceaSAndreas Gohr
1251*7017fceaSAndreas Gohr        $Inline['element']['attributes'] += $Link['element']['attributes'];
1252*7017fceaSAndreas Gohr
1253*7017fceaSAndreas Gohr        unset($Inline['element']['attributes']['href']);
1254*7017fceaSAndreas Gohr
1255*7017fceaSAndreas Gohr        return $Inline;
1256*7017fceaSAndreas Gohr    }
1257*7017fceaSAndreas Gohr
1258*7017fceaSAndreas Gohr    protected function inlineLink($Excerpt)
1259*7017fceaSAndreas Gohr    {
1260*7017fceaSAndreas Gohr        $Element = array(
1261*7017fceaSAndreas Gohr            'name' => 'a',
1262*7017fceaSAndreas Gohr            'handler' => 'line',
1263*7017fceaSAndreas Gohr            'nonNestables' => array('Url', 'Link'),
1264*7017fceaSAndreas Gohr            'text' => null,
1265*7017fceaSAndreas Gohr            'attributes' => array(
1266*7017fceaSAndreas Gohr                'href' => null,
1267*7017fceaSAndreas Gohr                'title' => null,
1268*7017fceaSAndreas Gohr            ),
1269*7017fceaSAndreas Gohr        );
1270*7017fceaSAndreas Gohr
1271*7017fceaSAndreas Gohr        $extent = 0;
1272*7017fceaSAndreas Gohr
1273*7017fceaSAndreas Gohr        $remainder = $Excerpt['text'];
1274*7017fceaSAndreas Gohr
1275*7017fceaSAndreas Gohr        if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
1276*7017fceaSAndreas Gohr        {
1277*7017fceaSAndreas Gohr            $Element['text'] = $matches[1];
1278*7017fceaSAndreas Gohr
1279*7017fceaSAndreas Gohr            $extent += strlen($matches[0]);
1280*7017fceaSAndreas Gohr
1281*7017fceaSAndreas Gohr            $remainder = substr($remainder, $extent);
1282*7017fceaSAndreas Gohr        }
1283*7017fceaSAndreas Gohr        else
1284*7017fceaSAndreas Gohr        {
1285*7017fceaSAndreas Gohr            return;
1286*7017fceaSAndreas Gohr        }
1287*7017fceaSAndreas Gohr
1288*7017fceaSAndreas Gohr        if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
1289*7017fceaSAndreas Gohr        {
1290*7017fceaSAndreas Gohr            $Element['attributes']['href'] = $matches[1];
1291*7017fceaSAndreas Gohr
1292*7017fceaSAndreas Gohr            if (isset($matches[2]))
1293*7017fceaSAndreas Gohr            {
1294*7017fceaSAndreas Gohr                $Element['attributes']['title'] = substr($matches[2], 1, - 1);
1295*7017fceaSAndreas Gohr            }
1296*7017fceaSAndreas Gohr
1297*7017fceaSAndreas Gohr            $extent += strlen($matches[0]);
1298*7017fceaSAndreas Gohr        }
1299*7017fceaSAndreas Gohr        else
1300*7017fceaSAndreas Gohr        {
1301*7017fceaSAndreas Gohr            if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
1302*7017fceaSAndreas Gohr            {
1303*7017fceaSAndreas Gohr                $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
1304*7017fceaSAndreas Gohr                $definition = strtolower($definition);
1305*7017fceaSAndreas Gohr
1306*7017fceaSAndreas Gohr                $extent += strlen($matches[0]);
1307*7017fceaSAndreas Gohr            }
1308*7017fceaSAndreas Gohr            else
1309*7017fceaSAndreas Gohr            {
1310*7017fceaSAndreas Gohr                $definition = strtolower($Element['text']);
1311*7017fceaSAndreas Gohr            }
1312*7017fceaSAndreas Gohr
1313*7017fceaSAndreas Gohr            if ( ! isset($this->DefinitionData['Reference'][$definition]))
1314*7017fceaSAndreas Gohr            {
1315*7017fceaSAndreas Gohr                return;
1316*7017fceaSAndreas Gohr            }
1317*7017fceaSAndreas Gohr
1318*7017fceaSAndreas Gohr            $Definition = $this->DefinitionData['Reference'][$definition];
1319*7017fceaSAndreas Gohr
1320*7017fceaSAndreas Gohr            $Element['attributes']['href'] = $Definition['url'];
1321*7017fceaSAndreas Gohr            $Element['attributes']['title'] = $Definition['title'];
1322*7017fceaSAndreas Gohr        }
1323*7017fceaSAndreas Gohr
1324*7017fceaSAndreas Gohr        return array(
1325*7017fceaSAndreas Gohr            'extent' => $extent,
1326*7017fceaSAndreas Gohr            'element' => $Element,
1327*7017fceaSAndreas Gohr        );
1328*7017fceaSAndreas Gohr    }
1329*7017fceaSAndreas Gohr
1330*7017fceaSAndreas Gohr    protected function inlineMarkup($Excerpt)
1331*7017fceaSAndreas Gohr    {
1332*7017fceaSAndreas Gohr        if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
1333*7017fceaSAndreas Gohr        {
1334*7017fceaSAndreas Gohr            return;
1335*7017fceaSAndreas Gohr        }
1336*7017fceaSAndreas Gohr
1337*7017fceaSAndreas Gohr        if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches))
1338*7017fceaSAndreas Gohr        {
1339*7017fceaSAndreas Gohr            return array(
1340*7017fceaSAndreas Gohr                'markup' => $matches[0],
1341*7017fceaSAndreas Gohr                'extent' => strlen($matches[0]),
1342*7017fceaSAndreas Gohr            );
1343*7017fceaSAndreas Gohr        }
1344*7017fceaSAndreas Gohr
1345*7017fceaSAndreas Gohr        if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
1346*7017fceaSAndreas Gohr        {
1347*7017fceaSAndreas Gohr            return array(
1348*7017fceaSAndreas Gohr                'markup' => $matches[0],
1349*7017fceaSAndreas Gohr                'extent' => strlen($matches[0]),
1350*7017fceaSAndreas Gohr            );
1351*7017fceaSAndreas Gohr        }
1352*7017fceaSAndreas Gohr
1353*7017fceaSAndreas Gohr        if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
1354*7017fceaSAndreas Gohr        {
1355*7017fceaSAndreas Gohr            return array(
1356*7017fceaSAndreas Gohr                'markup' => $matches[0],
1357*7017fceaSAndreas Gohr                'extent' => strlen($matches[0]),
1358*7017fceaSAndreas Gohr            );
1359*7017fceaSAndreas Gohr        }
1360*7017fceaSAndreas Gohr    }
1361*7017fceaSAndreas Gohr
1362*7017fceaSAndreas Gohr    protected function inlineSpecialCharacter($Excerpt)
1363*7017fceaSAndreas Gohr    {
1364*7017fceaSAndreas Gohr        if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
1365*7017fceaSAndreas Gohr        {
1366*7017fceaSAndreas Gohr            return array(
1367*7017fceaSAndreas Gohr                'markup' => '&amp;',
1368*7017fceaSAndreas Gohr                'extent' => 1,
1369*7017fceaSAndreas Gohr            );
1370*7017fceaSAndreas Gohr        }
1371*7017fceaSAndreas Gohr
1372*7017fceaSAndreas Gohr        $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
1373*7017fceaSAndreas Gohr
1374*7017fceaSAndreas Gohr        if (isset($SpecialCharacter[$Excerpt['text'][0]]))
1375*7017fceaSAndreas Gohr        {
1376*7017fceaSAndreas Gohr            return array(
1377*7017fceaSAndreas Gohr                'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
1378*7017fceaSAndreas Gohr                'extent' => 1,
1379*7017fceaSAndreas Gohr            );
1380*7017fceaSAndreas Gohr        }
1381*7017fceaSAndreas Gohr    }
1382*7017fceaSAndreas Gohr
1383*7017fceaSAndreas Gohr    protected function inlineStrikethrough($Excerpt)
1384*7017fceaSAndreas Gohr    {
1385*7017fceaSAndreas Gohr        if ( ! isset($Excerpt['text'][1]))
1386*7017fceaSAndreas Gohr        {
1387*7017fceaSAndreas Gohr            return;
1388*7017fceaSAndreas Gohr        }
1389*7017fceaSAndreas Gohr
1390*7017fceaSAndreas Gohr        if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
1391*7017fceaSAndreas Gohr        {
1392*7017fceaSAndreas Gohr            return array(
1393*7017fceaSAndreas Gohr                'extent' => strlen($matches[0]),
1394*7017fceaSAndreas Gohr                'element' => array(
1395*7017fceaSAndreas Gohr                    'name' => 'del',
1396*7017fceaSAndreas Gohr                    'text' => $matches[1],
1397*7017fceaSAndreas Gohr                    'handler' => 'line',
1398*7017fceaSAndreas Gohr                ),
1399*7017fceaSAndreas Gohr            );
1400*7017fceaSAndreas Gohr        }
1401*7017fceaSAndreas Gohr    }
1402*7017fceaSAndreas Gohr
1403*7017fceaSAndreas Gohr    protected function inlineUrl($Excerpt)
1404*7017fceaSAndreas Gohr    {
1405*7017fceaSAndreas Gohr        if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
1406*7017fceaSAndreas Gohr        {
1407*7017fceaSAndreas Gohr            return;
1408*7017fceaSAndreas Gohr        }
1409*7017fceaSAndreas Gohr
1410*7017fceaSAndreas Gohr        if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
1411*7017fceaSAndreas Gohr        {
1412*7017fceaSAndreas Gohr            $url = $matches[0][0];
1413*7017fceaSAndreas Gohr
1414*7017fceaSAndreas Gohr            $Inline = array(
1415*7017fceaSAndreas Gohr                'extent' => strlen($matches[0][0]),
1416*7017fceaSAndreas Gohr                'position' => $matches[0][1],
1417*7017fceaSAndreas Gohr                'element' => array(
1418*7017fceaSAndreas Gohr                    'name' => 'a',
1419*7017fceaSAndreas Gohr                    'text' => $url,
1420*7017fceaSAndreas Gohr                    'attributes' => array(
1421*7017fceaSAndreas Gohr                        'href' => $url,
1422*7017fceaSAndreas Gohr                    ),
1423*7017fceaSAndreas Gohr                ),
1424*7017fceaSAndreas Gohr            );
1425*7017fceaSAndreas Gohr
1426*7017fceaSAndreas Gohr            return $Inline;
1427*7017fceaSAndreas Gohr        }
1428*7017fceaSAndreas Gohr    }
1429*7017fceaSAndreas Gohr
1430*7017fceaSAndreas Gohr    protected function inlineUrlTag($Excerpt)
1431*7017fceaSAndreas Gohr    {
1432*7017fceaSAndreas Gohr        if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
1433*7017fceaSAndreas Gohr        {
1434*7017fceaSAndreas Gohr            $url = $matches[1];
1435*7017fceaSAndreas Gohr
1436*7017fceaSAndreas Gohr            return array(
1437*7017fceaSAndreas Gohr                'extent' => strlen($matches[0]),
1438*7017fceaSAndreas Gohr                'element' => array(
1439*7017fceaSAndreas Gohr                    'name' => 'a',
1440*7017fceaSAndreas Gohr                    'text' => $url,
1441*7017fceaSAndreas Gohr                    'attributes' => array(
1442*7017fceaSAndreas Gohr                        'href' => $url,
1443*7017fceaSAndreas Gohr                    ),
1444*7017fceaSAndreas Gohr                ),
1445*7017fceaSAndreas Gohr            );
1446*7017fceaSAndreas Gohr        }
1447*7017fceaSAndreas Gohr    }
1448*7017fceaSAndreas Gohr
1449*7017fceaSAndreas Gohr    # ~
1450*7017fceaSAndreas Gohr
1451*7017fceaSAndreas Gohr    protected function unmarkedText($text)
1452*7017fceaSAndreas Gohr    {
1453*7017fceaSAndreas Gohr        if ($this->breaksEnabled)
1454*7017fceaSAndreas Gohr        {
1455*7017fceaSAndreas Gohr            $text = preg_replace('/[ ]*\n/', "<br />\n", $text);
1456*7017fceaSAndreas Gohr        }
1457*7017fceaSAndreas Gohr        else
1458*7017fceaSAndreas Gohr        {
1459*7017fceaSAndreas Gohr            $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
1460*7017fceaSAndreas Gohr            $text = str_replace(" \n", "\n", $text);
1461*7017fceaSAndreas Gohr        }
1462*7017fceaSAndreas Gohr
1463*7017fceaSAndreas Gohr        return $text;
1464*7017fceaSAndreas Gohr    }
1465*7017fceaSAndreas Gohr
1466*7017fceaSAndreas Gohr    #
1467*7017fceaSAndreas Gohr    # Handlers
1468*7017fceaSAndreas Gohr    #
1469*7017fceaSAndreas Gohr
1470*7017fceaSAndreas Gohr    protected function element(array $Element)
1471*7017fceaSAndreas Gohr    {
1472*7017fceaSAndreas Gohr        if ($this->safeMode)
1473*7017fceaSAndreas Gohr        {
1474*7017fceaSAndreas Gohr            $Element = $this->sanitiseElement($Element);
1475*7017fceaSAndreas Gohr        }
1476*7017fceaSAndreas Gohr
1477*7017fceaSAndreas Gohr        $markup = '<'.$Element['name'];
1478*7017fceaSAndreas Gohr
1479*7017fceaSAndreas Gohr        if (isset($Element['attributes']))
1480*7017fceaSAndreas Gohr        {
1481*7017fceaSAndreas Gohr            foreach ($Element['attributes'] as $name => $value)
1482*7017fceaSAndreas Gohr            {
1483*7017fceaSAndreas Gohr                if ($value === null)
1484*7017fceaSAndreas Gohr                {
1485*7017fceaSAndreas Gohr                    continue;
1486*7017fceaSAndreas Gohr                }
1487*7017fceaSAndreas Gohr
1488*7017fceaSAndreas Gohr                $markup .= ' '.$name.'="'.self::escape($value).'"';
1489*7017fceaSAndreas Gohr            }
1490*7017fceaSAndreas Gohr        }
1491*7017fceaSAndreas Gohr
1492*7017fceaSAndreas Gohr        $permitRawHtml = false;
1493*7017fceaSAndreas Gohr
1494*7017fceaSAndreas Gohr        if (isset($Element['text']))
1495*7017fceaSAndreas Gohr        {
1496*7017fceaSAndreas Gohr            $text = $Element['text'];
1497*7017fceaSAndreas Gohr        }
1498*7017fceaSAndreas Gohr        // very strongly consider an alternative if you're writing an
1499*7017fceaSAndreas Gohr        // extension
1500*7017fceaSAndreas Gohr        elseif (isset($Element['rawHtml']))
1501*7017fceaSAndreas Gohr        {
1502*7017fceaSAndreas Gohr            $text = $Element['rawHtml'];
1503*7017fceaSAndreas Gohr            $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode'];
1504*7017fceaSAndreas Gohr            $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;
1505*7017fceaSAndreas Gohr        }
1506*7017fceaSAndreas Gohr
1507*7017fceaSAndreas Gohr        if (isset($text))
1508*7017fceaSAndreas Gohr        {
1509*7017fceaSAndreas Gohr            $markup .= '>';
1510*7017fceaSAndreas Gohr
1511*7017fceaSAndreas Gohr            if (!isset($Element['nonNestables']))
1512*7017fceaSAndreas Gohr            {
1513*7017fceaSAndreas Gohr                $Element['nonNestables'] = array();
1514*7017fceaSAndreas Gohr            }
1515*7017fceaSAndreas Gohr
1516*7017fceaSAndreas Gohr            if (isset($Element['handler']))
1517*7017fceaSAndreas Gohr            {
1518*7017fceaSAndreas Gohr                $markup .= $this->{$Element['handler']}($text, $Element['nonNestables']);
1519*7017fceaSAndreas Gohr            }
1520*7017fceaSAndreas Gohr            elseif (!$permitRawHtml)
1521*7017fceaSAndreas Gohr            {
1522*7017fceaSAndreas Gohr                $markup .= self::escape($text, true);
1523*7017fceaSAndreas Gohr            }
1524*7017fceaSAndreas Gohr            else
1525*7017fceaSAndreas Gohr            {
1526*7017fceaSAndreas Gohr                $markup .= $text;
1527*7017fceaSAndreas Gohr            }
1528*7017fceaSAndreas Gohr
1529*7017fceaSAndreas Gohr            $markup .= '</'.$Element['name'].'>';
1530*7017fceaSAndreas Gohr        }
1531*7017fceaSAndreas Gohr        else
1532*7017fceaSAndreas Gohr        {
1533*7017fceaSAndreas Gohr            $markup .= ' />';
1534*7017fceaSAndreas Gohr        }
1535*7017fceaSAndreas Gohr
1536*7017fceaSAndreas Gohr        return $markup;
1537*7017fceaSAndreas Gohr    }
1538*7017fceaSAndreas Gohr
1539*7017fceaSAndreas Gohr    protected function elements(array $Elements)
1540*7017fceaSAndreas Gohr    {
1541*7017fceaSAndreas Gohr        $markup = '';
1542*7017fceaSAndreas Gohr
1543*7017fceaSAndreas Gohr        foreach ($Elements as $Element)
1544*7017fceaSAndreas Gohr        {
1545*7017fceaSAndreas Gohr            $markup .= "\n" . $this->element($Element);
1546*7017fceaSAndreas Gohr        }
1547*7017fceaSAndreas Gohr
1548*7017fceaSAndreas Gohr        $markup .= "\n";
1549*7017fceaSAndreas Gohr
1550*7017fceaSAndreas Gohr        return $markup;
1551*7017fceaSAndreas Gohr    }
1552*7017fceaSAndreas Gohr
1553*7017fceaSAndreas Gohr    # ~
1554*7017fceaSAndreas Gohr
1555*7017fceaSAndreas Gohr    protected function li($lines)
1556*7017fceaSAndreas Gohr    {
1557*7017fceaSAndreas Gohr        $markup = $this->lines($lines);
1558*7017fceaSAndreas Gohr
1559*7017fceaSAndreas Gohr        $trimmedMarkup = trim($markup);
1560*7017fceaSAndreas Gohr
1561*7017fceaSAndreas Gohr        if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>')
1562*7017fceaSAndreas Gohr        {
1563*7017fceaSAndreas Gohr            $markup = $trimmedMarkup;
1564*7017fceaSAndreas Gohr            $markup = substr($markup, 3);
1565*7017fceaSAndreas Gohr
1566*7017fceaSAndreas Gohr            $position = strpos($markup, "</p>");
1567*7017fceaSAndreas Gohr
1568*7017fceaSAndreas Gohr            $markup = substr_replace($markup, '', $position, 4);
1569*7017fceaSAndreas Gohr        }
1570*7017fceaSAndreas Gohr
1571*7017fceaSAndreas Gohr        return $markup;
1572*7017fceaSAndreas Gohr    }
1573*7017fceaSAndreas Gohr
1574*7017fceaSAndreas Gohr    #
1575*7017fceaSAndreas Gohr    # Deprecated Methods
1576*7017fceaSAndreas Gohr    #
1577*7017fceaSAndreas Gohr
1578*7017fceaSAndreas Gohr    function parse($text)
1579*7017fceaSAndreas Gohr    {
1580*7017fceaSAndreas Gohr        $markup = $this->text($text);
1581*7017fceaSAndreas Gohr
1582*7017fceaSAndreas Gohr        return $markup;
1583*7017fceaSAndreas Gohr    }
1584*7017fceaSAndreas Gohr
1585*7017fceaSAndreas Gohr    protected function sanitiseElement(array $Element)
1586*7017fceaSAndreas Gohr    {
1587*7017fceaSAndreas Gohr        static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
1588*7017fceaSAndreas Gohr        static $safeUrlNameToAtt  = array(
1589*7017fceaSAndreas Gohr            'a'   => 'href',
1590*7017fceaSAndreas Gohr            'img' => 'src',
1591*7017fceaSAndreas Gohr        );
1592*7017fceaSAndreas Gohr
1593*7017fceaSAndreas Gohr        if (isset($safeUrlNameToAtt[$Element['name']]))
1594*7017fceaSAndreas Gohr        {
1595*7017fceaSAndreas Gohr            $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
1596*7017fceaSAndreas Gohr        }
1597*7017fceaSAndreas Gohr
1598*7017fceaSAndreas Gohr        if ( ! empty($Element['attributes']))
1599*7017fceaSAndreas Gohr        {
1600*7017fceaSAndreas Gohr            foreach ($Element['attributes'] as $att => $val)
1601*7017fceaSAndreas Gohr            {
1602*7017fceaSAndreas Gohr                # filter out badly parsed attribute
1603*7017fceaSAndreas Gohr                if ( ! preg_match($goodAttribute, $att))
1604*7017fceaSAndreas Gohr                {
1605*7017fceaSAndreas Gohr                    unset($Element['attributes'][$att]);
1606*7017fceaSAndreas Gohr                }
1607*7017fceaSAndreas Gohr                # dump onevent attribute
1608*7017fceaSAndreas Gohr                elseif (self::striAtStart($att, 'on'))
1609*7017fceaSAndreas Gohr                {
1610*7017fceaSAndreas Gohr                    unset($Element['attributes'][$att]);
1611*7017fceaSAndreas Gohr                }
1612*7017fceaSAndreas Gohr            }
1613*7017fceaSAndreas Gohr        }
1614*7017fceaSAndreas Gohr
1615*7017fceaSAndreas Gohr        return $Element;
1616*7017fceaSAndreas Gohr    }
1617*7017fceaSAndreas Gohr
1618*7017fceaSAndreas Gohr    protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
1619*7017fceaSAndreas Gohr    {
1620*7017fceaSAndreas Gohr        foreach ($this->safeLinksWhitelist as $scheme)
1621*7017fceaSAndreas Gohr        {
1622*7017fceaSAndreas Gohr            if (self::striAtStart($Element['attributes'][$attribute], $scheme))
1623*7017fceaSAndreas Gohr            {
1624*7017fceaSAndreas Gohr                return $Element;
1625*7017fceaSAndreas Gohr            }
1626*7017fceaSAndreas Gohr        }
1627*7017fceaSAndreas Gohr
1628*7017fceaSAndreas Gohr        $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
1629*7017fceaSAndreas Gohr
1630*7017fceaSAndreas Gohr        return $Element;
1631*7017fceaSAndreas Gohr    }
1632*7017fceaSAndreas Gohr
1633*7017fceaSAndreas Gohr    #
1634*7017fceaSAndreas Gohr    # Static Methods
1635*7017fceaSAndreas Gohr    #
1636*7017fceaSAndreas Gohr
1637*7017fceaSAndreas Gohr    protected static function escape($text, $allowQuotes = false)
1638*7017fceaSAndreas Gohr    {
1639*7017fceaSAndreas Gohr        return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
1640*7017fceaSAndreas Gohr    }
1641*7017fceaSAndreas Gohr
1642*7017fceaSAndreas Gohr    protected static function striAtStart($string, $needle)
1643*7017fceaSAndreas Gohr    {
1644*7017fceaSAndreas Gohr        $len = strlen($needle);
1645*7017fceaSAndreas Gohr
1646*7017fceaSAndreas Gohr        if ($len > strlen($string))
1647*7017fceaSAndreas Gohr        {
1648*7017fceaSAndreas Gohr            return false;
1649*7017fceaSAndreas Gohr        }
1650*7017fceaSAndreas Gohr        else
1651*7017fceaSAndreas Gohr        {
1652*7017fceaSAndreas Gohr            return strtolower(substr($string, 0, $len)) === strtolower($needle);
1653*7017fceaSAndreas Gohr        }
1654*7017fceaSAndreas Gohr    }
1655*7017fceaSAndreas Gohr
1656*7017fceaSAndreas Gohr    static function instance($name = 'default')
1657*7017fceaSAndreas Gohr    {
1658*7017fceaSAndreas Gohr        if (isset(self::$instances[$name]))
1659*7017fceaSAndreas Gohr        {
1660*7017fceaSAndreas Gohr            return self::$instances[$name];
1661*7017fceaSAndreas Gohr        }
1662*7017fceaSAndreas Gohr
1663*7017fceaSAndreas Gohr        $instance = new static();
1664*7017fceaSAndreas Gohr
1665*7017fceaSAndreas Gohr        self::$instances[$name] = $instance;
1666*7017fceaSAndreas Gohr
1667*7017fceaSAndreas Gohr        return $instance;
1668*7017fceaSAndreas Gohr    }
1669*7017fceaSAndreas Gohr
1670*7017fceaSAndreas Gohr    private static $instances = array();
1671*7017fceaSAndreas Gohr
1672*7017fceaSAndreas Gohr    #
1673*7017fceaSAndreas Gohr    # Fields
1674*7017fceaSAndreas Gohr    #
1675*7017fceaSAndreas Gohr
1676*7017fceaSAndreas Gohr    protected $DefinitionData;
1677*7017fceaSAndreas Gohr
1678*7017fceaSAndreas Gohr    #
1679*7017fceaSAndreas Gohr    # Read-Only
1680*7017fceaSAndreas Gohr
1681*7017fceaSAndreas Gohr    protected $specialCharacters = array(
1682*7017fceaSAndreas Gohr        '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
1683*7017fceaSAndreas Gohr    );
1684*7017fceaSAndreas Gohr
1685*7017fceaSAndreas Gohr    protected $StrongRegex = array(
1686*7017fceaSAndreas Gohr        '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
1687*7017fceaSAndreas Gohr        '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
1688*7017fceaSAndreas Gohr    );
1689*7017fceaSAndreas Gohr
1690*7017fceaSAndreas Gohr    protected $EmRegex = array(
1691*7017fceaSAndreas Gohr        '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
1692*7017fceaSAndreas Gohr        '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
1693*7017fceaSAndreas Gohr    );
1694*7017fceaSAndreas Gohr
1695*7017fceaSAndreas Gohr    protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
1696*7017fceaSAndreas Gohr
1697*7017fceaSAndreas Gohr    protected $voidElements = array(
1698*7017fceaSAndreas Gohr        'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
1699*7017fceaSAndreas Gohr    );
1700*7017fceaSAndreas Gohr
1701*7017fceaSAndreas Gohr    protected $textLevelElements = array(
1702*7017fceaSAndreas Gohr        'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
1703*7017fceaSAndreas Gohr        'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
1704*7017fceaSAndreas Gohr        'i', 'rp', 'del', 'code',          'strike', 'marquee',
1705*7017fceaSAndreas Gohr        'q', 'rt', 'ins', 'font',          'strong',
1706*7017fceaSAndreas Gohr        's', 'tt', 'kbd', 'mark',
1707*7017fceaSAndreas Gohr        'u', 'xm', 'sub', 'nobr',
1708*7017fceaSAndreas Gohr                   'sup', 'ruby',
1709*7017fceaSAndreas Gohr                   'var', 'span',
1710*7017fceaSAndreas Gohr                   'wbr', 'time',
1711*7017fceaSAndreas Gohr    );
1712*7017fceaSAndreas Gohr}
1713