1 <?php
2
3 4 5 6 7 8 9 10
11
12 use Patron\Hook;
13
14 @define('MARKDOWN_PARSER_CLASS', 'Textmark_Parser');
15
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
32 @define('txt_apostrophe', '\'');
33 @define('txt_quote_double_open', '« ');
34 @define('txt_quote_double_close', ' »');
35 @define('txt_prime', 'ʹ');
36 @define('txt_prime_double', 'ʺ');
37 @define('txt_ellipsis', '…');
38 @define('txt_emdash', '—');
39 @define('txt_endash', '–');
40 @define('txt_dimension', '×');
41 @define('txt_trademark', '™');
42 @define('txt_registered', '®');
43 @define('txt_copyright', '©');
44
45 require_once 'markdown/markdown.php';
46 require_once 'markdown/markdown_extras.php';
47
48 class Textmark_Parser extends MarkdownExtra_Parser
49 {
50 const NBSP = "\xC2\xA0";
51 const NBSP_TAB = "\xC2\xA0\xC2\xA0\xC2\xA0\xC2\xA0";
52
53 static public function parse($str)
54 {
55 return Markdown((string) $str);
56 }
57
58 function Textmark_Parser()
59 {
60 $this->early_gamut += array
61 (
62 'doShell' => 5
63 );
64
65 $this->span_gamut += array
66 (
67 'doFlash' => 9,
68 'doGlyphs' => 70,
69 'doSpan' => 71
70 );
71
72 parent::MarkdownExtra_Parser();
73 }
74
75 public function doShell($text)
76 {
77 return preg_replace_callback
78 (
79 '{^\$\s+([^\n]+)\n}xm', array($this, 'doShell_callback'), $text
80 );
81
82 return $text;
83 }
84
85 protected function doShell_callback($matches)
86 {
87 $text = '<pre class="markdown shell">$ ' . $matches[1] . '</pre>' . PHP_EOL;
88
89 return $this->hashBlock($text);
90 }
91
92 93 94 95 96 97 98
99
100 function hashHTMLBlocks($text)
101 {
102 103 104 105 106 107
108
109 return parent::hashHTMLBlocks($text);
110 }
111
112 function _doSourceCode($matches)
113 {
114
115
116 return $this->hashBlock($this->doSourceCode($matches[1], $matches[2]));
117 }
118
119 function doSourceCode($type, $text)
120 {
121 $text = trim($text);
122
123 switch ($type)
124 {
125 case 'php': return '<pre class="php"><code>' . $this->doSourcePHP($text) . '</code></pre>';
126 case 'html': return '<pre class="html"><code>' . $this->doSourceHTML($text) . '</code></pre>';
127 case 'raw': return '<pre><code>' . $this->doSourceRaw($text) . '</code></pre>';
128 case 'publish': return $this->doSourcePublish($text);
129 }
130
131 \ICanBoogie\log_error('\1: unknown source type "\1"', __FUNCTION__, $type);
132
133 return $text;
134 }
135
136 private function doSourcePublish($text)
137 {
138 return Patron($text);
139 }
140
141 142 143 144 145 146 147
148
149 function ($text)
150 {
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
172
173 $text = parent::doHeaders($text);
174
175 176 177 178 179 180 181 182 183 184
185
186 $text = preg_replace_callback
187 (
188 '{
189 ^h([1-6])\. # $1 = string of h?
190 [ ]*
191 (.+?) # $2 = Header text
192 [ ]*
193 \n+
194 }xm',
195
196 array(&$this, '_doHeaders_callback_tp'), $text
197 );
198
199 return $text;
200 }
201
202 function ($matches)
203 {
204 switch ($matches[3]{0})
205 {
206 case '#': $level = 1; break;
207 case '=': $level = 2; break;
208 case '-': $level = 3; break;
209 }
210
211 $block = "<h$level>" . $this->runSpanGamut($matches[1]) . "</h$level>";
212
213 return "\n" . $this->hashBlock($block) . "\n\n";
214 }
215
216 function ($matches)
217 {
218
219
220 $level = $matches[1];
221 $block = "<h$level>" . $this->runSpanGamut($matches[2]) . "</h$level>";
222 return "\n" . $this->hashBlock($block) . "\n\n";
223 }
224
225 226 227 228 229 230 231
232
233 function doSpan($text)
234 {
235 $clas = "(?:\([^)]+\))";
236 $styl = "(?:\{[^}]+\})";
237 $lnge = "(?:\[[^]]+\])";
238 $hlgn = "(?:\<(?!>)|(?<!<)\>|\<\>|\=|[()]+(?! ))";
239
240 $c = "(?:{$clas}|{$styl}|{$lnge}|{$hlgn})*";
241
242 $qtags = array('\*\*','\*','\?\?','-','__','_','%','\+','~','\^');
243 $pnct = ".,\"'?!;:";
244
245 foreach($qtags as $f) {
246 $text = preg_replace_callback("/
247 (^|(?<=[\s>$pnct\(])|[{[])
248 ($f)(?!$f)
249 (" . $c . ")
250 (?::(\S+))?
251 ([^\s$f]+|\S.*?[^\s$f\n])
252 ([$pnct]*)
253 $f
254 ($|[\]}]|(?=[[:punct:]]{1,2}|\s|\)))
255 /x", array(&$this, "fSpan"), $text);
256 }
257 return $text;
258 }
259
260 function fSpan($m)
261 {
262 $qtags = array(
263 '*' => 'strong',
264 '**' => 'b',
265 '??' => 'cite',
266 '_' => 'em',
267 '__' => 'i',
268 '-' => 'del',
269 '%' => 'span',
270 '+' => 'ins',
271 '~' => 'sub',
272 '^' => 'sup'
273 );
274
275 list(, $pre, $tag, $atts, $cite, $content, $end, $tail) = $m;
276 $tag = $qtags[$tag];
277
278
279
280 $out = "<$tag$atts>$content$end</$tag>";
281
282 if (($pre and !$tail) or ($tail and !$pre))
283 {
284 $out = $pre.$out.$tail;
285 }
286
287
288
289 return $out;
290
291 }
292
293 function doGlyphs($text)
294 {
295
296
297 $glyph_search = array
298 (
299
300 '/(\s)\'(\d+\w?)\b(?!\')/u',
301
302
303 '/(\S)\"(?=\s|[[:punct:]]|<|$)/u',
304 '/"/',
305 '/\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/u',
306 '/(?<=\s|^|[>(;-])([A-Z]{3,})([a-z]*)(?=\s|[[:punct:]]|<|$)/u',
307 '/([^.]?)\.{3}/u',
308 '/(\s?)--(\s?)/u',
309 '/\s-(?:\s|$)/u',
310 '/(\d+)( ?)x( ?)(?=\d+)/u',
311 '/(\b ?|\s|^)[([]TM[])]/iu',
312 '/(\b ?|\s|^)[([]R[])]/iu',
313 '/(\b ?|\s|^)[([]C[])]/iu',
314
315
316
317
318
319 '#\s(\!|\?|\:|\;|\-)#u',
320 );
321
322 $glyph_replace = array
323 (
324
325 '$1'.txt_apostrophe.'$2',
326
327
328 '$1'.txt_quote_double_close,
329 txt_quote_double_open,
330 '<acronym title="$2">$1</acronym>',
331 '<span class="caps">$1</span>$2',
332 '$1'.txt_ellipsis,
333 '$1'.txt_emdash.'$2',
334 ' '.txt_endash.' ',
335 '$1$2'.txt_dimension.'$3',
336 '$1'.txt_trademark,
337 '$1'.txt_registered,
338 '$1'.txt_copyright,
339
340
341
342
343
344 self::NBSP . '$1',
345 );
346
347 return preg_replace($glyph_search, $glyph_replace, $text);
348 }
349
350 351 352 353 354 355 356
357
358 function formParagraphs($text)
359 {
360
361
362
363
364
365
366 $text = preg_replace('/\A\n+|\n+\z/', '', $text);
367 $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
368
369
370
371
372 foreach ($grafs as $key => $value)
373 {
374
375
376
377
378
379
380 $style = NULL;
381
382 if (preg_match
383 (
384 '{
385 ( # the whole thing is saved in $1
386 ^p # start
387 (\<|\||\>|\=)? # alignement $2
388 \. # separator
389 [ ]+ # white space, at least one is mandatory
390 )
391 }sx', $value, $matches
392 ))
393 {
394 $value = substr($value, strlen($matches[1]));
395 $align = $matches[2];
396
397 if ($align)
398 {
399 switch ($align)
400 {
401 case '<': $align = 'left'; break;
402 case '|': $align = 'center'; break;
403 case '>': $align = 'right'; break;
404 case '=': $align = 'justify'; break;
405 }
406
407 $style .= "text-align: $align;";
408 }
409 }
410
411
412
413
414
415
416
417
418 $value = trim($this->runSpanGamut($value));
419
420
421
422
423 $is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value);
424
425 if ($is_p)
426 {
427 if ($style)
428 {
429 $value = '<p style="' . $style . '">' . $value . '</p>';
430 }
431 else
432 {
433 $value = "<p>$value</p>";
434 }
435 }
436
437 $grafs[$key] = $value;
438 }
439
440
441 $text = implode("\n\n", $grafs);
442
443
444 $text = $this->unhash($text);
445
446 return $text;
447 }
448
449 450 451 452 453 454 455
456
457 function _doItalicAndBold_em_callback($matches)
458 {
459 $text = $matches[2];
460 $text = $this->runSpanGamut($text);
461
462 $tag = ($matches[1] == '*') ? 'strong' : 'em';
463
464 return $this->hashPart("<$tag>$text</$tag>");
465 }
466
467 function _doItalicAndBold_strong_callback($matches)
468 {
469 $text = $matches[2];
470 $text = $this->runSpanGamut($text);
471
472 $tag = ($matches[1] == '**') ? 'b' : 'i';
473
474 return $this->hashPart("<$tag>$text</$tag>");
475 }
476
477 478 479 480 481 482 483
484
485 function doFlash($text)
486 {
487
488
489
490
491
492
493
494
495
496 $text = preg_replace_callback('{
497 ( # wrap whole match in $1
498 @
499 (\<|\||\>|\=)? # alignment = $2
500 \[
501 ('.$this->nested_brackets_re.') # title = $3
502 \]
503 \s? # One optional whitespace character
504 \( # literal paren
505 [ ]*
506 (?:
507 <(\S*)> # src url = $4
508 |
509 ('.$this->nested_url_parenthesis_re.') # src url = $5
510 )
511 [ ]*
512 ( # $6
513 ([\'"]) # quote char = $7
514 (.*?) # title = $8
515 \6 # matching quote
516 [ ]*
517 )? # title is optional
518 \)
519 )
520 }xs',
521 array(&$this, '_doFlash_inline_callback'), $text);
522
523 return $text;
524 }
525
526 function _doFlash_inline_callback($matches)
527 {
528
529
530 $whole_match = $matches[1];
531 $align = $matches[2];
532 $alt_text = $matches[3];
533 $url = empty($matches[4]) ? $matches[5] : $matches[4];
534 $title =& $matches[8];
535
536 $parts = parse_url($url);
537
538
539
540 $data = NULL;
541 $width = 0;
542 $height = 0;
543
544 preg_match('#www\.([^\.]+)#', $parts['host'], $matches);
545
546
547
548 switch ($matches['1'])
549 {
550 case 'youtube':
551 preg_match('#v=([^\&]+)#', $parts['query'], $matches);
552
553 $data = 'http://www.youtube.com/v/' . $matches[1] . '&fs=1&rel=1&border=0';
554 $width = 420;
555 $height = 360;
556
557 break;
558
559 case 'dailymotion':
560 preg_match('#video\/([^_]+)#', $parts['path'], $matches);
561
562
563
564 $data = 'http://www.dailymotion.com/swf/' . $matches[1];
565 $width = 420;
566 $height = 360;
567
568 break;
569 }
570
571 $rc = '<object width="' . $width . '" height="' . $height . '"';
572 $rc .= ' type="application/x-shockwave-flash"';
573
574 if ($align)
575 {
576 switch ($align)
577 {
578 case '<': $align = 'left'; break;
579 case '=':
580 case '|': $align = 'middle'; break;
581 case '>': $align = 'right'; break;
582 }
583
584 $rc .= ' align="' . $align . '"';
585 }
586
587 $rc .= ' data="' . $data . '">';
588 $rc .= '<param name="wmode" value="transparent" />';
589 $rc .= '<param name="movie" value="' . $data . '" />';
590 $rc .= '<param name="allowfullscreen" value="true" />';
591 $rc .= '<param name="allowscriptaccess" value="always" />';
592 $rc .= '</object>';
593
594 if ($alt_text)
595 {
596 $rc .= '<br />';
597 $rc .= '<a href="' . $url . '">' . \ICanBoogie\escape($alt_text) . '</a>';
598 }
599
600 return $this->hashPart($rc);
601 }
602
603 604 605 606 607 608 609
610 function doImages($text)
611 {
612
613
614
615
616
617
618
619
620
621
622
623
624 $text = preg_replace_callback
625 (
626 '{
627 ( # wrap whole match in $1
628 ! # start
629 (\<|\||\>|\=)? # alignment = $2
630 \[
631 ('.$this->nested_brackets_re.') # alt text = $3
632 \]
633
634 [ ]? # one optional space
635 (?:\n[ ]*)? # one optional newline followed by spaces
636
637 \[
638 (.*?) # id = $4
639 \]
640
641 )
642 }xs', array(&$this, '_doImages_reference_callback'), $text
643 );
644
645
646
647
648
649 $text = preg_replace_callback('{
650 ( # wrap whole match in $1
651 !
652 (\<|\||\>|\=)? # alignment = $2
653 \[
654 ('.$this->nested_brackets_re.') # alt text = $3
655 \]
656 \s? # One optional whitespace character
657 \( # literal paren
658 [ ]*
659 (?:
660 <(\S*)> # src url = $4
661 |
662 ('.$this->nested_url_parenthesis_re.') # src url = $5
663 )
664 [ ]*
665 ( # $6
666 ([\'"]) # quote char = $7
667 (.*?) # title = $8
668 \6 # matching quote
669 [ ]*
670 )? # title is optional
671 \)
672 )
673 }xs',
674 array(&$this, '_doImages_inline_callback'), $text);
675
676 return $text;
677 }
678
679 static protected $images_reference_callback;
680
681 function _doImages_reference_callback($matches)
682 {
683 if (self::$images_reference_callback === null)
684 {
685 $hook = false;
686
687 try
688 {
689 $hook = Hook::find('textmark', 'images.reference');
690
691 if (!$hook)
692 {
693 $hook = false;
694 }
695 }
696 catch (Exception $e) {}
697
698 self::$images_reference_callback = $hook;
699 }
700
701 if (self::$images_reference_callback !== false)
702 {
703 return $this->hashPart(self::$images_reference_callback->__invoke(array(), $this, $matches));
704 }
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729 static $module;
730
731 if (!$module)
732 {
733 global $core;
734
735 $module = $core->modules['images'];
736 }
737
738
739
740 $align = $matches[2];
741 $alt = $matches[3];
742 $id = $matches[4];
743
744
745
746 if (!$id)
747 {
748 $id = $alt;
749 }
750
751 $parts = explode(':', $id, 2);
752
753 if (isset($parts[1]))
754 {
755 $entry = $module->model()->loadRange
756 (
757 0, 1, 'WHERE `' . $module->getConstant('TITLE') . '` = ? AND `' . $module->getConstant('ALBUM') . '` = ?', array
758 (
759 $parts[1], $parts[0]
760 )
761 )
762 ->fetchAndClose();
763
764 }
765 else
766 {
767 $entry = $module->model()->loadRange
768 (
769 0, 1, 'WHERE `slug` = ? OR `title` = ?', array
770 (
771 $id, $id
772 )
773 )
774 ->fetchAndClose();
775 }
776
777 if (!$entry)
778 {
779 $matches[2] = $matches[3];
780 $matches[3] = $matches[4];
781
782 return parent::_doImages_reference_callback($matches);
783 }
784
785 $params = array
786 (
787 'src' => $entry->path,
788 'alt' => \ICanBoogie\escape($alt),
789 'width' => $entry->width,
790 'height' => $entry->height
791 );
792
793 if ($align)
794 {
795 switch ($align)
796 {
797 case '<': $align = 'left'; break;
798 case '=':
799 case '|': $align = 'middle'; break;
800 case '>': $align = 'right'; break;
801 }
802
803 $params['align'] = $align;
804 }
805
806
807
808 return $this->hashPart($this->createElement('img', $params));
809 }
810
811 function _doImages_inline_callback($matches)
812 {
813
814
815 $whole_match = $matches[1];
816 $align = $matches[2];
817 $alt_text = $matches[3];
818 $url = $matches[4] == '' ? $matches[5] : $matches[4];
819 $title =& $matches[8];
820
821 $alt_text = str_replace('"', '"', $alt_text);
822 $result = "<img src=\"$url\" alt=\"$alt_text\"";
823
824 if ($align)
825 {
826 switch ($align)
827 {
828 case '<': $align = 'left'; break;
829 case '=':
830 case '|': $align = 'middle'; break;
831 case '>': $align = 'right'; break;
832 }
833
834 $result .= ' align="' . $align . '"';
835 }
836
837 if (isset($title))
838 {
839 $title = str_replace('"', '"', $title);
840 $result .= " title=\"$title\"";
841 }
842
843 $result .= $this->empty_element_suffix;
844
845 return $this->hashPart($result);
846 }
847
848 849 850 851 852 853 854
855
856 function createElement($markup, $attrs, $body=NULL)
857 {
858 $rc = array();
859
860 foreach ($attrs as $name => $value)
861 {
862 $rc[] = $name . '="' . $value . '"';
863 }
864
865 return "<$markup " . implode(' ', $rc) . ($body ? ">$body</$markup>" : " />");
866 }
867
868 protected function format_codeblock($codeblock, $type)
869 {
870 switch ($type)
871 {
872 case 'html': return $this->doSourceHTML($codeblock);
873 case 'php': return $this->doSourcePHP($codeblock);
874 default: return $this->doSourceRaw($codeblock);
875 }
876 }
877
878
879
880
881
882 function doSourceRaw($text)
883 {
884 $text = \ICanBoogie\escape($text);
885 $text = str_replace("\t", self::NBSP_TAB, $text);
886 $text = str_replace(" ", self::NBSP, $text);
887
888 return $text;
889 }
890
891
892
893
894
895 protected function doSourceHTML($text)
896 {
897
898
899 $text = trim($text);
900
901
902
903
904
905 $text = preg_replace_callback
906 (
907 '#(\<\!?[^\s^\>]+)(\s+[^\>]+)?(\/?\>)#m', array($this, '_do_html_markup'), $text
908 );
909
910
911
912
913
914
915
916
917
918 $text = preg_replace_callback
919 (
920 '#\<\/[a-zA-Z]+\>#m', array($this, '_do_html_markup_close'), $text
921 );
922
923
924
925
926
927
928
929 $text = \ICanBoogie\escape($text);
930 $text = str_replace("\t", self::NBSP_TAB, $text);
931 $text = str_replace(" ", self::NBSP, $text);
932
933 return $text;
934 }
935
936 function _do_html_string($matches)
937 {
938
939
940 return $this->hashPart('<span class="string">' . \ICanBoogie\escape($matches[0]) . '</span>');
941 }
942
943 function _do_html_attribute($matches)
944 {
945
946
947 return $this->hashPart('<span class="attribute">' . \ICanBoogie\escape($matches[0]) . '</span>');
948 }
949
950 function _do_html_markup($matches)
951 {
952
953
954 $text = $matches[2];
955
956
957
958
959
960 $text = preg_replace_callback
961 (
962 '#\"[^\"]+\"#', array(&$this, '_do_html_string'), $text
963 );
964
965
966
967
968
969
970
971 $text = preg_replace_callback
972 (
973 '#[^\s\=]+#', array(&$this, '_do_html_attribute'), $text
974 );
975
976 $rc = $this->hashPart('<span class="markup">' . \ICanBoogie\escape($matches[1]) . '</span>');
977 $rc .= $text;
978 $rc .= $this->hashpart('<span class="markup">' . \ICanBoogie\escape($matches[3]) . '</span>');
979
980 return $rc;
981 }
982
983 function _do_html_markup_close($matches)
984 {
985
986
987 return $this->hashPart('<span class="markup">' . \ICanBoogie\escape($matches[0]) . '</span>');
988 }
989
990
991
992
993
994 function ($text, $marker)
995 {
996 $lines = explode("\n", $text);
997
998
999
1000 $marker_len = strlen($marker);
1001
1002 foreach ($lines as &$line)
1003 {
1004 $in_quotes = NULL;
1005
1006 for ($i = 0 ; $i < strlen($line) ; $i++)
1007 {
1008 $c = $line{$i};
1009
1010 if (($c == $in_quotes) && (($i > 1) && ($line{$i - 1} != '\\')))
1011 {
1012 $in_quotes = NULL;
1013 }
1014 else if ($in_quotes)
1015 {
1016 continue;
1017 }
1018 else if (($c == '\'') || ($c == '"'))
1019 {
1020 $in_quotes = $c;
1021 }
1022 else if (substr($line, $i, $marker_len) == $marker)
1023 {
1024
1025
1026 $line =
1027
1028 substr($line, 0, $i) .
1029 $this->hashPart('<code class="comment">' . \ICanBoogie\escape(substr($line, $i)) . '</code>');
1030
1031 break;
1032 }
1033 }
1034 }
1035
1036 return implode("\n", $lines);
1037 }
1038
1039 const QUOTE_SINGLE = '\'';
1040 const QUOTE_DOUBLE = '"';
1041 const ESCAPE = '\\';
1042
1043 function doSourceString($text)
1044 {
1045 $out = NULL;
1046 $swap = NULL;
1047
1048 $quote = NULL;
1049 $quote_start = null;
1050 $escaped = false;
1051
1052 $y = strlen($text);
1053
1054 for ($i = 0 ; $i < $y ; $i++)
1055 {
1056 $c = $text{$i};
1057
1058 if (($c == self::QUOTE_SINGLE || $c == self::QUOTE_DOUBLE) && ($quote_start === null || $quote == $c))
1059 {
1060 if ($escaped)
1061 {
1062 $escaped = false;
1063
1064 continue;
1065 }
1066 else if ($quote_start !== null && $c == $quote)
1067 {
1068 $out .= $this->hashPart('<span class="string">' . \ICanBoogie\escape($quote . substr($text, $quote_start + 1, $i - $quote_start - 1) . $quote) . '</span>');
1069
1070 $quote_start = null;
1071 $quote = null;
1072 }
1073 else
1074 {
1075 $quote = $c;
1076 $quote_start = $i;
1077 }
1078 }
1079 else
1080 {
1081 if ($c == '\\')
1082 {
1083 $escaped = !$escaped;
1084 }
1085 else
1086 {
1087 $escaped = false;
1088 }
1089
1090 if ($quote_start === null)
1091 {
1092 $out .= $c;
1093 }
1094 }
1095 }
1096
1097 return $out;
1098 }
1099
1100 function doSourcePHP($text)
1101 {
1102
1103
1104 $text = preg_replace('#^\<\?php\s*#', '', trim($text));
1105
1106 $text = $this->doSourceCommentLine($text, '#');
1107 $text = $this->doSourceCommentLine($text, '//');
1108
1109
1110
1111
1112
1113 $text = preg_replace_callback
1114 (
1115 '#/\*.*?\*/#ms', array(&$this, '_do_php_comment'), $text
1116 );
1117
1118 $text = $this->doSourceString($text);
1119
1120
1121
1122
1123
1124 $text = preg_replace_callback
1125 (
1126 '#(\$?[a-zA-z0-9_]+)\(#', array(&$this, '_do_php_function'), $text
1127 );
1128
1129
1130
1131
1132
1133
1134
1135 $text = preg_replace_callback
1136 (
1137 '#(\$|\-\>)([a-zA-z0-9_]+)(?!\x1A)#', array(&$this, '_do_php_variable'), $text
1138 );
1139
1140
1141
1142
1143
1144 $text = preg_replace_callback
1145 (
1146 '#0x[0-9a-fA-F]{1,8}#u', array(&$this, '_do_php_number'), $text
1147 );
1148
1149 $text = preg_replace_callback
1150 (
1151 '#(?<!\x1A)\d+(?![\w])#u', array(&$this, '_do_php_number'), $text
1152 );
1153
1154
1155
1156
1157
1158 $reserved = array
1159 (
1160 'include_once',
1161 'require_once',
1162 'endswitch',
1163 'namespace',
1164 'protected',
1165 'continue',
1166 'endwhile',
1167 'function',
1168 'default',
1169 'include',
1170 'require',
1171 'extends',
1172 'foreach',
1173 'private',
1174 'elseif',
1175 'global',
1176 'parent',
1177 'static',
1178 'return',
1179 'switch',
1180 'public',
1181 'break',
1182 'class',
1183 'const',
1184 'endif',
1185 'case',
1186 'true',
1187 'self',
1188 'echo',
1189 'TRUE',
1190 'else',
1191 'false',
1192 'FALSE',
1193 'while',
1194 'NULL',
1195 'for',
1196 'new',
1197 'use',
1198 'var',
1199 'as',
1200 'if',
1201 'do',
1202 );
1203
1204 foreach ($reserved as $k)
1205 {
1206 $text = preg_replace_callback
1207 (
1208 '#' . $k . '#', array(&$this, '_do_php_reserved'), $text
1209 );
1210 }
1211
1212
1213
1214
1215
1216 $text = preg_replace_callback
1217 (
1218 '#[\(\)\[\]\{\}\!\@\%\&\*\|\/\<\>\-\+\=]+#', array(&$this, '_do_php_symbol'), $text
1219 );
1220
1221
1222
1223
1224
1225
1226
1227 $text = str_replace("\t", self::NBSP_TAB, $text);
1228 $text = str_replace(" ", self::NBSP, $text);
1229
1230
1231
1232 $text =
1233
1234 '<span class="delimiter">' .
1235 "<?php</span>\n\n" .
1236
1237 $text;
1238
1239
1240
1241
1242
1243
1244
1245 return $text;
1246 }
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257 function _do_codeentities($text)
1258 {
1259 $text = \ICanBoogie\escape($text);
1260 $text = str_replace("\t", self::NBSP_TAB, $text);
1261 $text = str_replace(" ", self::NBSP, $text);
1262
1263 return $text;
1264 }
1265 1266 1267 1268 1269 1270 1271 1272
1273
1274 function ($matches)
1275 {
1276
1277
1278
1279 return $this->hashPart
1280 (
1281 '<span class="comment">' .
1282 $this->_do_codeentities($matches[0]) .
1283 '</span>'
1284 );
1285 }
1286
1287 function _do_php_variable($matches)
1288 {
1289
1290
1291 if ($matches[1] == '->')
1292 {
1293 return '->' . $this->hashPart('<span class="variable">' . $matches[2] . '</span>');
1294 }
1295
1296 return $this->hashPart('<span class="variable">' . $matches[0] . '</span>');
1297 }
1298
1299 function _do_php_reserved($matches)
1300 {
1301
1302
1303 return $this->hashPart('<span class="reserved">' . $matches[0] . '</span>');
1304 }
1305
1306 function _do_php_function_def($matches)
1307 {
1308
1309
1310 return $this->hashPart('<span class="function">function</span>' . $matches[1]);
1311
1312 }
1313
1314 function _do_php_function($matches)
1315 {
1316
1317
1318 if ($matches[1]{0} == '$')
1319 {
1320 return $matches[0];
1321 }
1322
1323 return $this->hashPart('<span class="function">' . $matches[1] . '</span>', 'F'). '(';
1324 }
1325
1326 function _do_php_symbol($matches)
1327 {
1328 return $this->hashPart('<span class="symbol">' . \ICanBoogie\escape($matches[0]) . '</span>');
1329 }
1330
1331 function _do_php_number($matches)
1332 {
1333
1334
1335 return $this->hashPart('<span class="number">' . $matches[0] . '</span>');
1336 }
1337
1338 1339 1340 1341 1342 1343 1344 1345
1346
1347 function _doAnchors_inline_callback($matches)
1348 {
1349 $whole_match = $matches[1];
1350 $link_text = $this->runSpanGamut($matches[2]);
1351 $url = $matches[3] == '' ? $matches[4] : $matches[3];
1352 $title =& $matches[7];
1353
1354 if ($matches[2] == 'video:')
1355 {
1356 $w = 500;
1357 $h = 375;
1358 $rc = '<p>Video link type not recognized: ' . \ICanBoogie\escape($url) . '</p>';
1359
1360 if (preg_match('#vimeo.com/(\d+)#', $url, $matches))
1361 {
1362 $id = $matches[1];
1363
1364 $rc = <<<EOT
1365 <p align="center"><iframe src="http://player.vimeo.com/video/$id?show_title=1&show_byline=1&show_portrait=0&color=F65FB8" width="$w" height="$h" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe></p>
1366 EOT;
1367 }
1368 else if (strpos($url, 'youtube.com/watch') !== false)
1369 {
1370 $query = parse_url($url, PHP_URL_QUERY);
1371 parse_str($query, $params);
1372
1373 if (empty($params['v']))
1374 {
1375 $rc = \ICanBoogie\format('Missing <q>v</q> param form: %url', array('url' => $url));
1376 }
1377 else
1378 {
1379 $id = $params['v'];
1380
1381 $rc = <<<EOT
1382 <p align="center"><iframe type="text/html" width="$w" height="$h" src="http://www.youtube.com/embed/$id?controls=2" frameborder="0"></iframe></p>
1383 EOT;
1384 }
1385 }
1386 else if (preg_match('#dailymotion.com/video/([^_]+)#', $url, $matches))
1387 {
1388 $id = $matches[1];
1389 $data = \ICanBoogie\escape("http://www.dailymotion.com/swf/$id");
1390
1391 $rc = <<<EOT
1392 <p align="center">
1393 <object width="$w" height="$h" data="$data" type="application/x-shockwave-flash">
1394 <param name="movie" value="$data" />
1395 <param name="wmode" value="transparent" />
1396 <param name="allowfullscreen" value="true" />
1397 <param name="allowscriptaccess" value="always" />
1398 </object>
1399 </p>
1400 EOT;
1401 }
1402
1403 return $this->hashPart($rc, 'B');
1404 }
1405 else if ($matches[2] == 'embed:')
1406 {
1407 if (preg_match('#soundcloud.com/#', $url, $matches))
1408 {
1409 $id = urlencode($url);
1410
1411 $rc = <<<EOT
1412 <p align="center"><iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=$url"></iframe></p>
1413 EOT;
1414
1415 return $this->hashPart($rc, 'B');
1416 }
1417 }
1418
1419
1420
1421
1422
1423 $url = $this->encodeAmpsAndAngles($url);
1424
1425 $result = "<a href=\"$url\"";
1426 if (isset($title)) {
1427 $title = str_replace('"', '"', $title);
1428 $title = $this->encodeAmpsAndAngles($title);
1429 $result .= " title=\"$title\"";
1430 }
1431
1432 if (substr($url, 0, 7) == 'http://')
1433 {
1434 $result .= ' target="_blank"';
1435 }
1436
1437 $link_text = $this->runSpanGamut($link_text);
1438 $result .= ">$link_text</a>";
1439
1440 return $this->hashPart($result);
1441 }
1442 }
1443