Overview

Packages

  • Text
    • Textile

Classes

  • Horde_Text_Textile
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * @category Horde
  4:  * @package  Text_Textile
  5:  */
  6: 
  7: /**
  8:  * References:
  9:  *   http://www.textism.com/tools/textile/
 10:  *   http://en.wikipedia.org/wiki/Textile_(markup_language)
 11:  *   http://hobix.com/textile/
 12:  *   http://whytheluckystiff.net/ruby/redcloth/
 13:  *   http://redcloth.rubyforge.org/rdoc/
 14:  *   http://code.whytheluckystiff.net/redcloth/browser/trunk/test/textism.yml
 15:  *
 16:  * Example: get XHTML from a given Textile-markup string ($string)
 17:  *   $textile = new Horde_Text_Textile;
 18:  *   echo $textile->toHtml($string);
 19:  *
 20:  * @category Horde
 21:  * @package  Text_Textile
 22:  */
 23: class Horde_Text_Textile {
 24: 
 25:     /**
 26:      * A_HLGN
 27:      */
 28:     const REGEX_A_HLGN = '(?:\<(?!>)|(?<!<)\>|\<\>|\=|[()]+(?! ))';
 29: 
 30:     /**
 31:      * A_VLGN
 32:      */
 33:     const REGEX_A_VLGN = '[\-^~]';
 34: 
 35:     /**
 36:      * '(?:' . A_HLGN . '|' . A_VLGN . ')*'
 37:      */
 38:     const REGEX_A = '(?:(?:\<(?!>)|(?<!<)\>|\<\>|\=|[()]+(?! ))|[\-^~])*';
 39: 
 40:     /**
 41:      * '(?:' . S_CSPN . '|' . S_RSPN . ')*'
 42:      */
 43:     const REGEX_S = '(?:(?:\\\\\d+)|(?:\/\d+))*';
 44: 
 45:     /**
 46:      * '(?:' . C_CLAS . '|' . C_STYL . '|' . C_LNGE . '|' . A_HLGN . ')*'
 47:      */
 48:     const REGEX_C = '(?:(?:\([^)]+\))|(?:\{[^}]+\})|(?:\[[^]]+\])|(?:\<(?!>)|(?<!<)\>|\<\>|\=|[()]+(?! )))*';
 49: 
 50:     /**
 51:      * PUNCT
 52:      */
 53:     const REGEX_PUNCT = '\!"#\$%&\'\*\+,-\.\/\:;\=\?@\\\^_`\|~';
 54: 
 55:     /**
 56:      * LINK_RE
 57:      */
 58:     const REGEX_URL = '[\w"$\-_.+!*\'(),";\/?:@=&%#{}|\\^~\[\]`]';
 59: 
 60:     /**
 61:      * Block tags
 62:      */
 63:     const REGEX_BLOCK_TAGS = 'bq|bc|notextile|pre|h[1-6]|fn\d+|p';
 64: 
 65:     /**
 66:      * Glyphs. Can be overridden if you want to substitute different
 67:      * entities.
 68:      */
 69:     public static $GLYPH_QUOTE_SINGLE_OPEN = '&#8216;';
 70:     public static $GLYPH_QUOTE_SINGLE_CLOSE = '&#8217;';
 71:     public static $GLYPH_QUOTE_DOUBLE_OPEN = '&#8220;';
 72:     public static $GLYPH_QUOTE_DOUBLE_CLOSE = '&#8221;';
 73:     public static $GLYPH_APOSTROPHE = '&#8217;';
 74:     public static $GLYPH_PRIME = '&#8242;';
 75:     public static $GLYPH_PRIME_DOUBLE = '&#8243;';
 76:     public static $GLYPH_ELLIPSIS = '&#8230;';
 77:     public static $GLYPH_EMDASH = '&#8212;';
 78:     public static $GLYPH_ENDASH = '&#8211;';
 79:     public static $GLYPH_DIMENSION = '&#215;';
 80:     public static $GLYPH_TRADEMARK = '&#8482;';
 81:     public static $GLYPH_REGISTERED = '&#174;';
 82:     public static $GLYPH_COPYRIGHT = '&#169;';
 83:     public static $GLYPH_RETURN_ARROW = '&#8617;';
 84: 
 85:     /**
 86:      * Show images? On by default.
 87:      *
 88:      * @var boolean
 89:      */
 90:     public $images = true;
 91: 
 92:     /**
 93:      * Rel attribute for links (ex: nofollow).
 94:      *
 95:      * @var string
 96:      */
 97:     public $rel = '';
 98: 
 99:     /**
100:      * Shelf of values being processed.
101:      *
102:      * @var array
103:      */
104:     protected $_shelf = array();
105: 
106:     public function transform($text, $rel = '')
107:     {
108:         if ($rel) {
109:             $this->rel = ' rel="' . $rel . '" ';
110:         }
111: 
112:         $text = $this->cleanWhiteSpace($text);
113:         $text = $this->getRefs($text);
114:         $text = $this->block($text);
115: 
116:         return $this->retrieve($text);
117:     }
118: 
119:     /**
120:      * parse block attributes
121:      * @ignore
122:      */
123:     public function parseBlockAttributes($in, $element = '')
124:     {
125:         $style = '';
126:         $class = '';
127:         $lang = '';
128:         $colspan = '';
129:         $rowspan = '';
130:         $id = '';
131:         $atts = '';
132: 
133:         if (!empty($in)) {
134:             $matched = $in;
135:             if ($element == 'td') {
136:                 if (preg_match("/\\\\(\d+)/", $matched, $csp)) $colspan = $csp[1];
137:                 if (preg_match("/\/(\d+)/", $matched, $rsp)) $rowspan = $rsp[1];
138:             }
139: 
140:             if ($element == 'td' || $element == 'tr') {
141:                 if (preg_match('/(' . self::REGEX_A_VLGN . ')/', $matched, $vert))
142:                     $style[] = 'vertical-align:' . $this->vAlign($vert[1]) . ';';
143:             }
144: 
145:             if (preg_match('/\{([^}]*)\}/', $matched, $sty)) {
146:                 $style[] = rtrim($sty[1], ';') . ';';
147:                 $matched = str_replace($sty[0], '', $matched);
148:             }
149: 
150:             if (preg_match('/\[([^]]+)\]/U', $matched, $lng)) {
151:                 $lang = $lng[1];
152:                 $matched = str_replace($lng[0], '', $matched);
153:             }
154: 
155:             if (preg_match('/\(([^()]+)\)/U', $matched, $cls)) {
156:                 $class = $cls[1];
157:                 $matched = str_replace($cls[0], '', $matched);
158:             }
159: 
160:             if (preg_match('/([(]+)/', $matched, $pl)) {
161:                 $style[] = 'padding-left:' . strlen($pl[1]) . 'em;';
162:                 $matched = str_replace($pl[0], '', $matched);
163:             }
164: 
165:             if (preg_match('/([)]+)/', $matched, $pr)) {
166:                 $style[] = 'padding-right:' . strlen($pr[1]) . 'em;';
167:                 $matched = str_replace($pr[0], '', $matched);
168:             }
169: 
170:             if (preg_match('/(' . self::REGEX_A_HLGN . ')/', $matched, $horiz)) {
171:                 $style[] = 'text-align:' . $this->hAlign($horiz[1]) . ';';
172:             }
173: 
174:             if (preg_match('/^(.*)#(.*)$/', $class, $ids)) {
175:                 $id = $ids[2];
176:                 $class = $ids[1];
177:             }
178: 
179:             return
180:                 ($style     ? ' style="'   . implode('', $style) . '"' : '')
181:                 . ($class   ? ' class="'   . $class              . '"' : '')
182:                 . ($lang    ? ' lang="'    . $lang               . '"' : '')
183:                 . ($id      ? ' id="'      . $id                 . '"' : '')
184:                 . ($colspan ? ' colspan="' . $colspan            . '"' : '')
185:                 . ($rowspan ? ' rowspan="' . $rowspan            . '"' : '');
186:         }
187: 
188:         return '';
189:     }
190: 
191:     /**
192:      * @ignore
193:      */
194:     public function hasRawText($text)
195:     {
196:         // Checks whether the text has text not already enclosed by a
197:         // block tag.
198:         $r = trim(preg_replace('@<(p|blockquote|div|form|table|ul|ol|pre|h\d)[^>]*?>.*</\1>@s', '', trim($text)));
199:         $r = trim(preg_replace('@<\/?(p|blockquote|div|form|table|ul|ol|pre|h\d)[^>]*?\/?>@s', '', $r));
200:         $r = trim(preg_replace('@<(hr|br)[^>]*?/>@', '', $r));
201:         return '' != $r;
202:     }
203: 
204:     /**
205:      * @ignore
206:      */
207:     public function table($text)
208:     {
209:         $text = $text . "\n\n";
210:         return preg_replace_callback("/^(?:table(_?" . self::REGEX_S . self::REGEX_A . self::REGEX_C . ")\. ?\n)?^(" . self::REGEX_A . self::REGEX_C . "\.? ?\|.*\|)\n\n/smU",
211:                                      array($this, 'fTable'), $text);
212:     }
213: 
214:     /**
215:      * @ignore
216:      */
217:     public function fTable($matches)
218:     {
219:         $tatts = $this->parseBlockAttributes($matches[1], 'table');
220: 
221:         foreach (preg_split("/\|$/m", $matches[2], -1, PREG_SPLIT_NO_EMPTY) as $row) {
222:             if (preg_match("/^(" . self::REGEX_A . self::REGEX_C . "\. )(.*)/m", ltrim($row), $rmtch)) {
223:                 $ratts = $this->parseBlockAttributes($rmtch[1], 'tr');
224:                 $row = $rmtch[2];
225:             } else {
226:                 $ratts = '';
227:             }
228:             $cells = array();
229:             foreach (explode('|', $row) as $cell) {
230:                 $ctyp = 'd';
231:                 if (preg_match("/^_/", $cell)) {
232:                     $ctyp = 'h';
233:                 }
234:                 if (preg_match("/^(_?" . self::REGEX_S . self::REGEX_A . self::REGEX_C . "\. )(.*)/", $cell, $cmtch)) {
235:                     $catts = $this->parseBlockAttributes($cmtch[1], 'td');
236:                     $cell = $cmtch[2];
237:                 } else {
238:                     $catts = '';
239:                 }
240: 
241:                 $cell = $this->paragraph($this->span($cell));
242:                 if (trim($cell) != '') {
243:                     $cells[] = "\t\t\t<t$ctyp$catts>$cell</t$ctyp>";
244:                 }
245:             }
246:             $rows[] = "\t\t<tr$ratts>\n" . implode("\n", $cells) . ($cells ? "\n" : '') . "\t\t</tr>";
247:             unset($cells, $catts);
248:         }
249:         return "\t<table$tatts>\n" . implode("\n", $rows) . "\n\t</table>\n\n";
250:     }
251: 
252:     /**
253:      * @ignore
254:      */
255:     public function lists($text)
256:     {
257:         return preg_replace_callback("/^([#*]+" . self::REGEX_C . ".*)$(?![^#*])/smU", array($this, 'fList'), $text);
258:     }
259: 
260:     /**
261:      * @ignore
262:      */
263:     public function fList($m)
264:     {
265:         $out = array();
266:         $lines = explode("\n", $m[0]);
267:         for ($i = 0, $i_max = count($lines); $i < $i_max; $i++) {
268:             $line = $lines[$i];
269:             $nextline = isset($lines[$i + 1]) ? $lines[$i + 1] : false;
270: 
271:             if (preg_match("/^([#*]+)(" . self::REGEX_A . self::REGEX_C . ") (.*)$/s", $line, $m)) {
272:                 list(, $tl, $atts, $content) = $m;
273:                 $nl = '';
274:                 if (preg_match("/^([#*]+)\s.*/", $nextline, $nm)) {
275:                     $nl = $nm[1];
276:                 }
277:                 $level = strlen($tl);
278:                 if (!isset($lists[$tl])) {
279:                     $lists[$tl] = true;
280:                     $atts = $this->parseBlockAttributes($atts);
281:                     $line = str_repeat("\t", $level) . '<' . $this->lT($tl) . "l$atts>\n" . str_repeat("\t", $level + 1) . '<li>' . $this->paragraph($content);
282:                 } else {
283:                     $line = str_repeat("\t", $level + 1) . '<li>' . $this->paragraph($content);
284:                 }
285: 
286:                 if (strlen($nl) <= strlen($tl)) {
287:                     $line .= '</li>';
288:                 }
289:                 foreach (array_reverse($lists) as $k => $v) {
290:                     if (strlen($k) > strlen($nl)) {
291:                         $line .= "\n" . str_repeat("\t", $level--) . '</' . $this->lT($k) . 'l>';
292:                         if (strlen($k) > 1) {
293:                             $line .= '</li>';
294:                         }
295:                         unset($lists[$k]);
296:                     }
297:                 }
298:             }
299: 
300:             $out[] = $line;
301:         }
302: 
303:         return implode("\n", $out);
304:     }
305: 
306:     /**
307:      * @ignore
308:      */
309:     public function lT($in)
310:     {
311:         return substr($in, 0, 1) == '#' ? 'o' : 'u';
312:     }
313: 
314:     /**
315:      * @ignore
316:      */
317:     public function doPBr($in)
318:     {
319:         return preg_replace_callback('@<(p)([^>]*?)>(.*)(</\1>)@s', array($this, 'doBr'), $in);
320:     }
321: 
322:     /**
323:      * @ignore
324:      */
325:     public function doBr($m)
326:     {
327:         $content = preg_replace("@(.+)(?<!<br>|<br />)\n(?![#*\s|])@", "\$1<br />\n", $m[3]);
328:         return '<' . $m[1] . $m[2] . '>' . $content . $m[4];
329:     }
330: 
331:     /**
332:      * @ignore
333:      */
334:     public function block($text)
335:     {
336:         $tag = 'p';
337:         $atts = $cite = $graf = $ext = '';
338: 
339:         $text = explode("\n\n", $text);
340:         foreach ($text as $line) {
341:             $anon = 0;
342:             if (preg_match('/^(' . self::REGEX_BLOCK_TAGS . ')(' . self::REGEX_A . self::REGEX_C . ')\.(\.?)(?::(\S+))? (.*)$/s', $line, $m)) {
343:                 if ($ext) {
344:                     // last block was extended, so close it
345:                     $out[count($out) - 1] .= $c1;
346:                 }
347:                 // new block
348:                 list(, $tag, $atts, $ext, $cite, $graf) = $m;
349:                 list($o1, $o2, $content, $c2, $c1) = $this->fBlock(array(0, $tag, $atts, $ext, $cite, $graf));
350: 
351:                 // leave off c1 if this block is extended, we'll close
352:                 // it at the start of the next block
353:                 if ($ext) {
354:                     $line = $o1 . $o2 . $content . $c2;
355:                 } else {
356:                     $line = $o1 . $o2 . $content . $c2 . $c1;
357:                 }
358:             } else {
359:                 // anonymous block
360:                 $anon = 1;
361:                 if ($ext || !preg_match('/^ /', $line)) {
362:                     list($o1, $o2, $content, $c2, $c1) = $this->fBlock(array(0, $tag, $atts, $ext, $cite, $line));
363:                     // skip $o1/$c1 because this is part of a
364:                     // continuing extended block
365:                     if ($tag == 'p' && !$this->hasRawText($content)) {
366:                         $line = $content;
367:                     } else {
368:                         $line = $o2 . $content . $c2;
369:                     }
370:                 } else {
371:                    $line = $this->paragraph($line);
372:                 }
373:             }
374: 
375:             $line = preg_replace('/<br>/', '<br />', $this->doPBr($line));
376: 
377:             if ($ext && $anon) {
378:                 $out[count($out) - 1] .= "\n" . $line;
379:             } else {
380:                 $out[] = $line;
381:             }
382: 
383:             if (!$ext) {
384:                 $tag = 'p';
385:                 $atts = '';
386:                 $cite = '';
387:                 $graf = '';
388:             }
389:         }
390:         if ($ext) {
391:             $out[count($out) - 1] .= $c1;
392:         }
393:         return implode("\n\n", $out);
394:     }
395: 
396:     /**
397:      * @ignore
398:      */
399:     public function fBlock($m)
400:     {
401:         list(, $tag, $atts, $ext, $cite, $content) = $m;
402:         $atts = $this->parseBlockAttributes($atts);
403: 
404:         $o1 = $o2 = $c2 = $c1 = '';
405: 
406:         if (preg_match('/fn(\d+)/', $tag, $fns)) {
407:             $tag = 'p';
408:             $fnid = $fns[1];
409:             $atts .= ' id="fn' . $fnid . '"';
410:             $content = '<sup>' . $fns[1] . '</sup> ' . $content . ' <a href="#fnr' . $fnid . '">' . self::$GLYPH_RETURN_ARROW . '</a>';
411:         }
412: 
413:         if ($tag == 'bq') {
414:             $cite = $this->checkRefs($cite);
415:             $cite = ($cite != '') ? ' cite="' . $cite . '"' : '';
416:             $o1 = '<blockquote' . $cite . $atts . ">\n";
417:             $o2 = "<p$atts>";
418:             $c2 = '</p>';
419:             $c1 = "\n</blockquote>";
420:         } elseif ($tag == 'bc') {
421:             $o1 = "<pre$atts>";
422:             $o2 = "<code$atts>";
423:             $c2 = '</code>';
424:             $c1 = '</pre>';
425:             $content = $this->shelve($this->encodeHtml(rtrim($content, "\n") . "\n"));
426:         } elseif ($tag == 'notextile') {
427:             $content = $this->shelve($content);
428:             $o1 = $o2 = '';
429:             $c1 = $c2 = '';
430:         } elseif ($tag == 'pre') {
431:             $content = $this->shelve($this->encodeHtml(rtrim($content, "\n") . "\n"));
432:             $o1 = "<pre$atts>";
433:             $o2 = $c2 = '';
434:             $c1 = '</pre>';
435:         } else {
436:             $o2 = "<$tag$atts>";
437:             $c2 = "</$tag>";
438:         }
439: 
440:         return array($o1, $o2, $this->paragraph($content), $c2, $c1);
441:     }
442: 
443:     /**
444:      * Handle normal paragraph text.
445:      * @ignore
446:      */
447:     public function paragraph($text)
448:     {
449:         $text = $this->code($this->noTextile($text));
450:         $text = $this->links($text);
451:         if ($this->images) {
452:             $text = $this->image($text);
453:         }
454: 
455:         $text = $this->table($this->lists($text));
456:         $text = $this->glyphs($this->footnoteRef($this->span($text)));
457:         return rtrim($text, "\n");
458:     }
459: 
460:     /**
461:      * @ignore
462:      */
463:     public function span($text)
464:     {
465:         $qtags = array('\*\*', '\*', '\?\?', '-', '__', '_', '%', '\+', '~', '\^');
466:         $pnct = ".,\"'?!;:";
467: 
468:         foreach ($qtags as $f) {
469:             $text = preg_replace_callback("/
470:                 (?:^|(?<=[\s>$pnct])|([{[]))
471:                 ($f)(?!$f)
472:                 (" . self::REGEX_C . ")
473:                 (?::(\S+))?
474:                 ([^\s$f]+|\S[^$f\n]*[^\s$f\n])
475:                 ([$pnct]*)
476:                 $f
477:                 (?:$|([\]}])|(?=[[:punct:]]{1,2}|\s))
478:             /x", array($this, 'fSpan'), $text);
479:         }
480:         return $text;
481:     }
482: 
483:     /**
484:      * @ignore
485:      */
486:     public function fSpan($m)
487:     {
488:         $qtags = array(
489:             '*'  => 'strong',
490:             '**' => 'b',
491:             '??' => 'cite',
492:             '_'  => 'em',
493:             '__' => 'i',
494:             '-'  => 'del',
495:             '%'  => 'span',
496:             '+'  => 'ins',
497:             '~'  => 'sub',
498:             '^'  => 'sup',
499:         );
500: 
501:         list(, , $tag, $atts, $cite, $content, $end) = $m;
502:         $tag = $qtags[$tag];
503:         $atts = $this->parseBlockAttributes($atts)
504:             . ($cite ? 'cite="' . $cite . '"' : '');
505: 
506:         return "<$tag$atts>$content$end</$tag>";
507:     }
508: 
509:     /**
510:      * @ignore
511:      */
512:     public function links($text)
513:     {
514:         $punct = preg_quote('!"#$%&\'*+,-./:;=?@\\^_`|~', '/');
515:         return preg_replace_callback('/
516:             (^|(?<=[\s>.' . self::REGEX_PUNCT . '\(])|([{[]))  # $pre
517:             "                                                  # $start
518:             (' . self::REGEX_C . ')                            # $atts
519:             ([^"]+)                                            # $text
520:             \s?
521:             (?:\(([^)]+)\)(?="))?                              # $title
522:             ":
523:             (' . self::REGEX_URL . '+)                         # $url
524:             (\/)?                                              # $slash
525:             ([^\w\/;]*)                                        # $post
526:             (?:([\]}])|(?=\s|$|\)))
527:         /Ux', array($this, 'fLink'), $text);
528:     }
529: 
530:     /**
531:      * @ignore
532:      */
533:     public function fLink($m)
534:     {
535:         list(, $pre, $start, $atts, $text, $title, $url, $slash, $post) = $m;
536: 
537:         $atts = $this->parseBlockAttributes($atts)
538:             . ($title != '') ? ' title="' . $this->encodeHtml($title) . '"' : '';
539: 
540:         if ($this->images) {
541:             $text = $this->image($text);
542:         }
543:         $text = $this->glyphs($this->span($text));
544: 
545:         $url = $this->checkRefs($url);
546: 
547:         return $this->shelve('<a href="'
548:                              . $this->encodeHtml($url . $slash)
549:                              . '"' . $atts . ($this->rel ? ' rel="' . $this->rel . '" ' : '') . '>'
550:                              . $text . '</a>' . $post);
551:     }
552: 
553:     /**
554:      * @ignore
555:      */
556:     public function getRefs($text)
557:     {
558:         return preg_replace_callback("/(?<=^|\s)\[(.+)\]((?:http:\/\/|\/)\S+)(?=\s|$)/U",
559:             array($this, 'refs'), $text);
560:     }
561: 
562:     /**
563:      * @ignore
564:      */
565:     public function refs($m)
566:     {
567:         list(, $flag, $url) = $m;
568:         $this->urlrefs[$flag] = $url;
569:         return '';
570:     }
571: 
572:     /**
573:      * @ignore
574:      */
575:     public function checkRefs($text)
576:     {
577:         return isset($this->urlrefs[$text]) ? $this->urlrefs[$text] : $text;
578:     }
579: 
580:     /**
581:      * @ignore
582:      */
583:     public function image($text)
584:     {
585:         return preg_replace_callback("/
586:             (?:[[{])?               # pre
587:             \!                      # opening !
588:             (\<|\=|\>)??            # optional alignment attributes
589:             (" . self::REGEX_C . ") # optional style, class attributes
590:             (?:\. )?                # optional dot-space
591:             ([^\s(!]+)              # presume this is the src
592:             \s?                     # optional space
593:             (?:\(([^\)]+)\))?       # optional title
594:             \!                      # closing
595:             (?::(\S+))?             # optional href
596:             (?:[\]}]|(?=\s|$))      # lookahead: space or end of string
597:         /Ux", array($this, 'fImage'), $text);
598:     }
599: 
600:     /**
601:      * @ignore
602:      */
603:     public function fImage($m)
604:     {
605:         list(, $algn, $atts, $url) = $m;
606:         $title = isset($m[4]) ? $m[4] : '';
607:         $atts = $this->parseBlockAttributes($atts)
608:             . ($algn != '' ? ' align="' . $this->iAlign($algn) . '"' : '')
609:             . ($title ? ' title="' . $title . '"' : '')
610:             . ' alt="'   . $title . '"';
611: 
612:         $href = isset($m[5]) ? $this->checkRefs($m[5]) : '';
613:         $url = $this->checkRefs($url);
614: 
615:         return ($href ? '<a href="' . $href . '">' : '')
616:             . '<img src="' . $url . '"' . $atts . ' />'
617:             . ($href ? '</a>' : '');
618:     }
619: 
620:     /**
621:      * @ignore
622:      */
623:     public function code($text)
624:     {
625:         $text = $this->doSpecial($text, '<code>', '</code>', 'fCode');
626:         $text = $this->doSpecial($text, '@', '@', 'fCode');
627:         $text = $this->doSpecial($text, '<pre>', '</pre>', 'fPre');
628:         return $text;
629:     }
630: 
631:     /**
632:      * @ignore
633:      */
634:     public function fCode($m)
635:     {
636:         @list(, $before, $text, $after) = $m;
637:         return $before . $this->shelve('<code>' . $this->encodeHtml($text, false) . '</code>') . $after;
638:     }
639: 
640:     /**
641:      * @ignore
642:      */
643:     public function fPre($m)
644:     {
645:         @list(, $before, $text, $after) = $m;
646:         return $before . '<pre>' . $this->shelve($this->encodeHtml($text, false)) . '</pre>' . $after;
647:     }
648: 
649:     /**
650:      * @ignore
651:      */
652:     public function shelve($val)
653:     {
654:         $i = uniqid(mt_rand());
655:         $this->_shelf[$i] = $val;
656:         return $i;
657:     }
658: 
659:     /**
660:      * @ignore
661:      */
662:     public function retrieve($text)
663:     {
664:         if (is_array($this->_shelf)) {
665:             do {
666:                 $old = $text;
667:                 $text = strtr($text, $this->_shelf);
668:             } while ($text != $old);
669:         }
670:         return $text;
671:     }
672: 
673:     /**
674:      * @ignore
675:      */
676:     public function cleanWhiteSpace($text)
677:     {
678:         return preg_replace(array("/\r\n/", "/\n{3,}/", "/\n *\n/"),
679:                             array("\n",     "\n\n",     "\n\n"),
680:                             $text);
681:     }
682: 
683:     /**
684:      * @ignore
685:      */
686:     public function doSpecial($text, $start, $end, $method = 'fSpecial')
687:     {
688:         return preg_replace_callback('/(^|\s|[[({>])' . preg_quote($start, '/') . '(.*?)' . preg_quote($end, '/') . '(\s|$|[\])}])?/ms',
689:                                      array($this, $method), $text);
690:     }
691: 
692:     /**
693:      * @ignore
694:      */
695:     public function fSpecial($m)
696:     {
697:         // A special block like notextile or code
698:         @list(, $before, $text, $after) = $m;
699:         return $before . $this->shelve($this->encodeHtml($text)) . $after;
700:     }
701: 
702:     /**
703:      * @ignore
704:      */
705:     public function noTextile($text)
706:     {
707:          $text = $this->doSpecial($text, '<notextile>', '</notextile>', 'fTextile');
708:          return $this->doSpecial($text, '==', '==', 'fTextile');
709:     }
710: 
711:     /**
712:      * @ignore
713:      */
714:     public function fTextile($m)
715:     {
716:         @list(, $before, $notextile, $after) = $m;
717:         return $before . $this->shelve($notextile) . $after;
718:     }
719: 
720:     /**
721:      * @ignore
722:      */
723:     public function footnoteRef($text)
724:     {
725:         return preg_replace('/\b\[([0-9]+)\](\s)?/U',
726:                             '<sup><a id="fnr$1" href="#fn$1">$1</a></sup>$2',
727:                             $text);
728:     }
729: 
730:     /**
731:      * @ignore
732:      */
733:     public function glyphs($text)
734:     {
735:         $glyph_search = array(
736:             '/(\w)\'(\w)/',                                           // apostrophe's
737:             '/(\s)\'(\d+\w?)\b(?!\')/',                               // back in '88
738:             '/(\S)\'(?=\s|[[:punct:]]|<|$)/',                         // single closing
739:             '/\'/',                                                   // single opening
740:             '/(\S)\"(?=\s|[[:punct:]]|<|$)/',                         // double closing
741:             '/"/',                                                    // double opening
742:             '/\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/',             // 3+ uppercase acronym
743:             '/\b([A-Z][A-Z\'\-]+[A-Z])(?=[\s.,\)>])/',                // 3+ uppercase
744:             '/\b( )?\.{3}/',                                          // ellipsis
745:             '/(\s?)--(\s?)/',                                         // em dash
746:             '/\s-(?:\s|$)/',                                          // en dash
747:             '/(\d+)( ?)x( ?)(?=\d+)/',                                // dimension sign
748:             '/\b ?[([]TM[])]/i',                                      // trademark
749:             '/\b ?[([]R[])]/i',                                       // registered
750:             '/\b ?[([]C[])]/i',                                       // copyright
751:         );
752: 
753:         $glyph_replace = array(
754:             '$1' . self::$GLYPH_APOSTROPHE . '$2',      // apostrophes
755:             '$1' . self::$GLYPH_APOSTROPHE . '$2',      // back in '88
756:             '$1' . self::$GLYPH_QUOTE_SINGLE_CLOSE,     // single closing
757:             self::$GLYPH_QUOTE_SINGLE_OPEN,             // single opening
758:             '$1' . self::$GLYPH_QUOTE_DOUBLE_CLOSE,     // double closing
759:             self::$GLYPH_QUOTE_DOUBLE_OPEN,             // double opening
760:             '<acronym title="$2">$1</acronym>',                       // 3+ uppercase acronym
761:             '<span class="caps">$1</span>',                           // 3+ uppercase
762:             '$1' . self::$GLYPH_ELLIPSIS,               // ellipsis
763:             self::$GLYPH_EMDASH,                        // em dash
764:             ' ' . self::$GLYPH_ENDASH . ' ',            // en dash
765:             '$1' . self::$GLYPH_DIMENSION,              // dimension sign
766:             self::$GLYPH_TRADEMARK,                     // trademark
767:             self::$GLYPH_REGISTERED,                    // registered
768:             self::$GLYPH_COPYRIGHT,                     // copyright
769:         );
770: 
771:         $text = preg_split('/(<.*>)/U', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
772:         foreach ($text as $line) {
773:             if (!preg_match('/<.*>/', $line)) {
774:                 $line = preg_replace($glyph_search, $glyph_replace, $line);
775:             }
776:             $glyph_out[] = $line;
777:         }
778: 
779:         return implode('', $glyph_out);
780:     }
781: 
782:     /**
783:      * @ignore
784:      */
785:     public function iAlign($in)
786:     {
787:         $vals = array(
788:             '<' => 'left',
789:             '=' => 'center',
790:             '>' => 'right');
791:         return isset($vals[$in]) ? $vals[$in] : '';
792:     }
793: 
794:     /**
795:      * @ignore
796:      */
797:     public function hAlign($in)
798:     {
799:         $vals = array(
800:             '<'  => 'left',
801:             '='  => 'center',
802:             '>'  => 'right',
803:             '<>' => 'justify');
804:         return isset($vals[$in]) ? $vals[$in] : '';
805:     }
806: 
807:     /**
808:      * @ignore
809:      */
810:     public function vAlign($in)
811:     {
812:         $vals = array(
813:             '^' => 'top',
814:             '-' => 'middle',
815:             '~' => 'bottom');
816:         return isset($vals[$in]) ? $vals[$in] : '';
817:     }
818: 
819:     /**
820:      * @ignore
821:      */
822:     public function encodeHtml($str, $quotes = true)
823:     {
824:         $a = array(
825:             '&' => '&amp;',
826:             '<' => '&lt;',
827:             '>' => '&gt;',
828:         );
829:         if ($quotes) {
830:             $a = $a + array(
831:                 "'" => '&#39;',
832:                 '"' => '&#34;',
833:             );
834:         }
835: 
836:         return strtr($str, $a);
837:     }
838: 
839: }
840: 
API documentation generated by ApiGen