", $text);
122 | }
123 | return $text;
124 | }
125 |
126 | function mdwp_strip_p($t) { return preg_replace('{?p>}i', '', $t); }
127 |
128 | function mdwp_hide_tags($text) {
129 | global $mdwp_hidden_tags, $mdwp_placeholders;
130 | return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text);
131 | }
132 | function mdwp_show_tags($text) {
133 | global $mdwp_hidden_tags, $mdwp_placeholders;
134 | return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text);
135 | }
136 | }
137 |
138 |
139 | ### bBlog Plugin Info ###
140 |
141 | function identify_modifier_markdown() {
142 | return array(
143 | 'name' => 'markdown',
144 | 'type' => 'modifier',
145 | 'nicename' => 'Markdown',
146 | 'description' => 'A text-to-HTML conversion tool for web writers',
147 | 'authors' => 'Michel Fortin and John Gruber',
148 | 'licence' => 'BSD-like',
149 | 'version' => MARKDOWN_VERSION,
150 | 'help' => 'Markdown syntax allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by John Gruber. More...'
151 | );
152 | }
153 |
154 |
155 | ### Smarty Modifier Interface ###
156 |
157 | function smarty_modifier_markdown($text) {
158 | return Markdown($text);
159 | }
160 |
161 |
162 | ### Textile Compatibility Mode ###
163 |
164 | # Rename this file to "classTextile.php" and it can replace Textile everywhere.
165 |
166 | if (strcasecmp(substr(__FILE__, -16), "classTextile.php") == 0) {
167 | # Try to include PHP SmartyPants. Should be in the same directory.
168 | @include_once 'smartypants.php';
169 | # Fake Textile class. It calls Markdown instead.
170 | class Textile {
171 | function TextileThis($text, $lite='', $encode='') {
172 | if ($lite == '' && $encode == '') $text = Markdown($text);
173 | if (function_exists('SmartyPants')) $text = SmartyPants($text);
174 | return $text;
175 | }
176 | # Fake restricted version: restrictions are not supported for now.
177 | function TextileRestricted($text, $lite='', $noimage='') {
178 | return $this->TextileThis($text, $lite);
179 | }
180 | # Workaround to ensure compatibility with TextPattern 4.0.3.
181 | function blockLite($text) { return $text; }
182 | }
183 | }
184 |
185 |
186 |
187 | #
188 | # Markdown Parser Class
189 | #
190 |
191 | class Markdown_Parser {
192 |
193 | # Regex to match balanced [brackets].
194 | # Needed to insert a maximum bracked depth while converting to PHP.
195 | var $nested_brackets_depth = 6;
196 | var $nested_brackets_re;
197 |
198 | var $nested_url_parenthesis_depth = 4;
199 | var $nested_url_parenthesis_re;
200 |
201 | # Table of hash values for escaped characters:
202 | var $escape_chars = '\`*_{}[]()>#+-.!';
203 | var $escape_chars_re;
204 |
205 | # Change to ">" for HTML output.
206 | var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
207 | var $tab_width = MARKDOWN_TAB_WIDTH;
208 |
209 | # Change to `true` to disallow markup or entities.
210 | var $no_markup = false;
211 | var $no_entities = false;
212 |
213 | # Predefined urls and titles for reference links and images.
214 | var $predef_urls = array();
215 | var $predef_titles = array();
216 |
217 |
218 | function Markdown_Parser() {
219 | #
220 | # Constructor function. Initialize appropriate member variables.
221 | #
222 | $this->_initDetab();
223 | $this->prepareItalicsAndBold();
224 |
225 | $this->nested_brackets_re =
226 | str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth).
227 | str_repeat('\])*', $this->nested_brackets_depth);
228 |
229 | $this->nested_url_parenthesis_re =
230 | str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth).
231 | str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth);
232 |
233 | $this->escape_chars_re = '['.preg_quote($this->escape_chars).']';
234 |
235 | # Sort document, block, and span gamut in ascendent priority order.
236 | asort($this->document_gamut);
237 | asort($this->block_gamut);
238 | asort($this->span_gamut);
239 | }
240 |
241 |
242 | # Internal hashes used during transformation.
243 | var $urls = array();
244 | var $titles = array();
245 | var $html_hashes = array();
246 |
247 | # Status flag to avoid invalid nesting.
248 | var $in_anchor = false;
249 |
250 |
251 | function setup() {
252 | #
253 | # Called before the transformation process starts to setup parser
254 | # states.
255 | #
256 | # Clear global hashes.
257 | $this->urls = $this->predef_urls;
258 | $this->titles = $this->predef_titles;
259 | $this->html_hashes = array();
260 |
261 | $in_anchor = false;
262 | }
263 |
264 | function teardown() {
265 | #
266 | # Called after the transformation process to clear any variable
267 | # which may be taking up memory unnecessarly.
268 | #
269 | $this->urls = array();
270 | $this->titles = array();
271 | $this->html_hashes = array();
272 | }
273 |
274 |
275 | function transform($text) {
276 | #
277 | # Main function. Performs some preprocessing on the input text
278 | # and pass it through the document gamut.
279 | #
280 | $this->setup();
281 |
282 | # Remove UTF-8 BOM and marker character in input, if present.
283 | $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text);
284 |
285 | # Standardize line endings:
286 | # DOS to Unix and Mac to Unix
287 | $text = preg_replace('{\r\n?}', "\n", $text);
288 |
289 | # Make sure $text ends with a couple of newlines:
290 | $text .= "\n\n";
291 |
292 | # Convert all tabs to spaces.
293 | $text = $this->detab($text);
294 |
295 | # Turn block-level HTML blocks into hash entries
296 | $text = $this->hashHTMLBlocks($text);
297 |
298 | # Strip any lines consisting only of spaces and tabs.
299 | # This makes subsequent regexen easier to write, because we can
300 | # match consecutive blank lines with /\n+/ instead of something
301 | # contorted like /[ ]*\n+/ .
302 | $text = preg_replace('/^[ ]+$/m', '', $text);
303 |
304 | # Run document gamut methods.
305 | foreach ($this->document_gamut as $method => $priority) {
306 | $text = $this->$method($text);
307 | }
308 |
309 | $this->teardown();
310 |
311 | return $text . "\n";
312 | }
313 |
314 | var $document_gamut = array(
315 | # Strip link definitions, store in hashes.
316 | "stripLinkDefinitions" => 20,
317 |
318 | "runBasicBlockGamut" => 30,
319 | );
320 |
321 |
322 | function stripLinkDefinitions($text) {
323 | #
324 | # Strips link definitions from text, stores the URLs and titles in
325 | # hash references.
326 | #
327 | $less_than_tab = $this->tab_width - 1;
328 |
329 | # Link defs are in the form: ^[id]: url "optional title"
330 | $text = preg_replace_callback('{
331 | ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1
332 | [ ]*
333 | \n? # maybe *one* newline
334 | [ ]*
335 | (?:
336 | <(.+?)> # url = $2
337 | |
338 | (\S+?) # url = $3
339 | )
340 | [ ]*
341 | \n? # maybe one newline
342 | [ ]*
343 | (?:
344 | (?<=\s) # lookbehind for whitespace
345 | ["(]
346 | (.*?) # title = $4
347 | [")]
348 | [ ]*
349 | )? # title is optional
350 | (?:\n+|\Z)
351 | }xm',
352 | array(&$this, '_stripLinkDefinitions_callback'),
353 | $text);
354 | return $text;
355 | }
356 | function _stripLinkDefinitions_callback($matches) {
357 | $link_id = strtolower($matches[1]);
358 | $url = $matches[2] == '' ? $matches[3] : $matches[2];
359 | $this->urls[$link_id] = $url;
360 | $this->titles[$link_id] =& $matches[4];
361 | return ''; # String that will replace the block
362 | }
363 |
364 |
365 | function hashHTMLBlocks($text) {
366 | if ($this->no_markup) return $text;
367 |
368 | $less_than_tab = $this->tab_width - 1;
369 |
370 | # Hashify HTML blocks:
371 | # We only want to do this for block-level HTML tags, such as headers,
372 | # lists, and tables. That's because we still want to wrap
s around
373 | # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
374 | # phrase emphasis, and spans. The list of tags we're looking for is
375 | # hard-coded:
376 | #
377 | # * List "a" is made of tags which can be both inline or block-level.
378 | # These will be treated block-level when the start tag is alone on
379 | # its line, otherwise they're not matched here and will be taken as
380 | # inline later.
381 | # * List "b" is made of tags which are always block-level;
382 | #
383 | $block_tags_a_re = 'ins|del';
384 | $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'.
385 | 'script|noscript|form|fieldset|iframe|math';
386 |
387 | # Regular expression for the content of a block tag.
388 | $nested_tags_level = 4;
389 | $attr = '
390 | (?> # optional tag attributes
391 | \s # starts with whitespace
392 | (?>
393 | [^>"/]+ # text outside quotes
394 | |
395 | /+(?!>) # slash not followed by ">"
396 | |
397 | "[^"]*" # text inside double quotes (tolerate ">")
398 | |
399 | \'[^\']*\' # text inside single quotes (tolerate ">")
400 | )*
401 | )?
402 | ';
403 | $content =
404 | str_repeat('
405 | (?>
406 | [^<]+ # content without tag
407 | |
408 | <\2 # nested opening tag
409 | '.$attr.' # attributes
410 | (?>
411 | />
412 | |
413 | >', $nested_tags_level). # end of opening tag
414 | '.*?'. # last level nested tag content
415 | str_repeat('
416 | \2\s*> # closing nested tag
417 | )
418 | |
419 | <(?!/\2\s*> # other tags with a different name
420 | )
421 | )*',
422 | $nested_tags_level);
423 | $content2 = str_replace('\2', '\3', $content);
424 |
425 | # First, look for nested blocks, e.g.:
426 | #
427 | #
428 | # tags for inner block must be indented.
429 | #
430 | #
431 | #
432 | # The outermost tags must start at the left margin for this to match, and
433 | # the inner nested divs must be indented.
434 | # We need to do this before the next, more liberal match, because the next
435 | # match will start at the first `
` and stop at the first `
`.
436 | $text = preg_replace_callback('{(?>
437 | (?>
438 | (?<=\n\n) # Starting after a blank line
439 | | # or
440 | \A\n? # the beginning of the doc
441 | )
442 | ( # save in $1
443 |
444 | # Match from `\n` to `\n`, handling nested tags
445 | # in between.
446 |
447 | [ ]{0,'.$less_than_tab.'}
448 | <('.$block_tags_b_re.')# start tag = $2
449 | '.$attr.'> # attributes followed by > and \n
450 | '.$content.' # content, support nesting
451 | \2> # the matching end tag
452 | [ ]* # trailing spaces/tabs
453 | (?=\n+|\Z) # followed by a newline or end of document
454 |
455 | | # Special version for tags of group a.
456 |
457 | [ ]{0,'.$less_than_tab.'}
458 | <('.$block_tags_a_re.')# start tag = $3
459 | '.$attr.'>[ ]*\n # attributes followed by >
460 | '.$content2.' # content, support nesting
461 | \3> # the matching end tag
462 | [ ]* # trailing spaces/tabs
463 | (?=\n+|\Z) # followed by a newline or end of document
464 |
465 | | # Special case just for . It was easier to make a special
466 | # case than to make the other regex more complicated.
467 |
468 | [ ]{0,'.$less_than_tab.'}
469 | <(hr) # start tag = $2
470 | '.$attr.' # attributes
471 | /?> # the matching end tag
472 | [ ]*
473 | (?=\n{2,}|\Z) # followed by a blank line or end of document
474 |
475 | | # Special case for standalone HTML comments:
476 |
477 | [ ]{0,'.$less_than_tab.'}
478 | (?s:
479 |
480 | )
481 | [ ]*
482 | (?=\n{2,}|\Z) # followed by a blank line or end of document
483 |
484 | | # PHP and ASP-style processor instructions ( and <%)
485 |
486 | [ ]{0,'.$less_than_tab.'}
487 | (?s:
488 | <([?%]) # $2
489 | .*?
490 | \2>
491 | )
492 | [ ]*
493 | (?=\n{2,}|\Z) # followed by a blank line or end of document
494 |
495 | )
496 | )}Sxmi',
497 | array(&$this, '_hashHTMLBlocks_callback'),
498 | $text);
499 |
500 | return $text;
501 | }
502 | function _hashHTMLBlocks_callback($matches) {
503 | $text = $matches[1];
504 | $key = $this->hashBlock($text);
505 | return "\n\n$key\n\n";
506 | }
507 |
508 |
509 | function hashPart($text, $boundary = 'X') {
510 | #
511 | # Called whenever a tag must be hashed when a function insert an atomic
512 | # element in the text stream. Passing $text to through this function gives
513 | # a unique text-token which will be reverted back when calling unhash.
514 | #
515 | # The $boundary argument specify what character should be used to surround
516 | # the token. By convension, "B" is used for block elements that needs not
517 | # to be wrapped into paragraph tags at the end, ":" is used for elements
518 | # that are word separators and "X" is used in the general case.
519 | #
520 | # Swap back any tag hash found in $text so we do not have to `unhash`
521 | # multiple times at the end.
522 | $text = $this->unhash($text);
523 |
524 | # Then hash the block.
525 | static $i = 0;
526 | $key = "$boundary\x1A" . ++$i . $boundary;
527 | $this->html_hashes[$key] = $text;
528 | return $key; # String that will replace the tag.
529 | }
530 |
531 |
532 | function hashBlock($text) {
533 | #
534 | # Shortcut function for hashPart with block-level boundaries.
535 | #
536 | return $this->hashPart($text, 'B');
537 | }
538 |
539 |
540 | var $block_gamut = array(
541 | #
542 | # These are all the transformations that form block-level
543 | # tags like paragraphs, headers, and list items.
544 | #
545 | "doHeaders" => 10,
546 | "doHorizontalRules" => 20,
547 |
548 | "doLists" => 40,
549 | "doCodeBlocks" => 50,
550 | "doBlockQuotes" => 60,
551 | );
552 |
553 | function runBlockGamut($text) {
554 | #
555 | # Run block gamut tranformations.
556 | #
557 | # We need to escape raw HTML in Markdown source before doing anything
558 | # else. This need to be done for each block, and not only at the
559 | # begining in the Markdown function since hashed blocks can be part of
560 | # list items and could have been indented. Indented blocks would have
561 | # been seen as a code block in a previous pass of hashHTMLBlocks.
562 | $text = $this->hashHTMLBlocks($text);
563 |
564 | return $this->runBasicBlockGamut($text);
565 | }
566 |
567 | function runBasicBlockGamut($text) {
568 | #
569 | # Run block gamut tranformations, without hashing HTML blocks. This is
570 | # useful when HTML blocks are known to be already hashed, like in the first
571 | # whole-document pass.
572 | #
573 | foreach ($this->block_gamut as $method => $priority) {
574 | $text = $this->$method($text);
575 | }
576 |
577 | # Finally form paragraph and restore hashed blocks.
578 | $text = $this->formParagraphs($text);
579 |
580 | return $text;
581 | }
582 |
583 |
584 | function doHorizontalRules($text) {
585 | # Do Horizontal Rules:
586 | return preg_replace(
587 | '{
588 | ^[ ]{0,3} # Leading space
589 | ([-*_]) # $1: First marker
590 | (?> # Repeated marker group
591 | [ ]{0,2} # Zero, one, or two spaces.
592 | \1 # Marker character
593 | ){2,} # Group repeated at least twice
594 | [ ]* # Tailing spaces
595 | $ # End of line.
596 | }mx',
597 | "\n".$this->hashBlock("empty_element_suffix")."\n",
598 | $text);
599 | }
600 |
601 |
602 | var $span_gamut = array(
603 | #
604 | # These are all the transformations that occur *within* block-level
605 | # tags like paragraphs, headers, and list items.
606 | #
607 | # Process character escapes, code spans, and inline HTML
608 | # in one shot.
609 | "parseSpan" => -30,
610 |
611 | # Process anchor and image tags. Images must come first,
612 | # because ![foo][f] looks like an anchor.
613 | "doImages" => 10,
614 | "doAnchors" => 20,
615 |
616 | # Make links out of things like ``
617 | # Must come after doAnchors, because you can use < and >
618 | # delimiters in inline links like [this]().
619 | "doAutoLinks" => 30,
620 | "encodeAmpsAndAngles" => 40,
621 |
622 | "doItalicsAndBold" => 50,
623 | "doHardBreaks" => 60,
624 | );
625 |
626 | function runSpanGamut($text) {
627 | #
628 | # Run span gamut tranformations.
629 | #
630 | foreach ($this->span_gamut as $method => $priority) {
631 | $text = $this->$method($text);
632 | }
633 |
634 | return $text;
635 | }
636 |
637 |
638 | function doHardBreaks($text) {
639 | # Do hard breaks:
640 | return preg_replace_callback('/ {2,}\n/',
641 | array(&$this, '_doHardBreaks_callback'), $text);
642 | }
643 | function _doHardBreaks_callback($matches) {
644 | return $this->hashPart(" empty_element_suffix\n");
645 | }
646 |
647 |
648 | function doAnchors($text) {
649 | #
650 | # Turn Markdown link shortcuts into XHTML tags.
651 | #
652 | if ($this->in_anchor) return $text;
653 | $this->in_anchor = true;
654 |
655 | #
656 | # First, handle reference-style links: [link text] [id]
657 | #
658 | $text = preg_replace_callback('{
659 | ( # wrap whole match in $1
660 | \[
661 | ('.$this->nested_brackets_re.') # link text = $2
662 | \]
663 |
664 | [ ]? # one optional space
665 | (?:\n[ ]*)? # one optional newline followed by spaces
666 |
667 | \[
668 | (.*?) # id = $3
669 | \]
670 | )
671 | }xs',
672 | array(&$this, '_doAnchors_reference_callback'), $text);
673 |
674 | #
675 | # Next, inline-style links: [link text](url "optional title")
676 | #
677 | $text = preg_replace_callback('{
678 | ( # wrap whole match in $1
679 | \[
680 | ('.$this->nested_brackets_re.') # link text = $2
681 | \]
682 | \( # literal paren
683 | [ \n]*
684 | (?:
685 | <(.+?)> # href = $3
686 | |
687 | ('.$this->nested_url_parenthesis_re.') # href = $4
688 | )
689 | [ \n]*
690 | ( # $5
691 | ([\'"]) # quote char = $6
692 | (.*?) # Title = $7
693 | \6 # matching quote
694 | [ \n]* # ignore any spaces/tabs between closing quote and )
695 | )? # title is optional
696 | \)
697 | )
698 | }xs',
699 | array(&$this, '_doAnchors_inline_callback'), $text);
700 |
701 | #
702 | # Last, handle reference-style shortcuts: [link text]
703 | # These must come last in case you've also got [link text][1]
704 | # or [link text](/foo)
705 | #
706 | $text = preg_replace_callback('{
707 | ( # wrap whole match in $1
708 | \[
709 | ([^\[\]]+) # link text = $2; can\'t contain [ or ]
710 | \]
711 | )
712 | }xs',
713 | array(&$this, '_doAnchors_reference_callback'), $text);
714 |
715 | $this->in_anchor = false;
716 | return $text;
717 | }
718 | function _doAnchors_reference_callback($matches) {
719 | $whole_match = $matches[1];
720 | $link_text = $matches[2];
721 | $link_id =& $matches[3];
722 |
723 | if ($link_id == "") {
724 | # for shortcut links like [this][] or [this].
725 | $link_id = $link_text;
726 | }
727 |
728 | # lower-case and turn embedded newlines into spaces
729 | $link_id = strtolower($link_id);
730 | $link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
731 |
732 | if (isset($this->urls[$link_id])) {
733 | $url = $this->urls[$link_id];
734 | $url = $this->encodeAttribute($url);
735 |
736 | $result = "titles[$link_id] ) ) {
738 | $title = $this->titles[$link_id];
739 | $title = $this->encodeAttribute($title);
740 | $result .= " title=\"$title\"";
741 | }
742 |
743 | $link_text = $this->runSpanGamut($link_text);
744 | $result .= ">$link_text";
745 | $result = $this->hashPart($result);
746 | }
747 | else {
748 | $result = $whole_match;
749 | }
750 | return $result;
751 | }
752 | function _doAnchors_inline_callback($matches) {
753 | $whole_match = $matches[1];
754 | $link_text = $this->runSpanGamut($matches[2]);
755 | $url = $matches[3] == '' ? $matches[4] : $matches[3];
756 | $title =& $matches[7];
757 |
758 | $url = $this->encodeAttribute($url);
759 |
760 | $result = "encodeAttribute($title);
763 | $result .= " title=\"$title\"";
764 | }
765 |
766 | $link_text = $this->runSpanGamut($link_text);
767 | $result .= ">$link_text";
768 |
769 | return $this->hashPart($result);
770 | }
771 |
772 |
773 | function doImages($text) {
774 | #
775 | # Turn Markdown image shortcuts into tags.
776 | #
777 | #
778 | # First, handle reference-style labeled images: ![alt text][id]
779 | #
780 | $text = preg_replace_callback('{
781 | ( # wrap whole match in $1
782 | !\[
783 | ('.$this->nested_brackets_re.') # alt text = $2
784 | \]
785 |
786 | [ ]? # one optional space
787 | (?:\n[ ]*)? # one optional newline followed by spaces
788 |
789 | \[
790 | (.*?) # id = $3
791 | \]
792 |
793 | )
794 | }xs',
795 | array(&$this, '_doImages_reference_callback'), $text);
796 |
797 | #
798 | # Next, handle inline images: 
799 | # Don't forget: encode * and _
800 | #
801 | $text = preg_replace_callback('{
802 | ( # wrap whole match in $1
803 | !\[
804 | ('.$this->nested_brackets_re.') # alt text = $2
805 | \]
806 | \s? # One optional whitespace character
807 | \( # literal paren
808 | [ \n]*
809 | (?:
810 | <(\S*)> # src url = $3
811 | |
812 | ('.$this->nested_url_parenthesis_re.') # src url = $4
813 | )
814 | [ \n]*
815 | ( # $5
816 | ([\'"]) # quote char = $6
817 | (.*?) # title = $7
818 | \6 # matching quote
819 | [ \n]*
820 | )? # title is optional
821 | \)
822 | )
823 | }xs',
824 | array(&$this, '_doImages_inline_callback'), $text);
825 |
826 | return $text;
827 | }
828 | function _doImages_reference_callback($matches) {
829 | $whole_match = $matches[1];
830 | $alt_text = $matches[2];
831 | $link_id = strtolower($matches[3]);
832 |
833 | if ($link_id == "") {
834 | $link_id = strtolower($alt_text); # for shortcut links like ![this][].
835 | }
836 |
837 | $alt_text = $this->encodeAttribute($alt_text);
838 | if (isset($this->urls[$link_id])) {
839 | $url = $this->encodeAttribute($this->urls[$link_id]);
840 | $result = "titles[$link_id])) {
842 | $title = $this->titles[$link_id];
843 | $title = $this->encodeAttribute($title);
844 | $result .= " title=\"$title\"";
845 | }
846 | $result .= $this->empty_element_suffix;
847 | $result = $this->hashPart($result);
848 | }
849 | else {
850 | # If there's no such link ID, leave intact:
851 | $result = $whole_match;
852 | }
853 |
854 | return $result;
855 | }
856 | function _doImages_inline_callback($matches) {
857 | $whole_match = $matches[1];
858 | $alt_text = $matches[2];
859 | $url = $matches[3] == '' ? $matches[4] : $matches[3];
860 | $title =& $matches[7];
861 |
862 | $alt_text = $this->encodeAttribute($alt_text);
863 | $url = $this->encodeAttribute($url);
864 | $result = "encodeAttribute($title);
867 | $result .= " title=\"$title\""; # $title already quoted
868 | }
869 | $result .= $this->empty_element_suffix;
870 |
871 | return $this->hashPart($result);
872 | }
873 |
874 |
875 | function doHeaders($text) {
876 | # Setext-style headers:
877 | # Header 1
878 | # ========
879 | #
880 | # Header 2
881 | # --------
882 | #
883 | $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
884 | array(&$this, '_doHeaders_callback_setext'), $text);
885 |
886 | # atx-style headers:
887 | # # Header 1
888 | # ## Header 2
889 | # ## Header 2 with closing hashes ##
890 | # ...
891 | # ###### Header 6
892 | #
893 | $text = preg_replace_callback('{
894 | ^(\#{1,6}) # $1 = string of #\'s
895 | [ ]*
896 | (.+?) # $2 = Header text
897 | [ ]*
898 | \#* # optional closing #\'s (not counted)
899 | \n+
900 | }xm',
901 | array(&$this, '_doHeaders_callback_atx'), $text);
902 |
903 | return $text;
904 | }
905 | function _doHeaders_callback_setext($matches) {
906 | # Terrible hack to check we haven't found an empty list item.
907 | if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1]))
908 | return $matches[0];
909 |
910 | $level = $matches[2]{0} == '=' ? 1 : 2;
911 | $block = "".$this->runSpanGamut($matches[1])."";
912 | return "\n" . $this->hashBlock($block) . "\n\n";
913 | }
914 | function _doHeaders_callback_atx($matches) {
915 | $level = strlen($matches[1]);
916 | $block = "".$this->runSpanGamut($matches[2])."";
917 | return "\n" . $this->hashBlock($block) . "\n\n";
918 | }
919 |
920 |
921 | function doLists($text) {
922 | #
923 | # Form HTML ordered (numbered) and unordered (bulleted) lists.
924 | #
925 | $less_than_tab = $this->tab_width - 1;
926 |
927 | # Re-usable patterns to match list item bullets and number markers:
928 | $marker_ul_re = '[*+-]';
929 | $marker_ol_re = '\d+[\.]';
930 | $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
931 |
932 | $markers_relist = array(
933 | $marker_ul_re => $marker_ol_re,
934 | $marker_ol_re => $marker_ul_re,
935 | );
936 |
937 | foreach ($markers_relist as $marker_re => $other_marker_re) {
938 | # Re-usable pattern to match any entirel ul or ol list:
939 | $whole_list_re = '
940 | ( # $1 = whole list
941 | ( # $2
942 | ([ ]{0,'.$less_than_tab.'}) # $3 = number of spaces
943 | ('.$marker_re.') # $4 = first list item marker
944 | [ ]+
945 | )
946 | (?s:.+?)
947 | ( # $5
948 | \z
949 | |
950 | \n{2,}
951 | (?=\S)
952 | (?! # Negative lookahead for another list item marker
953 | [ ]*
954 | '.$marker_re.'[ ]+
955 | )
956 | |
957 | (?= # Lookahead for another kind of list
958 | \n
959 | \3 # Must have the same indentation
960 | '.$other_marker_re.'[ ]+
961 | )
962 | )
963 | )
964 | '; // mx
965 |
966 | # We use a different prefix before nested lists than top-level lists.
967 | # See extended comment in _ProcessListItems().
968 |
969 | if ($this->list_level) {
970 | $text = preg_replace_callback('{
971 | ^
972 | '.$whole_list_re.'
973 | }mx',
974 | array(&$this, '_doLists_callback'), $text);
975 | }
976 | else {
977 | $text = preg_replace_callback('{
978 | (?:(?<=\n)\n|\A\n?) # Must eat the newline
979 | '.$whole_list_re.'
980 | }mx',
981 | array(&$this, '_doLists_callback'), $text);
982 | }
983 | }
984 |
985 | return $text;
986 | }
987 | function _doLists_callback($matches) {
988 | # Re-usable patterns to match list item bullets and number markers:
989 | $marker_ul_re = '[*+-]';
990 | $marker_ol_re = '\d+[\.]';
991 | $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
992 |
993 | $list = $matches[1];
994 | $list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol";
995 |
996 | $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re );
997 |
998 | $list .= "\n";
999 | $result = $this->processListItems($list, $marker_any_re);
1000 |
1001 | $result = $this->hashBlock("<$list_type>\n" . $result . "$list_type>");
1002 | return "\n". $result ."\n\n";
1003 | }
1004 |
1005 | var $list_level = 0;
1006 |
1007 | function processListItems($list_str, $marker_any_re) {
1008 | #
1009 | # Process the contents of a single ordered or unordered list, splitting it
1010 | # into individual list items.
1011 | #
1012 | # The $this->list_level global keeps track of when we're inside a list.
1013 | # Each time we enter a list, we increment it; when we leave a list,
1014 | # we decrement. If it's zero, we're not in a list anymore.
1015 | #
1016 | # We do this because when we're not inside a list, we want to treat
1017 | # something like this:
1018 | #
1019 | # I recommend upgrading to version
1020 | # 8. Oops, now this line is treated
1021 | # as a sub-list.
1022 | #
1023 | # As a single paragraph, despite the fact that the second line starts
1024 | # with a digit-period-space sequence.
1025 | #
1026 | # Whereas when we're inside a list (or sub-list), that line will be
1027 | # treated as the start of a sub-list. What a kludge, huh? This is
1028 | # an aspect of Markdown's syntax that's hard to parse perfectly
1029 | # without resorting to mind-reading. Perhaps the solution is to
1030 | # change the syntax rules such that sub-lists must start with a
1031 | # starting cardinal number; e.g. "1." or "a.".
1032 |
1033 | $this->list_level++;
1034 |
1035 | # trim trailing blank lines:
1036 | $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
1037 |
1038 | $list_str = preg_replace_callback('{
1039 | (\n)? # leading line = $1
1040 | (^[ ]*) # leading whitespace = $2
1041 | ('.$marker_any_re.' # list marker and space = $3
1042 | (?:[ ]+|(?=\n)) # space only required if item is not empty
1043 | )
1044 | ((?s:.*?)) # list item text = $4
1045 | (?:(\n+(?=\n))|\n) # tailing blank line = $5
1046 | (?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n))))
1047 | }xm',
1048 | array(&$this, '_processListItems_callback'), $list_str);
1049 |
1050 | $this->list_level--;
1051 | return $list_str;
1052 | }
1053 | function _processListItems_callback($matches) {
1054 | $item = $matches[4];
1055 | $leading_line =& $matches[1];
1056 | $leading_space =& $matches[2];
1057 | $marker_space = $matches[3];
1058 | $tailing_blank_line =& $matches[5];
1059 |
1060 | if ($leading_line || $tailing_blank_line ||
1061 | preg_match('/\n{2,}/', $item))
1062 | {
1063 | # Replace marker with the appropriate whitespace indentation
1064 | $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item;
1065 | $item = $this->runBlockGamut($this->outdent($item)."\n");
1066 | }
1067 | else {
1068 | # Recursion for sub-lists:
1069 | $item = $this->doLists($this->outdent($item));
1070 | $item = preg_replace('/\n+$/', '', $item);
1071 | $item = $this->runSpanGamut($item);
1072 | }
1073 |
1074 | return "
1470 | #
1471 | # Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
1472 | # With some optimizations by Milian Wolff.
1473 | #
1474 | $addr = "mailto:" . $addr;
1475 | $chars = preg_split('/(? $char) {
1479 | $ord = ord($char);
1480 | # Ignore non-ascii chars.
1481 | if ($ord < 128) {
1482 | $r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
1483 | # roughly 10% raw, 45% hex, 45% dec
1484 | # '@' *must* be encoded. I insist.
1485 | if ($r > 90 && $char != '@') /* do nothing */;
1486 | else if ($r < 45) $chars[$key] = ''.dechex($ord).';';
1487 | else $chars[$key] = ''.$ord.';';
1488 | }
1489 | }
1490 |
1491 | $addr = implode('', $chars);
1492 | $text = implode('', array_slice($chars, 7)); # text without `mailto:`
1493 | $addr = "$text";
1494 |
1495 | return $addr;
1496 | }
1497 |
1498 |
1499 | function parseSpan($str) {
1500 | #
1501 | # Take the string $str and parse it into tokens, hashing embeded HTML,
1502 | # escaped characters and handling code spans.
1503 | #
1504 | $output = '';
1505 |
1506 | $span_re = '{
1507 | (
1508 | \\\\'.$this->escape_chars_re.'
1509 | |
1510 | (?no_markup ? '' : '
1513 | |
1514 | # comment
1515 | |
1516 | <\?.*?\?> | <%.*?%> # processing instruction
1517 | |
1518 | <[/!$]?[-a-zA-Z0-9:_]+ # regular tags
1519 | (?>
1520 | \s
1521 | (?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
1522 | )?
1523 | >
1524 | ').'
1525 | )
1526 | }xs';
1527 |
1528 | while (1) {
1529 | #
1530 | # Each loop iteration seach for either the next tag, the next
1531 | # openning code span marker, or the next escaped character.
1532 | # Each token is then passed to handleSpanToken.
1533 | #
1534 | $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
1535 |
1536 | # Create token from text preceding tag.
1537 | if ($parts[0] != "") {
1538 | $output .= $parts[0];
1539 | }
1540 |
1541 | # Check if we reach the end.
1542 | if (isset($parts[1])) {
1543 | $output .= $this->handleSpanToken($parts[1], $parts[2]);
1544 | $str = $parts[2];
1545 | }
1546 | else {
1547 | break;
1548 | }
1549 | }
1550 |
1551 | return $output;
1552 | }
1553 |
1554 |
1555 | function handleSpanToken($token, &$str) {
1556 | #
1557 | # Handle $token provided by parseSpan by determining its nature and
1558 | # returning the corresponding value that should replace it.
1559 | #
1560 | switch ($token{0}) {
1561 | case "\\":
1562 | return $this->hashPart("". ord($token{1}). ";");
1563 | case "`":
1564 | # Search for end marker in remaining text.
1565 | if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm',
1566 | $str, $matches))
1567 | {
1568 | $str = $matches[2];
1569 | $codespan = $this->makeCodeSpan($matches[1]);
1570 | return $this->hashPart($codespan);
1571 | }
1572 | return $token; // return as text since no ending marker found.
1573 | default:
1574 | return $this->hashPart($token);
1575 | }
1576 | }
1577 |
1578 |
1579 | function outdent($text) {
1580 | #
1581 | # Remove one level of line-leading tabs or spaces
1582 | #
1583 | return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text);
1584 | }
1585 |
1586 |
1587 | # String length function for detab. `_initDetab` will create a function to
1588 | # hanlde UTF-8 if the default function does not exist.
1589 | var $utf8_strlen = 'mb_strlen';
1590 |
1591 | function detab($text) {
1592 | #
1593 | # Replace tabs with the appropriate amount of space.
1594 | #
1595 | # For each line we separate the line in blocks delemited by
1596 | # tab characters. Then we reconstruct every line by adding the
1597 | # appropriate number of space between each blocks.
1598 |
1599 | $text = preg_replace_callback('/^.*\t.*$/m',
1600 | array(&$this, '_detab_callback'), $text);
1601 |
1602 | return $text;
1603 | }
1604 | function _detab_callback($matches) {
1605 | $line = $matches[0];
1606 | $strlen = $this->utf8_strlen; # strlen function for UTF-8.
1607 |
1608 | # Split in blocks.
1609 | $blocks = explode("\t", $line);
1610 | # Add each blocks to the line.
1611 | $line = $blocks[0];
1612 | unset($blocks[0]); # Do not add first block twice.
1613 | foreach ($blocks as $block) {
1614 | # Calculate amount of space, insert spaces, insert block.
1615 | $amount = $this->tab_width -
1616 | $strlen($line, 'UTF-8') % $this->tab_width;
1617 | $line .= str_repeat(" ", $amount) . $block;
1618 | }
1619 | return $line;
1620 | }
1621 | function _initDetab() {
1622 | #
1623 | # Check for the availability of the function in the `utf8_strlen` property
1624 | # (initially `mb_strlen`). If the function is not available, create a
1625 | # function that will loosely count the number of UTF-8 characters with a
1626 | # regular expression.
1627 | #
1628 | if (function_exists($this->utf8_strlen)) return;
1629 | $this->utf8_strlen = create_function('$text', 'return preg_match_all(
1630 | "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/",
1631 | $text, $m);');
1632 | }
1633 |
1634 |
1635 | function unhash($text) {
1636 | #
1637 | # Swap back in all the tags hashed by _HashHTMLBlocks.
1638 | #
1639 | return preg_replace_callback('/(.)\x1A[0-9]+\1/',
1640 | array(&$this, '_unhash_callback'), $text);
1641 | }
1642 | function _unhash_callback($matches) {
1643 | return $this->html_hashes[$matches[0]];
1644 | }
1645 |
1646 | }
1647 |
1648 | /*
1649 |
1650 | PHP Markdown
1651 | ============
1652 |
1653 | Description
1654 | -----------
1655 |
1656 | This is a PHP translation of the original Markdown formatter written in
1657 | Perl by John Gruber.
1658 |
1659 | Markdown is a text-to-HTML filter; it translates an easy-to-read /
1660 | easy-to-write structured text format into HTML. Markdown's text format
1661 | is most similar to that of plain text email, and supports features such
1662 | as headers, *emphasis*, code blocks, blockquotes, and links.
1663 |
1664 | Markdown's syntax is designed not as a generic markup language, but
1665 | specifically to serve as a front-end to (X)HTML. You can use span-level
1666 | HTML tags anywhere in a Markdown document, and you can use block level
1667 | HTML tags (like
and
as well).
1668 |
1669 | For more information about Markdown's syntax, see:
1670 |
1671 |
1672 |
1673 |
1674 | Bugs
1675 | ----
1676 |
1677 | To file bug reports please send email to:
1678 |
1679 |
1680 |
1681 | Please include with your report: (1) the example input; (2) the output you
1682 | expected; (3) the output Markdown actually produced.
1683 |
1684 |
1685 | Version History
1686 | ---------------
1687 |
1688 | See the readme file for detailed release notes for this version.
1689 |
1690 |
1691 | Copyright and License
1692 | ---------------------
1693 |
1694 | PHP Markdown
1695 | Copyright (c) 2004-2009 Michel Fortin
1696 |
1697 | All rights reserved.
1698 |
1699 | Based on Markdown
1700 | Copyright (c) 2003-2006 John Gruber
1701 |
1702 | All rights reserved.
1703 |
1704 | Redistribution and use in source and binary forms, with or without
1705 | modification, are permitted provided that the following conditions are
1706 | met:
1707 |
1708 | * Redistributions of source code must retain the above copyright notice,
1709 | this list of conditions and the following disclaimer.
1710 |
1711 | * Redistributions in binary form must reproduce the above copyright
1712 | notice, this list of conditions and the following disclaimer in the
1713 | documentation and/or other materials provided with the distribution.
1714 |
1715 | * Neither the name "Markdown" nor the names of its contributors may
1716 | be used to endorse or promote products derived from this software
1717 | without specific prior written permission.
1718 |
1719 | This software is provided by the copyright holders and contributors "as
1720 | is" and any express or implied warranties, including, but not limited
1721 | to, the implied warranties of merchantability and fitness for a
1722 | particular purpose are disclaimed. In no event shall the copyright owner
1723 | or contributors be liable for any direct, indirect, incidental, special,
1724 | exemplary, or consequential damages (including, but not limited to,
1725 | procurement of substitute goods or services; loss of use, data, or
1726 | profits; or business interruption) however caused and on any theory of
1727 | liability, whether in contract, strict liability, or tort (including
1728 | negligence or otherwise) arising in any way out of the use of this
1729 | software, even if advised of the possibility of such damage.
1730 |
1731 | */
1732 | ?>
--------------------------------------------------------------------------------
/examples/blog/lib/mysql.php:
--------------------------------------------------------------------------------
1 | setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
13 | }
14 | return self::$instance;
15 | }
16 | }
--------------------------------------------------------------------------------
/examples/blog/lib/queries.php:
--------------------------------------------------------------------------------
1 | query("SELECT * FROM articles ORDER BY published DESC");
5 | return $query->fetchAll();
6 | }
7 |
8 | function get_article_by_slug($slug) {
9 | $query = MySQL::getInstance()->prepare("SELECT * FROM articles WHERE slug=:slug");
10 | $query->bindValue(':slug', $slug, PDO::PARAM_STR);
11 | $query->execute();
12 | return $query->fetch(PDO::FETCH_ASSOC);
13 | }
14 |
15 | function get_article_comments($article_id) {
16 | $query = MySQL::getInstance()->prepare("SELECT * FROM comments WHERE article_id=:article_id ORDER BY posted ASC");
17 | $query->bindValue(':article_id', $article_id, PDO::PARAM_INT);
18 | $query->execute();
19 | return $query->fetchAll(PDO::FETCH_ASSOC);
20 | }
21 |
22 | function save_comment($article_id, $name, $body) {
23 | $query = MySQL::getInstance()->prepare("INSERT INTO comments (article_id, name, body) VALUES (:article_id, :name, :body)");
24 | $query->bindValue(':article_id', $article_id, PDO::PARAM_INT);
25 | $query->bindValue(':name', $name, PDO::PARAM_STR);
26 | $query->bindValue(':body', $body, PDO::PARAM_STR);
27 | $query->execute();
28 | }
--------------------------------------------------------------------------------
/examples/blog/toroblog.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE `articles` (
2 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
3 | `title` varchar(25) NOT NULL DEFAULT '',
4 | `slug` varchar(25) NOT NULL,
5 | `body` text NOT NULL,
6 | `published` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
7 | PRIMARY KEY (`id`),
8 | UNIQUE KEY `slug` (`slug`)
9 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
10 |
11 | INSERT INTO `articles` (`id`, `title`, `slug`, `body`, `published`)
12 | VALUES
13 | (1,'First Post','first-post','This is the first post for ToroBlog. Hello, world?','2012-08-18 16:28:10'),
14 | (2,'Second Post','second-post','Just another post to test out some features.\n\nLine break and *asterisks* to show Markdown integration.','2012-08-18 16:39:03');
15 |
16 | DROP TABLE IF EXISTS `comments`;
17 |
18 | CREATE TABLE `comments` (
19 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
20 | `article_id` int(11) NOT NULL,
21 | `name` varchar(25) NOT NULL DEFAULT '',
22 | `body` text NOT NULL,
23 | `posted` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
24 | PRIMARY KEY (`id`)
25 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
26 |
27 | INSERT INTO `comments` (`id`, `article_id`, `name`, `body`, `posted`)
28 | VALUES
29 | (1,1,'Joe Shmoe','First!','2012-08-18 16:32:52');
30 |
--------------------------------------------------------------------------------
/examples/blog/views/_article.php:
--------------------------------------------------------------------------------
1 |