A tumblelog CMS built on AJAX, PHP and MySQL.

textile.class.php 38KB

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