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
$Id: classTextile.php 216 2006-10-17 22:31:53Z zem $
13
$LastChangedRevision$
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
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
Additions and fixes Copyright (c) 2006 Alex Shiels http://thresholdstate.com/
32
33
_____________
34
L I C E N S E
35
36
Redistribution and use in source and binary forms, with or without
37
modification, are permitted provided that the following conditions are met:
38
39
* Redistributions of source code must retain the above copyright notice,
40
  this list of conditions and the following disclaimer.
41
42
* Redistributions in binary form must reproduce the above copyright notice,
43
  this list of conditions and the following disclaimer in the documentation
44
  and/or other materials provided with the distribution.
45
46
* Neither the name Textile nor the names of its contributors may be used to
47
  endorse or promote products derived from this software without specific
48
  prior written permission.
49
50
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
51
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
52
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
53
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
54
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
55
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
56
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
57
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
58
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
59
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
60
POSSIBILITY OF SUCH DAMAGE.
61
62
_________
63
U S A G E
64
65
Block modifier syntax:
66
67
	Header: h(1-6).
68
	Paragraphs beginning with 'hn. ' (where n is 1-6) are wrapped in header tags.
69
	Example: h1. Header... -> <h1>Header...</h1>
70
71
	Paragraph: p. (also applied by default)
72
	Example: p. Text -> <p>Text</p>
73
74
	Blockquote: bq.
75
	Example: bq. Block quotation... -> <blockquote>Block quotation...</blockquote>
76
77
	Blockquote with citation: bq.:http://citation.url
78
	Example: bq.:http://textism.com/ Text...
79
	->	<blockquote cite="http://textism.com">Text...</blockquote>
80
81
	Footnote: fn(1-100).
82
	Example: fn1. Footnote... -> <p id="fn1">Footnote...</p>
83
84
	Numeric list: #, ##
85
	Consecutive paragraphs beginning with # are wrapped in ordered list tags.
86
	Example: <ol><li>ordered list</li></ol>
87
88
	Bulleted list: *, **
89
	Consecutive paragraphs beginning with * are wrapped in unordered list tags.
90
	Example: <ul><li>unordered list</li></ul>
91
92
Phrase modifier syntax:
93
94
		   _emphasis_	->	 <em>emphasis</em>
95
		   __italic__	->	 <i>italic</i>
96
			 *strong*	->	 <strong>strong</strong>
97
			 **bold**	->	 <b>bold</b>
98
		 ??citation??	->	 <cite>citation</cite>
99
	   -deleted text-	->	 <del>deleted</del>
100
	  +inserted text+	->	 <ins>inserted</ins>
101
		^superscript^	->	 <sup>superscript</sup>
102
		  ~subscript~	->	 <sub>subscript</sub>
103
			   @code@	->	 <code>computer code</code>
104
		  %(bob)span%	->	 <span class="bob">span</span>
105
106
		==notextile==	->	 leave text alone (do not format)
107
108
	   "linktext":url	->	 <a href="url">linktext</a>
109
 "linktext(title)":url	->	 <a href="url" title="title">linktext</a>
110
111
		   !imageurl!	->	 <img src="imageurl" />
112
  !imageurl(alt text)!	->	 <img src="imageurl" alt="alt text" />
113
	!imageurl!:linkurl	->	 <a href="linkurl"><img src="imageurl" /></a>
114
115
ABC(Always Be Closing)	->	 <acronym title="Always Be Closing">ABC</acronym>
116
117
118
Table syntax:
119
120
	Simple tables:
121
122
		|a|simple|table|row|
123
		|And|Another|table|row|
124
125
		|_. A|_. table|_. header|_.row|
126
		|A|simple|table|row|
127
128
	Tables with attributes:
129
130
		table{border:1px solid black}.
