PageRenderTime 51ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/vendors/texy/texy.php

http://github.com/klevo/wildflower
PHP | 959 lines | 472 code | 216 blank | 271 comment | 48 complexity | e8f99e309b10ba45d8d57e6a56f1ff85 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. /**
  3. * Texy! - web text markup-language
  4. * --------------------------------
  5. *
  6. * Copyright (c) 2004, 2009 David Grudl (http://davidgrudl.com)
  7. *
  8. * This source file is subject to the GNU GPL license that is bundled
  9. * with this package in the file license.txt.
  10. *
  11. * For more information please see http://texy.info
  12. *
  13. * @copyright Copyright (c) 2004, 2009 David Grudl
  14. * @license GNU GENERAL PUBLIC LICENSE version 2 or 3
  15. * @link http://texy.info
  16. * @package Texy
  17. * @version $Id: texy.php 226 2008-12-31 00:16:35Z David Grudl $
  18. */
  19. define('TEXY_VERSION', '2.0-beta');
  20. /**
  21. * Check PHP configuration.
  22. */
  23. if (function_exists('mb_get_info')) {
  24. if (mb_get_info('func_overload') & 2 && substr(mb_get_info('internal_encoding'), 0, 1) === 'U') { // U??
  25. mb_internal_encoding('pass');
  26. trigger_error("Texy: mb_internal_encoding changed to 'pass'", E_USER_WARNING);
  27. }
  28. }
  29. if (ini_get('zend.ze1_compatibility_mode') % 256 ||
  30. preg_match('#on$|true$|yes$#iA', ini_get('zend.ze1_compatibility_mode'))) {
  31. throw new RuntimeException("Texy cannot run with zend.ze1_compatibility_mode enabled.");
  32. }
  33. // Texy! libraries
  34. require_once dirname(__FILE__) . '/libs/RegExp.Patterns.php';
  35. require_once dirname(__FILE__) . '/libs/TexyObject.php';
  36. require_once dirname(__FILE__) . '/libs/TexyHtml.php';
  37. require_once dirname(__FILE__) . '/libs/TexyModifier.php';
  38. require_once dirname(__FILE__) . '/libs/TexyModule.php';
  39. require_once dirname(__FILE__) . '/libs/TexyParser.php';
  40. require_once dirname(__FILE__) . '/libs/TexyUtf.php';
  41. require_once dirname(__FILE__) . '/libs/TexyConfigurator.php';
  42. require_once dirname(__FILE__) . '/libs/TexyHandlerInvocation.php';
  43. require_once dirname(__FILE__) . '/modules/TexyParagraphModule.php';
  44. require_once dirname(__FILE__) . '/modules/TexyBlockModule.php';
  45. require_once dirname(__FILE__) . '/modules/TexyHeadingModule.php';
  46. require_once dirname(__FILE__) . '/modules/TexyHorizLineModule.php';
  47. require_once dirname(__FILE__) . '/modules/TexyHtmlModule.php';
  48. require_once dirname(__FILE__) . '/modules/TexyFigureModule.php';
  49. require_once dirname(__FILE__) . '/modules/TexyImageModule.php';
  50. require_once dirname(__FILE__) . '/modules/TexyLinkModule.php';
  51. require_once dirname(__FILE__) . '/modules/TexyListModule.php';
  52. require_once dirname(__FILE__) . '/modules/TexyLongWordsModule.php';
  53. require_once dirname(__FILE__) . '/modules/TexyPhraseModule.php';
  54. require_once dirname(__FILE__) . '/modules/TexyBlockQuoteModule.php';
  55. require_once dirname(__FILE__) . '/modules/TexyScriptModule.php';
  56. require_once dirname(__FILE__) . '/modules/TexyEmoticonModule.php';
  57. require_once dirname(__FILE__) . '/modules/TexyTableModule.php';
  58. require_once dirname(__FILE__) . '/modules/TexyTypographyModule.php';
  59. require_once dirname(__FILE__) . '/modules/TexyHtmlOutputModule.php';
  60. /**
  61. * Compatibility with PHP < 5.1.
  62. */
  63. if (!class_exists('LogicException', FALSE)) {
  64. class LogicException extends Exception {}
  65. }
  66. if (!class_exists('InvalidArgumentException', FALSE)) {
  67. class InvalidArgumentException extends LogicException {}
  68. }
  69. if (!class_exists('RuntimeException', FALSE)) {
  70. class RuntimeException extends Exception {}
  71. }
  72. if (!class_exists('UnexpectedValueException', FALSE)) {
  73. class UnexpectedValueException extends RuntimeException {}
  74. }
  75. /**
  76. * Compatibility with Nette
  77. */
  78. if (!class_exists('NotSupportedException', FALSE)) {
  79. class NotSupportedException extends LogicException {}
  80. }
  81. if (!class_exists('MemberAccessException', FALSE)) {
  82. class MemberAccessException extends LogicException {}
  83. }
  84. if (!class_exists('InvalidStateException', FALSE)) {
  85. class InvalidStateException extends RuntimeException {}
  86. }
  87. /**
  88. * For Texy 1 backward compatibility.
  89. */
  90. define('TEXY_ALL', TRUE);
  91. define('TEXY_NONE', FALSE);
  92. define('TEXY_CONTENT_MARKUP', "\x17");
  93. define('TEXY_CONTENT_REPLACED', "\x16");
  94. define('TEXY_CONTENT_TEXTUAL', "\x15");
  95. define('TEXY_CONTENT_BLOCK', "\x14");
  96. /**
  97. * Texy! - Convert plain text to XHTML format using {@link process()}.
  98. *
  99. * <code>
  100. * $texy = new Texy();
  101. * $html = $texy->process($text);
  102. * </code>
  103. *
  104. * @author David Grudl
  105. * @copyright Copyright (c) 2004, 2009 David Grudl
  106. * @package Texy
  107. */
  108. class Texy extends TexyObject
  109. {
  110. // configuration directives
  111. const ALL = TRUE;
  112. const NONE = FALSE;
  113. // Texy version
  114. const VERSION = TEXY_VERSION;
  115. const REVISION = '226 released on 2008/12/31 01:16:35';
  116. // types of protection marks
  117. const CONTENT_MARKUP = "\x17";
  118. const CONTENT_REPLACED = "\x16";
  119. const CONTENT_TEXTUAL = "\x15";
  120. const CONTENT_BLOCK = "\x14";
  121. // url filters
  122. const FILTER_ANCHOR = 'anchor';
  123. const FILTER_IMAGE = 'image';
  124. // HTML minor-modes
  125. const XML = 2;
  126. // HTML modes
  127. const HTML4_TRANSITIONAL = 0;
  128. const HTML4_STRICT = 1;
  129. const HTML5 = 4;
  130. const XHTML1_TRANSITIONAL = 2; // Texy::HTML4_TRANSITIONAL | Texy::XML;
  131. const XHTML1_STRICT = 3; // Texy::HTML4_STRICT | Texy::XML;
  132. const XHTML5 = 6; // Texy::HTML5 | Texy::XML;
  133. /** @var string input & output text encoding */
  134. public $encoding = 'utf-8';
  135. /** @var array Texy! syntax configuration */
  136. public $allowed = array();
  137. /** @var TRUE|FALSE|array Allowed HTML tags */
  138. public $allowedTags;
  139. /** @var TRUE|FALSE|array Allowed classes */
  140. public $allowedClasses = Texy::ALL; // all classes and id are allowed
  141. /** @var TRUE|FALSE|array Allowed inline CSS style */
  142. public $allowedStyles = Texy::ALL; // all inline styles are allowed
  143. /** @var int TAB width (for converting tabs to spaces) */
  144. public $tabWidth = 8;
  145. /** @var boolean Do obfuscate e-mail addresses? */
  146. public $obfuscateEmail = TRUE;
  147. /** @var array regexps to check URL schemes */
  148. public $urlSchemeFilters = NULL; // disable URL scheme filter
  149. /** @var bool Paragraph merging mode */
  150. public $mergeLines = TRUE;
  151. /** @var array Parsing summary */
  152. public $summary = array(
  153. 'images' => array(),
  154. 'links' => array(),
  155. 'preload' => array(),
  156. );
  157. /** @var string Generated stylesheet */
  158. public $styleSheet = '';
  159. /** @var array CSS classes for align modifiers */
  160. public $alignClasses = array(
  161. 'left' => NULL,
  162. 'right' => NULL,
  163. 'center' => NULL,
  164. 'justify' => NULL,
  165. 'top' => NULL,
  166. 'middle' => NULL,
  167. 'bottom' => NULL,
  168. );
  169. /** @var bool remove soft hyphens (SHY)? */
  170. public $removeSoftHyphens = TRUE;
  171. /** @var mixed */
  172. public static $advertisingNotice = 'once';
  173. /** @var string */
  174. public $nontextParagraph = 'div';
  175. /** @var TexyScriptModule */
  176. public $scriptModule;
  177. /** @var TexyParagraphModule */
  178. public $paragraphModule;
  179. /** @var TexyHtmlModule */
  180. public $htmlModule;
  181. /** @var TexyImageModule */
  182. public $imageModule;
  183. /** @var TexyLinkModule */
  184. public $linkModule;
  185. /** @var TexyPhraseModule */
  186. public $phraseModule;
  187. /** @var TexyEmoticonModule */
  188. public $emoticonModule;
  189. /** @var TexyBlockModule */
  190. public $blockModule;
  191. /** @var TexyHeadingModule */
  192. public $headingModule;
  193. /** @var TexyHorizLineModule */
  194. public $horizLineModule;
  195. /** @var TexyBlockQuoteModule */
  196. public $blockQuoteModule;
  197. /** @var TexyListModule */
  198. public $listModule;
  199. /** @var TexyTableModule */
  200. public $tableModule;
  201. /** @var TexyFigureModule */
  202. public $figureModule;
  203. /** @var TexyTypographyModule */
  204. public $typographyModule;
  205. /** @var TexyLongWordsModule */
  206. public $longWordsModule;
  207. /** @var TexyHtmlOutputModule */
  208. public $htmlOutputModule;
  209. /**
  210. * Registered regexps and associated handlers for inline parsing.
  211. * @var array of ('handler' => callback
  212. * 'pattern' => regular expression)
  213. */
  214. private $linePatterns = array();
  215. private $_linePatterns;
  216. /**
  217. * Registered regexps and associated handlers for block parsing.
  218. * @var array of ('handler' => callback
  219. * 'pattern' => regular expression)
  220. */
  221. private $blockPatterns = array();
  222. private $_blockPatterns;
  223. /** @var array */
  224. private $postHandlers = array();
  225. /** @var TexyHtml DOM structure for parsed text */
  226. private $DOM;
  227. /** @var array Texy protect markup table */
  228. private $marks = array();
  229. /** @var array for internal usage */
  230. public $_classes, $_styles;
  231. /** @var bool */
  232. private $processing;
  233. /** @var array of events and registered handlers */
  234. private $handlers = array();
  235. /**
  236. * DTD descriptor.
  237. * $dtd[element][0] - allowed attributes (as array keys)
  238. * $dtd[element][1] - allowed content for an element (content model) (as array keys)
  239. * - array of allowed elements (as keys)
  240. * - FALSE - empty element
  241. * - 0 - special case for ins & del
  242. * @var array
  243. */
  244. public $dtd;
  245. /** @var array */
  246. private static $dtdCache;
  247. /** @var int HTML mode */
  248. private $mode;
  249. /** DEPRECATED */
  250. public static $strictDTD;
  251. public $cleaner;
  252. public $xhtml;
  253. public function __construct()
  254. {
  255. // load all modules
  256. $this->loadModules();
  257. // DEPRECATED
  258. if (self::$strictDTD !== NULL) {
  259. $this->setOutputMode(self::$strictDTD ? self::XHTML1_STRICT : self::XHTML1_TRANSITIONAL);
  260. } else {
  261. $this->setOutputMode(self::XHTML1_TRANSITIONAL);
  262. }
  263. // DEPRECATED
  264. $this->cleaner = & $this->htmlOutputModule;
  265. // examples of link references ;-)
  266. $link = new TexyLink('http://texy.info/');
  267. $link->modifier->title = 'The best text -> HTML converter and formatter';
  268. $link->label = 'Texy!';
  269. $this->linkModule->addReference('texy', $link);
  270. $link = new TexyLink('http://www.google.com/search?q=%s');
  271. $this->linkModule->addReference('google', $link);
  272. $link = new TexyLink('http://en.wikipedia.org/wiki/Special:Search?search=%s');
  273. $this->linkModule->addReference('wikipedia', $link);
  274. }
  275. /**
  276. * Set HTML/XHTML output mode (overwrites self::$allowedTags)
  277. * @param int
  278. * @return void
  279. */
  280. public function setOutputMode($mode)
  281. {
  282. if (!in_array($mode, array(self::HTML4_TRANSITIONAL, self::HTML4_STRICT,
  283. self::HTML5, self::XHTML1_TRANSITIONAL, self::XHTML1_STRICT, self::XHTML5), TRUE)) {
  284. throw new InvalidArgumentException("Invalid mode.");
  285. }
  286. if (!isset(self::$dtdCache[$mode])) {
  287. require dirname(__FILE__) . '/libs/DTD.php';
  288. self::$dtdCache[$mode] = $dtd;
  289. }
  290. $this->mode = $mode;
  291. $this->dtd = self::$dtdCache[$mode];
  292. TexyHtml::$xhtml = (bool) ($mode & self::XML); // TODO: remove?
  293. // accept all valid HTML tags and attributes by default
  294. $this->allowedTags = array();
  295. foreach ($this->dtd as $tag => $dtd) {
  296. $this->allowedTags[$tag] = self::ALL;
  297. }
  298. }
  299. /**
  300. * Get HTML/XHTML output mode
  301. * @return int
  302. */
  303. public function getOutputMode()
  304. {
  305. return $this->mode;
  306. }
  307. /**
  308. * Create array of all used modules ($this->modules).
  309. * This array can be changed by overriding this method (by subclasses)
  310. */
  311. protected function loadModules()
  312. {
  313. // line parsing
  314. $this->scriptModule = new TexyScriptModule($this);
  315. $this->htmlModule = new TexyHtmlModule($this);
  316. $this->imageModule = new TexyImageModule($this);
  317. $this->phraseModule = new TexyPhraseModule($this);
  318. $this->linkModule = new TexyLinkModule($this);
  319. $this->emoticonModule = new TexyEmoticonModule($this);
  320. // block parsing
  321. $this->paragraphModule = new TexyParagraphModule($this);
  322. $this->blockModule = new TexyBlockModule($this);
  323. $this->figureModule = new TexyFigureModule($this);
  324. $this->horizLineModule = new TexyHorizLineModule($this);
  325. $this->blockQuoteModule = new TexyBlockQuoteModule($this);
  326. $this->tableModule = new TexyTableModule($this);
  327. $this->headingModule = new TexyHeadingModule($this);
  328. $this->listModule = new TexyListModule($this);
  329. // post process
  330. $this->typographyModule = new TexyTypographyModule($this);
  331. $this->longWordsModule = new TexyLongWordsModule($this);
  332. $this->htmlOutputModule = new TexyHtmlOutputModule($this);
  333. }
  334. final public function registerLinePattern($handler, $pattern, $name)
  335. {
  336. if (!isset($this->allowed[$name])) $this->allowed[$name] = TRUE;
  337. $this->linePatterns[$name] = array(
  338. 'handler' => $handler,
  339. 'pattern' => $pattern,
  340. );
  341. }
  342. final public function registerBlockPattern($handler, $pattern, $name)
  343. {
  344. // if (!preg_match('#(.)\^.*\$\\1[a-z]*#is', $pattern)) die("Texy: Not a block pattern $name");
  345. if (!isset($this->allowed[$name])) $this->allowed[$name] = TRUE;
  346. $this->blockPatterns[$name] = array(
  347. 'handler' => $handler,
  348. 'pattern' => $pattern . 'm', // force multiline
  349. );
  350. }
  351. final public function registerPostLine($handler, $name)
  352. {
  353. if (!isset($this->allowed[$name])) $this->allowed[$name] = TRUE;
  354. $this->postHandlers[$name] = $handler;
  355. }
  356. /**
  357. * Converts document in Texy! to (X)HTML code.
  358. *
  359. * @param string input text
  360. * @param bool is single line?
  361. * @return string output HTML code
  362. */
  363. public function process($text, $singleLine = FALSE)
  364. {
  365. if ($this->processing) {
  366. throw new InvalidStateException('Processing is in progress yet.');
  367. }
  368. // initialization
  369. $this->marks = array();
  370. $this->processing = TRUE;
  371. // speed-up
  372. if (is_array($this->allowedClasses)) $this->_classes = array_flip($this->allowedClasses);
  373. else $this->_classes = $this->allowedClasses;
  374. if (is_array($this->allowedStyles)) $this->_styles = array_flip($this->allowedStyles);
  375. else $this->_styles = $this->allowedStyles;
  376. // convert to UTF-8 (and check source encoding)
  377. $text = TexyUtf::toUtf($text, $this->encoding);
  378. if ($this->removeSoftHyphens) {
  379. $text = str_replace("\xC2\xAD", '', $text);
  380. }
  381. // standardize line endings and spaces
  382. $text = self::normalize($text);
  383. // replace tabs with spaces
  384. $this->tabWidth = max(1, (int) $this->tabWidth);
  385. while (strpos($text, "\t") !== FALSE) {
  386. $text = preg_replace_callback('#^(.*)\t#mU', array($this, 'tabCb'), $text);
  387. }
  388. // user before handler
  389. $this->invokeHandlers('beforeParse', array($this, & $text, $singleLine));
  390. // select patterns
  391. $this->_linePatterns = $this->linePatterns;
  392. $this->_blockPatterns = $this->blockPatterns;
  393. foreach ($this->_linePatterns as $name => $foo) {
  394. if (empty($this->allowed[$name])) unset($this->_linePatterns[$name]);
  395. }
  396. foreach ($this->_blockPatterns as $name => $foo) {
  397. if (empty($this->allowed[$name])) unset($this->_blockPatterns[$name]);
  398. }
  399. // parse Texy! document into internal DOM structure
  400. $this->DOM = TexyHtml::el();
  401. if ($singleLine) {
  402. $this->DOM->parseLine($this, $text);
  403. } else {
  404. $this->DOM->parseBlock($this, $text);
  405. }
  406. // user after handler
  407. $this->invokeHandlers('afterParse', array($this, $this->DOM, $singleLine));
  408. // converts internal DOM structure to final HTML code
  409. $html = $this->DOM->toHtml($this);
  410. // this notice should remain
  411. if (self::$advertisingNotice) {
  412. $html .= "\n<!-- by Texy2! -->";
  413. if (self::$advertisingNotice === 'once') {
  414. self::$advertisingNotice = FALSE;
  415. }
  416. }
  417. $this->processing = FALSE;
  418. return TexyUtf::utf2html($html, $this->encoding);
  419. }
  420. /**
  421. * Converts single line in Texy! to (X)HTML code.
  422. *
  423. * @param string input text
  424. * @return string output HTML code
  425. */
  426. public function processLine($text)
  427. {
  428. return $this->process($text, TRUE);
  429. }
  430. /**
  431. * Makes only typographic corrections.
  432. * @param string input text (in encoding defined by Texy::$encoding)
  433. * @return string output text (in UTF-8)
  434. */
  435. public function processTypo($text)
  436. {
  437. // convert to UTF-8 (and check source encoding)
  438. $text = TexyUtf::toUtf($text, $this->encoding);
  439. // standardize line endings and spaces
  440. $text = self::normalize($text);
  441. $this->typographyModule->beforeParse($this, $text);
  442. $text = $this->typographyModule->postLine($text);
  443. if (!empty($this->allowed['longwords'])) {
  444. $text = $this->longWordsModule->postLine($text);
  445. }
  446. return TexyUtf::utf2html($text, $this->encoding);
  447. }
  448. /**
  449. * Converts DOM structure to pure text.
  450. * @return string
  451. */
  452. public function toText()
  453. {
  454. if (!$this->DOM) {
  455. throw new InvalidStateException('Call $texy->process() first.');
  456. }
  457. return TexyUtf::utfTo($this->DOM->toText($this), $this->encoding);
  458. }
  459. /**
  460. * Converts internal string representation to final HTML code in UTF-8.
  461. * @return string
  462. */
  463. final public function stringToHtml($s)
  464. {
  465. // decode HTML entities to UTF-8
  466. $s = self::unescapeHtml($s);
  467. // line-postprocessing
  468. $blocks = explode(self::CONTENT_BLOCK, $s);
  469. foreach ($this->postHandlers as $name => $handler) {
  470. if (empty($this->allowed[$name])) continue;
  471. foreach ($blocks as $n => $s) {
  472. if ($n % 2 === 0 && $s !== '') {
  473. $blocks[$n] = call_user_func($handler, $s);
  474. }
  475. }
  476. }
  477. $s = implode(self::CONTENT_BLOCK, $blocks);
  478. // encode < > &
  479. $s = self::escapeHtml($s);
  480. // replace protected marks
  481. $s = $this->unProtect($s);
  482. // wellform and reformat HTML
  483. $this->invokeHandlers('postProcess', array($this, & $s));
  484. // unfreeze spaces
  485. $s = self::unfreezeSpaces($s);
  486. return $s;
  487. }
  488. /**
  489. * Converts internal string representation to final HTML code in UTF-8.
  490. * @return string
  491. */
  492. final public function stringToText($s)
  493. {
  494. $save = $this->htmlOutputModule->lineWrap;
  495. $this->htmlOutputModule->lineWrap = FALSE;
  496. $s = $this->stringToHtml( $s );
  497. $this->htmlOutputModule->lineWrap = $save;
  498. // remove tags
  499. $s = preg_replace('#<(script|style)(.*)</\\1>#Uis', '', $s);
  500. $s = strip_tags($s);
  501. $s = preg_replace('#\n\s*\n\s*\n[\n\s]*\n#', "\n\n", $s);
  502. // entities -> chars
  503. $s = self::unescapeHtml($s);
  504. // convert nbsp to normal space and remove shy
  505. $s = strtr($s, array(
  506. "\xC2\xAD" => '', // shy
  507. "\xC2\xA0" => ' ', // nbsp
  508. ));
  509. return $s;
  510. }
  511. /**
  512. * Add new event handler.
  513. *
  514. * @param string event name
  515. * @param callback
  516. * @return void
  517. */
  518. final public function addHandler($event, $callback)
  519. {
  520. if (!is_callable($callback)) {
  521. throw new InvalidArgumentException("Invalid callback.");
  522. }
  523. $this->handlers[$event][] = $callback;
  524. }
  525. /**
  526. * Invoke registered around-handlers.
  527. *
  528. * @param string event name
  529. * @param TexyParser actual parser object
  530. * @param array arguments passed into handler
  531. * @return mixed
  532. */
  533. final public function invokeAroundHandlers($event, $parser, $args)
  534. {
  535. if (!isset($this->handlers[$event])) return FALSE;
  536. $invocation = new TexyHandlerInvocation($this->handlers[$event], $parser, $args);
  537. $res = $invocation->proceed();
  538. $invocation->free();
  539. return $res;
  540. }
  541. /**
  542. * Invoke registered after-handlers.
  543. *
  544. * @param string event name
  545. * @param array arguments passed into handler
  546. * @return void
  547. */
  548. final public function invokeHandlers($event, $args)
  549. {
  550. if (!isset($this->handlers[$event])) return;
  551. foreach ($this->handlers[$event] as $handler) {
  552. call_user_func_array($handler, $args);
  553. }
  554. }
  555. /**
  556. * Translate all white spaces (\t \n \r space) to meta-spaces \x01-\x04.
  557. * which are ignored by TexyHtmlOutputModule routine
  558. * @param string
  559. * @return string
  560. */
  561. final public static function freezeSpaces($s)
  562. {
  563. return strtr($s, " \t\r\n", "\x01\x02\x03\x04");
  564. }
  565. /**
  566. * Reverts meta-spaces back to normal spaces.
  567. * @param string
  568. * @return string
  569. */
  570. final public static function unfreezeSpaces($s)
  571. {
  572. return strtr($s, "\x01\x02\x03\x04", " \t\r\n");
  573. }
  574. /**
  575. * Removes special controls characters and normalizes line endings and spaces.
  576. * @param string
  577. * @return string
  578. */
  579. final public static function normalize($s)
  580. {
  581. // standardize line endings to unix-like
  582. $s = str_replace("\r\n", "\n", $s); // DOS
  583. $s = strtr($s, "\r", "\n"); // Mac
  584. // remove special chars; leave \t + \n
  585. $s = preg_replace('#[\x00-\x08\x0B-\x1F]+#', '', $s);
  586. // right trim
  587. $s = preg_replace("#[\t ]+$#m", '', $s);
  588. // trailing spaces
  589. $s = trim($s, "\n");
  590. return $s;
  591. }
  592. /**
  593. * Converts to web safe characters [a-z0-9-] text.
  594. * @param string
  595. * @param string
  596. * @return string
  597. */
  598. final public static function webalize($s, $charlist = NULL)
  599. {
  600. $s = TexyUtf::utf2ascii($s);
  601. $s = strtolower($s);
  602. $s = preg_replace('#[^a-z0-9'.preg_quote($charlist, '#').']+#', '-', $s);
  603. $s = trim($s, '-');
  604. return $s;
  605. }
  606. /**
  607. * Texy! version of htmlSpecialChars (much faster than htmlSpecialChars!).
  608. * note: &quot; is not encoded!
  609. * @param string
  610. * @return string
  611. */
  612. final public static function escapeHtml($s)
  613. {
  614. return str_replace(array('&', '<', '>'), array('&amp;', '&lt;', '&gt;'), $s);
  615. }
  616. /**
  617. * Texy! version of html_entity_decode (always UTF-8, much faster than original!).
  618. * @param string
  619. * @return string
  620. */
  621. final public static function unescapeHtml($s)
  622. {
  623. if (strpos($s, '&') === FALSE) return $s;
  624. return html_entity_decode($s, ENT_QUOTES, 'UTF-8');
  625. }
  626. /**
  627. * Outdents text block.
  628. * @param string
  629. * @return string
  630. */
  631. final public static function outdent($s)
  632. {
  633. $s = trim($s, "\n");
  634. $spaces = strspn($s, ' ');
  635. if ($spaces) return preg_replace("#^ {1,$spaces}#m", '', $s);
  636. return $s;
  637. }
  638. /**
  639. * Generate unique mark - useful for freezing (folding) some substrings.
  640. * @param string any string to froze
  641. * @param int Texy::CONTENT_* constant
  642. * @return string internal mark
  643. */
  644. final public function protect($child, $contentType)
  645. {
  646. if ($child==='') return '';
  647. $key = $contentType
  648. . strtr(base_convert(count($this->marks), 10, 8), '01234567', "\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F")
  649. . $contentType;
  650. $this->marks[$key] = $child;
  651. return $key;
  652. }
  653. final public function unProtect($html)
  654. {
  655. return strtr($html, $this->marks);
  656. }
  657. /**
  658. * Filters bad URLs.
  659. * @param string user URL
  660. * @param string type: a-anchor, i-image, c-cite
  661. * @return bool
  662. */
  663. final public function checkURL($URL, $type)
  664. {
  665. // absolute URL with scheme? check scheme!
  666. if (!empty($this->urlSchemeFilters[$type])
  667. && preg_match('#'.TEXY_URLSCHEME.'#A', $URL)
  668. && !preg_match($this->urlSchemeFilters[$type], $URL))
  669. return FALSE;
  670. return TRUE;
  671. }
  672. /**
  673. * Is given URL relative?
  674. * @param string URL
  675. * @return bool
  676. */
  677. final public static function isRelative($URL)
  678. {
  679. // check for scheme, or absolute path, or absolute URL
  680. return !preg_match('#'.TEXY_URLSCHEME.'|[\#/?]#A', $URL);
  681. }
  682. /**
  683. * Prepends root to URL, if possible.
  684. * @param string URL
  685. * @param string root
  686. * @return string
  687. */
  688. final public static function prependRoot($URL, $root)
  689. {
  690. if ($root == NULL || !self::isRelative($URL)) return $URL;
  691. return rtrim($root, '/\\') . '/' . $URL;
  692. }
  693. final public function getLinePatterns()
  694. {
  695. return $this->_linePatterns;
  696. }
  697. final public function getBlockPatterns()
  698. {
  699. return $this->_blockPatterns;
  700. }
  701. final public function getDOM()
  702. {
  703. return $this->DOM;
  704. }
  705. private function tabCb($m)
  706. {
  707. return $m[1] . str_repeat(' ', $this->tabWidth - strlen($m[1]) % $this->tabWidth);
  708. }
  709. /**
  710. * PHP garbage collector helper.
  711. */
  712. final public function free()
  713. {
  714. if (version_compare(PHP_VERSION , '5.3', '<')) {
  715. foreach (array_keys(get_object_vars($this)) as $key) {
  716. $this->$key = NULL;
  717. }
  718. }
  719. }
  720. final public function __clone()
  721. {
  722. throw new NotSupportedException('Clone is not supported.');
  723. }
  724. }