Fixing content type ordering when content_type is not defined.
[akelos.git] / vendor / TextParsers / smartypants.php
blobd2e8f079e837f415840fc95c1c4db46ed367e37d
1 <?php
3 # SmartyPants Typographer - Smart typography for web sites
5 # PHP SmartyPants & Typographer
6 # Copyright (c) 2004-2006 Michel Fortin
7 # <http://www.michelf.com/>
9 # Original SmartyPants
10 # Copyright (c) 2003-2004 John Gruber
11 # <http://daringfireball.net/>
15 define( 'SMARTYPANTS_VERSION', "1.5.1oo2" ); # Unreleased
16 define( 'SMARTYPANTSTYPOGRAPHER_VERSION', "1.0" ); # Wed 28 Jun 2006
20 # Default configuration:
22 # 1 -> "--" for em-dashes; no en-dash support
23 # 2 -> "---" for em-dashes; "--" for en-dashes
24 # 3 -> "--" for em-dashes; "---" for en-dashes
25 # See docs for more configuration options.
27 define( 'SMARTYPANTS_ATTR', 1 );
29 # Openning and closing smart double-quotes.
30 define( 'SMARTYPANTS_SMART_DOUBLEQUOTE_OPEN', "&#8220;" );
31 define( 'SMARTYPANTS_SMART_DOUBLEQUOTE_CLOSE', "&#8221;" );
33 # Space around em-dashes. "He_—_or she_—_should change that."
34 define( 'SMARTYPANTS_SPACE_EMDASH', " " );
36 # Space around en-dashes. "He_–_or she_–_should change that."
37 define( 'SMARTYPANTS_SPACE_ENDASH', " " );
39 # Space before a colon. "He said_: here it is."
40 define( 'SMARTYPANTS_SPACE_COLON', "&#160;" );
42 # Space before a semicolon. "That's what I said_; that's what he said."
43 define( 'SMARTYPANTS_SPACE_SEMICOLON', "&#160;" );
45 # Space before a question mark and an exclamation mark: "¡_Holà_! What_?"
46 define( 'SMARTYPANTS_SPACE_MARKS', "&#160;" );
48 # Space inside french quotes. "Voici la «_chose_» qui m'a attaqué."
49 define( 'SMARTYPANTS_SPACE_FRENCHQUOTE', "&#160;" );
51 # Space as thousand separator. "On compte 10_000 maisons sur cette liste."
52 define( 'SMARTYPANTS_SPACE_THOUSAND', "&#160;" );
54 # Space before a unit abreviation. "This 12_kg of matter costs 10_$."
55 define( 'SMARTYPANTS_SPACE_UNIT', "&#160;" );
57 # SmartyPants will not alter the content of these tags:
58 define( 'SMARTYPANTS_TAGS_TO_SKIP', 'pre|code|kbd|script|math');
62 ### Standard Function Interface ###
64 define( 'SMARTYPANTS_PARSER_CLASS', 'SmartyPantsTypographer_Parser' );
66 function SmartyPants($text, $attr = SMARTYPANTS_ATTR) {
68 # Initialize the parser and return the result of its transform method.
70 # Setup static parser array.
71 static $parser = array();
72 if (!isset($parser[$attr])) {
73 $parser_class = SMARTYPANTS_PARSER_CLASS;
74 $parser[$attr] = new $parser_class($attr);
77 # Transform text using parser.
78 return $parser[$attr]->transform($text);
81 function SmartQuotes($text, $attr = 1) {
82 switch ($attr) {
83 case 0: return $text;
84 case 2: $attr = 'qb'; break;
85 default: $attr = 'q'; break;
87 return SmartyPants($text, $attr);
90 function SmartDashes($text, $attr = 1) {
91 switch ($attr) {
92 case 0: return $text;
93 case 2: $attr = 'D'; break;
94 case 3: $attr = 'i'; break;
95 default: $attr = 'd'; break;
97 return SmartyPants($text, $attr);
100 function SmartEllipsis($text, $attr = 1) {
101 switch ($attr) {
102 case 0: return $text;
103 default: $attr = 'e'; break;
105 return SmartyPants($text, $attr);
109 ### WordPress Plugin Interface ###
112 Plugin Name: SmartyPants Typographer
113 Plugin URI: http://www.michelf.com/projects/php-smartypants/
114 Description: SmartyPants is a web publishing utility that translates plain ASCII punctuation characters into &#8220;smart&#8221; typographic punctuation HTML entities. The Typographer extension will also replace normal spaces with unbrekable ones where appropriate to silently remove unwanted line breaks around punctuation and at some other places. This plugin <strong>replace the default WordPress Texturize algorithm</strong> for the content and the title of your posts, the comments body and author name, and everywhere else Texturize normally apply.
115 Version: 1.0
116 Author: Michel Fortin
117 Author URI: http://www.michelf.com/
120 if (isset($wp_version)) {
121 # Remove default Texturize filter that would conflict with SmartyPants.
122 remove_filter('category_description', 'wptexturize');
123 remove_filter('list_cats', 'wptexturize');
124 remove_filter('comment_author', 'wptexturize');
125 remove_filter('comment_text', 'wptexturize');
126 remove_filter('single_post_title', 'wptexturize');
127 remove_filter('the_title', 'wptexturize');
128 remove_filter('the_content', 'wptexturize');
129 remove_filter('the_excerpt', 'wptexturize');
130 # Add SmartyPants filter with priority 10 (same as Texturize).
131 add_filter('category_description', 'SmartyPants', 10);
132 add_filter('list_cats', 'SmartyPants', 10);
133 add_filter('comment_author', 'SmartyPants', 10);
134 add_filter('comment_text', 'SmartyPants', 10);
135 add_filter('single_post_title', 'SmartyPants', 10);
136 add_filter('the_title', 'SmartyPants', 10);
137 add_filter('the_content', 'SmartyPants', 10);
138 add_filter('the_excerpt', 'SmartyPants', 10);
142 ### Smarty Modifier Interface ###
144 function smarty_modifier_smartypants($text, $attr = NULL) {
145 return SmartyPants($text, $attr);
151 # SmartyPants Parser Class
154 class SmartyPants_Parser {
156 # Options to specify which transformations to make:
157 var $do_nothing = 0;
158 var $do_quotes = 0;
159 var $do_backticks = 0;
160 var $do_dashes = 0;
161 var $do_ellipses = 0;
162 var $do_stupefy = 0;
163 var $convert_quot = 0; # should we translate &quot; entities into normal quotes?
165 function SmartyPants_Parser($attr = SMARTYPANTS_ATTR) {
167 # Initialize a SmartyPants_Parser with certain attributes.
169 # Parser attributes:
170 # 0 : do nothing
171 # 1 : set all
172 # 2 : set all, using old school en- and em- dash shortcuts
173 # 3 : set all, using inverted old school en and em- dash shortcuts
175 # q : quotes
176 # b : backtick quotes (``double'' only)
177 # B : backtick quotes (``double'' and `single')
178 # d : dashes
179 # D : old school dashes
180 # i : inverted old school dashes
181 # e : ellipses
182 # w : convert &quot; entities to " for Dreamweaver users
184 if ($attr == "0") {
185 $this->do_nothing = 1;
187 else if ($attr == "1") {
188 # Do everything, turn all options on.
189 $this->do_quotes = 1;
190 $this->do_backticks = 1;
191 $this->do_dashes = 1;
192 $this->do_ellipses = 1;
194 else if ($attr == "2") {
195 # Do everything, turn all options on, use old school dash shorthand.
196 $this->do_quotes = 1;
197 $this->do_backticks = 1;
198 $this->do_dashes = 2;
199 $this->do_ellipses = 1;
201 else if ($attr == "3") {
202 # Do everything, turn all options on, use inverted old school dash shorthand.
203 $this->do_quotes = 1;
204 $this->do_backticks = 1;
205 $this->do_dashes = 3;
206 $this->do_ellipses = 1;
208 else if ($attr == "-1") {
209 # Special "stupefy" mode.
210 $this->do_stupefy = 1;
212 else {
213 $chars = preg_split('//', $attr);
214 foreach ($chars as $c){
215 if ($c == "q") { $this->do_quotes = 1; }
216 else if ($c == "b") { $this->do_backticks = 1; }
217 else if ($c == "B") { $this->do_backticks = 2; }
218 else if ($c == "d") { $this->do_dashes = 1; }
219 else if ($c == "D") { $this->do_dashes = 2; }
220 else if ($c == "i") { $this->do_dashes = 3; }
221 else if ($c == "e") { $this->do_ellipses = 1; }
222 else if ($c == "w") { $this->convert_quot = 1; }
223 else {
224 # Unknown attribute option, ignore.
230 function transform($text) {
232 if ($this->do_nothing) {
233 return $text;
236 $tokens = $this->tokenizeHTML($text);
237 $result = '';
238 $in_pre = 0; # Keep track of when we're inside <pre> or <code> tags.
240 $prev_token_last_char = ""; # This is a cheat, used to get some context
241 # for one-character tokens that consist of
242 # just a quote char. What we do is remember
243 # the last character of the previous text
244 # token, to use as context to curl single-
245 # character quote tokens correctly.
247 foreach ($tokens as $cur_token) {
248 if ($cur_token[0] == "tag") {
249 # Don't mess with quotes inside tags.
250 $result .= $cur_token[1];
251 if (preg_match('@<(/?)(?:'.SMARTYPANTS_TAGS_TO_SKIP.')[\s>]@', $cur_token[1], $matches)) {
252 $in_pre = isset($matches[1]) && $matches[1] == '/' ? 0 : 1;
254 } else {
255 $t = $cur_token[1];
256 $last_char = substr($t, -1); # Remember last char of this token before processing.
257 if (! $in_pre) {
258 $t = $this->educate($t, $prev_token_last_char);
260 $prev_token_last_char = $last_char;
261 $result .= $t;
265 return $result;
269 function educate($t, $prev_token_last_char) {
270 $t = $this->processEscapes($t);
272 if ($this->convert_quot) {
273 $t = preg_replace('/&quot;/', '"', $t);
276 if ($this->do_dashes) {
277 if ($this->do_dashes == 1) $t = $this->educateDashes($t);
278 if ($this->do_dashes == 2) $t = $this->educateDashesOldSchool($t);
279 if ($this->do_dashes == 3) $t = $this->educateDashesOldSchoolInverted($t);
282 if ($this->do_ellipses) $t = $this->educateEllipses($t);
284 # Note: backticks need to be processed before quotes.
285 if ($this->do_backticks) {
286 $t = $this->educateBackticks($t);
287 if ($this->do_backticks == 2) $t = $this->educateSingleBackticks($t);
290 if ($this->do_quotes) {
291 if ($t == "'") {
292 # Special case: single-character ' token
293 if (preg_match('/\S/', $prev_token_last_char)) {
294 $t = "&#8217;";
296 else {
297 $t = "&#8216;";
300 else if ($t == '"') {
301 # Special case: single-character " token
302 if (preg_match('/\S/', $prev_token_last_char)) {
303 $t = "&#8221;";
305 else {
306 $t = "&#8220;";
309 else {
310 # Normal case:
311 $t = $this->educateQuotes($t);
315 if ($this->do_stupefy) $t = $this->stupefyEntities($t);
317 return $t;
321 function educateQuotes($_) {
323 # Parameter: String.
325 # Returns: The string, with "educated" curly quote HTML entities.
327 # Example input: "Isn't this fun?"
328 # Example output: &#8220;Isn&#8217;t this fun?&#8221;
330 # Make our own "punctuation" character class, because the POSIX-style
331 # [:PUNCT:] is only available in Perl 5.6 or later:
332 $punct_class = "[!\"#\\$\\%'()*+,-.\\/:;<=>?\\@\\[\\\\\]\\^_`{|}~]";
334 # Special case if the very first character is a quote
335 # followed by punctuation at a non-word-break. Close the quotes by brute force:
336 $_ = preg_replace(
337 array("/^'(?=$punct_class\\B)/", "/^\"(?=$punct_class\\B)/"),
338 array('&#8217;', '&#8221;'), $_);
341 # Special case for double sets of quotes, e.g.:
342 # <p>He said, "'Quoted' words in a larger quote."</p>
343 $_ = preg_replace(
344 array("/\"'(?=\w)/", "/'\"(?=\w)/"),
345 array('&#8220;&#8216;', '&#8216;&#8220;'), $_);
347 # Special case for decade abbreviations (the '80s):
348 $_ = preg_replace("/'(?=\\d{2}s)/", '&#8217;', $_);
350 $close_class = '[^\ \t\r\n\[\{\(\-]';
351 $dec_dashes = '&\#8211;|&\#8212;';
353 # Get most opening single quotes:
354 $_ = preg_replace("{
356 \\s | # a whitespace char, or
357 &nbsp; | # a non-breaking space entity, or
358 -- | # dashes, or
359 &[mn]dash; | # named dash entities
360 $dec_dashes | # or decimal entities
361 &\\#x201[34]; # or hex
363 ' # the quote
364 (?=\\w) # followed by a word character
365 }x", '\1&#8216;', $_);
366 # Single closing quotes:
367 $_ = preg_replace("{
368 ($close_class)?
370 (?(1)| # If $1 captured, then do nothing;
371 (?=\\s | s\\b) # otherwise, positive lookahead for a whitespace
372 ) # char or an 's' at a word ending position. This
373 # is a special case to handle something like:
374 # \"<i>Custer</i>'s Last Stand.\"
375 }xi", '\1&#8217;', $_);
377 # Any remaining single quotes should be opening ones:
378 $_ = str_replace("'", '&#8216;', $_);
381 # Get most opening double quotes:
382 $_ = preg_replace("{
384 \\s | # a whitespace char, or
385 &nbsp; | # a non-breaking space entity, or
386 -- | # dashes, or
387 &[mn]dash; | # named dash entities
388 $dec_dashes | # or decimal entities
389 &\\#x201[34]; # or hex
391 \" # the quote
392 (?=\\w) # followed by a word character
393 }x", '\1&#8220;', $_);
395 # Double closing quotes:
396 $_ = preg_replace("{
397 ($close_class)?
399 (?(1)|(?=\\s)) # If $1 captured, then do nothing;
400 # if not, then make sure the next char is whitespace.
401 }x", '\1&#8221;', $_);
403 # Any remaining quotes should be opening ones.
404 $_ = str_replace('"', '&#8220;', $_);
406 return $_;
410 function educateBackticks($_) {
412 # Parameter: String.
413 # Returns: The string, with ``backticks'' -style double quotes
414 # translated into HTML curly quote entities.
416 # Example input: ``Isn't this fun?''
417 # Example output: &#8220;Isn't this fun?&#8221;
420 $_ = str_replace(array("``", "''",),
421 array('&#8220;', '&#8221;'), $_);
422 return $_;
426 function educateSingleBackticks($_) {
428 # Parameter: String.
429 # Returns: The string, with `backticks' -style single quotes
430 # translated into HTML curly quote entities.
432 # Example input: `Isn't this fun?'
433 # Example output: &#8216;Isn&#8217;t this fun?&#8217;
436 $_ = str_replace(array("`", "'",),
437 array('&#8216;', '&#8217;'), $_);
438 return $_;
442 function educateDashes($_) {
444 # Parameter: String.
446 # Returns: The string, with each instance of "--" translated to
447 # an em-dash HTML entity.
450 $_ = str_replace('--', '&#8212;', $_);
451 return $_;
455 function educateDashesOldSchool($_) {
457 # Parameter: String.
459 # Returns: The string, with each instance of "--" translated to
460 # an en-dash HTML entity, and each "---" translated to
461 # an em-dash HTML entity.
464 # em en
465 $_ = str_replace(array("---", "--",),
466 array('&#8212;', '&#8211;'), $_);
467 return $_;
471 function educateDashesOldSchoolInverted($_) {
473 # Parameter: String.
475 # Returns: The string, with each instance of "--" translated to
476 # an em-dash HTML entity, and each "---" translated to
477 # an en-dash HTML entity. Two reasons why: First, unlike the
478 # en- and em-dash syntax supported by
479 # EducateDashesOldSchool(), it's compatible with existing
480 # entries written before SmartyPants 1.1, back when "--" was
481 # only used for em-dashes. Second, em-dashes are more
482 # common than en-dashes, and so it sort of makes sense that
483 # the shortcut should be shorter to type. (Thanks to Aaron
484 # Swartz for the idea.)
487 # en em
488 $_ = str_replace(array("---", "--",),
489 array('&#8211;', '&#8212;'), $_);
490 return $_;
494 function educateEllipses($_) {
496 # Parameter: String.
497 # Returns: The string, with each instance of "..." translated to
498 # an ellipsis HTML entity. Also converts the case where
499 # there are spaces between the dots.
501 # Example input: Huh...?
502 # Example output: Huh&#8230;?
505 $_ = str_replace(array("...", ". . .",), '&#8230;', $_);
506 return $_;
510 function stupefyEntities($_) {
512 # Parameter: String.
513 # Returns: The string, with each SmartyPants HTML entity translated to
514 # its ASCII counterpart.
516 # Example input: &#8220;Hello &#8212; world.&#8221;
517 # Example output: "Hello -- world."
520 # en-dash em-dash
521 $_ = str_replace(array('&#8211;', '&#8212;'),
522 array('-', '--'), $_);
524 # single quote open close
525 $_ = str_replace(array('&#8216;', '&#8217;'), "'", $_);
527 # double quote open close
528 $_ = str_replace(array('&#8220;', '&#8221;'), '"', $_);
530 $_ = str_replace('&#8230;', '...', $_); # ellipsis
532 return $_;
536 function processEscapes($_) {
538 # Parameter: String.
539 # Returns: The string, with after processing the following backslash
540 # escape sequences. This is useful if you want to force a "dumb"
541 # quote or other character to appear.
543 # Escape Value
544 # ------ -----
545 # \\ &#92;
546 # \" &#34;
547 # \' &#39;
548 # \. &#46;
549 # \- &#45;
550 # \` &#96;
552 $_ = str_replace(
553 array('\\\\', '\"', "\'", '\.', '\-', '\`'),
554 array('&#92;', '&#34;', '&#39;', '&#46;', '&#45;', '&#96;'), $_);
556 return $_;
560 function tokenizeHTML($str) {
562 # Parameter: String containing HTML markup.
563 # Returns: An array of the tokens comprising the input
564 # string. Each token is either a tag (possibly with nested,
565 # tags contained therein, such as <a href="<MTFoo>">, or a
566 # run of text between tags. Each element of the array is a
567 # two-element array; the first is either 'tag' or 'text';
568 # the second is the actual value.
571 # Regular expression derived from the _tokenize() subroutine in
572 # Brad Choate's MTRegex plugin.
573 # <http://www.bradchoate.com/past/mtregex.php>
575 $index = 0;
576 $tokens = array();
578 $match = '(?s:<!(?:--.*?--\s*)+>)|'. # comment
579 '(?s:<\?.*?\?>)|'. # processing instruction
580 # regular tags
581 '(?:<[/!$]?[-a-zA-Z0-9:]+\b(?>[^"\'>]+|"[^"]*"|\'[^\']*\')*>)';
583 $parts = preg_split("{($match)}", $str, -1, PREG_SPLIT_DELIM_CAPTURE);
585 foreach ($parts as $part) {
586 if (++$index % 2 && $part != '')
587 $tokens[] = array('text', $part);
588 else
589 $tokens[] = array('tag', $part);
591 return $tokens;
598 # SmartyPants Typographer Parser Class
600 class SmartyPantsTypographer_Parser extends SmartyPants_Parser {
602 # Options to specify which transformations to make:
603 var $do_comma_quotes = 0;
604 var $do_guillemets = 0;
605 var $do_space_emdash = 0;
606 var $do_space_endash = 0;
607 var $do_space_colon = 0;
608 var $do_space_semicolon = 0;
609 var $do_space_marks = 0;
610 var $do_space_frenchquote = 0;
611 var $do_space_thousand = 0;
612 var $do_space_unit = 0;
614 # Smart quote characters:
615 var $smart_doublequote_open = SMARTYPANTS_SMART_DOUBLEQUOTE_OPEN;
616 var $smart_doublequote_close = SMARTYPANTS_SMART_DOUBLEQUOTE_CLOSE;
617 var $smart_singlequote_open = '&#8216;';
618 var $smart_singlequote_close = '&#8217;'; # Also apostrophe.
620 # Space characters for different places:
621 var $space_emdash = SMARTYPANTS_SPACE_EMDASH;
622 var $space_endash = SMARTYPANTS_SPACE_ENDASH;
623 var $space_colon = SMARTYPANTS_SPACE_COLON;
624 var $space_semicolon = SMARTYPANTS_SPACE_SEMICOLON;
625 var $space_marks = SMARTYPANTS_SPACE_MARKS;
626 var $space_frenchquote = SMARTYPANTS_SPACE_FRENCHQUOTE;
627 var $space_thousand = SMARTYPANTS_SPACE_THOUSAND;
628 var $space_unit = SMARTYPANTS_SPACE_UNIT;
630 # Expression of a space (breakable or not):
631 var $space = '(?: | |&nbsp;|&#0*160;|&#x0*[aA]0;)';
635 function SmartyPantsTypographer_Parser($attr = SMARTYPANTS_ATTR) {
637 # Initialize a SmartyPantsTypographer_Parser with certain attributes.
639 # Parser attributes:
640 # 0 : do nothing
641 # 1 : set all, except dash spacing
642 # 2 : set all, except dash spacing, using old school en- and em- dash shortcuts
643 # 3 : set all, except dash spacing, using inverted old school en and em- dash shortcuts
645 # Punctuation:
646 # q -> quotes
647 # b -> backtick quotes (``double'' only)
648 # B -> backtick quotes (``double'' and `single')
649 # c -> comma quotes (,,double`` only)
650 # g -> guillemets (<<double>> only)
651 # d -> dashes
652 # D -> old school dashes
653 # i -> inverted old school dashes
654 # e -> ellipses
655 # w -> convert &quot; entities to " for Dreamweaver users
657 # Spacing:
658 # : -> colon spacing +-
659 # ; -> semicolon spacing +-
660 # m -> question and exclamation marks spacing +-
661 # h -> em-dash spacing +-
662 # H -> en-dash spacing +-
663 # f -> french quote spacing +-
664 # t -> thousand separator spacing -
665 # u -> unit spacing +-
666 # (you can add a plus sign after some of these options denoted by + to
667 # add the space when it is not already present, or you can add a minus
668 # sign to completly remove any space present)
670 # Initialize inherited SmartyPants parser.
671 parent::SmartyPants_Parser($attr);
673 if ($attr == "1" || $attr == "2" || $attr == "3") {
674 # Do everything, turn all options on.
675 $this->do_comma_quotes = 1;
676 $this->do_guillemets = 1;
677 $this->do_space_emdash = 1;
678 $this->do_space_endash = 1;
679 $this->do_space_colon = 1;
680 $this->do_space_semicolon = 1;
681 $this->do_space_marks = 1;
682 $this->do_space_frenchquote = 1;
683 $this->do_space_thousand = 1;
684 $this->do_space_unit = 1;
686 else if ($attr == "-1") {
687 # Special "stupefy" mode.
688 $this->do_stupefy = 1;
690 else {
691 $chars = preg_split('//', $attr);
692 foreach ($chars as $c){
693 if ($c == "c") { $current =& $this->do_comma_quotes; }
694 else if ($c == "g") { $current =& $this->do_guillemets; }
695 else if ($c == ":") { $current =& $this->do_space_colon; }
696 else if ($c == ";") { $current =& $this->do_space_semicolon; }
697 else if ($c == "m") { $current =& $this->do_space_marks; }
698 else if ($c == "h") { $current =& $this->do_space_emdash; }
699 else if ($c == "H") { $current =& $this->do_space_endash; }
700 else if ($c == "f") { $current =& $this->do_space_frenchquote; }
701 else if ($c == "t") { $current =& $this->do_space_thousand; }
702 else if ($c == "u") { $current =& $this->do_space_unit; }
703 else if ($c == "+") {
704 $current = 2;
705 unset($current);
707 else if ($c == "-") {
708 $current = -1;
709 unset($current);
711 else {
712 # Unknown attribute option, ignore.
714 $current = 1;
720 function educate($t, $prev_token_last_char) {
721 $t = parent::educate($t, $prev_token_last_char);
723 if ($this->do_comma_quotes) $t = $this->educateCommaQuotes($t);
724 if ($this->do_guillemets) $t = $this->educateGuillemets($t);
726 if ($this->do_space_emdash) $t = $this->spaceEmDash($t);
727 if ($this->do_space_endash) $t = $this->spaceEnDash($t);
728 if ($this->do_space_colon) $t = $this->spaceColon($t);
729 if ($this->do_space_semicolon) $t = $this->spaceSemicolon($t);
730 if ($this->do_space_marks) $t = $this->spaceMarks($t);
731 if ($this->do_space_frenchquote) $t = $this->spaceFrenchQuotes($t);
732 if ($this->do_space_thousand) $t = $this->spaceThousandSeparator($t);
733 if ($this->do_space_unit) $t = $this->spaceUnit($t);
735 return $t;
739 function educateQuotes($_) {
741 # Parameter: String.
743 # Returns: The string, with "educated" curly quote HTML entities.
745 # Example input: "Isn't this fun?"
746 # Example output: &#8220;Isn&#8217;t this fun?&#8221;
748 $dq_open = $this->smart_doublequote_open;
749 $dq_close = $this->smart_doublequote_close;
750 $sq_open = $this->smart_singlequote_open;
751 $sq_close = $this->smart_singlequote_close;
753 # Make our own "punctuation" character class, because the POSIX-style
754 # [:PUNCT:] is only available in Perl 5.6 or later:
755 $punct_class = "[!\"#\\$\\%'()*+,-.\\/:;<=>?\\@\\[\\\\\]\\^_`{|}~]";
757 # Special case if the very first character is a quote
758 # followed by punctuation at a non-word-break. Close the quotes by brute force:
759 $_ = preg_replace(
760 array("/^'(?=$punct_class\\B)/", "/^\"(?=$punct_class\\B)/"),
761 array($sq_close, $dq_close), $_);
763 # Special case for double sets of quotes, e.g.:
764 # <p>He said, "'Quoted' words in a larger quote."</p>
765 $_ = preg_replace(
766 array("/\"'(?=\w)/", "/'\"(?=\w)/"),
767 array($dq_open.$sq_open, $sq_open.$dq_open), $_);
769 # Special case for decade abbreviations (the '80s):
770 $_ = preg_replace("/'(?=\\d{2}s)/", $sq_close, $_);
772 $close_class = '[^\ \t\r\n\[\{\(\-]';
773 $dec_dashes = '&\#8211;|&\#8212;';
775 # Get most opening single quotes:
776 $_ = preg_replace("{
778 \\s | # a whitespace char, or
779 &nbsp; | # a non-breaking space entity, or
780 -- | # dashes, or
781 &[mn]dash; | # named dash entities
782 $dec_dashes | # or decimal entities
783 &\\#x201[34]; # or hex
785 ' # the quote
786 (?=\\w) # followed by a word character
787 }x", '\1'.$sq_open, $_);
788 # Single closing quotes:
789 $_ = preg_replace("{
790 ($close_class)?
792 (?(1)| # If $1 captured, then do nothing;
793 (?=\\s | s\\b) # otherwise, positive lookahead for a whitespace
794 ) # char or an 's' at a word ending position. This
795 # is a special case to handle something like:
796 # \"<i>Custer</i>'s Last Stand.\"
797 }xi", '\1'.$sq_close, $_);
799 # Any remaining single quotes should be opening ones:
800 $_ = str_replace("'", $sq_open, $_);
803 # Get most opening double quotes:
804 $_ = preg_replace("{
806 \\s | # a whitespace char, or
807 &nbsp; | # a non-breaking space entity, or
808 -- | # dashes, or
809 &[mn]dash; | # named dash entities
810 $dec_dashes | # or decimal entities
811 &\\#x201[34]; # or hex
813 \" # the quote
814 (?=\\w) # followed by a word character
815 }x", '\1'.$dq_open, $_);
817 # Double closing quotes:
818 $_ = preg_replace("{
819 ($close_class)?
821 (?(1)|(?=\\s)) # If $1 captured, then do nothing;
822 # if not, then make sure the next char is whitespace.
823 }x", '\1'.$dq_close, $_);
825 # Any remaining quotes should be opening ones.
826 $_ = str_replace('"', $dq_open, $_);
828 return $_;
832 function educateCommaQuotes($_) {
834 # Parameter: String.
835 # Returns: The string, with ,,comma,, -style double quotes
836 # translated into HTML curly quote entities.
838 # Example input: ,,Isn't this fun?,,
839 # Example output: &#8222;Isn't this fun?&#8222;
841 # Note: this is meant to be used alongside with backtick quotes; there is
842 # no language that use only lower quotations alone mark like in the example.
844 $_ = str_replace(",,", '&#8222;', $_);
845 return $_;
849 function educateGuillemets($_) {
851 # Parameter: String.
852 # Returns: The string, with << guillemets >> -style quotes
853 # translated into HTML guillemets entities.
855 # Example input: << Isn't this fun? >>
856 # Example output: &#8222; Isn't this fun? &#8222;
858 $_ = preg_replace("/(?:<|&lt;){2}/", '&#171;', $_);
859 $_ = preg_replace("/(?:>|&gt;){2}/", '&#187;', $_);
860 return $_;
864 function spaceFrenchQuotes($_) {
866 # Parameters: String, replacement character, and forcing flag.
867 # Returns: The string, with appropriates spaces replaced
868 # inside french-style quotes, only french quotes.
870 # Example input: Quotes in « French », »German« and »Finnish» style.
871 # Example output: Quotes in «_French_», »German« and »Finnish» style.
873 $opt = ( $this->do_space_frenchquote == 2 ? '?' : '' );
874 $chr = ( $this->do_space_frenchquote != -1 ? $this->space_frenchquote : '' );
876 # Characters allowed immediatly outside quotes.
877 $outside_char = $this->space . '|\s|[.,:;!?\[\](){}|@*~=+-]|¡|¿';
879 $_ = preg_replace(
880 "/(^|$outside_char)(&#171;|«|&#8250;|‹)$this->space$opt/",
881 "\\1\\2$chr", $_);
882 $_ = preg_replace(
883 "/$this->space$opt(&#187;|»|&#8249;|›)($outside_char|$)/",
884 "$chr\\1\\2", $_);
885 return $_;
889 function spaceColon($_) {
891 # Parameters: String, replacement character, and forcing flag.
892 # Returns: The string, with appropriates spaces replaced
893 # before colons.
895 # Example input: Ingredients : fun.
896 # Example output: Ingredients_: fun.
898 $opt = ( $this->do_space_colon == 2 ? '?' : '' );
899 $chr = ( $this->do_space_colon != -1 ? $this->space_colon : '' );
901 $_ = preg_replace("/$this->space$opt(:)(\\s|$)/m",
902 "$chr\\1\\2", $_);
903 return $_;
907 function spaceSemicolon($_) {
909 # Parameters: String, replacement character, and forcing flag.
910 # Returns: The string, with appropriates spaces replaced
911 # before semicolons.
913 # Example input: There he goes ; there she goes.
914 # Example output: There he goes_; there she goes.
916 $opt = ( $this->do_space_semicolon == 2 ? '?' : '' );
917 $chr = ( $this->do_space_semicolon != -1 ? $this->space_semicolon : '' );
919 $_ = preg_replace("/$this->space(;)(?=\\s|$)/m",
920 " \\1", $_);
921 $_ = preg_replace("/((?:^|\\s)(?>[^&;\\s]+|&#?[a-zA-Z0-9]+;)*)".
922 " $opt(;)(?=\\s|$)/m",
923 "\\1$chr\\2", $_);
924 return $_;
928 function spaceMarks($_) {
930 # Parameters: String, replacement character, and forcing flag.
931 # Returns: The string, with appropriates spaces replaced
932 # around question and exclamation marks.
934 # Example input: ¡ Holà ! What ?
935 # Example output: ¡_Holà_! What_?
937 $opt = ( $this->do_space_marks == 2 ? '?' : '' );
938 $chr = ( $this->do_space_marks != -1 ? $this->space_marks : '' );
940 // Regular marks.
941 $_ = preg_replace("/$this->space$opt([?!]+)/", "$chr\\1", $_);
943 // Inverted marks.
944 $imarks = "(?:¡|&iexcl;|&#161;|&#x[Aa]1;|¿|&iquest;|&#191;|&#x[Bb][Ff];)";
945 $_ = preg_replace("/($imarks+)$this->space$opt/", "\\1$chr", $_);
947 return $_;
951 function spaceEmDash($_) {
953 # Parameters: String, two replacement characters separated by a hyphen (`-`),
954 # and forcing flag.
956 # Returns: The string, with appropriates spaces replaced
957 # around dashes.
959 # Example input: Then — without any plan — the fun happend.
960 # Example output: Then_—_without any plan_—_the fun happend.
962 $opt = ( $this->do_space_emdash == 2 ? '?' : '' );
963 $chr = ( $this->do_space_emdash != -1 ? $this->space_emdash : '' );
964 $_ = preg_replace("/$this->space$opt(&#8212;|—)$this->space$opt/",
965 "$chr\\1$chr", $_);
966 return $_;
970 function spaceEnDash($_) {
972 # Parameters: String, two replacement characters separated by a hyphen (`-`),
973 # and forcing flag.
975 # Returns: The string, with appropriates spaces replaced
976 # around dashes.
978 # Example input: Then — without any plan — the fun happend.
979 # Example output: Then_—_without any plan_—_the fun happend.
981 $opt = ( $this->do_space_endash == 2 ? '?' : '' );
982 $chr = ( $this->do_space_endash != -1 ? $this->space_endash : '' );
983 $_ = preg_replace("/$this->space$opt(&#8211;|–)$this->space$opt/",
984 "$chr\\1$chr", $_);
985 return $_;
989 function spaceThousandSeparator($_) {
991 # Parameters: String, replacement character, and forcing flag.
992 # Returns: The string, with appropriates spaces replaced
993 # inside numbers (thousand separator in french).
995 # Example input: Il y a 10 000 insectes amusants dans ton jardin.
996 # Example output: Il y a 10_000 insectes amusants dans ton jardin.
998 $chr = ( $this->do_space_thousand != -1 ? $this->space_thousand : '' );
999 $_ = preg_replace('/([0-9]) ([0-9])/', "\\1$chr\\2", $_);
1000 return $_;
1004 var $units = '
1005 ### Metric units (with prefixes)
1008 µ | &micro; | &\#0*181; | &\#[xX]0*[Bb]5; |
1009 [mcdhkMGT]
1012 [mgstAKNJWCVFSTHBL]|mol|cd|rad|Hz|Pa|Wb|lm|lx|Bq|Gy|Sv|kat|
1013 Ω | Ohm | &Omega; | &\#0*937; | &\#[xX]0*3[Aa]9;
1015 ### Computers units (KB, Kb, TB, Kbps)
1016 [kKMGT]?(?:[oBb]|[oBb]ps|flops)|
1017 ### Money
1018 ¢ | &cent; | &\#0*162; | &\#[xX]0*[Aa]2; |
1019 M?(?:
1020 £ | &pound; | &\#0*163; | &\#[xX]0*[Aa]3; |
1021 ¥ | &yen; | &\#0*165; | &\#[xX]0*[Aa]5; |
1022 € | &euro; | &\#0*8364; | &\#[xX]0*20[Aa][Cc]; |
1025 ### Other units
1026 (?: ° | &deg; | &\#0*176; | &\#[xX]0*[Bb]0; ) [CF]? |
1027 %|pt|pi|M?px|em|en|gal|lb|[NSEOW]|[NS][EOW]|ha|mbar
1028 '; //x
1030 function spaceUnit($_) {
1032 # Parameters: String, replacement character, and forcing flag.
1033 # Returns: The string, with appropriates spaces replaced
1034 # before unit symbols.
1036 # Example input: Get 3 mol of fun for 3 $.
1037 # Example output: Get 3_mol of fun for 3_$.
1039 $opt = ( $this->do_space_unit == 2 ? '?' : '' );
1040 $chr = ( $this->do_space_unit != -1 ? $this->space_unit : '' );
1042 $_ = preg_replace('/
1043 (?:([0-9])[ ]'.$opt.') # Number followed by space.
1044 ('.$this->units.') # Unit.
1045 (?![a-zA-Z0-9]) # Negative lookahead for other unit characters.
1046 /x',
1047 "\\1$chr\\2", $_);
1049 return $_;
1053 function spaceAbbr($_) {
1055 # Parameters: String, replacement character, and forcing flag.
1056 # Returns: The string, with appropriates spaces replaced
1057 # around abbreviations.
1059 # Example input: Fun i.e. something pleasant.
1060 # Example output: Fun i.e._something pleasant.
1062 $opt = ( $this->do_space_abbr == 2 ? '?' : '' );
1064 $_ = preg_replace("/(^|\s)($this->abbr_after) $opt/m",
1065 "\\1\\2$this->space_abbr", $_);
1066 $_ = preg_replace("/( )$opt($this->abbr_sp_before)(?![a-zA-Z'])/m",
1067 "\\1$this->space_abbr\\2", $_);
1068 return $_;
1072 function stupefyEntities($_) {
1074 # Adding angle quotes and lower quotes to SmartyPants's stupefy mode.
1076 $_ = parent::stupefyEntities($_);
1078 $_ = str_replace(array('&#8222;', '&#171;', '&#187'), '"', $_);
1080 return $_;
1084 function processEscapes($_) {
1086 # Adding a few more escapes to SmartyPants's escapes:
1088 # Escape Value
1089 # ------ -----
1090 # \, &#44;
1091 # \< &#60;
1092 # \> &#62;
1094 $_ = parent::processEscapes($_);
1096 $_ = str_replace(
1097 array('\,', '\<', '\>', '\&lt;', '\&gt;'),
1098 array('&#44;', '&#60;', '&#62;', '&#60;', '&#62;'), $_);
1100 return $_;
1107 PHP SmartyPants Typographer
1108 ===========================
1110 Version History
1111 ---------------
1113 1.0 (28 Jun 2006)
1115 * First public release of PHP SmartyPants Typographer.
1118 Bugs
1119 ----
1121 To file bug reports or feature requests (other than topics listed in the
1122 Caveats section above) please send email to:
1124 <michel.fortin@michelf.com>
1126 If the bug involves quotes being curled the wrong way, please send example
1127 text to illustrate.
1130 ### Algorithmic Shortcomings ###
1132 One situation in which quotes will get curled the wrong way is when
1133 apostrophes are used at the start of leading contractions. For example:
1135 'Twas the night before Christmas.
1137 In the case above, SmartyPants will turn the apostrophe into an opening
1138 single-quote, when in fact it should be a closing one. I don't think
1139 this problem can be solved in the general case -- every word processor
1140 I've tried gets this wrong as well. In such cases, it's best to use the
1141 proper HTML entity for closing single-quotes (`&#8217;`) by hand.
1144 Copyright and License
1145 ---------------------
1147 PHP SmartyPants & Typographer
1148 Copyright (c) 2004-2006 Michel Fortin
1149 <http://www.michelf.com>
1150 All rights reserved.
1152 Original SmartyPants
1153 Copyright (c) 2003-2004 John Gruber
1154 <http://daringfireball.net/>
1155 All rights reserved.
1157 Redistribution and use in source and binary forms, with or without
1158 modification, are permitted provided that the following conditions are met:
1160 * Redistributions of source code must retain the above copyright
1161 notice, this list of conditions and the following disclaimer.
1163 * Redistributions in binary form must reproduce the above copyright
1164 notice, this list of conditions and the following disclaimer in the
1165 documentation and/or other materials provided with the distribution.
1167 * Neither the name "SmartyPants" nor the names of its contributors may
1168 be used to endorse or promote products derived from this software
1169 without specific prior written permission.
1171 This software is provided by the copyright holders and contributors "as is"
1172 and any express or implied warranties, including, but not limited to, the
1173 implied warranties of merchantability and fitness for a particular purpose
1174 are disclaimed. In no event shall the copyright owner or contributors be
1175 liable for any direct, indirect, incidental, special, exemplary, or
1176 consequential damages (including, but not limited to, procurement of
1177 substitute goods or services; loss of use, data, or profits; or business
1178 interruption) however caused and on any theory of liability, whether in
1179 contract, strict liability, or tort (including negligence or otherwise)
1180 arising in any way out of the use of this software, even if advised of the
1181 possibility of such damage.