131
		{background:#ddd;color:red}. |{}| | | |
132
133
134
Applying Attributes:
135
136
	Most anywhere Textile code is used, attributes such as arbitrary css style,
137
	css classes, and ids can be applied. The syntax is fairly consistent.
138
139
	The following characters quickly alter the alignment of block elements:
140
141
		<  ->  left align	 ex. p<. left-aligned para
142
		>  ->  right align		 h3>. right-aligned header 3
143
		=  ->  centred			 h4=. centred header 4
144
		<> ->  justified		 p<>. justified paragraph
145
146
	These will change vertical alignment in table cells:
147
148
		^  ->  top		   ex. |^. top-aligned table cell|
149
		-  ->  middle		   |-. middle aligned|
150
		~  ->  bottom		   |~. bottom aligned cell|
151
152
	Plain (parentheses) inserted between block syntax and the closing dot-space
153
	indicate classes and ids:
154
155
		p(hector). paragraph -> <p class="hector">paragraph</p>
156
157
		p(#fluid). paragraph -> <p id="fluid">paragraph</p>
158
159
		(classes and ids can be combined)
160
		p(hector#fluid). paragraph -> <p class="hector" id="fluid">paragraph</p>
161
162
	Curly {brackets} insert arbitrary css style
163
164
		p{line-height:18px}. paragraph -> <p style="line-height:18px">paragraph</p>
165
166
		h3{color:red}. header 3 -> <h3 style="color:red">header 3</h3>
167
168
	Square [brackets] insert language attributes
169
170
		p[no]. paragraph -> <p lang="no">paragraph</p>
171
172
		%[fr]phrase% -> <span lang="fr">phrase</span>
173
174
	Usually Textile block element syntax requires a dot and space before the block
175
	begins, but since lists don't, they can be styled just using braces
176
177
		#{color:blue} one  ->  <ol style="color:blue">
178
		# big					<li>one</li>
179
		# list					<li>big</li>
180
								<li>list</li>
181
							   </ol>
182
183
	Using the span tag to style a phrase
184
185
		It goes like this, %{color:red}the fourth the fifth%
186
			  -> It goes like this, <span style="color:red">the fourth the fifth</span>
187
188
*/
189
190
// define these before including this file to override the standard glyphs
191
@define('txt_quote_single_open',  '&#8216;');
192
@define('txt_quote_single_close', '&#8217;');
193
@define('txt_quote_double_open',  '&#8220;');
194
@define('txt_quote_double_close', '&#8221;');
195
@define('txt_apostrophe',		  '&#8217;');
196
@define('txt_prime',			  '&#8242;');
197
@define('txt_prime_double', 	  '&#8243;');
198
@define('txt_ellipsis', 		  '&#8230;');
199
@define('txt_emdash',			  '&#8212;');
200
@define('txt_endash',			  '&#8211;');
201
@define('txt_dimension',		  '&#215;');
202
@define('txt_trademark',		  '&#8482;');
203
@define('txt_registered',		  '&#174;');
204
@define('txt_copyright',		  '&#169;');
205
206
class Textile
207
{
208
	var $hlgn;
209
	var $vlgn;
210
	var $clas;
211
	var $lnge;
212
	var $styl;
213
	var $cspn;
214
	var $rspn;
215
	var $a;
216
	var $s;
217
	var $c;
218
	var $pnct;
219
	var $rel;
220
	var $fn;
221
	
222
	var $shelf = array();
223
	var $restricted = false;
224
	var $noimage = false;
225
	var $lite = false;
226
	var $url_schemes = array();
227
	var $glyph = array();
228
	var $hu = '';
229
	
230
	var $ver = '2.0.0';
231
	var $rev = '$Rev$';
232
	
233
	var $doc_root;
234
235
// -------------------------------------------------------------
236
	function Textile()
237
	{
238
		$this->hlgn = "(?:\<(?!>)|(?<!<)\>|\<\>|\=|[()]+(?! ))";
239
		$this->vlgn = "[\-^~]";
240
		$this->clas = "(?:\([^)]+\))";
241
		$this->lnge = "(?:\[[^]]+\])";
242
		$this->styl = "(?:\{[^}]+\})";
243
		$this->cspn = "(?:\\\\\d+)";
244
		$this->rspn = "(?:\/\d+)";
245
		$this->a = "(?:{$this->hlgn}|{$this->vlgn})*";
246
		$this->s = "(?:{$this->cspn}|{$this->rspn})*";
247
		$this->c = "(?:{$this->clas}|{$this->styl}|{$this->lnge}|{$this->hlgn})*";
248
249
		$this->pnct = '[\!"#\$%&\'()\*\+,\-\./:;<=>\?@\[\\\]\^_`{\|}\~]';
250
		$this->urlch = '[\w"$\-_.+!*\'(),";\/?:@=&%#{}|\\^~\[\]`]';
251
252
		$this->url_schemes = array('http','https','ftp','mailto','file');
253
254
		$this->btag = array('bq', 'bc', 'notextile', 'pre', 'h[1-6]', 'fn\d+', 'p');
255
256
		$this->glyph = array(
257
		   'quote_single_open'	=> txt_quote_single_open,
258
		   'quote_single_close' => txt_quote_single_close,
259
		   'quote_double_open'	=> txt_quote_double_open,
260
		   'quote_double_close' => txt_quote_double_close,
261
		   'apostrophe' 		=> txt_apostrophe,
262
		   'prime'				=> txt_prime,
263
		   'prime_double'		=> txt_prime_double,
264
		   'ellipsis'			=> txt_ellipsis,
265
		   'emdash' 			=> txt_emdash,
266
		   'endash' 			=> txt_endash,
267
		   'dimension'			=> txt_dimension,
268
		   'trademark'			=> txt_trademark,
269
		   'registered' 		=> txt_registered,
270
		   'copyright'			=> txt_copyright,
271
		);
272
273
		if (defined('hu'))
274
			$this->hu = hu;
275
276
		if (defined('DIRECTORY_SEPARATOR'))
277
			$this->ds = constant('DIRECTORY_SEPARATOR');
278
		else
279
			$this->ds = '/';
280
281
		$this->doc_root = @$_SERVER['DOCUMENT_ROOT'];
282
		if (!$this->doc_root)
283
			$this->doc_root = @$_SERVER['PATH_TRANSLATED']; // IIS
284
			
285
		$this->doc_root = rtrim($this->doc_root, $this->ds).$this->ds;
286
287
	}
288
289
// -------------------------------------------------------------
290
291
	function TextileThis($text, $lite = '', $encode = '', $noimage = '', $strict = '', $rel = '')
292
	{
293
		$this->rel = ($rel) ? ' rel="'.$rel.'"' : '';
294
295
		$this->lite = $lite;
296
		$this->noimage = $noimage;
297
298
		if ($encode) {
299
		 $text = $this->incomingEntities($text);
300
			$text = str_replace("x%x%", "&#38;", $text);
301
			return $text;
302
		} else {
303
304
			if(!$strict) {
305
				$text = $this->cleanWhiteSpace($text);
306
			}
307
308
			if (!$lite) {
309
				$text = $this->block($text);
310
			}
311
312
			$text = $this->retrieve($text);
313
			$text = $this->retrieveURLs($text);
314
315
				// just to be tidy
316
			$text = str_replace("<br />", "<br />\n", $text);
317
318
			return $text;
319
		}
320
	}
321
322
// -------------------------------------------------------------
323
324
	function TextileRestricted($text, $lite = 1, $noimage = 1, $rel = 'nofollow')
325
	{
326
		$this->restricted = true;
327
		$this->lite = $lite;
328
		$this->noimage = $noimage;
329
330
		$this->rel = ($rel) ? ' rel="'.$rel.'"' : '';
331
332
			// escape any raw html
333
			$text = $this->encode_html($text, 0);
334
335
			$text = $this->cleanWhiteSpace($text);
336
337
			if ($lite) {
338
				$text = $this->blockLite($text);
339
			}
340
			else {
341
				$text = $this->block($text);
342
			}
343
344
			$text = $this->retrieve($text);
345
			$text = $this->retrieveURLs($text);
346
347
				// just to be tidy
348
			$text = str_replace("<br />", "<br />\n", $text);
349
350
			return $text;
351
	}
352
353
// -------------------------------------------------------------
354
	function pba($in, $element = "", $include_id = 1) // "parse block attributes"
355
	{
356
		$style = '';
357
		$class = '';
358
		$lang = '';
359
		$colspan = '';
360
		$rowspan = '';
361
		$id = '';
362
		$atts = '';
363
364
		if (!empty($in)) {
365
			$matched = $in;
366
			if ($element == 'td') {
367
				if (preg_match("/\\\\(\d+)/", $matched, $csp)) $colspan = $csp[1];
368
				if (preg_match("/\/(\d+)/", $matched, $rsp)) $rowspan = $rsp[1];
369
			}
370
371
			if ($element == 'td' or $element == 'tr') {
372
				if (preg_match("/($this->vlgn)/", $matched, $vert))
373
					$style[] = "vertical-align:" . $this->vAlign($vert[1]) . ";";
374
			}
375
376
			if (preg_match("/\{([^}]*)\}/", $matched, $sty)) {
377
				$style[] = rtrim($sty[1], ';') . ';';
378
				$matched = str_replace($sty[0], '', $matched);
379
			}
380
381
			if (preg_match("/\[([^]]+)\]/U", $matched, $lng)) {
382
				$lang = $lng[1];
383
				$matched = str_replace($lng[0], '', $matched);
384
			}
385
386
			if (preg_match("/\(([^()]+)\)/U", $matched, $cls)) {
387
				$class = $cls[1];
388
				$matched = str_replace($cls[0], '', $matched);
389
			}
390
391
			if (preg_match("/([(]+)/", $matched, $pl)) {
392
				$style[] = "padding-left:" . strlen($pl[1]) . "em;";
393
				$matched = str_replace($pl[0], '', $matched);
394
			}
395
396
			if (preg_match("/([)]+)/", $matched, $pr)) {
397
				// $this->dump($pr);
398
				$style[] = "padding-right:" . strlen($pr[1]) . "em;";
399
				$matched = str_replace($pr[0], '', $matched);
400
			}
401
402
			if (preg_match("/($this->hlgn)/", $matched, $horiz))
403
				$style[] = "text-align:" . $this->hAlign($horiz[1]) . ";";
404
405
			if (preg_match("/^(.*)#(.*)$/", $class, $ids)) {
406
				$id = $ids[2];
407
				$class = $ids[1];
408
			}
409
410
			if ($this->restricted)
411
				return ($lang)	  ? ' lang="'	 . $lang			.'"':'';
412
413
			return join('',array(
414
				($style)   ? ' style="'   . join("", $style) .'"':'',
415
				($class)   ? ' class="'   . $class			 .'"':'',
416
				($lang)    ? ' lang="'	  . $lang			 .'"':'',
417
				($id and $include_id) ? ' id="' 	 . $id				.'"':'',
418
				($colspan) ? ' colspan="' . $colspan		 .'"':'',
419
				($rowspan) ? ' rowspan="' . $rowspan		 .'"':''
420
			));
421
		}
422
		return '';
423
	}
424
425
// -------------------------------------------------------------
426
	function hasRawText($text)
427
	{
428
		// checks whether the text has text not already enclosed by a block tag
429
		$r = trim(preg_replace('@<(p|blockquote|div|form|table|ul|ol|pre|h\d)[^>]*?>.*</\1>@s', '', trim($text)));
430
		$r = trim(preg_replace('@<(hr|br)[^>]*?/>@', '', $r));
431
		return '' != $r;
432
	}
433
434
// -------------------------------------------------------------
435
	function table($text)
436
	{
437
		$text = $text . "\n\n";
438
		return preg_replace_callback("/^(?:table(_?{$this->s}{$this->a}{$this->c})\. ?\n)?^({$this->a}{$this->c}\.? ?\|.*\|)\n\n/smU",
439
		   array(&$this, "fTable"), $text);
440
	}
441
442
// -------------------------------------------------------------
443
	function fTable($matches)
444
	{
445
		$tatts = $this->pba($matches[1], 'table');
446
447
		foreach(preg_split("/\|$/m", $matches[2], -1, PREG_SPLIT_NO_EMPTY) as $row) {
448
			if (preg_match("/^($this->a$this->c\. )(.*)/m", ltrim($row), $rmtch)) {
449
				$ratts = $this->pba($rmtch[1], 'tr');
450
				$row = $rmtch[2];
451
			} else $ratts = '';
452
453
				$cells = array();
454
			foreach(explode("|", $row) as $cell) {
455
				$ctyp = "d";
456
				if (preg_match("/^_/", $cell)) $ctyp = "h";
457
				if (preg_match("/^(_?$this->s$this->a$this->c\. )(.*)/", $cell, $cmtch)) {
458
					$catts = $this->pba($cmtch[1], 'td');
459
					$cell = $cmtch[2];
460
				} else $catts = '';
461
462
				$cell = $this->graf($cell);
463
464
				if (trim($cell) != '')
465
					$cells[] = $this->doTagBr("t$ctyp", "\t\t\t<t$ctyp$catts>$cell</t$ctyp>");
466
			}
467
			$rows[] = "\t\t<tr$ratts>\n" . join("\n", $cells) . ($cells ? "\n" : "") . "\t\t</tr>";
468
			unset($cells, $catts);
469
		}
470
		return "\t<table$tatts>\n" . join("\n", $rows) . "\n\t</table>\n\n";
471
	}
472
473
// -------------------------------------------------------------
474
	function lists($text)
475
	{
476
		return preg_replace_callback("/^([#*]+$this->c .*)$(?![^#*])/smU", array(&$this, "fList"), $text);
477
	}
478
479
// -------------------------------------------------------------
480
	function fList($m)
481
	{
482
		$text = preg_split('/\n(?=[*#])/m', $m[0]);
483
		foreach($text as $nr => $line) {
484
			$nextline = isset($text[$nr+1]) ? $text[$nr+1] : false;
485
			if (preg_match("/^([#*]+)($this->a$this->c) (.*)$/s", $line, $m)) {
486
				list(, $tl, $atts, $content) = $m;
487
				$nl = '';
488
				if (preg_match("/^([#*]+)\s.*/", $nextline, $nm))
489
					$nl = $nm[1];
490
				if (!isset($lists[$tl])) {
491
					$lists[$tl] = true;
492
					$atts = $this->pba($atts);
493
					$line = "\t<" . $this->lT($tl) . "l$atts>\n\t\t<li>" . rtrim($content);
494
				} else {
495
					$line = "\t\t<li>" . rtrim($content);
496
				}
497
498
				if(strlen($nl) <= strlen($tl)) $line .= "</li>";
499
				foreach(array_reverse($lists) as $k => $v) {
500
					if(strlen($k) > strlen($nl)) {
501
						$line .= "\n\t</" . $this->lT($k) . "l>";
502
						if(strlen($k) > 1)
503
							$line .= "</li>";
504
						unset($lists[$k]);
505
					}
506
				}
507
			}
508
			else {
509
				$line .= n;
510
			}
511
			$out[] = $line;
512
		}
513
		return $this->doTagBr('li', join("\n", $out));
514
	}
515
516
// -------------------------------------------------------------
517
	function lT($in)
518
	{
519
		return preg_match("/^#+/", $in) ? 'o' : 'u';
520
	}
521
522
// -------------------------------------------------------------
523
	function doTagBr($tag, $in)
524
	{
525
		return preg_replace_callback('@<('.preg_quote($tag).')([^>]*?)>(.*)(</\1>)@s', array(&$this, 'doBr'), $in);
526
	}
527
528
529
// -------------------------------------------------------------
530
	function doPBr($in)
531
	{
532
		return $this->doTagBr('p', $in);
533
	}
534
535
// -------------------------------------------------------------
536
	function doBr($m)
537
	{
538
		$content = preg_replace("@(.+)(?<!<br>|<br />)\n(?![#*\s|])@", '$1<br />', $m[3]);
539
		return '<'.$m[1].$m[2].'>'.$content.$m[4];
540
	}
541
542
// -------------------------------------------------------------
543
	function block($text)
544
	{
545
		$find = $this->btag;
546
		$tre = join('|', $find);
547
548
		$text = explode("\n\n", $text);
549
550
		$tag = 'p';
551
		$atts = $cite = $graf = $ext  = '';
552
553
		foreach($text as $line) {
554
			$anon = 0;
555
			if (preg_match("/^($tre)($this->a$this->c)\.(\.?)(?::(\S+))? (.*)$/s", $line, $m)) {
556
				// last block was extended, so close it
557
				if ($ext)
558
					$out[count($out)-1] .= $c1;
559
				// new block
560
				list(,$tag,$atts,$ext,$cite,$graf) = $m;
561
				list($o1, $o2, $content, $c2, $c1) = $this->fBlock(array(0,$tag,$atts,$ext,$cite,$graf));
562
563
				// leave off c1 if this block is extended, we'll close it at the start of the next block
564
				if ($ext)
565
					$line = $o1.$o2.$content.$c2;
566
				else
567
					$line = $o1.$o2.$content.$c2.$c1;
568
			}
569
			else {
570
				// anonymous block
571
				$anon = 1;
572
				if ($ext or !preg_match('/^ /', $line)) {
573
					list($o1, $o2, $content, $c2, $c1) = $this->fBlock(array(0,$tag,$atts,$ext,$cite,$line));
574
					// skip $o1/$c1 because this is part of a continuing extended block
575
					if ($tag == 'p' and !$this->hasRawText($content)) {
576
						$line = $content;
577
					}
578
					else {
579
						$line = $o2.$content.$c2;
580
					}
581
				}
582
				else {
583
				   $line = $this->graf($line);
584
				}
585
			}
586
587
			$line = $this->doPBr($line);
588
			$line = preg_replace('/<br>/', '<br />', $line);
589
590
			if ($ext and $anon)
591
				$out[count($out)-1] .= "\n".$line;
592
			else
593
				$out[] = $line;
594
595
			if (!$ext) {
596
				$tag = 'p';
597
				$atts = '';
598
				$cite = '';
599
				$graf = '';
600
			}
601
		}
602
		if ($ext) $out[count($out)-1] .= $c1;
603
		return join("\n\n", $out);
604
	}
605
606
607
608
// -------------------------------------------------------------
609
	function fBlock($m)
610
	{
611
		// $this->dump($m);
612
		list(, $tag, $att, $ext, $cite, $content) = $m;
613
		$atts = $this->pba($att);
614
615
		$o1 = $o2 = $c2 = $c1 = '';
616
617
		if (preg_match("/fn(\d+)/", $tag, $fns)) {
618
			$tag = 'p';
619
			$fnid = empty($this->fn[$fns[1]]) ? $fns[1] : $this->fn[$fns[1]];
620
			$atts .= ' id="fn' . $fnid . '"';
621
			if (strpos($atts, 'class=') === false)
622
				$atts .= ' class="footnote"';
623
			$content = '<sup>' . $fns[1] . '</sup> ' . $content;
624
		}
625
626
		if ($tag == "bq") {
627
			$cite = $this->shelveURL($cite);
628
			$cite = ($cite != '') ? ' cite="' . $cite . '"' : '';
629
			$o1 = "\t<blockquote$cite$atts>\n";
630
			$o2 = "\t\t<p".$this->pba($att, '', 0).">";
631
			$c2 = "</p>";
632
			$c1 = "\n\t</blockquote>";
633
		}
634
		elseif ($tag == 'bc') {
635
			$o1 = "<pre$atts>";
636
			$o2 = "<code".$this->pba($att, '', 0).">";
637
			$c2 = "</code>";
638
			$c1 = "</pre>";
639
			$content = $this->shelve($this->r_encode_html(rtrim($content, "\n")."\n"));
640
		}
641
		elseif ($tag == 'notextile') {
642
			$content = $this->shelve($content);
643
			$o1 = $o2 = '';
644
			$c1 = $c2 = '';
645
		}
646
		elseif ($tag == 'pre') {
647
			$content = $this->shelve($this->r_encode_html(rtrim($content, "\n")."\n"));
648
			$o1 = "<pre$atts>";
649
			$o2 = $c2 = '';
650
			$c1 = "</pre>";
651
		}
652
		else {
653
			$o2 = "\t<$tag$atts>";
654
			$c2 = "</$tag>";
655
		  }
656
657
		$content = $this->graf($content);
658
659
		return array($o1, $o2, $content, $c2, $c1);
660
	}
661
662
// -------------------------------------------------------------
663
	function graf($text)
664
	{
665
		// handle normal paragraph text
666
		if (!$this->lite) {
667
			$text = $this->noTextile($text);
668
			$text = $this->code($text);
669
		}
670
671
		$text = $this->getRefs($text);
672
		$text = $this->links($text);
673
		if (!$this->noimage)
674
			$text = $this->image($text);
675
676
		if (!$this->lite) {
677
			$text = $this->table($text);
678
			$text = $this->lists($text);
679
		}
680
681
		$text = $this->span($text);
682
		$text = $this->footnoteRef($text);
683
		$text = $this->glyphs($text);
684
		return rtrim($text, "\n");
685
	}
686
687
// -------------------------------------------------------------
688
	function span($text)
689
	{
690
		$qtags = array('\*\*','\*','\?\?','-','__','_','%','\+','~','\^');
691
		$pnct = ".,\"'?!;:";
692
693
		foreach($qtags as $f) {
694
			$text = preg_replace_callback("/
695
				(^|(?<=[\s>$pnct\(])|[{[])
696
				($f)(?!$f)
697
				({$this->c})
698
				(?::(\S+))?
699
				([^\s$f]+|\S.*?[^\s$f\n])
700
				([$pnct]*)
701
				$f
702
				($|[\]}]|(?=[[:punct:]]{1,2}|\s|\)))
703
			/x", array(&$this, "fSpan"), $text);
704
		}
705
		return $text;
706
	}
707
708
// -------------------------------------------------------------
709
	function fSpan($m)
710
	{
711
		$qtags = array(
712
			'*'  => 'strong',
713
			'**' => 'b',
714
			'??' => 'cite',
715
			'_'  => 'em',
716
			'__' => 'i',
717
			'-'  => 'del',
718
			'%'  => 'span',
719
			'+'  => 'ins',
720
			'~'  => 'sub',
721
			'^'  => 'sup',
722
		);
723
724
		list(, $pre, $tag, $atts, $cite, $content, $end, $tail) = $m;
725
		$tag = $qtags[$tag];
726
		$atts = $this->pba($atts);
727
		$atts .= ($cite != '') ? 'cite="' . $cite . '"' : '';
728
729
		$out = "<$tag$atts>$content$end</$tag>";
730
731
		if (($pre and !$tail) or ($tail and !$pre))
732
			$out = $pre.$out.$tail;
733
734
//		$this->dump($out);
735
736
		return $out;
737
738
	}
739
740
// -------------------------------------------------------------
741
	function links($text)
742
	{
743
		return preg_replace_callback('/
744
			(^|(?<=[\s>.$pnct\(])|[{[]) # $pre
745
			"							 # start
746
			(' . $this->c . ')			 # $atts
747
			([^"]+?)					 # $text
748
			(?:\(([^)]+?)\)(?="))?		 # $title
749
			":
750
			('.$this->urlch.'+?)		 # $url
751
			(\/)?						 # $slash
752
			([^\w\/;]*?)				 # $post
753
			([\]}]|(?=\s|$|\)))
754
		/x', array(&$this, "fLink"), $text);
755
	}
756
757
// -------------------------------------------------------------
758
	function fLink($m)
759
	{
760
		list(, $pre, $atts, $text, $title, $url, $slash, $post, $tail) = $m;
761
762
		$atts = $this->pba($atts);
763
		$atts .= ($title != '') ? ' title="' . $this->encode_html($title) . '"' : '';
764
765
		if (!$this->noimage)
766
			$text = $this->image($text);
767
768
		$text = $this->span($text);
769
		$text = $this->glyphs($text);
770
771
		$url = $this->shelveURL($url.$slash);
772
773
		$out = '<a href="' . $url . '"' . $atts . $this->rel . '>' . trim($text) . '</a>' . $post;
774
		
775
		if (($pre and !$tail) or ($tail and !$pre))
776
			$out = $pre.$out.$tail;
777
778
		// $this->dump($out);
779
		return $this->shelve($out);
780
781
	}
782
783
// -------------------------------------------------------------
784
	function getRefs($text)
785
	{
786
		return preg_replace_callback("/^\[(.+)\]((?:http:\/\/|\/)\S+)(?=\s|$)/Um",
787
			array(&$this, "refs"), $text);
788
	}
789
790
// -------------------------------------------------------------
791
	function refs($m)
792
	{
793
		list(, $flag, $url) = $m;
794
		$this->urlrefs[$flag] = $url;
795
		return '';
796
	}
797
798
// -------------------------------------------------------------
799
	function shelveURL($text)
800
	{
801
		if (!$text) return '';
802
		$ref = md5($text);
803
		$this->urlshelf[$ref] = $text;
804
		return 'urlref:'.$ref;
805
	}
806
807
// -------------------------------------------------------------
808
	function retrieveURLs($text)
809
	{
810
		return preg_replace_callback('/urlref:(\w{32})/',
811
			array(&$this, "retrieveURL"), $text);
812
	}
813
814
// -------------------------------------------------------------
815
	function retrieveURL($m)
816
	{
817
		$ref = $m[1];
818
		if (!isset($this->urlshelf[$ref]))
819
			return $ref;
820
		$url = $this->urlshelf[$ref];
821
		if (isset($this->urlrefs[$url]))
822
			$url = $this->urlrefs[$url];
823
		return $this->r_encode_html($this->relURL($url));
824
	}
825
826
// -------------------------------------------------------------
827
	function relURL($url)
828
	{
829
		$parts = @parse_url(urldecode($url));
830
		if ((empty($parts['scheme']) or @$parts['scheme'] == 'http') and
831
			 empty($parts['host']) and
832
			 preg_match('/^\w/', @$parts['path']))
833
			$url = $this->hu.$url;
834
		if ($this->restricted and !empty($parts['scheme']) and
835
			  !in_array($parts['scheme'], $this->url_schemes))
836
			return '#';
837
		return $url;
838
	}
839
840
// -------------------------------------------------------------
841
	function isRelURL($url)
842
	{
843
		$parts = @parse_url($url);
844
		return (empty($parts['scheme']) and empty($parts['host']));
845
	}
846
847
// -------------------------------------------------------------
848
	function image($text)
849
	{
850
		return preg_replace_callback("/
851
			(?:[[{])?		   # pre
852
			\!				   # opening !
853
			(\<|\=|\>)? 	   # optional alignment atts
854
			($this->c)		   # optional style,class atts
855
			(?:\. )?		   # optional dot-space
856
			([^\s(!]+)		   # presume this is the src
857
			\s? 			   # optional space
858
			(?:\(([^\)]+)\))?  # optional title
859
			\!				   # closing
860
			(?::(\S+))? 	   # optional href
861
			(?:[\]}]|(?=\s|$|\))) # lookahead: space or end of string
862
		/x", array(&$this, "fImage"), $text);
863
	}
864
865
// -------------------------------------------------------------
866
	function fImage($m)
867
	{
868
		list(, $algn, $atts, $url) = $m;
869
		$atts  = $this->pba($atts);
870
		$atts .= ($algn != '')	? ' align="' . $this->iAlign($algn) . '"' : '';
871
		$atts .= (isset($m[4])) ? ' title="' . $m[4] . '"' : '';
872
		$atts .= (isset($m[4])) ? ' alt="'	 . $m[4] . '"' : ' alt=""';
873
		$size = false;
874
		if ($this->isRelUrl($url))
875
			$size = @getimagesize(realpath($this->doc_root.ltrim($url, $this->ds)));
876
		if ($size) $atts .= " $size[3]";
877
878
		$href = (isset($m[5])) ? $this->shelveURL($m[5]) : '';
879
		$url = $this->shelveURL($url);
880
881
		$out = array(
882
			($href) ? '<a href="' . $href . '">' : '',
883
			'<img src="' . $url . '"' . $atts . ' />',
884
			($href) ? '</a>' : ''
885
		);
886
887
		return $this->shelve(join('',$out));
888
	}
889
890
// -------------------------------------------------------------
891
	function code($text)
892
	{
893
		$text = $this->doSpecial($text, '<code>', '</code>', 'fCode');
894
		$text = $this->doSpecial($text, '@', '@', 'fCode');
895
		$text = $this->doSpecial($text, '<pre>', '</pre>', 'fPre');
896
		return $text;
897
	}
898
899
// -------------------------------------------------------------
900
	function fCode($m)
901
	{
902
	  @list(, $before, $text, $after) = $m;
903
	  return $before.$this->shelve('<code>'.$this->r_encode_html($text).'</code>').$after;
904
	}
905
906
// -------------------------------------------------------------
907
	function fPre($m)
908
	{
909
	  @list(, $before, $text, $after) = $m;
910
	  return $before.'<pre>'.$this->shelve($this->r_encode_html($text)).'</pre>'.$after;
911
	}
912
// -------------------------------------------------------------
913
	function shelve($val)
914
	{
915
		$i = uniqid(rand());
916
		$this->shelf[$i] = $val;
917
		return $i;
918
	}
919
920
// -------------------------------------------------------------
921
	function retrieve($text)
922
	{
923
		if (is_array($this->shelf))
924
			do {
925
				$old = $text;
926
				$text = strtr($text, $this->shelf);
927
			 } while ($text != $old);
928
929
		return $text;
930
	}
931
932
// -------------------------------------------------------------
933
// NOTE: deprecated
934
	function incomingEntities($text)
935
	{
936
		return preg_replace("/&(?![#a-z0-9]+;)/i", "x%x%", $text);
937
	}
938
939
// -------------------------------------------------------------
940
// NOTE: deprecated
941
	function encodeEntities($text)
942
	{
943
		return (function_exists('mb_encode_numericentity'))
944
		?	 $this->encode_high($text)
945
		:	 htmlentities($text, ENT_NOQUOTES, "utf-8");
946
	}
947
948
// -------------------------------------------------------------
949
// NOTE: deprecated
950
	function fixEntities($text)
951
	{
952
		/*	de-entify any remaining angle brackets or ampersands */
953
		return str_replace(array("&gt;", "&lt;", "&amp;"),
954
			array(">", "<", "&"), $text);
955
	}
956
957
// -------------------------------------------------------------
958
	function cleanWhiteSpace($text)
959
	{
960
		$out = str_replace("\r\n", "\n", $text);		# DOS line endings
961
		$out = preg_replace("/^[ \t]*\n/m", "\n", $out);	# lines containing only whitespace
962
		$out = preg_replace("/\n{3,}/", "\n\n", $out);	# 3 or more line ends
963
		$out = preg_replace("/^\n*/", "", $out);		# leading blank lines
964
		return $out;
965
	}
966
967
// -------------------------------------------------------------
968
	function doSpecial($text, $start, $end, $method='fSpecial')
969
	{
970
	  return preg_replace_callback('/(^|\s|[[({>])'.preg_quote($start, '/').'(.*?)'.preg_quote($end, '/').'(\s|$|[\])}])?/ms',
971
			array(&$this, $method), $text);
972
	}
973
974
// -------------------------------------------------------------
975
	function fSpecial($m)
976
	{
977
		// A special block like notextile or code
978
	  @list(, $before, $text, $after) = $m;
979
		return $before.$this->shelve($this->encode_html($text)).$after;
980
	}
981
982
// -------------------------------------------------------------
983
	function noTextile($text)
984
	{
985
		 $text = $this->doSpecial($text, '<notextile>', '</notextile>', 'fTextile');
986
		 return $this->doSpecial($text, '==', '==', 'fTextile');
987
988
	}
989
990
// -------------------------------------------------------------
991
	function fTextile($m)
992
	{
993
		@list(, $before, $notextile, $after) = $m;
994
		#$notextile = str_replace(array_keys($modifiers), array_values($modifiers), $notextile);
995
		return $before.$this->shelve($notextile).$after;
996
	}
997
998
// -------------------------------------------------------------
999
	function footnoteRef($text)
1000
	{
1001
		return preg_replace('/(?<=\S)\[([0-9]+)\](\s)?/Ue',
1002
			'$this->footnoteID(\'\1\',\'\2\')', $text);
1003
	}
1004
1005
// -------------------------------------------------------------
1006
	function footnoteID($id, $t)
1007
	{
1008
		if (empty($this->fn[$id]))
1009
			$this->fn[$id] = uniqid(rand());
1010
		$fnid = $this->fn[$id];
1011
		return '<sup class="footnote"><a href="#fn'.$fnid.'">'.$id.'</a></sup>'.$t;
1012
	}
1013
1014
// -------------------------------------------------------------
1015
	function glyphs($text)
1016
	{
1017
1018
		// fix: hackish
1019
		$text = preg_replace('/"\z/', "\" ", $text);
1020
		$pnc = '[[:punct:]]';
1021
1022
		$glyph_search = array(
1023
			'/(\w)\'(\w)/', 									 // apostrophe's
1024
			'/(\s)\'(\d+\w?)\b(?!\')/', 						 // back in '88
1025
			'/(\S)\'(?=\s|'.$pnc.'|<|$)/',						 //  single closing
1026
			'/\'/', 											 //  single opening
1027
			'/(\S)\"(?=\s|'.$pnc.'|<|$)/',						 //  double closing
1028
			'/"/',												 //  double opening
1029
			'/\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/',		 //  3+ uppercase acronym
1030
			'/(?<=\s|^|[>(;-])([A-Z]{3,})([a-z]*)(?=\s|'.$pnc.'|<|$)/',  //  3+ uppercase
1031
			'/([^.]?)\.{3}/',									 //  ellipsis
1032
			'/(\s?)--(\s?)/',									 //  em dash
1033
			'/\s-(?:\s|$)/',									 //  en dash
1034
			'/(\d+)( ?)x( ?)(?=\d+)/',							 //  dimension sign
1035
			'/(\b ?|\s|^)[([]TM[])]/i', 						 //  trademark
1036
			'/(\b ?|\s|^)[([]R[])]/i',							 //  registered
1037
			'/(\b ?|\s|^)[([]C[])]/i',							 //  copyright
1038
		 );
1039
1040
		extract($this->glyph, EXTR_PREFIX_ALL, 'txt');
1041
1042
		$glyph_replace = array(
1043
			'$1'.$txt_apostrophe.'$2',			 // apostrophe's
1044
			'$1'.$txt_apostrophe.'$2',			 // back in '88
1045
			'$1'.$txt_quote_single_close,		 //  single closing
1046
			$txt_quote_single_open, 			 //  single opening
1047
			'$1'.$txt_quote_double_close,		 //  double closing
1048
			$txt_quote_double_open, 			 //  double opening
1049
			'<acronym title="$2">$1</acronym>',  //  3+ uppercase acronym
1050
			'<span class="caps">$1</span>$2',	 //  3+ uppercase
1051
			'$1'.$txt_ellipsis, 				 //  ellipsis
1052
			'$1'.$txt_emdash.'$2',				 //  em dash
1053
			' '.$txt_endash.' ',				 //  en dash
1054
			'$1$2'.$txt_dimension.'$3', 		 //  dimension sign
1055
			'$1'.$txt_trademark,				 //  trademark
1056
			'$1'.$txt_registered,				 //  registered
1057
			'$1'.$txt_copyright,				 //  copyright
1058
		 );
1059
1060
		 $text = preg_split("@(<[\w/!?].*>)@Us", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
1061
		 $i = 0;
1062
		 foreach($text as $line) {
1063
			 // text tag text tag text ...
1064
			 if (++$i % 2) {
1065
				 // raw < > & chars are already entity encoded in restricted mode
1066
				 if (!$this->restricted) {
1067
					 $line = $this->encode_raw_amp($line);
1068
					 $line = $this->encode_lt_gt($line);
1069
				 }
1070
				 $line = preg_replace($glyph_search, $glyph_replace, $line);
1071
			 }
1072
			  $glyph_out[] = $line;
1073
		 }
1074
		 return join('', $glyph_out);
1075
	}
1076
1077
// -------------------------------------------------------------
1078
	function iAlign($in)
1079
	{
1080
		$vals = array(
1081
			'<' => 'left',
1082
			'=' => 'center',
1083
			'>' => 'right');
1084
		return (isset($vals[$in])) ? $vals[$in] : '';
1085
	}
1086
1087
// -------------------------------------------------------------
1088
	function hAlign($in)
1089
	{
1090
		$vals = array(
1091
			'<'  => 'left',
1092
			'='  => 'center',
1093
			'>'  => 'right',
1094
			'<>' => 'justify');
1095
		return (isset($vals[$in])) ? $vals[$in] : '';
1096
	}
1097
1098
// -------------------------------------------------------------
1099
	function vAlign($in)
1100
	{
1101
		$vals = array(
1102
			'^' => 'top',
1103
			'-' => 'middle',
1104
			'~' => 'bottom');
1105
		return (isset($vals[$in])) ? $vals[$in] : '';
1106
	}
1107
1108
// -------------------------------------------------------------
1109
// NOTE: deprecated
1110
	function encode_high($text, $charset = "UTF-8")
1111
	{
1112
		return mb_encode_numericentity($text, $this->cmap(), $charset);
1113
	}
1114
1115
// -------------------------------------------------------------
1116
// NOTE: deprecated
1117
	function decode_high($text, $charset = "UTF-8")
1118
	{
1119
		return mb_decode_numericentity($text, $this->cmap(), $charset);
1120
	}
1121
1122
// -------------------------------------------------------------
1123
// NOTE: deprecated
1124
	function cmap()
1125
	{
1126
		$f = 0xffff;
1127
		$cmap = array(
1128
			0x0080, 0xffff, 0, $f);
1129
		return $cmap;
1130
	}
1131
1132
// -------------------------------------------------------------
1133
	function encode_raw_amp($text)
1134
	 {
1135
		return preg_replace('/&(?!#?[a-z0-9]+;)/i', '&#38;', $text);
1136
	}
1137
1138
// -------------------------------------------------------------
1139
	function encode_lt_gt($text)
1140
	 {
1141
		return strtr($text, array('<' => '&#60;', '>' => '&#62;'));
1142
	}
1143
1144
// -------------------------------------------------------------
1145
	function encode_html($str, $quotes=1)
1146
	{
1147
		$a = array(
1148
			'&' => '&#38;',
1149
			'<' => '&#60;',
1150
			'>' => '&#62;',
1151
		);
1152
		if ($quotes) $a = $a + array(
1153
			"'" => '&#39;',
1154
			'"' => '&#34;',
1155
		);
1156
1157
		return strtr($str, $a);
1158
	}
1159
1160
// -------------------------------------------------------------
1161
	function r_encode_html($str, $quotes=1)
1162
	{
1163
		// in restricted mode, input has already been escaped
1164
		if ($this->restricted)
1165
			return $str;
1166
		return $this->encode_html($str, $quotes);
1167
	}
1168
1169
// -------------------------------------------------------------
1170
	function textile_popup_help($name, $helpvar, $windowW, $windowH)
1171
	{
1172
		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 />';
1173
1174
		return $out;
1175
	}
1176
1177
// -------------------------------------------------------------
1178
// NOTE: deprecated
1179
	function txtgps($thing)
1180
	{
1181
		if (isset($_POST[$thing])) {
1182
			if (get_magic_quotes_gpc()) {
1183
				return stripslashes($_POST[$thing]);
1184
			}
1185
			else {
1186
				return $_POST[$thing];
1187
			}
1188
		}
1189
		else {
1190
			return '';
1191
		}
1192
	}
1193
1194
// -------------------------------------------------------------
1195
// NOTE: deprecated
1196
	function dump()
1197
	{
1198
		foreach (func_get_args() as $a)
1199
			echo "\n<pre>",(is_array($a)) ? print_r($a) : $a, "</pre>\n";
1200
	}
1201
1202
// -------------------------------------------------------------
1203
1204
	function blockLite($text)
1205
	{
1206
		$this->btag = array('bq', 'p');
1207
		return $this->block($text."\n\n");
1208
	}
1209
1210
1211
} // end class
1212
1213
?>