A tumblelog CMS built on AJAX, PHP and MySQL.

textile.class.php 37KB

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