Commit | Line | Data |
---|---|---|
6855525e JL |
1 | <?php |
2 | ||
3 | /** | |
4 | * Example: get XHTML from a given Textile-markup string ($string) | |
5 | * | |
6 | * $textile = new Textile; | |
7 | * echo $textile->TextileThis($string); | |
8 | * | |
9 | */ | |
10 | ||
11 | /* | |
12 | $HeadURL: http://svn.textpattern.com/development/4.0/textpattern/lib/classTextile.php $ | |
13 | $LastChangedRevision: 1072 $ | |
14 | */ | |
15 | ||
16 | /* | |
17 | ||
18 | _____________ | |
19 | T E X T I L E | |
20 | ||
21 | A Humane Web Text Generator | |
22 | ||
23 | Version 2.0 beta | |
24 | ||
25 | Copyright (c) 2003-2004, Dean Allen <dean@textism.com> | |
26 | All rights reserved. | |
27 | ||
28 | Thanks to Carlo Zottmann <carlo@g-blog.net> for refactoring | |
29 | Textile's procedural code into a class framework | |
30 | ||
31 | _____________ | |
32 | L I C E N S E | |
33 | ||
34 | Redistribution and use in source and binary forms, with or without | |
35 | modification, are permitted provided that the following conditions are met: | |
36 | ||
37 | * Redistributions of source code must retain the above copyright notice, | |
38 | this list of conditions and the following disclaimer. | |
39 | ||
40 | * Redistributions in binary form must reproduce the above copyright notice, | |
41 | this list of conditions and the following disclaimer in the documentation | |
42 | and/or other materials provided with the distribution. | |
43 | ||
44 | * Neither the name Textile nor the names of its contributors may be used to | |
45 | endorse or promote products derived from this software without specific | |
46 | prior written permission. | |
47 | ||
48 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
49 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
50 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
51 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
52 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
53 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
54 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
55 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
56 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
57 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
58 | POSSIBILITY OF SUCH DAMAGE. | |
59 | ||
60 | _________ | |
61 | U S A G E | |
62 | ||
63 | Block modifier syntax: | |
64 | ||
65 | Header: h(1-6). | |
66 | Paragraphs beginning with 'hn. ' (where n is 1-6) are wrapped in header tags. | |
67 | Example: h1. Header... -> <h1>Header...</h1> | |
68 | ||
69 | Paragraph: p. (also applied by default) | |
70 | Example: p. Text -> <p>Text</p> | |
71 | ||
72 | Blockquote: bq. | |
73 | Example: bq. Block quotation... -> <blockquote>Block quotation...</blockquote> | |
74 | ||
75 | Blockquote with citation: bq.:http://citation.url | |
76 | Example: bq.:http://textism.com/ Text... | |
77 | -> <blockquote cite="http://textism.com">Text...</blockquote> | |
78 | ||
79 | Footnote: fn(1-100). | |
80 | Example: fn1. Footnote... -> <p id="fn1">Footnote...</p> | |
81 | ||
82 | Numeric list: #, ## | |
83 | Consecutive paragraphs beginning with # are wrapped in ordered list tags. | |
84 | Example: <ol><li>ordered list</li></ol> | |
85 | ||
86 | Bulleted list: *, ** | |
87 | Consecutive paragraphs beginning with * are wrapped in unordered list tags. | |
88 | Example: <ul><li>unordered list</li></ul> | |
89 | ||
90 | Phrase modifier syntax: | |
91 | ||
92 | _emphasis_ -> <em>emphasis</em> | |
93 | __italic__ -> <i>italic</i> | |
94 | *strong* -> <strong>strong</strong> | |
95 | **bold** -> <b>bold</b> | |
96 | ??citation?? -> <cite>citation</cite> | |
97 | -deleted text- -> <del>deleted</del> | |
98 | +inserted text+ -> <ins>inserted</ins> | |
99 | ^superscript^ -> <sup>superscript</sup> | |
100 | ~subscript~ -> <sub>subscript</sub> | |
101 | @code@ -> <code>computer code</code> | |
102 | %(bob)span% -> <span class="bob">span</span> | |
103 | ||
104 | ==notextile== -> leave text alone (do not format) | |
105 | ||
106 | "linktext":url -> <a href="url">linktext</a> | |
107 | "linktext(title)":url -> <a href="url" title="title">linktext</a> | |
108 | ||
109 | !imageurl! -> <img src="imageurl" /> | |
110 | !imageurl(alt text)! -> <img src="imageurl" alt="alt text" /> | |
111 | !imageurl!:linkurl -> <a href="linkurl"><img src="imageurl" /></a> | |
112 | ||
113 | ABC(Always Be Closing) -> <acronym title="Always Be Closing">ABC</acronym> | |
114 | ||
115 | ||
116 | Table syntax: | |
117 | ||
118 | Simple tables: | |
119 | ||
120 | |a|simple|table|row| | |
121 | |And|Another|table|row| | |
122 | ||
123 | |_. A|_. table|_. header|_.row| | |
124 | |A|simple|table|row| | |
125 | ||
126 | Tables with attributes: | |
127 | ||
128 | table{border:1px solid black}. | |
129 | {background:#ddd;color:red}. |{}| | | | | |
130 | ||
131 | ||
132 | Applying Attributes: | |
133 | ||
134 | Most anywhere Textile code is used, attributes such as arbitrary css style, | |
135 | css classes, and ids can be applied. The syntax is fairly consistent. | |
136 | ||
137 | The following characters quickly alter the alignment of block elements: | |
138 | ||
139 | < -> left align ex. p<. left-aligned para | |
140 | > -> right align h3>. right-aligned header 3 | |
141 | = -> centred h4=. centred header 4 | |
142 | <> -> justified p<>. justified paragraph | |
143 | ||
144 | These will change vertical alignment in table cells: | |
145 | ||
146 | ^ -> top ex. |^. top-aligned table cell| | |
147 | - -> middle |-. middle aligned| | |
148 | ~ -> bottom |~. bottom aligned cell| | |
149 | ||
150 | Plain (parentheses) inserted between block syntax and the closing dot-space | |
151 | indicate classes and ids: | |
152 | ||
153 | p(hector). paragraph -> <p class="hector">paragraph</p> | |
154 | ||
155 | p(#fluid). paragraph -> <p id="fluid">paragraph</p> | |
156 | ||
157 | (classes and ids can be combined) | |
158 | p(hector#fluid). paragraph -> <p class="hector" id="fluid">paragraph</p> | |
159 | ||
160 | Curly {brackets} insert arbitrary css style | |
161 | ||
162 | p{line-height:18px}. paragraph -> <p style="line-height:18px">paragraph</p> | |
163 | ||
164 | h3{color:red}. header 3 -> <h3 style="color:red">header 3</h3> | |
165 | ||
166 | Square [brackets] insert language attributes | |
167 | ||
168 | p[no]. paragraph -> <p lang="no">paragraph</p> | |
169 | ||
170 | %[fr]phrase% -> <span lang="fr">phrase</span> | |
171 | ||
172 | Usually Textile block element syntax requires a dot and space before the block | |
173 | begins, but since lists don't, they can be styled just using braces | |
174 | ||
175 | #{color:blue} one -> <ol style="color:blue"> | |
176 | # big <li>one</li> | |
177 | # list <li>big</li> | |
178 | <li>list</li> | |
179 | </ol> | |
180 | ||
181 | Using the span tag to style a phrase | |
182 | ||
183 | It goes like this, %{color:red}the fourth the fifth% | |
184 | -> It goes like this, <span style="color:red">the fourth the fifth</span> | |
185 | ||
186 | */ | |
187 | ||
188 | class Textile | |
189 | { | |
190 | var $hlgn; | |
191 | var $vlgn; | |
192 | var $clas; | |
193 | var $lnge; | |
194 | var $styl; | |
195 | var $cspn; | |
196 | var $rspn; | |
197 | var $a; | |
198 | var $s; | |
199 | var $c; | |
200 | var $pnct; | |
201 | var $rel; | |
202 | var $fn; | |
203 | ||
204 | // ------------------------------------------------------------- | |
205 | function Textile() | |
206 | { | |
207 | $this->hlgn = "(?:\<(?!>)|(?<!<)\>|\<\>|\=|[()]+)"; | |
208 | $this->vlgn = "[\-^~]"; | |
209 | $this->clas = "(?:\([^)]+\))"; | |
210 | $this->lnge = "(?:\[[^]]+\])"; | |
211 | $this->styl = "(?:\{[^}]+\})"; | |
212 | $this->cspn = "(?:\\\\\d+)"; | |
213 | $this->rspn = "(?:\/\d+)"; | |
214 | $this->a = "(?:{$this->hlgn}?{$this->vlgn}?|{$this->vlgn}?{$this->hlgn}?)"; | |
215 | $this->s = "(?:{$this->cspn}?{$this->rspn}?|{$this->rspn}?{$this->cspn}?)"; | |
216 | $this->c = "(?:{$this->clas}?{$this->styl}?{$this->lnge}?|{$this->styl}?{$this->lnge}?{$this->clas}?|{$this->lnge}?{$this->styl}?{$this->clas}?)"; | |
217 | $this->pnct = '[\!"#\$%&\'()\*\+,\-\./:;<=>\?@\[\\\]\^_`{\|}\~]'; | |
218 | ||
219 | } | |
220 | ||
221 | // ------------------------------------------------------------- | |
222 | function TextileThis($text, $lite='', $encode='', $noimage='', $strict='', $rel='') | |
223 | { | |
224 | if ($rel) | |
225 | $this->rel = ' rel="'.$rel.'" '; | |
226 | ||
227 | $text = $this->incomingEntities($text); | |
228 | ||
229 | if ($encode) { | |
230 | $text = str_replace("x%x%", "&", $text); | |
231 | return $text; | |
232 | } else { | |
233 | ||
234 | if(!$strict) { | |
235 | $text = $this->fixEntities($text); | |
236 | $text = $this->cleanWhiteSpace($text); | |
237 | } | |
238 | ||
239 | $text = $this->getRefs($text); | |
240 | ||
241 | $text = $this->noTextile($text); | |
242 | $text = $this->links($text); | |
243 | if (!$noimage) { | |
244 | $text = $this->image($text); | |
245 | } | |
246 | $text = $this->code($text); | |
247 | $text = $this->span($text); | |
248 | $text = $this->superscript($text); | |
249 | $text = $this->footnoteRef($text); | |
250 | $text = $this->glyphs($text); | |
251 | $text = $this->retrieve($text); | |
252 | ||
253 | if (!$lite) { | |
254 | $text = $this->lists($text); | |
255 | $text = $this->table($text); | |
256 | $text = $this->block($text); | |
257 | } | |
258 | ||
259 | // clean up <notextile> | |
260 | $text = preg_replace('/<\/?notextile>/', "", $text); | |
261 | ||
262 | // turn the temp char back to an ampersand entity | |
263 | $text = str_replace("x%x%", "&", $text); | |
264 | ||
265 | // just to be tidy | |
266 | $text = str_replace("<br />", "<br />\n", $text); | |
267 | ||
268 | return $text; | |
269 | } | |
270 | } | |
271 | ||
272 | // ------------------------------------------------------------- | |
273 | function pba($in, $element = "") // "parse block attributes" | |
274 | { | |
275 | $style = ''; | |
276 | $class = ''; | |
277 | $lang = ''; | |
278 | $colspan = ''; | |
279 | $rowspan = ''; | |
280 | $id = ''; | |
281 | $atts = ''; | |
282 | ||
283 | if (!empty($in)) { | |
284 | $matched = $in; | |
285 | if ($element == 'td') { | |
286 | if (preg_match("/\\\\(\d+)/", $matched, $csp)) $colspan = $csp[1]; | |
287 | if (preg_match("/\/(\d+)/", $matched, $rsp)) $rowspan = $rsp[1]; | |
288 | ||
289 | if (preg_match("/($this->vlgn)/", $matched, $vert)) | |
290 | $style[] = "vertical-align:" . $this->vAlign($vert[1]) . ";"; | |
291 | } | |
292 | ||
293 | if (preg_match("/\{([^}]*)\}/", $matched, $sty)) { | |
294 | $style[] = $sty[1] . ';'; | |
295 | $matched = str_replace($sty[0], '', $matched); | |
296 | } | |
297 | ||
298 | if (preg_match("/\[([^)]+)\]/U", $matched, $lng)) { | |
299 | $lang = $lng[1]; | |
300 | $matched = str_replace($lng[0], '', $matched); | |
301 | } | |
302 | ||
303 | if (preg_match("/\(([^()]+)\)/U", $matched, $cls)) { | |
304 | $class = $cls[1]; | |
305 | $matched = str_replace($cls[0], '', $matched); | |
306 | } | |
307 | ||
308 | if (preg_match("/([(]+)/", $matched, $pl)) { | |
309 | $style[] = "padding-left:" . strlen($pl[1]) . "em;"; | |
310 | $matched = str_replace($pl[0], '', $matched); | |
311 | } | |
312 | ||
313 | if (preg_match("/([)]+)/", $matched, $pr)) { | |
314 | // $this->dump($pr); | |
315 | $style[] = "padding-right:" . strlen($pr[1]) . "em;"; | |
316 | $matched = str_replace($pr[0], '', $matched); | |
317 | } | |
318 | ||
319 | if (preg_match("/($this->hlgn)/", $matched, $horiz)) | |
320 | $style[] = "text-align:" . $this->hAlign($horiz[1]) . ";"; | |
321 | ||
322 | if (preg_match("/^(.*)#(.*)$/", $class, $ids)) { | |
323 | $id = $ids[2]; | |
324 | $class = $ids[1]; | |
325 | } | |
326 | ||
327 | return join('',array( | |
328 | ($style) ? ' style="' . join("", $style) .'"':'', | |
329 | ($class) ? ' class="' . $class .'"':'', | |
330 | ($lang) ? ' lang="' . $lang .'"':'', | |
331 | ($id) ? ' id="' . $id .'"':'', | |
332 | ($colspan) ? ' colspan="' . $colspan .'"':'', | |
333 | ($rowspan) ? ' rowspan="' . $rowspan .'"':'' | |
334 | )); | |
335 | } | |
336 | return ''; | |
337 | } | |
338 | ||
339 | // ------------------------------------------------------------- | |
340 | function table($text) | |
341 | { | |
342 | $text = $text . "\n\n"; | |
343 | return preg_replace_callback("/^(?:table(_?{$this->s}{$this->a}{$this->c})\. ?\n)?^({$this->a}{$this->c}\.? ?\|.*\|)\n\n/smU", | |
344 | array(&$this, "fTable"), $text); | |
345 | } | |
346 | ||
347 | // ------------------------------------------------------------- | |
348 | function fTable($matches) | |
349 | { | |
350 | $tatts = $this->pba($matches[1], 'table'); | |
351 | ||
352 | foreach(preg_split("/\|$/m", $matches[2], -1, PREG_SPLIT_NO_EMPTY) as $row) { | |
353 | if (preg_match("/^($this->a$this->c\. )(.*)/m", $row, $rmtch)) { | |
354 | $ratts = $this->pba($rmtch[1], 'tr'); | |
355 | $row = $rmtch[2]; | |
356 | } else $ratts = ''; | |
357 | ||
358 | foreach(explode("|", $row) as $cell) { | |
359 | $ctyp = "d"; | |
360 | if (preg_match("/^_/", $cell)) $ctyp = "h"; | |
361 | if (preg_match("/^(_?$this->s$this->a$this->c\. )(.*)/", $cell, $cmtch)) { | |
362 | $catts = $this->pba($cmtch[1], 'td'); | |
363 | $cell = $cmtch[2]; | |
364 | } else $catts = ''; | |
365 | ||
366 | if (trim($cell) != '') | |
367 | $cells[] = "\t\t\t<t$ctyp$catts>$cell</t$ctyp>"; | |
368 | } | |
369 | $rows[] = "\t\t<tr$ratts>\n" . join("\n", $cells) . "\n\t\t</tr>"; | |
370 | unset($cells, $catts); | |
371 | } | |
372 | return "\t<table$tatts>\n" . join("\n", $rows) . "\n\t</table>\n\n"; | |
373 | } | |
374 | ||
375 | // ------------------------------------------------------------- | |
376 | function lists($text) | |
377 | { | |
378 | return preg_replace_callback("/^([#*]+$this->c .*)$(?![^#*])/smU", array(&$this, "fList"), $text); | |
379 | } | |
380 | ||
381 | // ------------------------------------------------------------- | |
382 | function fList($m) | |
383 | { | |
384 | $text = explode("\n", $m[0]); | |
385 | foreach($text as $line) { | |
386 | $nextline = next($text); | |
387 | if (preg_match("/^([#*]+)($this->a$this->c) (.*)$/s", $line, $m)) { | |
388 | list(, $tl, $atts, $content) = $m; | |
389 | $nl = preg_replace("/^([#*]+)\s.*/", "$1", $nextline); | |
390 | if (!isset($lists[$tl])) { | |
391 | $lists[$tl] = true; | |
392 | $atts = $this->pba($atts); | |
393 | $line = "\t<" . $this->lT($tl) . "l$atts>\n\t\t<li>" . $content; | |
394 | } else { | |
395 | $line = "\t\t<li>" . $content; | |
396 | } | |
397 | ||
398 | if(strlen($nl) <= strlen($tl)) $line .= "</li>"; | |
399 | foreach(array_reverse($lists) as $k => $v) { | |
400 | if(strlen($k) > strlen($nl)) { | |
401 | $line .= "\n\t</" . $this->lT($k) . "l>"; | |
402 | if(strlen($k) > 1) | |
403 | $line .= "</li>"; | |
404 | unset($lists[$k]); | |
405 | } | |
406 | } | |
407 | } | |
408 | $out[] = $line; | |
409 | } | |
410 | return join("\n", $out); | |
411 | } | |
412 | ||
413 | // ------------------------------------------------------------- | |
414 | function lT($in) | |
415 | { | |
416 | return preg_match("/^#+/", $in) ? 'o' : 'u'; | |
417 | } | |
418 | ||
419 | // ------------------------------------------------------------- | |
420 | function block($text) | |
421 | { | |
422 | $pre = $php = $txp = false; | |
423 | $find = array('bq', 'h[1-6]', 'fn\d+', 'p'); | |
424 | ||
425 | $text = preg_replace("/(.+)\n(?![#*\s|])/", | |
426 | "$1<br />", $text); | |
427 | ||
428 | $text = explode("\n", $text); | |
429 | array_push($text, " "); | |
430 | ||
431 | foreach($text as $line) { | |
432 | if (preg_match('/<pre>/i', $line)) { | |
433 | $pre = true; | |
434 | } | |
435 | elseif (preg_match('/<txp:php>/i', $line)) { | |
436 | $php = true; | |
437 | } | |
438 | elseif (preg_match('/^\s*<txp:/i', $line)) { | |
439 | $txp = true; | |
440 | } | |
441 | ||
442 | ||
443 | foreach($find as $tag) { | |
444 | $line = ($pre == false and $php == false and $txp == false) | |
445 | ? preg_replace_callback("/^($tag)($this->a$this->c)\.(?::(\S+))? (.*)$/", | |
446 | array(&$this, "fBlock"), $line) | |
447 | : $line; | |
448 | } | |
449 | ||
450 | $line = (!$php and !$txp) ? preg_replace('/^(?!\t|<\/?pre|<\/?code|$| )(.*)/', "\t<p>$1</p>", $line) : $line; | |
451 | ||
452 | $line = ($pre or $php) ? str_replace("<br />", "\n", $line):$line; | |
453 | if (preg_match('/<\/pre>/i', $line)) { | |
454 | $pre = false; | |
455 | } | |
456 | elseif (preg_match('/<\/txp:php>/i', $line)) { | |
457 | $php = false; | |
458 | } | |
459 | if ($txp == true) $txp = false; | |
460 | $out[] = $line; | |
461 | } | |
462 | return join("\n", $out); | |
463 | } | |
464 | ||
465 | // ------------------------------------------------------------- | |
466 | function fBlock($m) | |
467 | { | |
468 | // $this->dump($m); | |
469 | list(, $tag, $atts, $cite, $content) = $m; | |
470 | ||
471 | $atts = $this->pba($atts); | |
472 | ||
473 | if (preg_match("/fn(\d+)/", $tag, $fns)) { | |
474 | $tag = 'p'; | |
475 | $fnid = empty($this->fn[$fns[1]]) ? $fns[1] : $this->fn[$fns[1]]; | |
476 | $atts .= ' id="fn' . $fnid . '"'; | |
477 | $content = '<sup>' . $fns[1] . '</sup> ' . $content; | |
478 | } | |
479 | ||
480 | $start = "\t<$tag"; | |
481 | $end = "</$tag>"; | |
482 | ||
483 | if ($tag == "bq") { | |
484 | $cite = $this->checkRefs($cite); | |
485 | $cite = ($cite != '') ? ' cite="' . $cite . '"' : ''; | |
486 | $start = "\t<blockquote$cite>\n\t\t<p"; | |
487 | $end = "</p>\n\t</blockquote>"; | |
488 | } | |
489 | ||
490 | return "$start$atts>$content$end"; | |
491 | } | |
492 | ||
493 | // ------------------------------------------------------------- | |
494 | function span($text) | |
495 | { | |
496 | $qtags = array('\*\*','\*','\?\?','-','__','_','%','\+','~'); | |
497 | ||
498 | foreach($qtags as $f) { | |
499 | $text = preg_replace_callback("/ | |
500 | (?<=^|\s|[[:punct:]]|[{([]) | |
501 | ($f) | |
502 | ($this->c) | |
503 | (?::(\S+))? | |
504 | ([\w<&].*) | |
505 | ([[:punct:];]*) | |
506 | $f | |
507 | (?=[])}]|[[:punct:]]+|\s|$) | |
508 | /xmU", array(&$this, "fSpan"), $text); | |
509 | } | |
510 | return $text; | |
511 | } | |
512 | ||
513 | // ------------------------------------------------------------- | |
514 | function fSpan($m) | |
515 | { | |
516 | $qtags = array( | |
517 | '*' => 'strong', | |
518 | '**' => 'b', | |
519 | '??' => 'cite', | |
520 | '_' => 'em', | |
521 | '__' => 'i', | |
522 | '-' => 'del', | |
523 | '%' => 'span', | |
524 | '+' => 'ins', | |
525 | '~' => 'sub' | |
526 | ); | |
527 | ||
528 | list(, $tag, $atts, $cite, $content, $end) = $m; | |
529 | $tag = $qtags[$tag]; | |
530 | $atts = $this->pba($atts); | |
531 | $atts .= ($cite != '') ? 'cite="' . $cite . '"' : ''; | |
532 | ||
533 | $out = "<$tag$atts>$content$end</$tag>"; | |
534 | ||
535 | // $this->dump($out); | |
536 | ||
537 | return $out; | |
538 | ||
539 | } | |
540 | ||
541 | // ------------------------------------------------------------- | |
542 | function links($text) | |
543 | { | |
544 | return preg_replace_callback('/ | |
545 | ([\s[{(]|[[:punct:]])? # $pre | |
546 | " # start | |
547 | (' . $this->c . ') # $atts | |
548 | ([^"]+) # $text | |
549 | \s? | |
550 | (?:\(([^)]+)\)(?="))? # $title | |
551 | ": | |
552 | (\S+\b) # $url | |
553 | (\/)? # $slash | |
554 | ([^\w\/;]*) # $post | |
555 | (?=\s|$) | |
556 | /Ux', array(&$this, "fLink"), $text); | |
557 | } | |
558 | ||
559 | // ------------------------------------------------------------- | |
560 | function fLink($m) | |
561 | { | |
562 | list(, $pre, $atts, $text, $title, $url, $slash, $post) = $m; | |
563 | ||
564 | $url = $this->checkRefs($url); | |
565 | ||
566 | $atts = $this->pba($atts); | |
567 | $atts .= ($title != '') ? 'title="' . $title . '"' : ''; | |
568 | ||
569 | $atts = ($atts) ? $this->shelve($atts) : ''; | |
570 | ||
571 | $parts = parse_url($url); | |
572 | if (empty($parts['host']) and preg_match('/^\w/', @$parts['path'])) | |
573 | $url = hu.$url; | |
574 | ||
575 | $out = $pre . '<a href="' . $url . $slash . '"' . $atts . $this->rel . '>' . $text . '</a>' . $post; | |
576 | ||
577 | // $this->dump($out); | |
578 | return $out; | |
579 | ||
580 | } | |
581 | ||
582 | // ------------------------------------------------------------- | |
583 | function getRefs($text) | |
584 | { | |
585 | return preg_replace_callback("/(?<=^|\s)\[(.+)\]((?:http:\/\/|\/)\S+)(?=\s|$)/U", | |
586 | array(&$this, "refs"), $text); | |
587 | } | |
588 | // ------------------------------------------------------------- | |
589 | ||
590 | function refs($m) | |
591 | { | |
592 | list(, $flag, $url) = $m; | |
593 | $this->urlrefs[$flag] = $url; | |
594 | return ''; | |
595 | } | |
596 | ||
597 | // ------------------------------------------------------------- | |
598 | function checkRefs($text) | |
599 | { | |
600 | return (isset($this->urlrefs[$text])) ? $this->urlrefs[$text] : $text; | |
601 | } | |
602 | ||
603 | // ------------------------------------------------------------- | |
604 | function image($text) | |
605 | { | |
606 | return preg_replace_callback("/ | |
607 | \! # opening ! | |
608 | (\<|\=|\>)? # optional alignment atts | |
609 | ($this->c) # optional style,class atts | |
610 | (?:\. )? # optional dot-space | |
611 | ([^\s(!]+) # presume this is the src | |
612 | \s? # optional space | |
613 | (?:\(([^\)]+)\))? # optional title | |
614 | \! # closing | |
615 | (?::(\S+))? # optional href | |
616 | (?=\s|$) # lookahead: space or end of string | |
617 | /Ux", array(&$this, "fImage"), $text); | |
618 | } | |
619 | ||
620 | // ------------------------------------------------------------- | |
621 | function fImage($m) | |
622 | { | |
623 | list(, $algn, $atts, $url) = $m; | |
624 | $atts = $this->pba($atts); | |
625 | $atts .= ($algn != '') ? ' align="' . $this->iAlign($algn) . '"' : ''; | |
626 | $atts .= (isset($m[4])) ? ' title="' . $m[4] . '"' : ''; | |
627 | $atts .= (isset($m[4])) ? ' alt="' . $m[4] . '"' : ' alt=""'; | |
628 | $size = @getimagesize($url); | |
629 | if ($size) $atts .= " $size[3]"; | |
630 | ||
631 | $href = (isset($m[5])) ? $this->checkRefs($m[5]) : ''; | |
632 | $url = $this->checkRefs($url); | |
633 | ||
634 | $parts = parse_url($url); | |
635 | if (empty($parts['host']) and preg_match('/^\w/', @$parts['path'])) | |
636 | $url = hu.$url; | |
637 | ||
638 | $out = array( | |
639 | ($href) ? '<a href="' . $href . '">' : '', | |
640 | '<img src="' . $url . '"' . $atts . ' />', | |
641 | ($href) ? '</a>' : '' | |
642 | ); | |
643 | ||
644 | return join('',$out); | |
645 | } | |
646 | ||
647 | // ------------------------------------------------------------- | |
648 | function code($text) | |
649 | { | |
650 | return preg_replace_callback("/ | |
651 | (?:^|(?<=[\s\(])|([[{])) # before | |
652 | @ | |
653 | (?:\|(\w+)\|)? # lang | |
654 | (.+) # code | |
655 | @ | |
656 | (?:$|([\]}])| | |
657 | (?=[[:punct:]]{1,2}| | |
658 | \s|$)) # after | |
659 | /Ux", array(&$this, "fCode"), $text); | |
660 | } | |
661 | ||
662 | // ------------------------------------------------------------- | |
663 | function fCode($m) | |
664 | { | |
665 | @list(, $before, $lang, $code, $after) = $m; | |
666 | $lang = ($lang) ? ' language="' . $lang . '"' : ''; | |
667 | return $before . '<code' . $lang . '>' . $code . '</code>' . $after; | |
668 | } | |
669 | ||
670 | // ------------------------------------------------------------- | |
671 | function shelve($val) | |
672 | { | |
673 | $this->shelf[] = $val; | |
674 | return ' <' . count($this->shelf) . '>'; | |
675 | } | |
676 | ||
677 | // ------------------------------------------------------------- | |
678 | function retrieve($text) | |
679 | { | |
680 | $i = 0; | |
681 | if (isset($this->shelf) && is_array($this->shelf)) { | |
682 | foreach($this->shelf as $r) { | |
683 | $i++; | |
684 | $text = str_replace("<$i>", $r, $text); | |
685 | } | |
686 | } | |
687 | return $text; | |
688 | } | |
689 | ||
690 | // ------------------------------------------------------------- | |
691 | function incomingEntities($text) | |
692 | { | |
693 | return preg_replace("/&(?![#a-z0-9]+;)/i", "x%x%", $text); | |
694 | } | |
695 | ||
696 | // ------------------------------------------------------------- | |
697 | function encodeEntities($text) | |
698 | { | |
699 | return (function_exists('mb_encode_numericentity')) | |
700 | ? $this->encode_high($text) | |
701 | : htmlentities($text, ENT_NOQUOTES, "utf-8"); | |
702 | } | |
703 | ||
704 | // ------------------------------------------------------------- | |
705 | function fixEntities($text) | |
706 | { | |
707 | /* de-entify any remaining angle brackets or ampersands */ | |
708 | return str_replace(array(">", "<", "&"), | |
709 | array(">", "<", "&"), $text); | |
710 | } | |
711 | ||
712 | // ------------------------------------------------------------- | |
713 | function cleanWhiteSpace($text) | |
714 | { | |
715 | $out = str_replace(array("\r\n", "\t"), array("\n", ''), $text); | |
716 | $out = preg_replace("/\n{3,}/", "\n\n", $out); | |
717 | $out = preg_replace("/\n *\n/", "\n\n", $out); | |
718 | $out = preg_replace('/"$/', "\" ", $out); | |
719 | return $out; | |
720 | } | |
721 | ||
722 | // ------------------------------------------------------------- | |
723 | function noTextile($text) | |
724 | { | |
725 | $text = preg_replace_callback('/(^|\s)<notextile>(.*)<\/notextile>(\s|$)?/msU', | |
726 | array(&$this, "fTextile"), $text); | |
727 | return preg_replace_callback('/(^|\s)==(.*)==(\s|$)?/msU', | |
728 | array(&$this, "fTextile"), $text); | |
729 | } | |
730 | ||
731 | // ------------------------------------------------------------- | |
732 | function fTextile($m) | |
733 | { | |
734 | $modifiers = array( | |
735 | '"' => '"', | |
736 | '%' => '%', | |
737 | '*' => '*', | |
738 | '+' => '+', | |
739 | '-' => '-', | |
740 | '<' => '<', | |
741 | '=' => '=', | |
742 | '>' => '>', | |
743 | '?' => '?', | |
744 | '^' => '^', | |
745 | '_' => '_', | |
746 | '~' => '~', | |
747 | ); | |
748 | ||
749 | @list(, $before, $notextile, $after) = $m; | |
750 | $notextile = str_replace(array_keys($modifiers), array_values($modifiers), $notextile); | |
751 | return $before . '<notextile>' . $notextile . '</notextile>' . $after; | |
752 | } | |
753 | ||
754 | // ------------------------------------------------------------- | |
755 | function superscript($text) | |
756 | { | |
757 | return preg_replace('/\^(.*)\^/mU', '<sup>$1</sup>', $text); | |
758 | } | |
759 | ||
760 | // ------------------------------------------------------------- | |
761 | function footnoteRef($text) | |
762 | { | |
763 | return preg_replace('/\b\[([0-9]+)\](\s)?/Ue', | |
764 | '$this->footnoteID(\'\1\',\'\2\')', $text); | |
765 | } | |
766 | ||
767 | // ------------------------------------------------------------- | |
768 | function footnoteID($id, $t) | |
769 | { | |
770 | if (empty($this->fn[$id])) | |
771 | $this->fn[$id] = uniqid(rand()); | |
772 | $fnid = $this->fn[$id]; | |
773 | return '<sup><a href="#fn'.$fnid.'">'.$id.'</a></sup>'.$t; | |
774 | } | |
775 | ||
776 | // ------------------------------------------------------------- | |
777 | function glyphs($text) | |
778 | { | |
779 | // fix: hackish | |
780 | $text = preg_replace('/"\z/', "\" ", $text); | |
781 | $pnc = '[[:punct:]]'; | |
782 | ||
783 | $glyph_search = array( | |
784 | '/([^\s[{(>_*])?\'(?(1)|(?=\s|s\b|'.$pnc.'))/', // single closing | |
785 | '/\'/', // single opening | |
786 | '/([^\s[{(>_*])?"(?(1)|(?=\s|'.$pnc.'))/', // double closing | |
787 | '/"/', // double opening | |
788 | '/\b( )?\.{3}/', // ellipsis | |
789 | '/\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/', // 3+ uppercase acronym | |
790 | '/\s?--\s?/', // em dash | |
791 | '/\s-\s/', // en dash | |
792 | '/(\d+) ?x ?(\d+)/', // dimension sign | |
793 | '/\b ?[([]TM[])]/i', // trademark | |
794 | '/\b ?[([]R[])]/i', // registered | |
795 | '/\b ?[([]C[])]/i'); // copyright | |
796 | ||
797 | $glyph_replace = array('$1’$2', // single closing | |
798 | '‘', // single opening | |
799 | '$1”', // double closing | |
800 | '“', // double opening | |
801 | '$1…', // ellipsis | |
802 | '<acronym title="$2">$1</acronym>', // 3+ uppercase acronym | |
803 | '—', // em dash | |
804 | ' – ', // en dash | |
805 | '$1×$2', // dimension sign | |
806 | '™', // trademark | |
807 | '®', // registered | |
808 | '©'); // copyright | |
809 | ||
810 | $codepre = false; | |
811 | /* if no html, do a simple search and replace... */ | |
812 | if (!preg_match("/<.*>/", $text)) { | |
813 | $text = preg_replace($glyph_search, $glyph_replace, $text); | |
814 | return $text; | |
815 | } | |
816 | else { | |
817 | $text = preg_split("/(<.*>)/U", $text, -1, PREG_SPLIT_DELIM_CAPTURE); | |
818 | foreach($text as $line) { | |
819 | $offtags = ('code|pre|kbd|notextile|txp:php'); | |
820 | ||
821 | /* matches are off if we're between <code>, <pre> etc. */ | |
822 | if (preg_match('/<(' . $offtags . ')>/i', $line)) $codepre = true; | |
823 | if (preg_match('/<\/(' . $offtags . ')>/i', $line)) $codepre = false; | |
824 | ||
825 | if (!preg_match("/<.*>/", $line) && $codepre == false) { | |
826 | $line = preg_replace($glyph_search, $glyph_replace, $line); | |
827 | } | |
828 | ||
829 | /* do htmlspecial if between <code> */ | |
830 | if ($codepre == true) { | |
831 | $line = htmlspecialchars($line, ENT_NOQUOTES, "UTF-8"); | |
832 | $line = preg_replace('/<(\/?' . $offtags . ')>/', "<$1>", $line); | |
833 | $line = str_replace("&#","&#",$line); | |
834 | } | |
835 | ||
836 | $glyph_out[] = $line; | |
837 | } | |
838 | return join('', $glyph_out); | |
839 | } | |
840 | } | |
841 | ||
842 | // ------------------------------------------------------------- | |
843 | function iAlign($in) | |
844 | { | |
845 | $vals = array( | |
846 | '<' => 'left', | |
847 | '=' => 'center', | |
848 | '>' => 'right'); | |
849 | return (isset($vals[$in])) ? $vals[$in] : ''; | |
850 | } | |
851 | ||
852 | // ------------------------------------------------------------- | |
853 | function hAlign($in) | |
854 | { | |
855 | $vals = array( | |
856 | '<' => 'left', | |
857 | '=' => 'center', | |
858 | '>' => 'right', | |
859 | '<>' => 'justify'); | |
860 | return (isset($vals[$in])) ? $vals[$in] : ''; | |
861 | } | |
862 | ||
863 | // ------------------------------------------------------------- | |
864 | function vAlign($in) | |
865 | { | |
866 | $vals = array( | |
867 | '^' => 'top', | |
868 | '-' => 'middle', | |
869 | '~' => 'bottom'); | |
870 | return (isset($vals[$in])) ? $vals[$in] : ''; | |
871 | } | |
872 | ||
873 | // ------------------------------------------------------------- | |
874 | function encode_high($text, $charset = "UTF-8") | |
875 | { | |
876 | return mb_encode_numericentity($text, $this->cmap(), $charset); | |
877 | } | |
878 | ||
879 | // ------------------------------------------------------------- | |
880 | function decode_high($text, $charset = "UTF-8") | |
881 | { | |
882 | return mb_decode_numericentity($text, $this->cmap(), $charset); | |
883 | } | |
884 | ||
885 | // ------------------------------------------------------------- | |
886 | function cmap() | |
887 | { | |
888 | $f = 0xffff; | |
889 | $cmap = array( | |
890 | 0x0080, 0xffff, 0, $f); | |
891 | return $cmap; | |
892 | } | |
893 | ||
894 | // ------------------------------------------------------------- | |
895 | function textile_popup_help($name, $helpvar, $windowW, $windowH) | |
896 | { | |
897 | return ' <a target="_blank" href="http://www.textpattern.com/help/?item=' . $helpvar . '" onclick="window.open(this.href, \'popupwindow\', \'width=' . $windowW . ',height=' . $windowH . ',scrollbars,resizable\'); return false;">' . $name . '</a><br />'; | |
898 | ||
899 | return $out; | |
900 | } | |
901 | ||
902 | // ------------------------------------------------------------- | |
903 | function txtgps($thing) | |
904 | { | |
905 | if (isset($_POST[$thing])) { | |
906 | if (get_magic_quotes_gpc()) { | |
907 | return stripslashes($_POST[$thing]); | |
908 | } | |
909 | else { | |
910 | return $_POST[$thing]; | |
911 | } | |
912 | } | |
913 | else { | |
914 | return ''; | |
915 | } | |
916 | } | |
917 | ||
918 | // ------------------------------------------------------------- | |
919 | function dump() | |
920 | { | |
921 | foreach (func_get_args() as $a) | |
922 | echo "\n<pre>",(is_array($a)) ? print_r($a) : $a, "</pre>\n"; | |
923 | } | |
924 | ||
925 | // ------------------------------------------------------------- | |
926 | function blockLite($text) | |
927 | { | |
928 | $find = array('bq', 'p'); | |
929 | ||
930 | $text = preg_replace("/(.+)\n(?![#*\s|])/", | |
931 | "$1<br />", $text); | |
932 | ||
933 | $text = explode("\n", $text); | |
934 | array_push($text, " "); | |
935 | ||
936 | foreach($text as $line) { | |
937 | ||
938 | foreach($find as $tag) { | |
939 | $line = preg_replace_callback("/^($tag)($this->a$this->c)\.(?::(\S+))? (.*)$/", | |
940 | array(&$this, "fBlock"), $line); | |
941 | } | |
942 | ||
943 | $line = preg_replace('/^(?!\t|<\/?pre|<\/?code|$| )(.*)/', "\t<p>$1</p>", $line); | |
944 | $out[] = $line; | |
945 | } | |
946 | return join("\n", $out); | |
947 | } | |
948 | ||
949 | ||
950 | } // end class | |
951 | ||
952 | ?> |