package.xml100777 0 0 7051 12172327332 6317 0 Texy pear.texy.info Human-readable text to HTML converter. Texy allows you to enter content using an easy to read Texy syntax which is filtered into structurally valid XHTML. No knowledge of HTML is required. Texy is one of the most complex formatting tools. It allows adding of images, links, nested lists, tables and has full support for CSS. Texy supports hyphenation of long words (which reflects language rules), clickable emails and URL (emails are obfuscated against spambots), national typographic single and double quotation marks, ellipses, em dashes, dimension sign, nonbreakable spaces (e.g. in phone numbers), acronyms, arrows and many others. David Grudl dg david@grudl.com yes 2013-07-14 2.3.0 2.3.0 stable stable New BSD, GPL v2, GPL v3 - 5.0.0 1.4.0 iconv pcre spl Texy-2.3.0/ 40777 0 0 0 12172334277 5474 5Texy-2.3.0/license.txt100777 0 0 5072 12171641710 7753 0Licenses ======== Good news! You may use Texy under the terms of either the New BSD License or the GNU General Public License (GPL) version 2 or 3. The BSD License is recommended for most projects. It is easy to understand and it places almost no restrictions on what you can do with the library. If the GPL fits better to your project, you can use the Texy under this license. You don't have to notify anyone which license you are using. You can freely use Texy in commercial projects as long as the copyright header remains intact. Please do not use "Texy" in the name of your project or top-level domain, and choose a name that stands on its own merits. If your stuff is good, it will not take long to establish a reputation for yourselves. New BSD License --------------- Copyright (c) 2004, 2012 David Grudl (http://davidgrudl.com) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of "Texy" nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. GNU General Public License -------------------------- GPL licenses are very very long, so instead of including them here we offer you URLs with full text: - GPL version 2: http://www.gnu.org/licenses/gpl-2.0.html - GPL version 3: http://www.gnu.org/licenses/gpl-3.0.html Texy-2.3.0/readme.txt100777 0 0 2420 12171641710 7560 0Texy! ==== Texy is text-to-HTML formatter and converter library. It allows you to write structured documents without knowledge or using of HTML language. You write documents in humane easy-to-read plain text format and Texy! converts it to structurally and valid (X)HTML code. Texy is one of the most complex formatters. Its possibilities covers images, links (anchors), nested lists, tables and has full support for CSS and UTF-8. Texy formats text in accordance with typographic rules. Special attention is paid to locale specifications. Texy! replaces single and double quotes with typographic correct quotes, hyphens with em-dash entity, converts letter x to dimension sign etc. Code is written in object PHP. Library is well-advised designed, so any enhancements or specific requirements adaptations are as easy as possible. Texy is sexy! Documentation and Examples -------------------------- Refer to the 'examples' directory for examples. Texy documentation is available on the homepage http://texy.info. Texy.minified ------------- This is shrinked single-file version of whole Texy, useful when you don't want to modify library, but just use it. This is exactly the same as normal version, just only comments and whitespaces are removed. Texy-2.3.0/src/ 40777 0 0 0 12172334277 6263 5Texy-2.3.0/src/Texy/ 40777 0 0 0 12172334277 7214 5Texy-2.3.0/src/texy.php100777 0 0 6144 12172334252 10063 01,'class'=>1,'style'=>1,'title'=>1,'xml:id'=>1); // extra: xml:id $i18n = array('lang'=>1,'dir'=>1,'xml:lang'=>1); // extra: xml:lang $attrs = $coreattrs + $i18n + array('onclick'=>1,'ondblclick'=>1,'onmousedown'=>1,'onmouseup'=>1, 'onmouseover'=>1, 'onmousemove'=>1,'onmouseout'=>1,'onkeypress'=>1,'onkeydown'=>1,'onkeyup'=>1); $cellalign = $attrs + array('align'=>1,'char'=>1,'charoff'=>1,'valign'=>1); // content elements // %block; $b = array('ins'=>1,'del'=>1,'p'=>1,'h1'=>1,'h2'=>1,'h3'=>1,'h4'=>1, 'h5'=>1,'h6'=>1,'ul'=>1,'ol'=>1,'dl'=>1,'pre'=>1,'div'=>1,'blockquote'=>1,'noscript'=>1, 'noframes'=>1,'form'=>1,'hr'=>1,'table'=>1,'address'=>1,'fieldset'=>1); if (!$strict) { $b += array( 'dir'=>1,'menu'=>1,'center'=>1,'iframe'=>1,'isindex'=>1, // transitional 'marquee'=>1, // proprietary ); } // %inline; $i = array('ins'=>1,'del'=>1,'tt'=>1,'i'=>1,'b'=>1,'big'=>1,'small'=>1,'em'=>1, 'strong'=>1,'dfn'=>1,'code'=>1,'samp'=>1,'kbd'=>1,'var'=>1,'cite'=>1,'abbr'=>1,'acronym'=>1, 'sub'=>1,'sup'=>1,'q'=>1,'span'=>1,'bdo'=>1,'a'=>1,'object'=>1,'img'=>1,'br'=>1,'script'=>1, 'map'=>1,'input'=>1,'select'=>1,'textarea'=>1,'label'=>1,'button'=>1,'%DATA'=>1); if (!$strict) { $i += array( 'u'=>1,'s'=>1,'strike'=>1,'font'=>1,'applet'=>1,'basefont'=>1, // transitional 'embed'=>1,'wbr'=>1,'nobr'=>1,'canvas'=>1, // proprietary ); } $bi = $b + $i; // build DTD $dtd = array( 'html' => array( $strict ? $i18n + array('xmlns'=>1) : $i18n + array('version'=>1,'xmlns'=>1), // extra: xmlns array('head'=>1,'body'=>1), ), 'head' => array( $i18n + array('profile'=>1), array('title'=>1,'script'=>1,'style'=>1,'base'=>1,'meta'=>1,'link'=>1,'object'=>1,'isindex'=>1), ), 'title' => array( array(), array('%DATA'=>1), ), 'body' => array( $attrs + array('onload'=>1,'onunload'=>1), $strict ? array('script'=>1) + $b : $bi, ), 'script' => array( array('charset'=>1,'type'=>1,'src'=>1,'defer'=>1,'event'=>1,'for'=>1), array('%DATA'=>1), ), 'style' => array( $i18n + array('type'=>1,'media'=>1,'title'=>1), array('%DATA'=>1), ), 'p' => array( $strict ? $attrs : $attrs + array('align'=>1), $i, ), 'h1' => array( $strict ? $attrs : $attrs + array('align'=>1), $i, ), 'h2' => array( $strict ? $attrs : $attrs + array('align'=>1), $i, ), 'h3' => array( $strict ? $attrs : $attrs + array('align'=>1), $i, ), 'h4' => array( $strict ? $attrs : $attrs + array('align'=>1), $i, ), 'h5' => array( $strict ? $attrs : $attrs + array('align'=>1), $i, ), 'h6' => array( $strict ? $attrs : $attrs + array('align'=>1), $i, ), 'ul' => array( $strict ? $attrs : $attrs + array('type'=>1,'compact'=>1), array('li'=>1), ), 'ol' => array( $strict ? $attrs : $attrs + array('type'=>1,'compact'=>1,'start'=>1), array('li'=>1), ), 'li' => array( $strict ? $attrs : $attrs + array('type'=>1,'value'=>1), $bi, ), 'dl' => array( $strict ? $attrs : $attrs + array('compact'=>1), array('dt'=>1,'dd'=>1), ), 'dt' => array( $attrs, $i, ), 'dd' => array( $attrs, $bi, ), 'pre' => array( $strict ? $attrs : $attrs + array('width'=>1), array_flip(array_diff(array_keys($i), array('img','object','applet','big','small','sub','sup','font','basefont'))), ), 'div' => array( $strict ? $attrs : $attrs + array('align'=>1), $bi, ), 'blockquote' => array( $attrs + array('cite'=>1), $strict ? array('script'=>1) + $b : $bi, ), 'noscript' => array( $attrs, $bi, ), 'form' => array( $attrs + array('action'=>1,'method'=>1,'enctype'=>1,'accept'=>1,'name'=>1,'onsubmit'=>1,'onreset'=>1,'accept-charset'=>1), $strict ? array('script'=>1) + $b : $bi, ), 'table' => array( $attrs + array('summary'=>1,'width'=>1,'border'=>1,'frame'=>1,'rules'=>1,'cellspacing'=>1,'cellpadding'=>1,'datapagesize'=>1), array('caption'=>1,'colgroup'=>1,'col'=>1,'thead'=>1,'tbody'=>1,'tfoot'=>1,'tr'=>1), ), 'caption' => array( $strict ? $attrs : $attrs + array('align'=>1), $i, ), 'colgroup' => array( $cellalign + array('span'=>1,'width'=>1), array('col'=>1), ), 'thead' => array( $cellalign, array('tr'=>1), ), 'tbody' => array( $cellalign, array('tr'=>1), ), 'tfoot' => array( $cellalign, array('tr'=>1), ), 'tr' => array( $strict ? $cellalign : $cellalign + array('bgcolor'=>1), array('td'=>1,'th'=>1), ), 'td' => array( $cellalign + array('abbr'=>1,'axis'=>1,'headers'=>1,'scope'=>1,'rowspan'=>1,'colspan'=>1), $bi, ), 'th' => array( $cellalign + array('abbr'=>1,'axis'=>1,'headers'=>1,'scope'=>1,'rowspan'=>1,'colspan'=>1), $bi, ), 'address' => array( $attrs, $strict ? $i : array('p'=>1) + $i, ), 'fieldset' => array( $attrs, array('legend'=>1) + $bi, ), 'legend' => array( $strict ? $attrs + array('accesskey'=>1) : $attrs + array('accesskey'=>1,'align'=>1), $i, ), 'tt' => array( $attrs, $i, ), 'i' => array( $attrs, $i, ), 'b' => array( $attrs, $i, ), 'big' => array( $attrs, $i, ), 'small' => array( $attrs, $i, ), 'em' => array( $attrs, $i, ), 'strong' => array( $attrs, $i, ), 'dfn' => array( $attrs, $i, ), 'code' => array( $attrs, $i, ), 'samp' => array( $attrs, $i, ), 'kbd' => array( $attrs, $i, ), 'var' => array( $attrs, $i, ), 'cite' => array( $attrs, $i, ), 'abbr' => array( $attrs, $i, ), 'acronym' => array( $attrs, $i, ), 'sub' => array( $attrs, $i, ), 'sup' => array( $attrs, $i, ), 'q' => array( $attrs + array('cite'=>1), $i, ), 'span' => array( $attrs, $i, ), 'bdo' => array( $coreattrs + array('lang'=>1,'dir'=>1), $i, ), 'a' => array( $attrs + array('charset'=>1,'type'=>1,'name'=>1,'href'=>1,'hreflang'=>1,'rel'=>1,'rev'=>1,'accesskey'=>1,'shape'=>1,'coords'=>1,'tabindex'=>1,'onfocus'=>1,'onblur'=>1), $i, ), 'object' => array( $attrs + array('declare'=>1,'classid'=>1,'codebase'=>1,'data'=>1,'type'=>1,'codetype'=>1,'archive'=>1,'standby'=>1,'height'=>1,'width'=>1,'usemap'=>1,'name'=>1,'tabindex'=>1), array('param'=>1) + $bi, ), 'map' => array( $attrs + array('name'=>1), array('area'=>1) + $b, ), 'select' => array( $attrs + array('name'=>1,'size'=>1,'multiple'=>1,'disabled'=>1,'tabindex'=>1,'onfocus'=>1,'onblur'=>1,'onchange'=>1), array('option'=>1,'optgroup'=>1), ), 'optgroup' => array( $attrs + array('disabled'=>1,'label'=>1), array('option'=>1), ), 'option' => array( $attrs + array('selected'=>1,'disabled'=>1,'label'=>1,'value'=>1), array('%DATA'=>1), ), 'textarea' => array( $attrs + array('name'=>1,'rows'=>1,'cols'=>1,'disabled'=>1,'readonly'=>1,'tabindex'=>1,'accesskey'=>1,'onfocus'=>1,'onblur'=>1,'onselect'=>1,'onchange'=>1), array('%DATA'=>1), ), 'label' => array( $attrs + array('for'=>1,'accesskey'=>1,'onfocus'=>1,'onblur'=>1), $i, // - label by TexyHtml::$prohibits ), 'button' => array( $attrs + array('name'=>1,'value'=>1,'type'=>1,'disabled'=>1,'tabindex'=>1,'accesskey'=>1,'onfocus'=>1,'onblur'=>1), $bi, // - a input select textarea label button form fieldset, by TexyHtml::$prohibits ), 'ins' => array( $attrs + array('cite'=>1,'datetime'=>1), 0, // special case ), 'del' => array( $attrs + array('cite'=>1,'datetime'=>1), 0, // special case ), // empty elements 'img' => array( $attrs + array('src'=>1,'alt'=>1,'longdesc'=>1,'name'=>1,'height'=>1,'width'=>1,'usemap'=>1,'ismap'=>1), FALSE, ), 'hr' => array( $strict ? $attrs : $attrs + array('align'=>1,'noshade'=>1,'size'=>1,'width'=>1), FALSE, ), 'br' => array( $strict ? $coreattrs : $coreattrs + array('clear'=>1), FALSE, ), 'input' => array( $attrs + array('type'=>1,'name'=>1,'value'=>1,'checked'=>1,'disabled'=>1,'readonly'=>1,'size'=>1,'maxlength'=>1,'src'=>1,'alt'=>1,'usemap'=>1,'ismap'=>1,'tabindex'=>1,'accesskey'=>1,'onfocus'=>1,'onblur'=>1,'onselect'=>1,'onchange'=>1,'accept'=>1), FALSE, ), 'meta' => array( $i18n + array('http-equiv'=>1,'name'=>1,'content'=>1,'scheme'=>1), FALSE, ), 'area' => array( $attrs + array('shape'=>1,'coords'=>1,'href'=>1,'nohref'=>1,'alt'=>1,'tabindex'=>1,'accesskey'=>1,'onfocus'=>1,'onblur'=>1), FALSE, ), 'base' => array( $strict ? array('href'=>1) : array('href'=>1,'target'=>1), FALSE, ), 'col' => array( $cellalign + array('span'=>1,'width'=>1), FALSE, ), 'link' => array( $attrs + array('charset'=>1,'href'=>1,'hreflang'=>1,'type'=>1,'rel'=>1,'rev'=>1,'media'=>1), FALSE, ), 'param' => array( array('id'=>1,'name'=>1,'value'=>1,'valuetype'=>1,'type'=>1), FALSE, ), // special "base content" '%BASE' => array( NULL, array('html'=>1,'head'=>1,'body'=>1,'script'=>1) + $bi, ), ); if ($strict) { return $dtd; } // LOOSE DTD $dtd += array( // transitional 'dir' => array( $attrs + array('compact'=>1), array('li'=>1), ), 'menu' => array( $attrs + array('compact'=>1), array('li'=>1), // it's inline-li, ignored ), 'center' => array( $attrs, $bi, ), 'iframe' => array( $coreattrs + array('longdesc'=>1,'name'=>1,'src'=>1,'frameborder'=>1,'marginwidth'=>1,'marginheight'=>1,'scrolling'=>1,'align'=>1,'height'=>1,'width'=>1), $bi, ), 'noframes' => array( $attrs, $bi, ), 'u' => array( $attrs, $i, ), 's' => array( $attrs, $i, ), 'strike' => array( $attrs, $i, ), 'font' => array( $coreattrs + $i18n + array('size'=>1,'color'=>1,'face'=>1), $i, ), 'applet' => array( $coreattrs + array('codebase'=>1,'archive'=>1,'code'=>1,'object'=>1,'alt'=>1,'name'=>1,'width'=>1,'height'=>1,'align'=>1,'hspace'=>1,'vspace'=>1), array('param'=>1) + $bi, ), 'basefont' => array( array('id'=>1,'size'=>1,'color'=>1,'face'=>1), FALSE, ), 'isindex' => array( $coreattrs + $i18n + array('prompt'=>1), FALSE, ), // proprietary 'marquee' => array( Texy::ALL, $bi, ), 'nobr' => array( array(), $i, ), 'canvas' => array( Texy::ALL, $i, ), 'embed' => array( Texy::ALL, FALSE, ), 'wbr' => array( array(), FALSE, ), ); // transitional modified $dtd['a'][0] += array('target'=>1); $dtd['area'][0] += array('target'=>1); $dtd['body'][0] += array('background'=>1,'bgcolor'=>1,'text'=>1,'link'=>1,'vlink'=>1,'alink'=>1); $dtd['form'][0] += array('target'=>1); $dtd['img'][0] += array('align'=>1,'border'=>1,'hspace'=>1,'vspace'=>1); $dtd['input'][0] += array('align'=>1); $dtd['link'][0] += array('target'=>1); $dtd['object'][0] += array('align'=>1,'border'=>1,'hspace'=>1,'vspace'=>1); $dtd['script'][0] += array('language'=>1); $dtd['table'][0] += array('align'=>1,'bgcolor'=>1); $dtd['td'][0] += array('nowrap'=>1,'bgcolor'=>1,'width'=>1,'height'=>1); $dtd['th'][0] += array('nowrap'=>1,'bgcolor'=>1,'width'=>1,'height'=>1); // missing: FRAMESET, FRAME, BGSOUND, XMP, ... Texy-2.3.0/src/Texy/modules/ 40777 0 0 0 12172334277 10664 5Texy-2.3.0/src/Texy/modules/TexyBlockModule.php100777 0 0 14310 12172334252 14557 0texy = $texy; //$texy->allowed['blocks'] = TRUE; $texy->allowed['block/default'] = TRUE; $texy->allowed['block/pre'] = TRUE; $texy->allowed['block/code'] = TRUE; $texy->allowed['block/html'] = TRUE; $texy->allowed['block/text'] = TRUE; $texy->allowed['block/texysource'] = TRUE; $texy->allowed['block/comment'] = TRUE; $texy->allowed['block/div'] = TRUE; $texy->addHandler('block', array($this, 'solve')); $texy->addHandler('beforeBlockParse', array($this, 'beforeBlockParse')); $texy->registerBlockPattern( array($this, 'pattern'), '#^/--++ *+(.*)'.TexyPatterns::MODIFIER_H.'?$((?:\n(?0)|\n.*+)*)(?:\n\\\\--.*$|\z)#mUi', 'blocks' ); } /** * Single block pre-processing. * @param TexyBlockParser * @param string * @return void */ public function beforeBlockParse($parser, & $text) { // autoclose exclusive blocks $text = preg_replace( '#^(/--++ *+(?!div|texysource).*)$((?:\n.*+)*?)(?:\n\\\\--.*$|(?=(\n/--.*$)))#mi', "\$1\$2\n\\--", $text ); } /** * Callback for:. * /-----code html .(title)[class]{style} * .... * .... * \---- * * @param TexyBlockParser * @param array regexp matches * @param string pattern name * @return TexyHtml|string|FALSE */ public function pattern($parser, $matches) { list(, $mParam, $mMod, $mContent) = $matches; // [1] => code | text | ... // [2] => ... additional parameters // [3] => .(title)[class]{style}<> // [4] => ... content $mod = new TexyModifier($mMod); $parts = preg_split('#\s+#u', $mParam, 2); $blocktype = empty($parts[0]) ? 'block/default' : 'block/' . $parts[0]; $param = empty($parts[1]) ? NULL : $parts[1]; return $this->texy->invokeAroundHandlers('block', $parser, array($blocktype, $mContent, $param, $mod)); } // for backward compatibility function outdent($s) { trigger_error('Use Texy::outdent()', E_USER_WARNING); return Texy::outdent($s); } /** * Finish invocation. * * @param TexyHandlerInvocation handler invocation * @param string blocktype * @param string content * @param string additional parameter * @param TexyModifier * @return TexyHtml|string|FALSE */ public function solve($invocation, $blocktype, $s, $param, $mod) { $tx = $this->texy; $parser = $invocation->parser; if ($blocktype === 'block/texy') { $el = TexyHtml::el(); $el->parseBlock($tx, $s, $parser->isIndented()); return $el; } if (empty($tx->allowed[$blocktype])) { return FALSE; } if ($blocktype === 'block/texysource') { $s = Texy::outdent($s); if ($s === '') { return "\n"; } $el = TexyHtml::el(); if ($param === 'line') { $el->parseLine($tx, $s); } else { $el->parseBlock($tx, $s); } $s = $el->toHtml($tx); $blocktype = 'block/code'; $param = 'html'; // to be continue (as block/code) } if ($blocktype === 'block/code') { $s = Texy::outdent($s); if ($s === '') { return "\n"; } $s = Texy::escapeHtml($s); $s = $tx->protect($s, Texy::CONTENT_BLOCK); $el = TexyHtml::el('pre'); $mod->decorate($tx, $el); $el->attrs['class'][] = $param; // lang $el->create('code', $s); return $el; } if ($blocktype === 'block/default') { $s = Texy::outdent($s); if ($s === '') { return "\n"; } $el = TexyHtml::el('pre'); $mod->decorate($tx, $el); $el->attrs['class'][] = $param; // lang $s = Texy::escapeHtml($s); $s = $tx->protect($s, Texy::CONTENT_BLOCK); $el->setText($s); return $el; } if ($blocktype === 'block/pre') { $s = Texy::outdent($s); if ($s === '') { return "\n"; } $el = TexyHtml::el('pre'); $mod->decorate($tx, $el); $lineParser = new TexyLineParser($tx, $el); // special mode - parse only html tags $tmp = $lineParser->patterns; $lineParser->patterns = array(); if (isset($tmp['html/tag'])) { $lineParser->patterns['html/tag'] = $tmp['html/tag']; } if (isset($tmp['html/comment'])) { $lineParser->patterns['html/comment'] = $tmp['html/comment']; } unset($tmp); $lineParser->parse($s); $s = $el->getText(); $s = Texy::unescapeHtml($s); $s = Texy::escapeHtml($s); $s = $tx->unprotect($s); $s = $tx->protect($s, Texy::CONTENT_BLOCK); $el->setText($s); return $el; } if ($blocktype === 'block/html') { $s = trim($s, "\n"); if ($s === '') { return "\n"; } $el = TexyHtml::el(); $lineParser = new TexyLineParser($tx, $el); // special mode - parse only html tags $tmp = $lineParser->patterns; $lineParser->patterns = array(); if (isset($tmp['html/tag'])) { $lineParser->patterns['html/tag'] = $tmp['html/tag']; } if (isset($tmp['html/comment'])) { $lineParser->patterns['html/comment'] = $tmp['html/comment']; } unset($tmp); $lineParser->parse($s); $s = $el->getText(); $s = Texy::unescapeHtml($s); $s = Texy::escapeHtml($s); $s = $tx->unprotect($s); return $tx->protect($s, Texy::CONTENT_BLOCK) . "\n"; } if ($blocktype === 'block/text') { $s = trim($s, "\n"); if ($s === '') { return "\n"; } $s = Texy::escapeHtml($s); $s = str_replace("\n", TexyHtml::el('br')->startTag() , $s); // nl2br return $tx->protect($s, Texy::CONTENT_BLOCK) . "\n"; } if ($blocktype === 'block/comment') { return "\n"; } if ($blocktype === 'block/div') { $s = Texy::outdent($s); if ($s === '') { return "\n"; } $el = TexyHtml::el('div'); $mod->decorate($tx, $el); $el->parseBlock($tx, $s, $parser->isIndented()); // TODO: INDENT or NORMAL ? return $el; } return FALSE; } } Texy-2.3.0/src/Texy/modules/TexyBlockQuoteModule.php100777 0 0 6471 12172334252 15566 0texy = $texy; $texy->registerBlockPattern( array($this, 'pattern'), '#^(?:'.TexyPatterns::MODIFIER_H.'\n)?\>(\ ++|:)(\S.*+)$#mU', // original // '#^(?:'.TexyPatterns::MODIFIER_H.'\n)?\>(?:(\>|\ +?|:)(.*))?()$#mU', // >>>> // '#^(?:'.TexyPatterns::MODIFIER_H.'\n)?\>(?:(\ +?|:)(.*))()$#mU', // only > 'blockquote' ); } /** * Callback for:. * * > They went in single file, running like hounds on a strong scent, * and an eager light was in their eyes. Nearly due west the broad * swath of the marching Orcs tramped its ugly slot; the sweet grass * of Rohan had been bruised and blackened as they passed. * >:http://www.mycom.com/tolkien/twotowers.html * * @param TexyBlockParser * @param array regexp matches * @param string pattern name * @return TexyHtml|string|FALSE */ public function pattern($parser, $matches) { list(, $mMod, $mPrefix, $mContent) = $matches; // [1] => .(title)[class]{style}<> // [2] => spaces | // [3] => ... / LINK $tx = $this->texy; $el = TexyHtml::el('blockquote'); $mod = new TexyModifier($mMod); $mod->decorate($tx, $el); $content = ''; $spaces = ''; do { if ($mPrefix === ':') { $mod->cite = $tx->blockQuoteModule->citeLink($mContent); $content .= "\n"; } else { if ($spaces === '') { $spaces = max(1, strlen($mPrefix)); } $content .= $mContent . "\n"; } if (!$parser->next("#^>(?:|(\\ {1,$spaces}|:)(.*))()$#mA", $matches)) { break; } /* if ($mPrefix === '>') { $content .= $mPrefix . $mContent . "\n"; } elseif ($mPrefix === ':') { $mod->cite = $tx->blockQuoteModule->citeLink($mContent); $content .= "\n"; } else { if ($spaces === '') $spaces = max(1, strlen($mPrefix)); $content .= $mContent . "\n"; } if (!$parser->next("#^\\>(?:(\\>|\\ {1,$spaces}|:)(.*))?()$#mA", $matches)) break; */ list(, $mPrefix, $mContent) = $matches; } while (TRUE); $el->attrs['cite'] = $mod->cite; $el->parseBlock($tx, $content, $parser->isIndented()); // no content? if (!$el->count()) { return FALSE; } // event listener $tx->invokeHandlers('afterBlockquote', array($parser, $el, $mod)); return $el; } /** * Converts cite source to URL. * @param string * @return string|NULL */ public function citeLink($link) { $tx = $this->texy; if ($link == NULL) { return NULL; } if ($link{0} === '[') { // [ref] $link = substr($link, 1, -1); $ref = $tx->linkModule->getReference($link); if ($ref) { return Texy::prependRoot($ref->URL, $tx->linkModule->root); } } // special supported case if (strncasecmp($link, 'www.', 4) === 0) { return 'http://' . $link; } return Texy::prependRoot($link, $tx->linkModule->root); } } Texy-2.3.0/src/Texy/modules/TexyEmoticonModule.php100777 0 0 6267 12172334252 15276 0 'smile.gif', ':-(' => 'sad.gif', ';-)' => 'wink.gif', ':-D' => 'biggrin.gif', '8-O' => 'eek.gif', '8-)' => 'cool.gif', ':-?' => 'confused.gif', ':-x' => 'mad.gif', ':-P' => 'razz.gif', ':-|' => 'neutral.gif', ); /** @var string CSS class for emoticons */ public $class; /** @var string root of relative images (default value is $texy->imageModule->root) */ public $root; /** @var string physical location of images on server (default value is $texy->imageModule->fileRoot) */ public $fileRoot; public function __construct($texy) { $this->texy = $texy; $texy->allowed['emoticon'] = FALSE; $texy->addHandler('emoticon', array($this, 'solve')); $texy->addHandler('beforeParse', array($this, 'beforeParse')); } public function beforeParse() { if (empty($this->texy->allowed['emoticon'])) { return; } krsort($this->icons); $pattern = array(); foreach ($this->icons as $key => $foo) { $pattern[] = preg_quote($key, '#') . '+'; // last char can be repeated } $this->texy->registerLinePattern( array($this, 'pattern'), '#(?<=^|[\\x00-\\x20])(' . implode('|', $pattern) . ')#', 'emoticon', '#' . implode('|', $pattern) . '#' ); } /** * Callback for: :-))). * * @param TexyLineParser * @param array regexp matches * @param string pattern name * @return TexyHtml|string|FALSE */ public function pattern($parser, $matches) { $match = $matches[0]; $tx = $this->texy; // find the closest match foreach ($this->icons as $emoticon => $foo) { if (strncmp($match, $emoticon, strlen($emoticon)) === 0) { return $tx->invokeAroundHandlers('emoticon', $parser, array($emoticon, $match)); } } return FALSE; // tohle se nestane } /** * Finish invocation. * * @param TexyHandlerInvocation handler invocation * @param string * @param string * @return TexyHtml|FALSE */ public function solve($invocation, $emoticon, $raw) { $tx = $this->texy; $file = $this->icons[$emoticon]; $el = TexyHtml::el('img'); $el->attrs['src'] = Texy::prependRoot($file, $this->root === NULL ? $tx->imageModule->root : $this->root); $el->attrs['alt'] = $raw; $el->attrs['class'][] = $this->class; // file path $file = rtrim($this->fileRoot === NULL ? $tx->imageModule->fileRoot : $this->fileRoot, '/\\') . '/' . $file; if (@is_file($file)) { // intentionally @ $size = @getImageSize($file); // intentionally @ if (is_array($size)) { $el->attrs['width'] = $size[0]; $el->attrs['height'] = $size[1]; } } $tx->summary['images'][] = $el->attrs['src']; return $el; } } Texy-2.3.0/src/Texy/modules/TexyFigureModule.php100777 0 0 6662 12172334252 14741 0texy = $texy; $texy->addHandler('figure', array($this, 'solve')); $texy->registerBlockPattern( array($this, 'pattern'), '#^\[\* *+([^\n'.TexyPatterns::MARK.']{1,1000})'.TexyPatterns::MODIFIER.'? *+(\*|(?|<)\]' // [* urls .(title)[class]{style} >] . '(?::('.TexyPatterns::LINK_URL.'|:))?? ++\*\*\* ++(.{0,2000})'.TexyPatterns::MODIFIER_H.'?()$#mUu', 'figure' ); } /** * Callback for [*image*]:link *** .... .(title)[class]{style}>. * * @param TexyBlockParser * @param array regexp matches * @param string pattern name * @return TexyHtml|string|FALSE */ public function pattern($parser, $matches) { list(, $mURLs, $mImgMod, $mAlign, $mLink, $mContent, $mMod) = $matches; // [1] => URLs // [2] => .(title)[class]{style}<> // [3] => * < > // [4] => url | [ref] | [*image*] // [5] => ... // [6] => .(title)[class]{style}<> $tx = $this->texy; $image = $tx->imageModule->factoryImage($mURLs, $mImgMod.$mAlign); $mod = new TexyModifier($mMod); $mContent = ltrim($mContent); if ($mLink) { if ($mLink === ':') { $link = new TexyLink($image->linkedURL === NULL ? $image->URL : $image->linkedURL); $link->raw = ':'; $link->type = TexyLink::IMAGE; } else { $link = $tx->linkModule->factoryLink($mLink, NULL, NULL); } } else { $link = NULL; } return $tx->invokeAroundHandlers('figure', $parser, array($image, $link, $mContent, $mod)); } /** * Finish invocation. * * @param TexyHandlerInvocation handler invocation * @param TexyImage * @param TexyLink * @param string * @param TexyModifier * @return TexyHtml|FALSE */ public function solve($invocation, TexyImage $image, $link, $content, $mod) { $tx = $this->texy; $hAlign = $image->modifier->hAlign; $image->modifier->hAlign = NULL; $elImg = $tx->imageModule->solve(NULL, $image, $link); // returns TexyHtml or false! if (!$elImg) { return FALSE; } $el = TexyHtml::el('div'); if (!empty($image->width) && $this->widthDelta !== FALSE) { $el->attrs['style']['width'] = ($image->width + $this->widthDelta) . 'px'; } $mod->decorate($tx, $el); $el[0] = $elImg; $el[1] = TexyHtml::el('p'); $el[1]->parseLine($tx, ltrim($content)); $class = $this->class; if ($hAlign) { $var = $hAlign . 'Class'; // leftClass, rightClass if (!empty($this->$var)) { $class = $this->$var; } elseif (empty($tx->alignClasses[$hAlign])) { $el->attrs['style']['float'] = $hAlign; } else { $class .= '-' . $tx->alignClasses[$hAlign]; } } $el->attrs['class'][] = $class; return $el; } } Texy-2.3.0/src/Texy/modules/TexyHeadingModule.php100777 0 0 14110 12172334252 15062 0 0, // # --> $levels['#'] + $top = 0 + 1 = 1 -->

...

'*' => 1, '=' => 2, '-' => 3, ); /** @var array used ID's */ private $usedID; public function __construct($texy) { $this->texy = $texy; $texy->addHandler('heading', array($this, 'solve')); $texy->addHandler('beforeParse', array($this, 'beforeParse')); $texy->addHandler('afterParse', array($this, 'afterParse')); $texy->registerBlockPattern( array($this, 'patternUnderline'), '#^(\S.{0,1000})'.TexyPatterns::MODIFIER_H.'?\n' . '(\#{3,}+|\*{3,}+|={3,}+|-{3,}+)$#mU', 'heading/underlined' ); $texy->registerBlockPattern( array($this, 'patternSurround'), '#^(\#{2,}+|={2,}+)(.+)'.TexyPatterns::MODIFIER_H.'?()$#mU', 'heading/surrounded' ); } public function beforeParse() { $this->title = NULL; $this->usedID = array(); $this->TOC = array(); } /** * @param Texy * @param TexyHtml * @param bool * @return void */ public function afterParse($texy, $DOM, $isSingleLine) { if ($isSingleLine) { return; } if ($this->balancing === self::DYNAMIC) { $top = $this->top; $map = array(); $min = 100; foreach ($this->TOC as $item) { $level = $item['level']; if ($item['type'] === 'surrounded') { $min = min($level, $min); $top = $this->top - $min; } elseif ($item['type'] === 'underlined') { $map[$level] = $level; } } asort($map); $map = array_flip(array_values($map)); } foreach ($this->TOC as $key => $item) { if ($this->balancing === self::DYNAMIC) { if ($item['type'] === 'surrounded') { $level = $item['level'] + $top; } elseif ($item['type'] === 'underlined') { $level = $map[$item['level']] + $this->top; } else { $level = $item['level']; } $item['el']->setName('h' . min(6, max(1, $level))); $this->TOC[$key]['level'] = $level; } if ($this->generateID) { if (!empty($item['el']->style['toc']) && is_array($item['el']->style)) { $title = $item['el']->style['toc']; unset($item['el']->style['toc']); } else { $title = trim($item['el']->toText($this->texy)); } $this->TOC[$key]['title'] = $title; if (empty($item['el']->attrs['id'])) { $id = $this->idPrefix . Texy::webalize($title); $counter = ''; if (isset($this->usedID[$id . $counter])) { $counter = 2; while (isset($this->usedID[$id . '-' . $counter])) { $counter++; } $id .= '-' . $counter; } $this->usedID[$id] = TRUE; $item['el']->attrs['id'] = $id; } } } // document title if ($this->title === NULL && count($this->TOC)) { $item = reset($this->TOC); $this->title = isset($item['title']) ? $item['title'] : trim($item['el']->toText($this->texy)); } } /** * Callback for underlined heading. * * Heading .(title)[class]{style}> * ------------------------------- * * @param TexyBlockParser * @param array regexp matches * @param string pattern name * @return TexyHtml|string|FALSE */ public function patternUnderline($parser, $matches) { list(, $mContent, $mMod, $mLine) = $matches; // $matches: // [1] => ... // [2] => .(title)[class]{style}<> // [3] => ... $mod = new TexyModifier($mMod); $level = $this->levels[$mLine[0]]; return $this->texy->invokeAroundHandlers('heading', $parser, array($level, $mContent, $mod, FALSE)); } /** * Callback for surrounded heading. * * ### Heading .(title)[class]{style}> * * @param TexyBlockParser * @param array regexp matches * @param string pattern name * @return TexyHtml|string|FALSE */ public function patternSurround($parser, $matches) { list(, $mLine, $mContent, $mMod) = $matches; // [1] => ### // [2] => ... // [3] => .(title)[class]{style}<> $mod = new TexyModifier($mMod); $level = min(7, max(2, strlen($mLine))); $level = $this->moreMeansHigher ? 7 - $level : $level - 2; $mContent = rtrim($mContent, $mLine[0] . ' '); return $this->texy->invokeAroundHandlers('heading', $parser, array($level, $mContent, $mod, TRUE)); } /** * Finish invocation. * * @param TexyHandlerInvocation handler invocation * @param int 0..5 * @param string * @param TexyModifier * @param bool * @return TexyHtml */ public function solve($invocation, $level, $content, $mod, $isSurrounded) { // as fixed balancing, for block/texysource & correct decorating $el = TexyHtml::el('h' . min(6, max(1, $level + $this->top))); $mod->decorate($this->texy, $el); $el->parseLine($this->texy, trim($content)); $this->TOC[] = array( 'el' => $el, 'level' => $level, 'type' => $isSurrounded ? 'surrounded' : 'underlined', ); return $el; } } Texy-2.3.0/src/Texy/modules/TexyHorizLineModule.php100777 0 0 3310 12172334252 15406 0 NULL, '*' => NULL, ); public function __construct($texy) { $this->texy = $texy; $texy->addHandler('horizline', array($this, 'solve')); $texy->registerBlockPattern( array($this, 'pattern'), '#^(\*{3,}+|-{3,}+)\ *'.TexyPatterns::MODIFIER.'?()$#mU', 'horizline' ); } /** * Callback for: -------. * * @param TexyBlockParser * @param array regexp matches * @param string pattern name * @return TexyHtml */ public function pattern($parser, $matches) { list(, $mType, $mMod) = $matches; // [1] => --- // [2] => .(title)[class]{style}<> $mod = new TexyModifier($mMod); return $this->texy->invokeAroundHandlers('horizline', $parser, array($mType, $mod)); } /** * Finish invocation. * * @param TexyHandlerInvocation handler invocation * @param string * @param TexyModifier * @return TexyHtml */ public function solve($invocation, $type, $modifier) { $el = TexyHtml::el('hr'); $modifier->decorate($invocation->texy, $el); $class = $this->classes[ $type[0] ]; if ($class && !isset($modifier->classes[$class])) { $el->attrs['class'][] = $class; } return $el; } } Texy-2.3.0/src/Texy/modules/TexyHtmlModule.php100777 0 0 16223 12172334252 14436 0texy = $texy; $texy->addHandler('htmlComment', array($this, 'solveComment')); $texy->addHandler('htmlTag', array($this, 'solveTag')); $texy->registerLinePattern( array($this, 'patternTag'), '#<(/?)([a-z][a-z0-9_:-]{0,50})((?:\s++[a-z0-9:-]++|=\s*+"[^"'.TexyPatterns::MARK.']*+"|=\s*+\'[^\''.TexyPatterns::MARK.']*+\'|=[^\s>'.TexyPatterns::MARK.']++)*)\s*+(/?)>#isu', 'html/tag' ); $texy->registerLinePattern( array($this, 'patternComment'), '##is', 'html/comment' ); } /** * Callback for: . * * @param TexyLineParser * @param array regexp matches * @param string pattern name * @return TexyHtml|string|FALSE */ public function patternComment($parser, $matches) { list(, $mComment) = $matches; return $this->texy->invokeAroundHandlers('htmlComment', $parser, array($mComment)); } /** * Callback for: . * * @param TexyLineParser * @param array regexp matches * @param string pattern name * @return TexyHtml|string|FALSE */ public function patternTag($parser, $matches) { list(, $mEnd, $mTag, $mAttr, $mEmpty) = $matches; // [1] => / // [2] => tag // [3] => attributes // [4] => / $tx = $this->texy; $isStart = $mEnd !== '/'; $isEmpty = $mEmpty === '/'; if (!$isEmpty && substr($mAttr, -1) === '/') { // uvizlo v $mAttr? $mAttr = substr($mAttr, 0, -1); $isEmpty = TRUE; } // error - can't close empty element if ($isEmpty && !$isStart) { return FALSE; } // error - end element with atttrs $mAttr = trim(strtr($mAttr, "\n", ' ')); if ($mAttr && !$isStart) { return FALSE; } $el = TexyHtml::el($mTag); if ($isStart) { // parse attributes $matches2 = NULL; preg_match_all( '#([a-z0-9:-]+)\s*(?:=\s*(\'[^\']*\'|"[^"]*"|[^\'"\s]+))?()#isu', $mAttr, $matches2, PREG_SET_ORDER ); foreach ($matches2 as $m) { $key = strtolower($m[1]); $value = $m[2]; if ($value == NULL) { $el->attrs[$key] = TRUE; } elseif ($value{0} === '\'' || $value{0} === '"') { $el->attrs[$key] = Texy::unescapeHtml(substr($value, 1, -1)); } else { $el->attrs[$key] = Texy::unescapeHtml($value); } } } $res = $tx->invokeAroundHandlers('htmlTag', $parser, array($el, $isStart, $isEmpty)); if ($res instanceof TexyHtml) { return $tx->protect($isStart ? $res->startTag() : $res->endTag(), $res->getContentType()); } return $res; } /** * Finish invocation. * * @param TexyHandlerInvocation handler invocation * @param TexyHtml element * @param bool is start tag? * @param bool is empty? * @return TexyHtml|string|FALSE */ public function solveTag($invocation, TexyHtml $el, $isStart, $forceEmpty = NULL) { $tx = $this->texy; // tag & attibutes $allowedTags = $tx->allowedTags; // speed-up if (!$allowedTags) { return FALSE; // all tags are disabled } // convert case $name = $el->getName(); $lower = strtolower($name); if (isset($tx->dtd[$lower]) || $name === strtoupper($name)) { // complete UPPER convert to lower $name = $lower; $el->setName($name); } if (is_array($allowedTags)) { if (!isset($allowedTags[$name])) { return FALSE; } $allowedAttrs = $allowedTags[$name]; // allowed attrs } else { // allowedTags === Texy::ALL if ($forceEmpty) { $el->setName($name, TRUE); } $allowedAttrs = Texy::ALL; // all attrs are allowed } // end tag? we are finished if (!$isStart) { return $el; } $elAttrs = & $el->attrs; // process attributes if (!$allowedAttrs) { $elAttrs = array(); } elseif (is_array($allowedAttrs)) { // skip disabled $allowedAttrs = array_flip($allowedAttrs); foreach ($elAttrs as $key => $foo) { if (!isset($allowedAttrs[$key])) { unset($elAttrs[$key]); } } } // apply allowedClasses $tmp = $tx->_classes; // speed-up if (isset($elAttrs['class'])) { if (is_array($tmp)) { $elAttrs['class'] = explode(' ', $elAttrs['class']); foreach ($elAttrs['class'] as $key => $value) { if (!isset($tmp[$value])) { unset($elAttrs['class'][$key]); // id & class are case-sensitive } } } elseif ($tmp !== Texy::ALL) { $elAttrs['class'] = NULL; } } // apply allowedClasses for ID if (isset($elAttrs['id'])) { if (is_array($tmp)) { if (!isset($tmp['#' . $elAttrs['id']])) { $elAttrs['id'] = NULL; } } elseif ($tmp !== Texy::ALL) { $elAttrs['id'] = NULL; } } // apply allowedStyles if (isset($elAttrs['style'])) { $tmp = $tx->_styles; // speed-up if (is_array($tmp)) { $styles = explode(';', $elAttrs['style']); $elAttrs['style'] = NULL; foreach ($styles as $value) { $pair = explode(':', $value, 2); $prop = trim($pair[0]); if (isset($pair[1]) && isset($tmp[strtolower($prop)])) { // CSS is case-insensitive $elAttrs['style'][$prop] = $pair[1]; } } } elseif ($tmp !== Texy::ALL) { $elAttrs['style'] = NULL; } } if ($name === 'img') { if (!isset($elAttrs['src']) || !$tx->checkURL($elAttrs['src'], Texy::FILTER_IMAGE)) { return FALSE; } $tx->summary['images'][] = $elAttrs['src']; } elseif ($name === 'a') { if (!isset($elAttrs['href']) && !isset($elAttrs['name']) && !isset($elAttrs['id'])) { return FALSE; } if (isset($elAttrs['href'])) { if ($tx->linkModule->forceNoFollow && strpos($elAttrs['href'], '//') !== FALSE) { if (isset($elAttrs['rel'])) { $elAttrs['rel'] = (array) $elAttrs['rel']; } $elAttrs['rel'][] = 'nofollow'; } if (!$tx->checkURL($elAttrs['href'], Texy::FILTER_ANCHOR)) { return FALSE; } $tx->summary['links'][] = $elAttrs['href']; } } elseif (preg_match('#^h[1-6]#i', $name)) { $tx->headingModule->TOC[] = array( 'el' => $el, 'level' => (int) substr($name, 1), 'type' => 'html', ); } $el->validateAttrs($tx->dtd); return $el; } /** * Finish invocation. * * @param TexyHandlerInvocation handler invocation * @param string * @return string */ public function solveComment($invocation, $content) { if (!$this->passComment) { return ''; } // sanitize comment $content = preg_replace('#-{2,}#', ' - ', $content); $content = trim($content, '-'); return $this->texy->protect('', Texy::CONTENT_MARKUP); } } Texy-2.3.0/src/Texy/modules/TexyHtmlOutputModule.php100777 0 0 17520 12172334252 15660 0texy = $texy; $texy->addHandler('postProcess', array($this, 'postProcess')); } /** * Converts ... ... . * into ... ... */ public function postProcess($texy, & $s) { $this->space = $this->baseIndent; $this->tagStack = array(); $this->tagUsed = array(); $this->xml = $texy->getOutputMode() & Texy::XML; // special "base content" $this->baseDTD = $texy->dtd['div'][1] + $texy->dtd['html'][1] /*+ $texy->dtd['head'][1]*/ + $texy->dtd['body'][1] + array('html'=>1); // wellform and reformat $s = preg_replace_callback( '#([^<]*+)<(?:(!--.*--)|(/?)([a-z][a-z0-9._:-]*)(|[ \n].*)\s*(/?))>()#Uis', array($this, 'cb'), $s . '' ); // empty out stack foreach ($this->tagStack as $item) { $s .= $item['close']; } // right trim $s = preg_replace("#[\t ]+(\n|\r|$)#", '$1', $s); // right trim // join double \r to single \n $s = str_replace("\r\r", "\n", $s); $s = strtr($s, "\r", "\n"); // greedy chars $s = preg_replace("#\\x07 *#", '', $s); // back-tabs $s = preg_replace("#\\t? *\\x08#", '', $s); // line wrap if ($this->lineWrap > 0) { $s = preg_replace_callback( '#^(\t*)(.*)$#m', array($this, 'wrap'), $s ); } // remove HTML 4.01 optional end tags if (!$this->xml && $this->removeOptional) { $s = preg_replace('#\\s*#u', '', $s); } } /** * Callback function: | | .... * @return string */ private function cb($matches) { // html tag list(, $mText, $mComment, $mEnd, $mTag, $mAttr, $mEmpty) = $matches; // [1] => text // [1] => !-- comment -- // [2] => / // [3] => TAG // [4] => ... (attributes) // [5] => / (empty) $s = ''; // phase #1 - stuff between tags if ($mText !== '') { $item = reset($this->tagStack); if ($item && !isset($item['dtdContent']['%DATA'])) { // text not allowed? } elseif (!empty($this->tagUsed['pre']) || !empty($this->tagUsed['textarea']) || !empty($this->tagUsed['script'])) {// inside pre & textarea preserve spaces $s = Texy::freezeSpaces($mText); } else { $s = preg_replace('#[ \n]+#', ' ', $mText); // otherwise shrink multiple spaces } } // phase #2 - HTML comment if ($mComment) { return $s . '<' . Texy::freezeSpaces($mComment) . '>'; } // phase #3 - HTML tag $mEmpty = $mEmpty || isset(TexyHtml::$emptyElements[$mTag]); if ($mEmpty && $mEnd) { return $s; // bad tag; /end/ } if ($mEnd) { // end tag // has start tag? if (empty($this->tagUsed[$mTag])) { return $s; } // autoclose tags $tmp = array(); $back = TRUE; foreach ($this->tagStack as $i => $item) { $tag = $item['tag']; $s .= $item['close']; $this->space -= $item['indent']; $this->tagUsed[$tag]--; $back = $back && isset(TexyHtml::$inlineElements[$tag]); unset($this->tagStack[$i]); if ($tag === $mTag) { break; } array_unshift($tmp, $item); } if (!$back || !$tmp) { return $s; } // allowed-check (nejspis neni ani potreba) $item = reset($this->tagStack); $dtdContent = $item ? $item['dtdContent'] : $this->baseDTD; if (!isset($dtdContent[$tmp[0]['tag']])) { return $s; } // autoopen tags foreach ($tmp as $item) { $s .= $item['open']; $this->space += $item['indent']; $this->tagUsed[$item['tag']]++; array_unshift($this->tagStack, $item); } } else { // start tag $dtdContent = $this->baseDTD; if (!isset($this->texy->dtd[$mTag])) { // unknown (non-html) tag $allowed = TRUE; $item = reset($this->tagStack); if ($item) { $dtdContent = $item['dtdContent']; } } else { // optional end tag closing foreach ($this->tagStack as $i => $item) { // is tag allowed here? $dtdContent = $item['dtdContent']; if (isset($dtdContent[$mTag])) { break; } $tag = $item['tag']; // auto-close hidden, optional and inline tags if ($item['close'] && (!isset(TexyHtml::$optionalEnds[$tag]) && !isset(TexyHtml::$inlineElements[$tag]))) { break; } // close it $s .= $item['close']; $this->space -= $item['indent']; $this->tagUsed[$tag]--; unset($this->tagStack[$i]); $dtdContent = $this->baseDTD; } // is tag allowed in this content? $allowed = isset($dtdContent[$mTag]); // check deep element prohibitions if ($allowed && isset(TexyHtml::$prohibits[$mTag])) { foreach (TexyHtml::$prohibits[$mTag] as $pTag) { if (!empty($this->tagUsed[$pTag])) { $allowed = FALSE; break; } } } } // empty elements se neukladaji do zasobniku if ($mEmpty) { if (!$allowed) { return $s; } if ($this->xml) { $mAttr .= " /"; } $indent = $this->indent && empty($this->tagUsed['pre']) && empty($this->tagUsed['textarea']); if ($indent && $mTag === 'br') { // formatting exception return rtrim($s) . '<' . $mTag . $mAttr . ">\n" . str_repeat("\t", max(0, $this->space - 1)) . "\x07"; } elseif ($indent && !isset(TexyHtml::$inlineElements[$mTag])) { $space = "\r" . str_repeat("\t", $this->space); return $s . $space . '<' . $mTag . $mAttr . '>' . $space; } else { return $s . '<' . $mTag . $mAttr . '>'; } } $open = NULL; $close = NULL; $indent = 0; /* if (!isset(TexyHtml::$inlineElements[$mTag])) { // block tags always decorate with \n $s .= "\n"; $close = "\n"; } */ if ($allowed) { $open = '<' . $mTag . $mAttr . '>'; // receive new content (ins & del are special cases) if (!empty($this->texy->dtd[$mTag][1])) { $dtdContent = $this->texy->dtd[$mTag][1]; } // format output if ($this->indent && !isset(TexyHtml::$inlineElements[$mTag])) { $close = "\x08" . '' . "\n" . str_repeat("\t", $this->space); $s .= "\n" . str_repeat("\t", $this->space++) . $open . "\x07"; $indent = 1; } else { $close = ''; $s .= $open; } // TODO: problematic formatting of select / options, object / params } // open tag, put to stack, increase counter $item = array( 'tag' => $mTag, 'open' => $open, 'close' => $close, 'dtdContent' => $dtdContent, 'indent' => $indent, ); array_unshift($this->tagStack, $item); $tmp = & $this->tagUsed[$mTag]; $tmp++; } return $s; } /** * Callback function: wrap lines. * @return string */ private function wrap($m) { list(, $space, $s) = $m; return $space . wordwrap($s, $this->lineWrap, "\n" . $space); } } Texy-2.3.0/src/Texy/modules/TexyImageModule.php100777 0 0 22432 12172334252 14553 0texy = $texy; $texy->allowed['image/definition'] = TRUE; $texy->addHandler('image', array($this, 'solve')); $texy->addHandler('beforeParse', array($this, 'beforeParse')); // [*image*]:LINK $texy->registerLinePattern( array($this, 'patternImage'), '#\[\* *+([^\n'.TexyPatterns::MARK.']{1,1000})'.TexyPatterns::MODIFIER.'? *+(\*|(?|<)\]' // [* urls .(title)[class]{style} >] . '(?::('.TexyPatterns::LINK_URL.'|:))??()#Uu', 'image' ); } /** * Text pre-processing. * @param Texy * @param string * @return void */ public function beforeParse($texy, & $text) { if (!empty($texy->allowed['image/definition'])) { // [*image*]: urls .(title)[class]{style} $text = preg_replace_callback( '#^\[\*([^\n]{1,100})\*\]:\ +(.{1,1000})\ *'.TexyPatterns::MODIFIER.'?\s*()$#mUu', array($this, 'patternReferenceDef'), $text ); } } /** * Callback for: [*image*]: urls .(title)[class]{style}. * * @param array regexp matches * @return string */ private function patternReferenceDef($matches) { list(, $mRef, $mURLs, $mMod) = $matches; // [1] => [* (reference) *] // [2] => urls // [3] => .(title)[class]{style}<> $image = $this->factoryImage($mURLs, $mMod, FALSE); $this->addReference($mRef, $image); return ''; } /** * Callback for [* small.jpg 80x13 | small-over.jpg | big.jpg .(alternative text)[class]{style}>]:LINK. * * @param TexyLineParser * @param array regexp matches * @param string pattern name * @return TexyHtml|string|FALSE */ public function patternImage($parser, $matches) { list(, $mURLs, $mMod, $mAlign, $mLink) = $matches; // [1] => URLs // [2] => .(title)[class]{style}<> // [3] => * < > // [4] => url | [ref] | [*image*] $tx = $this->texy; $image = $this->factoryImage($mURLs, $mMod.$mAlign); if ($mLink) { if ($mLink === ':') { $link = new TexyLink($image->linkedURL === NULL ? $image->URL : $image->linkedURL); $link->raw = ':'; $link->type = TexyLink::IMAGE; } else { $link = $tx->linkModule->factoryLink($mLink, NULL, NULL); } } else { $link = NULL; } return $tx->invokeAroundHandlers('image', $parser, array($image, $link)); } /** * Adds new named reference to image. * * @param string reference name * @param TexyImage * @return void */ public function addReference($name, TexyImage $image) { $image->name = TexyUtf::strtolower($name); $this->references[$image->name] = $image; } /** * Returns named reference. * * @param string reference name * @return TexyImage reference descriptor (or FALSE) */ public function getReference($name) { $name = TexyUtf::strtolower($name); if (isset($this->references[$name])) { return clone $this->references[$name]; } return FALSE; } /** * Parses image's syntax. * @param string input: small.jpg 80x13 | small-over.jpg | linked.jpg * @param string * @param bool * @return TexyImage */ public function factoryImage($content, $mod, $tryRef = TRUE) { $image = $tryRef ? $this->getReference(trim($content)) : FALSE; if (!$image) { $tx = $this->texy; $content = explode('|', $content); $image = new TexyImage; // dimensions $matches = NULL; if (preg_match('#^(.*) (\d+|\?) *(X|x) *(\d+|\?) *()$#U', $content[0], $matches)) { $image->URL = trim($matches[1]); $image->asMax = $matches[3] === 'X'; $image->width = $matches[2] === '?' ? NULL : (int) $matches[2]; $image->height = $matches[4] === '?' ? NULL : (int) $matches[4]; } else { $image->URL = trim($content[0]); } if (!$tx->checkURL($image->URL, Texy::FILTER_IMAGE)) { $image->URL = NULL; } // onmouseover image if (isset($content[1])) { $tmp = trim($content[1]); if ($tmp !== '' && $tx->checkURL($tmp, Texy::FILTER_IMAGE)) { $image->overURL = $tmp; } } // linked image if (isset($content[2])) { $tmp = trim($content[2]); if ($tmp !== '' && $tx->checkURL($tmp, Texy::FILTER_ANCHOR)) { $image->linkedURL = $tmp; } } } $image->modifier->setProperties($mod); return $image; } /** * Finish invocation. * * @param TexyHandlerInvocation handler invocation * @param TexyImage * @param TexyLink * @return TexyHtml|FALSE */ public function solve($invocation, TexyImage $image, $link) { if ($image->URL == NULL) { return FALSE; } $tx = $this->texy; $mod = $image->modifier; $alt = $mod->title; $mod->title = NULL; $hAlign = $mod->hAlign; $mod->hAlign = NULL; $el = TexyHtml::el('img'); $el->attrs['src'] = NULL; // trick - move to front $mod->decorate($tx, $el); $el->attrs['src'] = Texy::prependRoot($image->URL, $this->root); if (!isset($el->attrs['alt'])) { $el->attrs['alt'] = $alt === NULL ? $this->defaultAlt : $tx->typographyModule->postLine($alt); } if ($hAlign) { $var = $hAlign . 'Class'; // leftClass, rightClass if (!empty($this->$var)) { $el->attrs['class'][] = $this->$var; } elseif (empty($tx->alignClasses[$hAlign])) { $el->attrs['style']['float'] = $hAlign; } else { $el->attrs['class'][] = $tx->alignClasses[$hAlign]; } } if (!is_int($image->width) || !is_int($image->height) || $image->asMax) { // autodetect fileRoot if ($this->fileRoot === NULL && isset($_SERVER['SCRIPT_FILENAME'])) { $this->fileRoot = dirname($_SERVER['SCRIPT_FILENAME']) . '/' . $this->root; } // detect dimensions // absolute URL & security check for double dot if (Texy::isRelative($image->URL) && strpos($image->URL, '..') === FALSE) { $file = rtrim($this->fileRoot, '/\\') . '/' . $image->URL; if (@is_file($file)) { // intentionally @ $size = @getImageSize($file); // intentionally @ if (is_array($size)) { if ($image->asMax) { $ratio = 1; if (is_int($image->width)) { $ratio = min($ratio, $image->width / $size[0]); } if (is_int($image->height)) { $ratio = min($ratio, $image->height / $size[1]); } $image->width = round($ratio * $size[0]); $image->height = round($ratio * $size[1]); } elseif (is_int($image->width)) { $ratio = round($size[1] / $size[0] * $image->width); $image->height = round($size[1] / $size[0] * $image->width); } elseif (is_int($image->height)) { $image->width = round($size[0] / $size[1] * $image->height); } else { $image->width = $size[0]; $image->height = $size[1]; } } } } } $el->attrs['width'] = $image->width; $el->attrs['height'] = $image->height; // onmouseover actions generate if ($image->overURL !== NULL) { $overSrc = Texy::prependRoot($image->overURL, $this->root); $el->attrs['onmouseover'] = 'this.src=\'' . addSlashes($overSrc) . '\''; $el->attrs['onmouseout'] = 'this.src=\'' . addSlashes($el->attrs['src']) . '\''; $el->attrs['onload'] = str_replace('%i', addSlashes($overSrc), $this->onLoad); $tx->summary['preload'][] = $overSrc; } $tx->summary['images'][] = $el->attrs['src']; if ($link) { return $tx->linkModule->solve(NULL, $link, $el); } return $el; } } /** * @package Texy */ final class TexyImage extends TexyObject { /** @var string base image URL */ public $URL; /** @var string on-mouse-over image URL */ public $overURL; /** @var string anchored image URL */ public $linkedURL; /** @var int optional image width */ public $width; /** @var int optional image height */ public $height; /** @var bool image width and height are maximal */ public $asMax; /** @var TexyModifier */ public $modifier; /** @var string reference name (if is stored as reference) */ public $name; public function __construct() { $this->modifier = new TexyModifier; } public function __clone() { if ($this->modifier) { $this->modifier = clone $this->modifier; } } } Texy-2.3.0/src/Texy/modules/TexyLinkModule.php100777 0 0 27552 12172334252 14436 0texy = $texy; $texy->allowed['link/definition'] = TRUE; $texy->addHandler('newReference', array($this, 'solveNewReference')); $texy->addHandler('linkReference', array($this, 'solve')); $texy->addHandler('linkEmail', array($this, 'solveUrlEmail')); $texy->addHandler('linkURL', array($this, 'solveUrlEmail')); $texy->addHandler('beforeParse', array($this, 'beforeParse')); // [reference] $texy->registerLinePattern( array($this, 'patternReference'), '#(\[[^\[\]\*\n'.TexyPatterns::MARK.']++\])#U', 'link/reference' ); // direct url and email $texy->registerLinePattern( array($this, 'patternUrlEmail'), '#(?<=^|[\s([<:\x17])(?:https?://|www\.|ftp://)[0-9.'.TexyPatterns::CHAR.'-][/\d'.TexyPatterns::CHAR.'+\.~%&?@=_:;\#,\x{ad}-]{1,1000}[/\d'.TexyPatterns::CHAR.'+~%?@=_\#]#u', 'link/url', '#(?:https?://|www\.|ftp://)#u' ); self::$EMAIL = '['.TexyPatterns::CHAR.'][0-9.+_'.TexyPatterns::CHAR.'-]{0,63}@[0-9.+_'.TexyPatterns::CHAR.'\x{ad}-]{1,252}\.['.TexyPatterns::CHAR.'\x{ad}]{2,19}'; $texy->registerLinePattern( array($this, 'patternUrlEmail'), '#(?<=^|[\s([<\x17])'.self::$EMAIL.'#u', 'link/email', '#'.self::$EMAIL.'#u' ); } /** * Text pre-processing. * @param Texy * @param string * @return void */ public function beforeParse($texy, & $text) { self::$livelock = array(); // [la trine]: http://www.latrine.cz/ text odkazu .(title)[class]{style} if (!empty($texy->allowed['link/definition'])) { $text = preg_replace_callback( '#^\[([^\[\]\#\?\*\n]{1,100})\]: ++(\S{1,1000})(\ .{1,1000})?'.TexyPatterns::MODIFIER.'?\s*()$#mUu', array($this, 'patternReferenceDef'), $text ); } } /** * Callback for: [la trine]: http://www.latrine.cz/ text odkazu .(title)[class]{style}. * * @param array regexp matches * @return string */ private function patternReferenceDef($matches) { list(, $mRef, $mLink, $mLabel, $mMod) = $matches; // [1] => [ (reference) ] // [2] => link // [3] => ... // [4] => .(title)[class]{style} $link = new TexyLink($mLink); $link->label = trim($mLabel); $link->modifier->setProperties($mMod); $this->checkLink($link); $this->addReference($mRef, $link); return ''; } /** * Callback for: [ref]. * * @param TexyLineParser * @param array regexp matches * @param string pattern name * @return TexyHtml|string|FALSE */ public function patternReference($parser, $matches) { list(, $mRef) = $matches; // [1] => [ref] $tx = $this->texy; $name = substr($mRef, 1, -1); $link = $this->getReference($name); if (!$link) { return $tx->invokeAroundHandlers('newReference', $parser, array($name)); } $link->type = TexyLink::BRACKET; if ($link->label != '') { // NULL or '' // prevent circular references if (isset(self::$livelock[$link->name])) { $content = $link->label; } else { self::$livelock[$link->name] = TRUE; $el = TexyHtml::el(); $lineParser = new TexyLineParser($tx, $el); $lineParser->parse($link->label); $content = $el->toString($tx); unset(self::$livelock[$link->name]); } } else { $content = $this->textualUrl($link); $content = $this->texy->protect($content, Texy::CONTENT_TEXTUAL); } return $tx->invokeAroundHandlers('linkReference', $parser, array($link, $content)); } /** * Callback for: http://davidgrudl.com david@grudl.com. * * @param TexyLineParser * @param array regexp matches * @param string pattern name * @return TexyHtml|string|FALSE */ public function patternUrlEmail($parser, $matches, $name) { list($mURL) = $matches; // [0] => URL $link = new TexyLink($mURL); $this->checkLink($link); return $this->texy->invokeAroundHandlers( $name === 'link/email' ? 'linkEmail' : 'linkURL', $parser, array($link) ); } /** * Adds new named reference. * * @param string reference name * @param TexyLink * @return void */ public function addReference($name, TexyLink $link) { $link->name = TexyUtf::strtolower($name); $this->references[$link->name] = $link; } /** * Returns named reference. * * @param string reference name * @return TexyLink reference descriptor (or FALSE) */ public function getReference($name) { $name = TexyUtf::strtolower($name); if (isset($this->references[$name])) { return clone $this->references[$name]; } else { $pos = strpos($name, '?'); if ($pos === FALSE) { $pos = strpos($name, '#'); } if ($pos !== FALSE) { // try to extract ?... #... part $name2 = substr($name, 0, $pos); if (isset($this->references[$name2])) { $link = clone $this->references[$name2]; $link->URL .= substr($name, $pos); return $link; } } } return FALSE; } /** * @param string * @param string * @param string * @return TexyLink */ public function factoryLink($dest, $mMod, $label) { $tx = $this->texy; $type = TexyLink::COMMON; // [ref] if (strlen($dest)>1 && $dest{0} === '[' && $dest{1} !== '*') { $type = TexyLink::BRACKET; $dest = substr($dest, 1, -1); $link = $this->getReference($dest); // [* image *] } elseif (strlen($dest)>1 && $dest{0} === '[' && $dest{1} === '*') { $type = TexyLink::IMAGE; $dest = trim(substr($dest, 2, -2)); $image = $tx->imageModule->getReference($dest); if ($image) { $link = new TexyLink($image->linkedURL === NULL ? $image->URL : $image->linkedURL); $link->modifier = $image->modifier; } } if (empty($link)) { $link = new TexyLink(trim($dest)); $this->checkLink($link); } if (strpos($link->URL, '%s') !== FALSE) { $link->URL = str_replace('%s', urlencode($tx->stringToText($label)), $link->URL); } $link->modifier->setProperties($mMod); $link->type = $type; return $link; } /** * Finish invocation. * * @param TexyHandlerInvocation handler invocation * @param TexyLink * @param TexyHtml|string * @return TexyHtml|string */ public function solve($invocation, $link, $content = NULL) { if ($link->URL == NULL) { return $content; } $tx = $this->texy; $el = TexyHtml::el('a'); if (empty($link->modifier)) { $nofollow = $popup = FALSE; } else { $nofollow = isset($link->modifier->classes['nofollow']); $popup = isset($link->modifier->classes['popup']); unset($link->modifier->classes['nofollow'], $link->modifier->classes['popup']); $el->attrs['href'] = NULL; // trick - move to front $link->modifier->decorate($tx, $el); } if ($link->type === TexyLink::IMAGE) { // image $el->attrs['href'] = Texy::prependRoot($link->URL, $tx->imageModule->linkedRoot); $el->attrs['onclick'] = $this->imageOnClick; } else { $el->attrs['href'] = Texy::prependRoot($link->URL, $this->root); // rel="nofollow" if ($nofollow || ($this->forceNoFollow && strpos($el->attrs['href'], '//') !== FALSE)) { $el->attrs['rel'] = 'nofollow'; } } // popup on click if ($popup) { $el->attrs['onclick'] = $this->popupOnClick; } if ($content !== NULL) { $el->add($content); } $tx->summary['links'][] = $el->attrs['href']; return $el; } /** * Finish invocation. * * @param TexyHandlerInvocation handler invocation * @param TexyLink * @return TexyHtml|string */ public function solveUrlEmail($invocation, $link) { $content = $this->textualUrl($link); $content = $this->texy->protect($content, Texy::CONTENT_TEXTUAL); return $this->solve(NULL, $link, $content); } /** * Finish invocation. * * @param TexyHandlerInvocation handler invocation * @param string * @return FALSE */ public function solveNewReference($invocation, $name) { // no change return FALSE; } /** * Checks and corrects $URL. * @param TexyLink * @return void */ private function checkLink($link) { // remove soft hyphens; if not removed by Texy::process() $link->URL = str_replace("\xC2\xAD", '', $link->URL); if (strncasecmp($link->URL, 'www.', 4) === 0) { // special supported case $link->URL = 'http://' . $link->URL; } elseif (preg_match('#'.self::$EMAIL.'$#Au', $link->URL)) { // email $link->URL = 'mailto:' . $link->URL; } elseif (!$this->texy->checkURL($link->URL, Texy::FILTER_ANCHOR)) { $link->URL = NULL; } else { $link->URL = str_replace('&', '&', $link->URL); // replace unwanted & } } /** * Returns textual representation of URL. * @param TexyLink * @return string */ private function textualUrl($link) { if ($this->texy->obfuscateEmail && preg_match('#^'.self::$EMAIL.'$#u', $link->raw)) { // email return str_replace('@', "@", $link->raw); } if ($this->shorten && preg_match('#^(https?://|ftp://|www\.|/)#i', $link->raw)) { $raw = strncasecmp($link->raw, 'www.', 4) === 0 ? 'none://' . $link->raw : $link->raw; // parse_url() in PHP damages UTF-8 - use regular expression if (!preg_match('~^(?:(?P[a-z]+):)?(?://(?P[^/?#]+))?(?P(?:/|^)(?!/)[^?#]*)?(?:\?(?P[^#]*))?(?:#(?P.*))?()$~', $raw, $parts)) { return $link->raw; } $res = ''; if ($parts['scheme'] !== '' && $parts['scheme'] !== 'none') { $res .= $parts['scheme'] . '://'; } if ($parts['host'] !== '') { $res .= $parts['host']; } if ($parts['path'] !== '') { $res .= (iconv_strlen($parts['path'], 'UTF-8') > 16 ? ("/\xe2\x80\xa6" . iconv_substr($parts['path'], -12, 12, 'UTF-8')) : $parts['path']); } if ($parts['query'] !== '') { $res .= iconv_strlen($parts['query'], 'UTF-8') > 4 ? "?\xe2\x80\xa6" : ('?' . $parts['query']); } elseif ($parts['fragment'] !== '') { $res .= iconv_strlen($parts['fragment'], 'UTF-8') > 4 ? "#\xe2\x80\xa6" : ('#' . $parts['fragment']); } return $res; } return $link->raw; } } /** * @package Texy */ final class TexyLink extends TexyObject { /** @see $type */ const COMMON = 1, BRACKET = 2, IMAGE = 3; /** @var string URL in resolved form */ public $URL; /** @var string URL as written in text */ public $raw; /** @var TexyModifier */ public $modifier; /** @var int how was link created? */ public $type = TexyLink::COMMON; /** @var string optional label, used by references */ public $label; /** @var string reference name (if is stored as reference) */ public $name; public function __construct($URL) { $this->URL = $URL; $this->raw = $URL; $this->modifier = new TexyModifier; } public function __clone() { if ($this->modifier) { $this->modifier = clone $this->modifier; } } } Texy-2.3.0/src/Texy/modules/TexyListModule.php100777 0 0 14366 12172334252 14453 0 array('\*\ ', 0, ''), '-' => array('[\x{2013}-](?![>-])',0, ''), '+' => array('\+\ ', 0, ''), '1.' => array('1\.\ ',/* not \d !*/ 1, '', '\d{1,3}\.\ '), '1)' => array('\d{1,3}\)\ ', 1, ''), 'I.' => array('I\.\ ', 1, 'upper-roman', '[IVX]{1,4}\.\ '), 'I)' => array('[IVX]+\)\ ', 1, 'upper-roman'), // before A) ! 'a)' => array('[a-z]\)\ ', 1, 'lower-alpha'), 'A)' => array('[A-Z]\)\ ', 1, 'upper-alpha'), ); public function __construct($texy) { $this->texy = $texy; $texy->addHandler('beforeParse', array($this, 'beforeParse')); $texy->allowed['list'] = TRUE; $texy->allowed['list/definition'] = TRUE; } public function beforeParse() { $RE = $REul = array(); foreach ($this->bullets as $desc) { $RE[] = $desc[0]; if (!$desc[1]) { $REul[] = $desc[0]; } } $this->texy->registerBlockPattern( array($this, 'patternList'), '#^(?:'.TexyPatterns::MODIFIER_H.'\n)?' // .{color: red} . '('.implode('|', $RE).')\ *+\S.*$#mUu', // item (unmatched) 'list' ); $this->texy->registerBlockPattern( array($this, 'patternDefList'), '#^(?:'.TexyPatterns::MODIFIER_H.'\n)?' // .{color:red} . '(\S.{0,2000})\:\ *'.TexyPatterns::MODIFIER_H.'?\n' // Term: . '(\ ++)('.implode('|', $REul).')\ *+\S.*$#mUu', // - description 'list/definition' ); } /** * Callback for:. * * 1) .... .(title)[class]{style}> * 2) .... * + ... * + ... * 3) .... * * @param TexyBlockParser * @param array regexp matches * @param string pattern name * @return TexyHtml|FALSE */ public function patternList($parser, $matches) { list(, $mMod, $mBullet) = $matches; // [1] => .(title)[class]{style}<> // [2] => bullet * + - 1) a) A) IV) $tx = $this->texy; $el = TexyHtml::el(); $bullet = $min = NULL; foreach ($this->bullets as $type => $desc) { if (preg_match('#'.$desc[0].'#Au', $mBullet)) { $bullet = isset($desc[3]) ? $desc[3] : $desc[0]; $min = isset($desc[3]) ? 2 : 1; $el->setName($desc[1] ? 'ol' : 'ul'); $el->attrs['style']['list-style-type'] = $desc[2]; if ($desc[1]) { // ol if ($type[0] === '1' && (int) $mBullet > 1) { $el->attrs['start'] = (int) $mBullet; } elseif ($type[0] === 'a' && $mBullet[0] > 'a') { $el->attrs['start'] = ord($mBullet[0]) - 96; } elseif ($type[0] === 'A' && $mBullet[0] > 'A') { $el->attrs['start'] = ord($mBullet[0]) - 64; } } break; } } $mod = new TexyModifier($mMod); $mod->decorate($tx, $el); $parser->moveBackward(1); while ($elItem = $this->patternItem($parser, $bullet, FALSE, 'li')) { $el->add($elItem); } if ($el->count() < $min) { return FALSE; } // event listener $tx->invokeHandlers('afterList', array($parser, $el, $mod)); return $el; } /** * Callback for:. * * Term: .(title)[class]{style}> * - description 1 * - description 2 * - description 3 * * @param TexyBlockParser * @param array regexp matches * @param string pattern name * @return TexyHtml */ public function patternDefList($parser, $matches) { list(, $mMod, , , , $mBullet) = $matches; // [1] => .(title)[class]{style}<> // [2] => ... // [3] => .(title)[class]{style}<> // [4] => space // [5] => - * + $tx = $this->texy; $bullet = NULL; foreach ($this->bullets as $desc) { if (preg_match('#'.$desc[0].'#Au', $mBullet)) { $bullet = isset($desc[3]) ? $desc[3] : $desc[0]; break; } } $el = TexyHtml::el('dl'); $mod = new TexyModifier($mMod); $mod->decorate($tx, $el); $parser->moveBackward(2); $patternTerm = '#^\n?(\S.*)\:\ *'.TexyPatterns::MODIFIER_H.'?()$#mUA'; while (TRUE) { if ($elItem = $this->patternItem($parser, $bullet, TRUE, 'dd')) { $el->add($elItem); continue; } if ($parser->next($patternTerm, $matches)) { list(, $mContent, $mMod) = $matches; // [1] => ... // [2] => .(title)[class]{style}<> $elItem = TexyHtml::el('dt'); $modItem = new TexyModifier($mMod); $modItem->decorate($tx, $elItem); $elItem->parseLine($tx, $mContent); $el->add($elItem); continue; } break; } // event listener $tx->invokeHandlers('afterDefinitionList', array($parser, $el, $mod)); return $el; } /** * Callback for single list item. * * @param TexyBlockParser * @param string bullet type * @param string left space * @param string html tag * @return TexyHtml|FALSE */ public function patternItem($parser, $bullet, $indented, $tag) { $tx = $this->texy; $spacesBase = $indented ? ('\ {1,}') : ''; $patternItem = "#^\n?($spacesBase)$bullet\\ *(\\S.*)?".TexyPatterns::MODIFIER_H."?()$#mAUu"; // first line with bullet $matches = NULL; if (!$parser->next($patternItem, $matches)) { return FALSE; } list(, $mIndent, $mContent, $mMod) = $matches; // [1] => indent // [2] => ... // [3] => .(title)[class]{style}<> $elItem = TexyHtml::el($tag); $mod = new TexyModifier($mMod); $mod->decorate($tx, $elItem); // next lines $spaces = ''; $content = ' ' . $mContent; // trick while ($parser->next('#^(\n*)'.$mIndent.'(\ {1,'.$spaces.'})(.*)()$#Am', $matches)) { list(, $mBlank, $mSpaces, $mContent) = $matches; // [1] => blank line? // [2] => spaces // [3] => ... if ($spaces === '') { $spaces = strlen($mSpaces); } $content .= "\n" . $mBlank . $mContent; } // parse content $elItem->parseBlock($tx, $content, TRUE); if (isset($elItem[0]) && $elItem[0] instanceof TexyHtml) { $elItem[0]->setName(NULL); } return $elItem; } } Texy-2.3.0/src/Texy/modules/TexyLongWordsModule.php100777 0 0 13204 12172334252 15444 0texy = $texy; $this->consonants = array_flip($this->consonants); $this->vowels = array_flip($this->vowels); $this->before_r = array_flip($this->before_r); $this->before_l = array_flip($this->before_l); $this->before_h = array_flip($this->before_h); $this->doubleVowels = array_flip($this->doubleVowels); $texy->registerPostLine(array($this, 'postLine'), 'longwords'); } public function postLine($text) { return preg_replace_callback( '#[^\ \n\t\x14\x15\x16\x{2013}\x{2014}\x{ad}-]{'.$this->wordLimit.',}#u', array($this, 'pattern'), $text ); } /** * Callback for long words. * (c) David Grudl * @param array * @return string */ private function pattern($matches) { list($mWord) = $matches; // [0] => lllloooonnnnggggwwwoorrdddd if (iconv_strlen($mWord, 'UTF-8') > self::SAFE_LIMIT) { return $mWord; } $chars = array(); preg_match_all( '#['.TexyPatterns::MARK.']+|.#u', $mWord, $chars ); $chars = $chars[0]; if (count($chars) < $this->wordLimit) { return $mWord; } $consonants = $this->consonants; $vowels = $this->vowels; $before_r = $this->before_r; $before_l = $this->before_l; $before_h = $this->before_h; $doubleVowels = $this->doubleVowels; $s = array(); $trans = array(); $s[] = ''; $trans[] = -1; foreach ($chars as $key => $char) { if (ord($char{0}) < 32) { continue; } $s[] = $char; $trans[] = $key; } $s[] = ''; $len = count($s) - 2; $positions = array(); $a = 0; $last = 1; while (++$a < $len) { $hyphen = self::DONT; // Do not hyphenate do { if ($s[$a] === "\xC2\xA0") { $a++; continue 2; // here and after never } if ($s[$a] === '.') { $hyphen = self::HERE; break; } if (isset($consonants[$s[$a]])) { // consonants if (isset($vowels[$s[$a+1]])) { if (isset($vowels[$s[$a-1]])) { $hyphen = self::HERE; } break; } if (($s[$a] === 's') && ($s[$a-1] === 'n') && isset($consonants[$s[$a+1]])) { $hyphen = self::AFTER; break; } if (isset($consonants[$s[$a+1]]) && isset($vowels[$s[$a-1]])) { if ($s[$a+1] === 'r') { $hyphen = isset($before_r[$s[$a]]) ? self::HERE : self::AFTER; break; } if ($s[$a+1] === 'l') { $hyphen = isset($before_l[$s[$a]]) ? self::HERE : self::AFTER; break; } if ($s[$a+1] === 'h') { // CH $hyphen = isset($before_h[$s[$a]]) ? self::DONT : self::AFTER; break; } $hyphen = self::AFTER; break; } break; } // end of consonants if (($s[$a] === 'u') && isset($doubleVowels[$s[$a-1]])) { $hyphen = self::AFTER; break; } if (isset($vowels[$s[$a]]) && isset($vowels[$s[$a-1]])) { $hyphen = self::HERE; break; } } while(0); if ($hyphen === self::DONT && ($a - $last > $this->wordLimit*0.6)) { $positions[] = $last = $a-1; // Hyphenate here } if ($hyphen === self::HERE) { $positions[] = $last = $a-1; // Hyphenate here } if ($hyphen === self::AFTER) { $positions[] = $last = $a; $a++; // Hyphenate after } } // while $a = end($positions); if (($a === $len-1) && isset($consonants[$s[$len]])) { array_pop($positions); } $syllables = array(); $last = 0; foreach ($positions as $pos) { if ($pos - $last > $this->wordLimit*0.6) { $syllables[] = implode('', array_splice($chars, 0, $trans[$pos] - $trans[$last])); $last = $pos; } } $syllables[] = implode('', $chars); //$s = implode("\xC2\xAD", $syllables); // insert shy //$s = str_replace(array("\xC2\xAD\xC2\xA0", "\xC2\xA0\xC2\xAD"), array(' ', ' '), $s); // shy+nbsp = normal space return implode("\xC2\xAD", $syllables);; } } Texy-2.3.0/src/Texy/modules/TexyParagraphModule.php100777 0 0 6332 12172334252 15417 0texy = $texy; $texy->addHandler('paragraph', array($this, 'solve')); } /** * @param TexyBlockParser * @param string text * @param array * @param TexyHtml * @return vois */ public function process($parser, $content, $el) { $tx = $this->texy; if ($parser->isIndented()) { $parts = preg_split('#(\n(?! )|\n{2,})#', $content, -1, PREG_SPLIT_NO_EMPTY); } else { $parts = preg_split('#(\n{2,})#', $content, -1, PREG_SPLIT_NO_EMPTY); } foreach ($parts as $s) { $s = trim($s); if ($s === '') { continue; } // try to find modifier $mx = $mod = NULL; if (preg_match('#'.TexyPatterns::MODIFIER_H.'(?=\n|\z)#sUm', $s, $mx, PREG_OFFSET_CAPTURE)) { list($mMod) = $mx[1]; $s = trim(substr_replace($s, '', $mx[0][1], strlen($mx[0][0]))); if ($s === '') { continue; } $mod = new TexyModifier; $mod->setProperties($mMod); } $res = $tx->invokeAroundHandlers('paragraph', $parser, array($s, $mod)); if ($res) { $el->insert(NULL, $res); } } } /** * Finish invocation. * * @param TexyHandlerInvocation handler invocation * @param string * @param TexyModifier|NULL * @return TexyHtml|FALSE */ public function solve($invocation, $content, $mod) { $tx = $this->texy; // find hard linebreaks if ($tx->mergeLines) { // .... // ... => \r means break line $content = preg_replace('#\n +(?=\S)#', "\r", $content); } else { $content = preg_replace('#\n#', "\r", $content); } $el = TexyHtml::el('p'); $el->parseLine($tx, $content); $content = $el->getText(); // string // check content type // block contains block tag if (strpos($content, Texy::CONTENT_BLOCK) !== FALSE) { $el->setName(NULL); // ignores modifier! // block contains text (protected) } elseif (strpos($content, Texy::CONTENT_TEXTUAL) !== FALSE) { // leave element p // block contains text } elseif (preg_match('#[^\s'.TexyPatterns::MARK.']#u', $content)) { // leave element p // block contains only replaced element } elseif (strpos($content, Texy::CONTENT_REPLACED) !== FALSE) { $el->setName($tx->nontextParagraph); // block contains only markup tags or spaces or nothing } else { // if {ignoreEmptyStuff} return FALSE; if (!$mod) { $el->setName(NULL); } } if ($el->getName()) { // apply modifier if ($mod) { $mod->decorate($tx, $el); } // add
if (strpos($content, "\r") !== FALSE) { $key = $tx->protect('
', Texy::CONTENT_REPLACED); $content = str_replace("\r", $key, $content); }; } $content = strtr($content, "\r\n", ' '); $el->setText($content); return $el; } } Texy-2.3.0/src/Texy/modules/TexyPhraseModule.php100777 0 0 22504 12172334252 14753 0 'strong', // or 'b' 'phrase/em' => 'em', // or 'i' 'phrase/em-alt' => 'em', 'phrase/em-alt2' => 'em', 'phrase/ins' => 'ins', 'phrase/del' => 'del', 'phrase/sup' => 'sup', 'phrase/sup-alt' => 'sup', 'phrase/sub' => 'sub', 'phrase/sub-alt' => 'sub', 'phrase/span' => 'a', 'phrase/span-alt' => 'a', 'phrase/cite' => 'cite', 'phrase/acronym' => 'acronym', 'phrase/acronym-alt' => 'acronym', 'phrase/code' => 'code', 'phrase/quote' => 'q', 'phrase/quicklink' => 'a', ); /** @var bool are links allowed? */ public $linksAllowed = TRUE; public function __construct($texy) { $this->texy = $texy; $texy->addHandler('phrase', array($this, 'solve')); /* // UNIVERSAL $texy->registerLinePattern( array($this, 'patternPhrase'), '#((?>([*+/^_"~`-])+?))(?!\s)(.*(?!\\2).)'.TexyPatterns::MODIFIER.'?(?registerLinePattern( array($this, 'patternPhrase'), '#(?registerLinePattern( array($this, 'patternPhrase'), '#(?registerLinePattern( array($this, 'patternPhrase'), '#(?registerLinePattern( array($this, 'patternPhrase'), '#(?registerLinePattern( array($this, 'patternPhrase'), '#(?()"\''.TexyPatterns::MARK.'-])\*(?![\s*])((?:[^ *]++|[ *])+)'.TexyPatterns::MODIFIER.'?(?()"?!\'-])(?::('.TexyPatterns::LINK_URL.'))??()#Uus', 'phrase/em-alt2' ); // ++inserted++ $texy->registerLinePattern( array($this, 'patternPhrase'), '#(?registerLinePattern( array($this, 'patternPhrase'), '#(?-])((?:[^\r\n -]++|[ -])+)'.TexyPatterns::MODIFIER.'?(?-])()#Uu', 'phrase/del' ); // ^^superscript^^ $texy->registerLinePattern( array($this, 'patternPhrase'), '#(?registerLinePattern( array($this, 'patternSupSub'), '#(?<=[a-z0-9])\^([n0-9+-]{1,4}?)(?![a-z0-9])#Uui', 'phrase/sup-alt' ); // __subscript__ $texy->registerLinePattern( array($this, 'patternPhrase'), '#(?registerLinePattern( array($this, 'patternSupSub'), '#(?<=[a-z])\_([n0-9]{1,3})(?![a-z0-9])#Uui', 'phrase/sub-alt' ); // "span" $texy->registerLinePattern( array($this, 'patternPhrase'), '#(?registerLinePattern( array($this, 'patternPhrase'), '#(?registerLinePattern( array($this, 'patternPhrase'), '#(?>quote<< $texy->registerLinePattern( array($this, 'patternPhrase'), '#(?)\>\>(?![\s>])((?:[^\r\n <]++|[ <])+)'.TexyPatterns::MODIFIER.'?(?registerLinePattern( array($this, 'patternPhrase'), '#(?registerLinePattern( array($this, 'patternPhrase'), '#(?registerLinePattern( array($this, 'patternNoTexy'), '#(?registerLinePattern( array($this, 'patternPhrase'), '#\`(\S(?:[^'.TexyPatterns::MARK.'\r\n `]++|[ `])*)'.TexyPatterns::MODIFIER.'?(?registerLinePattern( array($this, 'patternPhrase'), '#(['.TexyPatterns::CHAR.'0-9@\#$%&.,_-]++)()(?=:\[)(?::('.TexyPatterns::LINK_URL.'))()#Uu', 'phrase/quicklink' ); $texy->allowed['phrase/ins'] = FALSE; $texy->allowed['phrase/del'] = FALSE; $texy->allowed['phrase/sup'] = FALSE; $texy->allowed['phrase/sub'] = FALSE; $texy->allowed['phrase/cite'] = FALSE; } /** * Callback for: **.... .(title)[class]{style}**:LINK. * * @param TexyLineParser * @param array regexp matches * @param string pattern name * @return TexyHtml|string|FALSE */ public function patternPhrase($parser, $matches, $phrase) { list(, $mContent, $mMod, $mLink) = $matches; // [1] => ** // [2] => ... // [3] => .(title)[class]{style} // [4] => LINK $tx = $this->texy; $mod = new TexyModifier($mMod); $link = NULL; $parser->again = $phrase !== 'phrase/code' && $phrase !== 'phrase/quicklink'; if ($phrase === 'phrase/span' || $phrase === 'phrase/span-alt') { if ($mLink == NULL) { if (!$mMod) { return FALSE; // means "..." } } else { $link = $tx->linkModule->factoryLink($mLink, $mMod, $mContent); } } elseif ($phrase === 'phrase/acronym' || $phrase === 'phrase/acronym-alt') { $mod->title = trim(Texy::unescapeHtml($mLink)); } elseif ($phrase === 'phrase/quote') { $mod->cite = $tx->blockQuoteModule->citeLink($mLink); } elseif ($mLink != NULL) { $link = $tx->linkModule->factoryLink($mLink, NULL, $mContent); } return $tx->invokeAroundHandlers('phrase', $parser, array($phrase, $mContent, $mod, $link)); } /** * Callback for: any^2 any_2. * * @param TexyLineParser * @param array regexp matches * @param string pattern name * @return TexyHtml|string|FALSE */ public function patternSupSub($parser, $matches, $phrase) { list(, $mContent) = $matches; $mod = new TexyModifier(); $link = NULL; $mContent = str_replace('-', "\xE2\x88\x92", $mContent); // − return $this->texy->invokeAroundHandlers('phrase', $parser, array($phrase, $mContent, $mod, $link)); } /** * @param TexyLineParser * @param array regexp matches * @param string pattern name * @return string */ public function patternNoTexy($parser, $matches) { list(, $mContent) = $matches; return $this->texy->protect(Texy::escapeHtml($mContent), Texy::CONTENT_TEXTUAL); } /** * Finish invocation. * * @param TexyHandlerInvocation handler invocation * @param string * @param string * @param TexyModifier * @param TexyLink * @return TexyHtml */ public function solve($invocation, $phrase, $content, $mod, $link) { $tx = $this->texy; $tag = isset($this->tags[$phrase]) ? $this->tags[$phrase] : NULL; if ($tag === 'a') { $tag = $link && $this->linksAllowed ? NULL : 'span'; } if ($phrase === 'phrase/code') { $content = $tx->protect(Texy::escapeHtml($content), Texy::CONTENT_TEXTUAL); } if ($phrase === 'phrase/strong+em') { $el = TexyHtml::el($this->tags['phrase/strong']); $el->create($this->tags['phrase/em'], $content); $mod->decorate($tx, $el); } elseif ($tag) { $el = TexyHtml::el($tag)->setText($content); $mod->decorate($tx, $el); if ($tag === 'q') { $el->attrs['cite'] = $mod->cite; } } else { $el = $content; // trick } if ($link && $this->linksAllowed) { return $tx->linkModule->solve(NULL, $link, $el); } return $el; } } Texy-2.3.0/src/Texy/modules/TexyScriptModule.php100777 0 0 5204 12172334252 14753 0texy = $texy; $texy->addHandler('script', array($this, 'solve')); $texy->registerLinePattern( array($this, 'pattern'), '#\{\{((?:[^'.TexyPatterns::MARK.'}]++|[}])+)\}\}()#U', 'script' ); } /** * Callback for: {{...}}. * * @param TexyLineParser * @param array regexp matches * @param string pattern name * @return TexyHtml|string|FALSE */ public function pattern($parser, $matches) { list(, $mContent) = $matches; // [1] => ... $cmd = trim($mContent); if ($cmd === '') { return FALSE; } $args = $raw = NULL; // function(arg, arg, ...) or function: arg, arg if (preg_match('#^([a-z_][a-z0-9_-]*)\s*(?:\(([^()]*)\)|:(.*))$#iu', $cmd, $matches)) { $cmd = $matches[1]; $raw = isset($matches[3]) ? trim($matches[3]) : trim($matches[2]); if ($raw === '') { $args = array(); } else { $args = preg_split('#\s*' . preg_quote($this->separator, '#') . '\s*#u', $raw); } } // Texy 1.x way if ($this->handler) { if (is_callable(array($this->handler, $cmd))) { array_unshift($args, $parser); return call_user_func_array(array($this->handler, $cmd), $args); } if (is_callable($this->handler)) { return call_user_func_array($this->handler, array($parser, $cmd, $args, $raw)); } } // Texy 2 way return $this->texy->invokeAroundHandlers('script', $parser, array($cmd, $args, $raw)); } /** * Finish invocation. * * @param TexyHandlerInvocation handler invocation * @param string command * @param array arguments * @param string arguments in raw format * @return TexyHtml|string|FALSE */ public function solve($invocation, $cmd, $args, $raw) { if ($cmd === 'texy') { if (!$args) { return FALSE; } switch ($args[0]) { case 'nofollow': $this->texy->linkModule->forceNoFollow = TRUE; break; } return ''; } else { return FALSE; } } } Texy-2.3.0/src/Texy/modules/TexyTableModule.php100777 0 0 16260 12172334252 14562 0texy = $texy; $texy->registerBlockPattern( array($this, 'patternTable'), '#^(?:'.TexyPatterns::MODIFIER_HV.'\n)?' // .{color: red} . '\|.*()$#mU', // | .... 'table' ); } /** * Callback for:. * * .(title)[class]{style}> * |------------------ * | xxx | xxx | xxx | .(..){..}[..] * |------------------ * | aa | bb | cc | * * @param TexyBlockParser * @param array regexp matches * @param string pattern name * @return TexyHtml|string|FALSE */ public function patternTable($parser, $matches) { if ($this->disableTables) { return FALSE; } list(, $mMod) = $matches; // [1] => .(title)[class]{style}<>_ $tx = $this->texy; $el = TexyHtml::el('table'); $mod = new TexyModifier($mMod); $mod->decorate($tx, $el); $parser->moveBackward(); if ($parser->next('#^\|(\#|\=){2,}(?![|\#=+])(.+)\\1*\|? *'.TexyPatterns::MODIFIER_H.'?()$#Um', $matches)) { list(, , $mContent, $mMod) = $matches; // [1] => # / = // [2] => .... // [3] => .(title)[class]{style}<> $caption = $el->create('caption'); $mod = new TexyModifier($mMod); $mod->decorate($tx, $caption); $caption->parseLine($tx, $mContent); } $isHead = FALSE; $colModifier = array(); $prevRow = array(); // rowSpan building helper $rowCounter = 0; $colCounter = 0; $elPart = NULL; $lineMode = FALSE; // rows must be separated by lines while (TRUE) { if ($parser->next('#^\|([=-])[+|=-]{2,}$#Um', $matches)) { // line if ($lineMode) { if ($matches[1] === '=') { $isHead = !$isHead; } } else { $isHead = !$isHead; $lineMode = $matches[1] === '='; } $prevRow = array(); continue; } if ($parser->next('#^\|(.*)(?:|\|\ *'.TexyPatterns::MODIFIER_HV.'?)()$#U', $matches)) { // smarter head detection if ($rowCounter === 0 && !$isHead && $parser->next('#^\|[=-][+|=-]{2,}$#Um', $foo)) { $isHead = TRUE; $parser->moveBackward(); } if ($elPart === NULL) { $elPart = $el->create($isHead ? 'thead' : 'tbody'); } elseif (!$isHead && $elPart->getName() === 'thead') { $this->finishPart($elPart); $elPart = $el->create('tbody'); } // PARSE ROW list(, $mContent, $mMod) = $matches; // [1] => .... // [2] => .(title)[class]{style}<>_ $elRow = TexyHtml::el('tr'); $mod = new TexyModifier($mMod); $mod->decorate($tx, $elRow); $rowClass = $rowCounter % 2 === 0 ? $this->oddClass : $this->evenClass; if ($rowClass && !isset($mod->classes[$this->oddClass]) && !isset($mod->classes[$this->evenClass])) { $elRow->attrs['class'][] = $rowClass; } $col = 0; $elCell = NULL; // special escape sequence \| $mContent = str_replace('\\|', "\x13", $mContent); $mContent = preg_replace('#(\[[^\]]*)\|#', "$1\x13", $mContent); // HACK: support for [..|..] foreach (explode('|', $mContent) as $cell) { $cell = strtr($cell, "\x13", '|'); // rowSpan if (isset($prevRow[$col]) && ($lineMode || preg_match('#\^\ *$|\*??(.*)\ +\^$#AU', $cell, $matches))) { $prevRow[$col]->rowSpan++; if (!$lineMode) { $cell = isset($matches[1]) ? $matches[1] : ''; } $prevRow[$col]->text .= "\n" . $cell; $col += $prevRow[$col]->colSpan; $elCell = NULL; continue; } // colSpan if ($cell === '' && $elCell) { $elCell->colSpan++; unset($prevRow[$col]); $col++; continue; } // common cell if (!preg_match('#(\*??)\ *'.TexyPatterns::MODIFIER_HV.'??(.*)'.TexyPatterns::MODIFIER_HV.'?\ *()$#AU', $cell, $matches)) { continue; } list(, $mHead, $mModCol, $mContent, $mMod) = $matches; // [1] => * ^ // [2] => .(title)[class]{style}<>_ // [3] => .... // [4] => .(title)[class]{style}<>_ if ($mModCol) { $colModifier[$col] = new TexyModifier($mModCol); } if (isset($colModifier[$col])) { $mod = clone $colModifier[$col]; } else { $mod = new TexyModifier; } $mod->setProperties($mMod); $elCell = new TexyTableCellElement; $elCell->setName($isHead || ($mHead === '*') ? 'th' : 'td'); $mod->decorate($tx, $elCell); $elCell->text = $mContent; $elRow->add($elCell); $prevRow[$col] = $elCell; $col++; } // even up with empty cells while ($col < $colCounter) { if (isset($prevRow[$col]) && $lineMode) { $prevRow[$col]->rowSpan++; $prevRow[$col]->text .= "\n"; } else { $elCell = new TexyTableCellElement; $elCell->setName($isHead ? 'th' : 'td'); if (isset($colModifier[$col])) { $colModifier[$col]->decorate($tx, $elCell); } $elRow->add($elCell); $prevRow[$col] = $elCell; } $col++; } $colCounter = $col; if ($elRow->count()) { $elPart->add($elRow); $rowCounter++; } else { // redundant row foreach ($prevRow as $elCell) { $elCell->rowSpan--; } } continue; } break; } if ($elPart === NULL) { // invalid table return FALSE; } if ($elPart->getName() === 'thead') { // thead is optional, tbody is required $elPart->setName('tbody'); } $this->finishPart($elPart); // event listener $tx->invokeHandlers('afterTable', array($parser, $el, $mod)); return $el; } /** * Parse text in all cells. * @param TexyHtml * @return void */ private function finishPart($elPart) { $tx = $this->texy; foreach ($elPart->getChildren() as $elRow) { foreach ($elRow->getChildren() as $elCell) { if ($elCell->colSpan > 1) { $elCell->attrs['colspan'] = $elCell->colSpan; } if ($elCell->rowSpan > 1) { $elCell->attrs['rowspan'] = $elCell->rowSpan; } $text = rtrim($elCell->text); if (strpos($text, "\n") !== FALSE) { // multiline parse as block // HACK: disable tables $this->disableTables = TRUE; $elCell->parseBlock($tx, Texy::outdent($text)); $this->disableTables = FALSE; } else { $elCell->parseLine($tx, ltrim($text)); } if ($elCell->getText() === '') { $elCell->setText("\xC2\xA0"); //   } } } } } /** * Table cell TD / TH. * @package Texy */ class TexyTableCellElement extends TexyHtml { /** @var int */ public $colSpan = 1; /** @var int */ public $rowSpan = 1; /** @var string */ public $text; } Texy-2.3.0/src/Texy/modules/TexyTypographyModule.php100777 0 0 13351 12172334252 15677 0 array( 'singleQuotes' => array("\xe2\x80\x9a", "\xe2\x80\x98"), // U+201A, U+2018 'doubleQuotes' => array("\xe2\x80\x9e", "\xe2\x80\x9c"), // U+201E, U+201C ), 'en' => array( 'singleQuotes' => array("\xe2\x80\x98", "\xe2\x80\x99"), // U+2018, U+2019 'doubleQuotes' => array("\xe2\x80\x9c", "\xe2\x80\x9d"), // U+201C, U+201D ), 'fr' => array( 'singleQuotes' => array("\xe2\x80\xb9", "\xe2\x80\xba"), // U+2039, U+203A 'doubleQuotes' => array("\xc2\xab", "\xc2\xbb"), // U+00AB, U+00BB ), 'de' => array( 'singleQuotes' => array("\xe2\x80\x9a", "\xe2\x80\x98"), // U+201A, U+2018 'doubleQuotes' => array("\xe2\x80\x9e", "\xe2\x80\x9c"), // U+201E, U+201C ), 'pl' => array( 'singleQuotes' => array("\xe2\x80\x9a", "\xe2\x80\x99"), // U+201A, U+2019 'doubleQuotes' => array("\xe2\x80\x9e", "\xe2\x80\x9d"), // U+201E, U+201D ), ); /** @var string */ public $locale = 'cs'; /** @var array */ private $pattern, $replace; public function __construct($texy) { $this->texy = $texy; $texy->registerPostLine(array($this, 'postLine'), 'typography'); $texy->addHandler('beforeParse', array($this, 'beforeParse')); } /** * Text pre-processing. * @param Texy * @param string * @return void */ public function beforeParse($texy, & $text) { // CONTENT_MARKUP mark: \x17-\x1F // CONTENT_REPLACED mark: \x16 // CONTENT_TEXTUAL mark: \x17 // CONTENT_BLOCK: not used in postLine if (isset(self::$locales[$this->locale])) { $locale = self::$locales[$this->locale]; } else { // fall back $locale = self::$locales['en']; } $pairs = array( '#(? "\xe2\x80\xa6", // ellipsis ... '#(?<=[\d ]|^)-(?=[\d ]|$)#' => "\xe2\x80\x93", // en dash 123-123 '#(?<=[^!*+,/:;<=>@\\\\_|-])--(?=[^!*+,/:;<=>@\\\\_|-])#' => "\xe2\x80\x93", // en dash alphanum--alphanum '#,-#' => ",\xe2\x80\x93", // en dash ,- '#(? "\$1\xc2\xa0\$2\xc2\xa0\$3", // date 23. 1. 1978 '#(? "\$1\xc2\xa0\$2", // date 23. 1. '# --- #' => "\xc2\xa0\xe2\x80\x94 ", // em dash --- '# ([\x{2013}\x{2014}])#u' => "\xc2\xa0\$1", //   behind dash (dash stays at line end) '# <-{1,2}> #' => " \xe2\x86\x94 ", // left right arrow <--> '#-{1,}> #' => " \xe2\x86\x92 ", // right arrow --> '# <-{1,}#' => " \xe2\x86\x90 ", // left arrow <-- '#={1,}> #' => " \xe2\x87\x92 ", // right arrow ==> '#\\+-#' => "\xc2\xb1", // +- '#(\d++)( ?)x\\2(?=\d)#' => "\$1\xc3\x97", // dimension sign 123 x 123... '#(?<=\d)x(?= |,|.|$)#m' => "\xc3\x97", // dimension sign 123x '#(\S ?)\(TM\)#i' => "\$1\xe2\x84\xa2", // trademark (TM) '#(\S ?)\(R\)#i' => "\$1\xc2\xae", // registered (R) '#\(C\)( ?\S)#i' => "\xc2\xa9\$1", // copyright (C) '#\(EUR\)#' => "\xe2\x82\xac", // Euro (EUR) '#(\d) (?=\d{3})#' => "\$1\xc2\xa0", // (phone) number 1 123 123 123... '#(?<=[^\s\x17])\s++([\x17-\x1F]++)(?=\s)#u'=> "\$1", // remove intermarkup space phase 1 '#(?<=\s)([\x17-\x1F]++)\s++#u' => "\$1", // remove intermarkup space phase 2 '#(?<=.{50})\s++(?=[\x17-\x1F]*\S{1,6}[\x17-\x1F]*$)#us' => "\xc2\xa0", // space before last short word // nbsp space between number (optionally followed by dot) and word, symbol, punctation, currency symbol '#(?<=^| |\.|,|-|\+|\x16|\(|\d\x{A0})([\x17-\x1F]*\d++\.?[\x17-\x1F]*)\s++(?=[\x17-\x1F]*[%'.TexyPatterns::CHAR.'\x{b0}-\x{be}\x{2020}-\x{214f}])#mu' => "\$1\xc2\xa0", // space between preposition and word '#(?<=^|[^0-9'.TexyPatterns::CHAR.'])([\x17-\x1F]*[ksvzouiKSVZOUIA][\x17-\x1F]*)\s++(?=[\x17-\x1F]*[0-9'.TexyPatterns::CHAR.'])#mus' => "\$1\xc2\xa0", '#(? $locale['doubleQuotes'][0].'$1'.$locale['doubleQuotes'][1], // double "" '#(? $locale['singleQuotes'][0].'$1'.$locale['singleQuotes'][1], // single '' ); $this->pattern = array_keys($pairs); $this->replace = array_values($pairs); } public function postLine($text, $preserveSpaces = FALSE) { if (!$preserveSpaces) { $text = preg_replace('# {2,}#', ' ', $text); } return preg_replace($this->pattern, $this->replace, $text); } } Texy-2.3.0/src/Texy/Texy.php100777 0 0 53273 12172334252 11001 0 * $texy = new Texy(); * $html = $texy->process($text); * * * @author David Grudl * @package Texy */ class Texy extends TexyObject { // configuration directives const ALL = TRUE; const NONE = FALSE; // Texy version const VERSION = '2.3'; const REVISION = '11c34e6 released on 2013-07-20'; // types of protection marks const CONTENT_MARKUP = "\x17"; const CONTENT_REPLACED = "\x16"; const CONTENT_TEXTUAL = "\x15"; const CONTENT_BLOCK = "\x14"; // url filters const FILTER_ANCHOR = 'anchor'; const FILTER_IMAGE = 'image'; // HTML minor-modes const XML = 2; // HTML modes const HTML4_TRANSITIONAL = 0; const HTML4_STRICT = 1; const HTML5 = 4; const XHTML1_TRANSITIONAL = 2; // Texy::HTML4_TRANSITIONAL | Texy::XML; const XHTML1_STRICT = 3; // Texy::HTML4_STRICT | Texy::XML; const XHTML5 = 6; // Texy::HTML5 | Texy::XML; /** @var string input & output text encoding */ public $encoding = 'utf-8'; /** @var array Texy! syntax configuration */ public $allowed = array(); /** @var TRUE|FALSE|array Allowed HTML tags */ public $allowedTags; /** @var TRUE|FALSE|array Allowed classes */ public $allowedClasses = Texy::ALL; // all classes and id are allowed /** @var TRUE|FALSE|array Allowed inline CSS style */ public $allowedStyles = Texy::ALL; // all inline styles are allowed /** @var int TAB width (for converting tabs to spaces) */ public $tabWidth = 8; /** @var boolean Do obfuscate e-mail addresses? */ public $obfuscateEmail = TRUE; /** @var array regexps to check URL schemes */ public $urlSchemeFilters = NULL; // disable URL scheme filter /** @var bool Paragraph merging mode */ public $mergeLines = TRUE; /** @var array Parsing summary */ public $summary = array( 'images' => array(), 'links' => array(), 'preload' => array(), ); /** @var string Generated stylesheet */ public $styleSheet = ''; /** @var array CSS classes for align modifiers */ public $alignClasses = array( 'left' => NULL, 'right' => NULL, 'center' => NULL, 'justify' => NULL, 'top' => NULL, 'middle' => NULL, 'bottom' => NULL, ); /** @var bool remove soft hyphens (SHY)? */ public $removeSoftHyphens = TRUE; /** @var mixed */ public static $advertisingNotice = 'once'; /** @var string */ public $nontextParagraph = 'div'; /** @var TexyScriptModule */ public $scriptModule; /** @var TexyParagraphModule */ public $paragraphModule; /** @var TexyHtmlModule */ public $htmlModule; /** @var TexyImageModule */ public $imageModule; /** @var TexyLinkModule */ public $linkModule; /** @var TexyPhraseModule */ public $phraseModule; /** @var TexyEmoticonModule */ public $emoticonModule; /** @var TexyBlockModule */ public $blockModule; /** @var TexyHeadingModule */ public $headingModule; /** @var TexyHorizLineModule */ public $horizLineModule; /** @var TexyBlockQuoteModule */ public $blockQuoteModule; /** @var TexyListModule */ public $listModule; /** @var TexyTableModule */ public $tableModule; /** @var TexyFigureModule */ public $figureModule; /** @var TexyTypographyModule */ public $typographyModule; /** @var TexyLongWordsModule */ public $longWordsModule; /** @var TexyHtmlOutputModule */ public $htmlOutputModule; /** * Registered regexps and associated handlers for inline parsing. * @var array of ('handler' => callback, 'pattern' => regular expression) */ private $linePatterns = array(); private $_linePatterns; /** * Registered regexps and associated handlers for block parsing. * @var array of ('handler' => callback, 'pattern' => regular expression) */ private $blockPatterns = array(); private $_blockPatterns; /** @var array */ private $postHandlers = array(); /** @var TexyHtml DOM structure for parsed text */ private $DOM; /** @var array Texy protect markup table */ private $marks = array(); /** @var array for internal usage */ public $_classes, $_styles; /** @var bool */ private $processing; /** @var array of events and registered handlers */ private $handlers = array(); /** * DTD descriptor. * $dtd[element][0] - allowed attributes (as array keys) * $dtd[element][1] - allowed content for an element (content model) (as array keys) * - array of allowed elements (as keys) * - FALSE - empty element * - 0 - special case for ins & del * @var array */ public $dtd; /** @var array */ private static $dtdCache; /** @var int HTML mode */ private $mode; /** DEPRECATED */ public static $strictDTD; public $cleaner; public $xhtml; public function __construct() { // load all modules $this->loadModules(); // DEPRECATED if (self::$strictDTD !== NULL) { $this->setOutputMode(self::$strictDTD ? self::XHTML1_STRICT : self::XHTML1_TRANSITIONAL); } else { $this->setOutputMode(self::XHTML1_TRANSITIONAL); } // DEPRECATED $this->cleaner = & $this->htmlOutputModule; // examples of link references ;-) $link = new TexyLink('http://texy.info/'); $link->modifier->title = 'The best text -> HTML converter and formatter'; $link->label = 'Texy!'; $this->linkModule->addReference('texy', $link); $link = new TexyLink('http://www.google.com/search?q=%s'); $this->linkModule->addReference('google', $link); $link = new TexyLink('http://en.wikipedia.org/wiki/Special:Search?search=%s'); $this->linkModule->addReference('wikipedia', $link); } /** * Set HTML/XHTML output mode (overwrites self::$allowedTags) * @param int * @return void */ public function setOutputMode($mode) { if (!in_array($mode, array(self::HTML4_TRANSITIONAL, self::HTML4_STRICT, self::HTML5, self::XHTML1_TRANSITIONAL, self::XHTML1_STRICT, self::XHTML5), TRUE) ) { throw new InvalidArgumentException("Invalid mode."); } if (!isset(self::$dtdCache[$mode])) { require dirname(__FILE__) . '/DTD.php'; self::$dtdCache[$mode] = $dtd; } $this->mode = $mode; $this->dtd = self::$dtdCache[$mode]; TexyHtml::$xhtml = (bool) ($mode & self::XML); // TODO: remove? // accept all valid HTML tags and attributes by default $this->allowedTags = array(); foreach ($this->dtd as $tag => $dtd) { $this->allowedTags[$tag] = self::ALL; } } /** * Get HTML/XHTML output mode * @return int */ public function getOutputMode() { return $this->mode; } /** * Create array of all used modules ($this->modules). * This array can be changed by overriding this method (by subclasses) */ protected function loadModules() { // line parsing $this->scriptModule = new TexyScriptModule($this); $this->htmlModule = new TexyHtmlModule($this); $this->imageModule = new TexyImageModule($this); $this->phraseModule = new TexyPhraseModule($this); $this->linkModule = new TexyLinkModule($this); $this->emoticonModule = new TexyEmoticonModule($this); // block parsing $this->paragraphModule = new TexyParagraphModule($this); $this->blockModule = new TexyBlockModule($this); $this->figureModule = new TexyFigureModule($this); $this->horizLineModule = new TexyHorizLineModule($this); $this->blockQuoteModule = new TexyBlockQuoteModule($this); $this->tableModule = new TexyTableModule($this); $this->headingModule = new TexyHeadingModule($this); $this->listModule = new TexyListModule($this); // post process $this->typographyModule = new TexyTypographyModule($this); $this->longWordsModule = new TexyLongWordsModule($this); $this->htmlOutputModule = new TexyHtmlOutputModule($this); } final public function registerLinePattern($handler, $pattern, $name, $againTest = NULL) { if (!is_callable($handler)) { $able = is_callable($handler, TRUE, $textual); throw new InvalidArgumentException("Handler '$textual' is not " . ($able ? 'callable.' : 'valid PHP callback.')); } if (!isset($this->allowed[$name])) { $this->allowed[$name] = TRUE; } $this->linePatterns[$name] = array( 'handler' => $handler, 'pattern' => $pattern, 'again' => $againTest, ); } final public function registerBlockPattern($handler, $pattern, $name) { if (!is_callable($handler)) { $able = is_callable($handler, TRUE, $textual); throw new InvalidArgumentException("Handler '$textual' is not " . ($able ? 'callable.' : 'valid PHP callback.')); } // if (!preg_match('#(.)\^.*\$\\1[a-z]*#is', $pattern)) die("Texy: Not a block pattern $name"); if (!isset($this->allowed[$name])) { $this->allowed[$name] = TRUE; } $this->blockPatterns[$name] = array( 'handler' => $handler, 'pattern' => $pattern . 'm', // force multiline ); } final public function registerPostLine($handler, $name) { if (!is_callable($handler)) { $able = is_callable($handler, TRUE, $textual); throw new InvalidArgumentException("Handler '$textual' is not " . ($able ? 'callable.' : 'valid PHP callback.')); } if (!isset($this->allowed[$name])) { $this->allowed[$name] = TRUE; } $this->postHandlers[$name] = $handler; } /** * Converts document in Texy! to (X)HTML code. * * @param string input text * @param bool is single line? * @return string output HTML code */ public function process($text, $singleLine = FALSE) { if ($this->processing) { throw new RuntimeException('Processing is in progress yet.'); } // initialization $this->marks = array(); $this->processing = TRUE; // speed-up if (is_array($this->allowedClasses)) { $this->_classes = array_flip($this->allowedClasses); } else { $this->_classes = $this->allowedClasses; } if (is_array($this->allowedStyles)) { $this->_styles = array_flip($this->allowedStyles); } else { $this->_styles = $this->allowedStyles; } // convert to UTF-8 (and check source encoding) $text = TexyUtf::toUtf($text, $this->encoding); if ($this->removeSoftHyphens) { $text = str_replace("\xC2\xAD", '', $text); } // standardize line endings and spaces $text = self::normalize($text); // replace tabs with spaces $this->tabWidth = max(1, (int) $this->tabWidth); while (strpos($text, "\t") !== FALSE) { $text = preg_replace_callback('#^([^\t\n]*+)\t#mU', array($this, 'tabCb'), $text); } // user before handler $this->invokeHandlers('beforeParse', array($this, & $text, $singleLine)); // select patterns $this->_linePatterns = $this->linePatterns; $this->_blockPatterns = $this->blockPatterns; foreach ($this->_linePatterns as $name => $foo) { if (empty($this->allowed[$name])) { unset($this->_linePatterns[$name]); } } foreach ($this->_blockPatterns as $name => $foo) { if (empty($this->allowed[$name])) { unset($this->_blockPatterns[$name]); } } // parse Texy! document into internal DOM structure $this->DOM = TexyHtml::el(); if ($singleLine) { $this->DOM->parseLine($this, $text); } else { $this->DOM->parseBlock($this, $text); } // user after handler $this->invokeHandlers('afterParse', array($this, $this->DOM, $singleLine)); // converts internal DOM structure to final HTML code $html = $this->DOM->toHtml($this); // created by TexyParagraphModule and then protected $html = str_replace("\r", "\n", $html); // this notice should remain if (self::$advertisingNotice) { $html .= "\n"; if (self::$advertisingNotice === 'once') { self::$advertisingNotice = FALSE; } } $this->processing = FALSE; return TexyUtf::utf2html($html, $this->encoding); } /** * Converts single line in Texy! to (X)HTML code. * * @param string input text * @return string output HTML code */ public function processLine($text) { return $this->process($text, TRUE); } /** * Makes only typographic corrections. * @param string input text (in encoding defined by Texy::$encoding) * @return string output text (in UTF-8) */ public function processTypo($text) { // convert to UTF-8 (and check source encoding) $text = TexyUtf::toUtf($text, $this->encoding); // standardize line endings and spaces $text = self::normalize($text); $this->typographyModule->beforeParse($this, $text); $text = $this->typographyModule->postLine($text, TRUE); if (!empty($this->allowed['longwords'])) { $text = $this->longWordsModule->postLine($text); } return TexyUtf::utf2html($text, $this->encoding); } /** * Converts DOM structure to pure text. * @return string */ public function toText() { if (!$this->DOM) { throw new RuntimeException('Call $texy->process() first.'); } return TexyUtf::utfTo($this->DOM->toText($this), $this->encoding); } /** * Converts internal string representation to final HTML code in UTF-8. * @return string */ final public function stringToHtml($s) { // decode HTML entities to UTF-8 $s = self::unescapeHtml($s); // line-postprocessing $blocks = explode(self::CONTENT_BLOCK, $s); foreach ($this->postHandlers as $name => $handler) { if (empty($this->allowed[$name])) { continue; } foreach ($blocks as $n => $s) { if ($n % 2 === 0 && $s !== '') { $blocks[$n] = call_user_func($handler, $s); } } } $s = implode(self::CONTENT_BLOCK, $blocks); // encode < > & $s = self::escapeHtml($s); // replace protected marks $s = $this->unProtect($s); // wellform and reformat HTML $this->invokeHandlers('postProcess', array($this, & $s)); // unfreeze spaces $s = self::unfreezeSpaces($s); return $s; } /** * Converts internal string representation to final HTML code in UTF-8. * @return string */ final public function stringToText($s) { $save = $this->htmlOutputModule->lineWrap; $this->htmlOutputModule->lineWrap = FALSE; $s = $this->stringToHtml( $s ); $this->htmlOutputModule->lineWrap = $save; // remove tags $s = preg_replace('#<(script|style)(.*)#Uis', '', $s); $s = strip_tags($s); $s = preg_replace('#\n\s*\n\s*\n[\n\s]*\n#', "\n\n", $s); // entities -> chars $s = self::unescapeHtml($s); // convert nbsp to normal space and remove shy $s = strtr($s, array( "\xC2\xAD" => '', // shy "\xC2\xA0" => ' ', // nbsp )); return $s; } /** * Add new event handler. * * @param string event name * @param callback * @return void */ final public function addHandler($event, $callback) { if (!is_callable($callback)) { $able = is_callable($callback, TRUE, $textual); throw new InvalidArgumentException("Handler '$textual' is not " . ($able ? 'callable.' : 'valid PHP callback.')); } $this->handlers[$event][] = $callback; } /** * Invoke registered around-handlers. * * @param string event name * @param TexyParser actual parser object * @param array arguments passed into handler * @return mixed */ final public function invokeAroundHandlers($event, $parser, $args) { if (!isset($this->handlers[$event])) { return FALSE; } $invocation = new TexyHandlerInvocation($this->handlers[$event], $parser, $args); $res = $invocation->proceed(); $invocation->free(); return $res; } /** * Invoke registered after-handlers. * * @param string event name * @param array arguments passed into handler * @return void */ final public function invokeHandlers($event, $args) { if (!isset($this->handlers[$event])) { return; } foreach ($this->handlers[$event] as $handler) { call_user_func_array($handler, $args); } } /** * Translate all white spaces (\t \n \r space) to meta-spaces \x01-\x04. * which are ignored by TexyHtmlOutputModule routine * @param string * @return string */ final public static function freezeSpaces($s) { return strtr($s, " \t\r\n", "\x01\x02\x03\x04"); } /** * Reverts meta-spaces back to normal spaces. * @param string * @return string */ final public static function unfreezeSpaces($s) { return strtr($s, "\x01\x02\x03\x04", " \t\r\n"); } /** * Removes special controls characters and normalizes line endings and spaces. * @param string * @return string */ final public static function normalize($s) { // standardize line endings to unix-like $s = str_replace("\r\n", "\n", $s); // DOS $s = strtr($s, "\r", "\n"); // Mac // remove special chars; leave \t + \n $s = preg_replace('#[\x00-\x08\x0B-\x1F]+#', '', $s); // right trim $s = preg_replace("#[\t ]+$#m", '', $s); // trailing spaces $s = trim($s, "\n"); return $s; } /** * Converts to web safe characters [a-z0-9-] text. * @param string * @param string * @return string */ final public static function webalize($s, $charlist = NULL) { $s = TexyUtf::utf2ascii($s); $s = strtolower($s); $s = preg_replace('#[^a-z0-9'.preg_quote($charlist, '#').']+#', '-', $s); $s = trim($s, '-'); return $s; } /** * Texy! version of htmlSpecialChars (much faster than htmlSpecialChars!). * note: " is not encoded! * @param string * @return string */ final public static function escapeHtml($s) { return str_replace(array('&', '<', '>'), array('&', '<', '>'), $s); } /** * Texy! version of html_entity_decode (always UTF-8, much faster than original!). * @param string * @return string */ final public static function unescapeHtml($s) { if (strpos($s, '&') === FALSE) { return $s; } return html_entity_decode($s, ENT_QUOTES, 'UTF-8'); } /** * Outdents text block. * @param string * @return string */ final public static function outdent($s) { $s = trim($s, "\n"); $spaces = strspn($s, ' '); if ($spaces) { return preg_replace("#^ {1,$spaces}#m", '', $s); } return $s; } /** * Generate unique mark - useful for freezing (folding) some substrings. * @param string any string to froze * @param int Texy::CONTENT_* constant * @return string internal mark */ final public function protect($child, $contentType) { if ($child === '') { return ''; } $key = $contentType . strtr(base_convert(count($this->marks), 10, 8), '01234567', "\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F") . $contentType; $this->marks[$key] = $child; return $key; } final public function unProtect($html) { return strtr($html, $this->marks); } /** * Filters bad URLs. * @param string user URL * @param string type: a-anchor, i-image, c-cite * @return bool */ final public function checkURL($URL, $type) { // absolute URL with scheme? check scheme! return empty($this->urlSchemeFilters[$type]) || !preg_match('#[a-z][a-z0-9+.-]{0,20}:#A', $URL) // http: | mailto: || preg_match($this->urlSchemeFilters[$type], $URL); } /** * Is given URL relative? * @param string URL * @return bool */ final public static function isRelative($URL) { // check for scheme, or absolute path, or absolute URL return !preg_match('#[a-z][a-z0-9+.-]{0,20}:|[\#/?]#A', $URL); } /** * Prepends root to URL, if possible. * @param string URL * @param string root * @return string */ final public static function prependRoot($URL, $root) { if ($root == NULL || !self::isRelative($URL)) { return $URL; } return rtrim($root, '/\\') . '/' . $URL; } final public function getLinePatterns() { return $this->_linePatterns; } final public function getBlockPatterns() { return $this->_blockPatterns; } final public function getDOM() { return $this->DOM; } private function tabCb($m) { return $m[1] . str_repeat(' ', $this->tabWidth - strlen($m[1]) % $this->tabWidth); } /** * PHP garbage collector helper. */ final public function free() { if (version_compare(PHP_VERSION , '5.3', '<')) { foreach (array_keys(get_object_vars($this)) as $key) { $this->$key = NULL; } } } final public function __clone() { throw new Exception('Clone is not supported.'); } } /** * For Texy 1 backward compatibility. */ define('TEXY_ALL', Texy::ALL); define('TEXY_NONE', Texy::NONE); define('TEXY_CONTENT_MARKUP', Texy::CONTENT_MARKUP); define('TEXY_CONTENT_REPLACED', Texy::CONTENT_REPLACED); define('TEXY_CONTENT_TEXTUAL', Texy::CONTENT_TEXTUAL); define('TEXY_CONTENT_BLOCK', Texy::CONTENT_BLOCK); define('TEXY_VERSION', Texy::VERSION); /** * For Texy 2.2 compatibility */ define('TEXY_CHAR', TexyPatterns::CHAR); define('TEXY_MARK', TexyPatterns::MARK); define('TEXY_MODIFIER', TexyPatterns::MODIFIER); define('TEXY_MODIFIER_H', TexyPatterns::MODIFIER_H); define('TEXY_MODIFIER_HV', TexyPatterns::MODIFIER_HV); define('TEXY_IMAGE', TexyPatterns::IMAGE); define('TEXY_LINK_URL', TexyPatterns::LINK_URL); define('TEXY_LINK', '(?::('.TEXY_LINK_URL.'))'); define('TEXY_LINK_N', '(?::('.TEXY_LINK_URL.'|:))'); define('TEXY_EMAIL', '['.TEXY_CHAR.'][0-9.+_'.TEXY_CHAR.'-]{0,63}@[0-9.+_'.TEXY_CHAR.'\x{ad}-]{1,252}\.['.TEXY_CHAR.'\x{ad}]{2,19}'); define('TEXY_URLSCHEME', '[a-z][a-z0-9+.-]{0,20}:'); Texy-2.3.0/src/Texy/TexyConfigurator.php100777 0 0 5430 12172334252 13334 0 * $texy = new Texy(); * TexyConfigurator::safeMode($texy); * * * @author David Grudl * @package Texy */ class TexyConfigurator { public static $safeTags = array( 'a' => array('href', 'title'), 'acronym' => array('title'), 'b' => array(), 'br' => array(), 'cite' => array(), 'code' => array(), 'em' => array(), 'i' => array(), 'strong' => array(), 'sub' => array(), 'sup' => array(), 'q' => array(), 'small' => array(), ); /** * static class. */ final public function __construct() { throw new LogicException("Cannot instantiate static class " . get_class($this)); } /** * Configure Texy! for web comments and other usages, where input text may insert attacker. * * @param Texy object to configure * @return void */ public static function safeMode(Texy $texy) { $texy->allowedClasses = Texy::NONE; // no class or ID are allowed $texy->allowedStyles = Texy::NONE; // style modifiers are disabled $texy->allowedTags = self::$safeTags; // only some "safe" HTML tags and attributes are allowed $texy->urlSchemeFilters[Texy::FILTER_ANCHOR] = '#https?:|ftp:|mailto:#A'; $texy->urlSchemeFilters[Texy::FILTER_IMAGE] = '#https?:#A'; $texy->allowed['image'] = FALSE; // disable images $texy->allowed['link/definition'] = FALSE; // disable [ref]: URL reference definitions $texy->allowed['html/comment'] = FALSE; // disable HTML comments $texy->linkModule->forceNoFollow = TRUE; // force rel="nofollow" } /** * Disable all links. * * @param Texy object to configure * @return void */ public static function disableLinks(Texy $texy) { $texy->allowed['link/reference'] = FALSE; $texy->allowed['link/email'] = FALSE; $texy->allowed['link/url'] = FALSE; $texy->allowed['link/definition'] = FALSE; $texy->phraseModule->linksAllowed = FALSE; if (is_array($texy->allowedTags)) { unset($texy->allowedTags['a']); } // TODO: else... } /** * Disable all images. * * @param Texy object to configure * @return void */ public static function disableImages(Texy $texy) { $texy->allowed['image'] = FALSE; $texy->allowed['figure'] = FALSE; $texy->allowed['image/definition'] = FALSE; if (is_array($texy->allowedTags)) { unset($texy->allowedTags['img'], $texy->allowedTags['object'], $texy->allowedTags['embed'], $texy->allowedTags['applet']); } // TODO: else... } } Texy-2.3.0/src/Texy/TexyHandlerInvocation.php100777 0 0 3531 12172334252 14301 0handlers = $handlers; $this->pos = count($handlers); $this->parser = $parser; array_unshift($args, $this); $this->args = $args; } /** * @param mixed * @return mixed */ public function proceed() { if ($this->pos === 0) { throw new RuntimeException('No more handlers.'); } if (func_num_args()) { $this->args = func_get_args(); array_unshift($this->args, $this); } $this->pos--; $res = call_user_func_array($this->handlers[$this->pos], $this->args); if ($res === NULL) { throw new UnexpectedValueException("Invalid value returned from handler '" . print_r($this->handlers[$this->pos], TRUE) . "'."); } return $res; } /** * @return TexyParser */ public function getParser() { return $this->parser; } /** * @return Texy */ public function getTexy() { return $this->parser->getTexy(); } /** * PHP garbage collector helper. */ public function free() { $this->handlers = $this->parser = $this->args = NULL; } } Texy-2.3.0/src/Texy/TexyHtml.php100777 0 0 30574 12172334252 11625 0href($link)->setText('Texy'); * $el->class = 'myclass'; * * echo $el->startTag(), $el->endTag(); * * @property mixed element's attributes * @author David Grudl * @package Texy */ class TexyHtml extends TexyObject implements ArrayAccess, /* Countable, */ IteratorAggregate { /** @var string element's name */ private $name; /** @var bool is element empty? */ private $isEmpty; /** @var array element's attributes */ public $attrs = array(); /** @var array of TexyHtml | string nodes */ protected $children = array(); /** @var bool use XHTML syntax? */ public static $xhtml = TRUE; /** @var array empty elements */ public static $emptyElements = array('img'=>1,'hr'=>1,'br'=>1,'input'=>1,'meta'=>1,'area'=>1, 'base'=>1,'col'=>1,'link'=>1,'param'=>1,'basefont'=>1,'frame'=>1,'isindex'=>1,'wbr'=>1,'embed'=>1); /** @var array %inline; elements; replaced elements + br have value '1' */ public static $inlineElements = array('ins'=>0,'del'=>0,'tt'=>0,'i'=>0,'b'=>0,'big'=>0,'small'=>0,'em'=>0, 'strong'=>0,'dfn'=>0,'code'=>0,'samp'=>0,'kbd'=>0,'var'=>0,'cite'=>0,'abbr'=>0,'acronym'=>0, 'sub'=>0,'sup'=>0,'q'=>0,'span'=>0,'bdo'=>0,'a'=>0,'object'=>1,'img'=>1,'br'=>1,'script'=>1, 'map'=>0,'input'=>1,'select'=>1,'textarea'=>1,'label'=>0,'button'=>1, 'u'=>0,'s'=>0,'strike'=>0,'font'=>0,'applet'=>1,'basefont'=>0, // transitional 'embed'=>1,'wbr'=>0,'nobr'=>0,'canvas'=>1, // proprietary ); /** @var array elements with optional end tag in HTML */ public static $optionalEnds = array('body'=>1,'head'=>1,'html'=>1,'colgroup'=>1,'dd'=>1, 'dt'=>1,'li'=>1,'option'=>1,'p'=>1,'tbody'=>1,'td'=>1,'tfoot'=>1,'th'=>1,'thead'=>1,'tr'=>1); /** @see http://www.w3.org/TR/xhtml1/prohibitions.html */ public static $prohibits = array( 'a' => array('a','button'), 'img' => array('pre'), 'object' => array('pre'), 'big' => array('pre'), 'small' => array('pre'), 'sub' => array('pre'), 'sup' => array('pre'), 'input' => array('button'), 'select' => array('button'), 'textarea' => array('button'), 'label' => array('button', 'label'), 'button' => array('button'), 'form' => array('button', 'form'), 'fieldset' => array('button'), 'iframe' => array('button'), 'isindex' => array('button'), ); /** * Static factory. * @param string element name (or NULL) * @param array|string element's attributes (or textual content) * @return TexyHtml */ public static function el($name = NULL, $attrs = NULL) { $el = new self; $el->setName($name); if (is_array($attrs)) { $el->attrs = $attrs; } elseif ($attrs !== NULL) { $el->setText($attrs); } return $el; } /** * Changes element's name. * @param string * @param bool Is element empty? * @return self * @throws InvalidArgumentException */ final public function setName($name, $empty = NULL) { if ($name !== NULL && !is_string($name)) { throw new InvalidArgumentException("Name must be string or NULL."); } $this->name = $name; $this->isEmpty = $empty === NULL ? isset(self::$emptyElements[$name]) : (bool) $empty; return $this; } /** * Returns element's name. * @return string */ final public function getName() { return $this->name; } /** * Is element empty? * @return bool */ final public function isEmpty() { return $this->isEmpty; } /** * Overloaded setter for element's attribute. * @param string property name * @param mixed property value * @return void */ final public function __set($name, $value) { $this->attrs[$name] = $value; } /** * Overloaded getter for element's attribute. * @param string property name * @return mixed property value */ final public function & __get($name) { return $this->attrs[$name]; } /** * Overloaded setter for element's attribute. * @param string attribute name * @param array value * @return self */ /* final public function __call($m, $args) { if (count($args) !== 1) { throw new InvalidArgumentException("Just one argument is required."); } $this->attrs[$m] = $args[0]; return $this; } */ /** * Special setter for element's attribute. * @param string path * @param array query * @return self */ final public function href($path, $query = NULL) { if ($query) { $query = http_build_query($query, NULL, '&'); if ($query !== '') { $path .= '?' . $query; } } $this->attrs['href'] = $path; return $this; } /** * Sets element's textual content. * @param string * @return self */ final public function setText($text) { if (is_scalar($text)) { $this->removeChildren(); $this->children = array($text); } elseif ($text !== NULL) { throw new InvalidArgumentException('Content must be scalar.'); } return $this; } /** * Gets element's textual content. * @return string */ final public function getText() { $s = ''; foreach ($this->children as $child) { if (is_object($child)) { return FALSE; } $s .= $child; } return $s; } /** * Adds new element's child. * @param TexyHtml|string child node * @return self */ final public function add($child) { return $this->insert(NULL, $child); } /** * Creates and adds a new TexyHtml child. * @param string elements's name * @param array|string element's attributes (or textual content) * @return TexyHtml created element */ final public function create($name, $attrs = NULL) { $this->insert(NULL, $child = self::el($name, $attrs)); return $child; } /** * Inserts child node. * @param int * @param TexyHtml node * @param bool * @return self * @throws Exception */ public function insert($index, $child, $replace = FALSE) { if ($child instanceof TexyHtml || is_string($child)) { if ($index === NULL) { // append $this->children[] = $child; } else { // insert or replace array_splice($this->children, (int) $index, $replace ? 1 : 0, array($child)); } } else { throw new /*::*/InvalidArgumentException('Child node must be scalar or TexyHtml object.'); } return $this; } /** * Inserts (replaces) child node (ArrayAccess implementation). * @param int * @param TexyHtml node * @return void */ final public function offsetSet($index, $child) { $this->insert($index, $child, TRUE); } /** * Returns child node (ArrayAccess implementation). * @param int index * @return mixed */ final public function offsetGet($index) { return $this->children[$index]; } /** * Exists child node? (ArrayAccess implementation). * @param int index * @return bool */ final public function offsetExists($index) { return isset($this->children[$index]); } /** * Removes child node (ArrayAccess implementation). * @param int index * @return void */ public function offsetUnset($index) { if (isset($this->children[$index])) { array_splice($this->children, (int) $index, 1); } } /** * Required by the Countable interface. * @return int */ final public function count() { return count($this->children); } /** * Removed all children. * @return void */ public function removeChildren() { $this->children = array(); } /** * Required by the IteratorAggregate interface. * @return ArrayIterator */ final public function getIterator() { return new ArrayIterator($this->children); } /** * Returns all of children. * return array */ final public function getChildren() { return $this->children; } /** * Renders element's start tag, content and end tag to internal string representation. * @param Texy * @return string */ final public function toString(Texy $texy) { $ct = $this->getContentType(); $s = $texy->protect($this->startTag(), $ct); // empty elements are finished now if ($this->isEmpty) { return $s; } // add content foreach ($this->children as $child) { if (is_object($child)) { $s .= $child->toString($texy); } else { $s .= $child; } } // add end tag return $s . $texy->protect($this->endTag(), $ct); } /** * Renders to final HTML. * @param Texy * @return string */ final public function toHtml(Texy $texy) { return $texy->stringToHtml($this->toString($texy)); } /** * Renders to final text. * @param Texy * @return string */ final public function toText(Texy $texy) { return $texy->stringToText($this->toString($texy)); } /** * Returns element's start tag. * @return string */ public function startTag() { if (!$this->name) { return ''; } $s = '<' . $this->name; if (is_array($this->attrs)) { foreach ($this->attrs as $key => $value) { // skip NULLs and false boolean attributes if ($value === NULL || $value === FALSE) { continue; } // true boolean attribute if ($value === TRUE) { // in XHTML must use unminimized form if (self::$xhtml) { $s .= ' ' . $key . '="' . $key . '"'; } else { $s .= ' ' . $key; } continue; } elseif (is_array($value)) { // prepare into temporary array $tmp = NULL; foreach ($value as $k => $v) { // skip NULLs & empty string; composite 'style' vs. 'others' if ($v == NULL) { continue; } elseif (is_string($k)) { $tmp[] = $k . ':' . $v; } else { $tmp[] = $v; } } if (!$tmp) { continue; } $value = implode($key === 'style' ? ';' : ' ', $tmp); } else { $value = (string) $value; } // add new attribute $value = str_replace(array('&', '"', '<', '>', '@'), array('&', '"', '<', '>', '@'), $value); $s .= ' ' . $key . '="' . Texy::freezeSpaces($value) . '"'; } } // finish start tag if (self::$xhtml && $this->isEmpty) { return $s . ' />'; } return $s . '>'; } /** * Returns element's end tag. * @return string */ public function endTag() { if ($this->name && !$this->isEmpty) { return 'name . '>'; } return ''; } /** * Clones all children too. */ public function __clone() { foreach ($this->children as $key => $value) { if (is_object($value)) { $this->children[$key] = clone $value; } } } /** * @return int */ final public function getContentType() { if (!isset(self::$inlineElements[$this->name])) { return Texy::CONTENT_BLOCK; } return self::$inlineElements[$this->name] ? Texy::CONTENT_REPLACED : Texy::CONTENT_MARKUP; } /** * @param array * @return void */ final public function validateAttrs($dtd) { if (isset($dtd[$this->name])) { $allowed = $dtd[$this->name][0]; if (is_array($allowed)) { foreach ($this->attrs as $attr => $foo) { if (!isset($allowed[$attr])) { unset($this->attrs[$attr]); } } } } } public function validateChild($child, $dtd) { if (isset($dtd[$this->name])) { if ($child instanceof TexyHtml) { $child = $child->name; } return isset($dtd[$this->name][1][$child]); } else { return TRUE; // unknown element } } /** * Parses text as single line. * @param Texy * @param string * @return void */ final public function parseLine(Texy $texy, $s) { // TODO! // special escape sequences $s = str_replace(array('\)', '\*'), array(')', '*'), $s); $parser = new TexyLineParser($texy, $this); $parser->parse($s); return $parser; } /** * Parses text as block. * @param Texy * @param string * @param bool * @return void */ final public function parseBlock(Texy $texy, $s, $indented = FALSE) { $parser = new TexyBlockParser($texy, $this, $indented); $parser->parse($s); } } Texy-2.3.0/src/Texy/TexyModifier.php100777 0 0 14444 12172334252 12455 0^ * . starts with dot * (...) title or alt modifier * [...] classes or ID modifier * {...} inner style modifier * < > <> = horizontal align modifier * ^ - _ vertical align modifier * * @author David Grudl * @package Texy */ final class TexyModifier extends TexyObject { /** @var string */ public $id; /** @var array of classes (as keys) */ public $classes = array(); /** @var array of CSS styles */ public $styles = array(); /** @var array of HTML element attributes */ public $attrs = array(); /** @var string */ public $hAlign; /** @var string */ public $vAlign; /** @var string */ public $title; /** @var string */ public $cite; /** @var array list of properties which are regarded as HTML element attributes */ public static $elAttrs = array( 'abbr'=>1,'accesskey'=>1,'align'=>1,'alt'=>1,'archive'=>1,'axis'=>1,'bgcolor'=>1,'cellpadding'=>1, 'cellspacing'=>1,'char'=>1,'charoff'=>1,'charset'=>1,'cite'=>1,'classid'=>1,'codebase'=>1,'codetype'=>1, 'colspan'=>1,'compact'=>1,'coords'=>1,'data'=>1,'datetime'=>1,'declare'=>1,'dir'=>1,'face'=>1,'frame'=>1, 'headers'=>1,'href'=>1,'hreflang'=>1,'hspace'=>1,'ismap'=>1,'lang'=>1,'longdesc'=>1,'name'=>1, 'noshade'=>1,'nowrap'=>1,'onblur'=>1,'onclick'=>1,'ondblclick'=>1,'onkeydown'=>1,'onkeypress'=>1, 'onkeyup'=>1,'onmousedown'=>1,'onmousemove'=>1,'onmouseout'=>1,'onmouseover'=>1,'onmouseup'=>1,'rel'=>1, 'rev'=>1,'rowspan'=>1,'rules'=>1,'scope'=>1,'shape'=>1,'size'=>1,'span'=>1,'src'=>1,'standby'=>1, 'start'=>1,'summary'=>1,'tabindex'=>1,'target'=>1,'title'=>1,'type'=>1,'usemap'=>1,'valign'=>1, 'value'=>1,'vspace'=>1, ); /** * @param string modifier to parse */ public function __construct($mod = NULL) { $this->setProperties($mod); } public function setProperties($mod) { if (!$mod) { return; } $p = 0; $len = strlen($mod); while ($p < $len) { $ch = $mod[$p]; if ($ch === '(') { // title $a = strpos($mod, ')', $p) + 1; $this->title = Texy::unescapeHtml(trim(substr($mod, $p + 1, $a - $p - 2))); $p = $a; } elseif ($ch === '{') { // style & attributes $a = strpos($mod, '}', $p) + 1; foreach (explode(';', substr($mod, $p + 1, $a - $p - 2)) as $value) { $pair = explode(':', $value, 2); $prop = strtolower(trim($pair[0])); if ($prop === '' || !isset($pair[1])) { continue; } $value = trim($pair[1]); if (isset(self::$elAttrs[$prop])) { // attribute $this->attrs[$prop] = $value; } elseif ($value !== '') { // style $this->styles[$prop] = $value; } } $p = $a; } elseif ($ch === '[') { // classes & ID $a = strpos($mod, ']', $p) + 1; $s = str_replace('#', ' #', substr($mod, $p + 1, $a - $p - 2)); foreach (explode(' ', $s) as $value) { if ($value === '') { continue; } elseif ($value{0} === '#') { $this->id = substr($value, 1); } else { $this->classes[$value] = TRUE; } } $p = $a; } elseif ($ch === '^') { // alignment $this->vAlign = 'top'; $p++; } elseif ($ch === '-') { $this->vAlign = 'middle'; $p++; } elseif ($ch === '_') { $this->vAlign = 'bottom'; $p++; } elseif ($ch === '=') { $this->hAlign = 'justify'; $p++; } elseif ($ch === '>') { $this->hAlign = 'right'; $p++; } elseif (substr($mod, $p, 2) === '<>') { $this->hAlign = 'center'; $p+=2; } elseif ($ch === '<') { $this->hAlign = 'left'; $p++; } else { break; } } } /** * Decorates TexyHtml element. * @param Texy base Texy object * @param TexyHtml element to decorate * @return void */ public function decorate($texy, $el) { $elAttrs = & $el->attrs; // tag & attibutes $tmp = $texy->allowedTags; // speed-up if (!$this->attrs) { } elseif ($tmp === Texy::ALL) { $elAttrs = $this->attrs; $el->validateAttrs($texy->dtd); } elseif (is_array($tmp) && isset($tmp[$el->getName()])) { $tmp = $tmp[$el->getName()]; if ($tmp === Texy::ALL) { $elAttrs = $this->attrs; } elseif (is_array($tmp) && count($tmp)) { $tmp = array_flip($tmp); foreach ($this->attrs as $key => $value) { if (isset($tmp[$key])) { $el->attrs[$key] = $value; } } } $el->validateAttrs($texy->dtd); } // title if ($this->title !== NULL) { $elAttrs['title'] = $texy->typographyModule->postLine($this->title); } // classes & ID if ($this->classes || $this->id !== NULL) { $tmp = $texy->_classes; // speed-up if ($tmp === Texy::ALL) { foreach ($this->classes as $value => $foo) { $elAttrs['class'][] = $value; } $elAttrs['id'] = $this->id; } elseif (is_array($tmp)) { foreach ($this->classes as $value => $foo) { if (isset($tmp[$value])) { $elAttrs['class'][] = $value; } } if (isset($tmp['#' . $this->id])) { $elAttrs['id'] = $this->id; } } } // styles if ($this->styles) { $tmp = $texy->_styles; // speed-up if ($tmp === Texy::ALL) { foreach ($this->styles as $prop => $value) { $elAttrs['style'][$prop] = $value; } } elseif (is_array($tmp)) { foreach ($this->styles as $prop => $value) { if (isset($tmp[$prop])) { $elAttrs['style'][$prop] = $value; } } } } // horizontal align if ($this->hAlign) { if (empty($texy->alignClasses[$this->hAlign])) { $elAttrs['style']['text-align'] = $this->hAlign; } else { $elAttrs['class'][] = $texy->alignClasses[$this->hAlign]; } } // vertical align if ($this->vAlign) { if (empty($texy->alignClasses[$this->vAlign])) { $elAttrs['style']['vertical-align'] = $this->vAlign; } else { $elAttrs['class'][] = $texy->alignClasses[$this->vAlign]; } } return $el; } } Texy-2.3.0/src/Texy/TexyModule.php100777 0 0 736 12172334252 12103 0 * $val = $obj->label; // equivalent to $val = $obj->getLabel(); * $obj->label = 'Nette'; // equivalent to $obj->setLabel('Nette'); * * Property names are case-sensitive, and they are written in the camelCaps * or PascalCaps. * * Event functionality is provided by declaration of property named 'on{Something}' * Multiple handlers are allowed. * * public $onClick; // declaration in class * $this->onClick[] = 'callback'; // attaching event handler * if (!empty($this->onClick)) ... // are there any handlers? * $this->onClick($sender, $arg); // raises the event with arguments * * * Adding method to class (i.e. to all instances) works similar to JavaScript * prototype property. The syntax for adding a new method is: * * MyClass::extensionMethod('newMethod', function(MyClass $obj, $arg, ...) { ... }); * $obj = new MyClass; * $obj->newMethod($x); * * * @author David Grudl * @package Texy */ abstract class TexyObject { /** @var array (method => array(type => callback)) */ private static $extMethods; /** * Returns the name of the class of this object. * * @return string */ final public /*static*/ function getClass() { return /*get_called_class()*/ /**/get_class($this)/**/; } /** * Access to reflection. * * @return ReflectionObject */ final public function getReflection() { return new ReflectionObject($this); } /** * Call to undefined method. * * @param string method name * @param array arguments * @return mixed * @throws LogicException */ public function __call($name, $args) { $class = get_class($this); if ($name === '') { throw new LogicException("Call to class '$class' method without name."); } // event functionality if (preg_match('#^on[A-Z]#', $name)) { $rp = new ReflectionProperty($class, $name); if ($rp->isPublic() && !$rp->isStatic()) { $list = $this->$name; if (is_array($list) || $list instanceof Traversable) { foreach ($list as $handler) { /**/if (is_object($handler)) { call_user_func_array(array($handler, '__invoke'), $args); } else /**/{ call_user_func_array($handler, $args); } } } return NULL; } } // extension methods if ($cb = self::extensionMethod("$class::$name")) { array_unshift($args, $this); return call_user_func_array($cb, $args); } throw new LogicException("Call to undefined method $class::$name()."); } /** * Call to undefined static method. * * @param string method name (in lower case!) * @param array arguments * @return mixed * @throws LogicException */ public static function __callStatic($name, $args) { $class = get_called_class(); throw new LogicException("Call to undefined static method $class::$name()."); } /** * Adding method to class. * * @param string method name * @param mixed callback or closure * @return mixed */ public static function extensionMethod($name, $callback = NULL) { if (self::$extMethods === NULL || $name === NULL) { // for backwards compatibility $list = get_defined_functions(); foreach ($list['user'] as $fce) { $pair = explode('_prototype_', $fce); if (count($pair) === 2) { self::$extMethods[$pair[1]][$pair[0]] = $fce; self::$extMethods[$pair[1]][''] = NULL; } } if ($name === NULL) { return NULL; } } $name = strtolower($name); $a = strrpos($name, ':'); // search :: if ($a === FALSE) { $class = strtolower(get_called_class()); $l = & self::$extMethods[$name]; } else { $class = substr($name, 0, $a - 1); $l = & self::$extMethods[substr($name, $a + 1)]; } if ($callback !== NULL) { // works as setter $l[$class] = $callback; $l[''] = NULL; return NULL; } // works as getter if (empty($l)) { return FALSE; } elseif (isset($l[''][$class])) { // cached value return $l[''][$class]; } $cl = $class; do { $cl = strtolower($cl); if (isset($l[$cl])) { return $l[''][$class] = $l[$cl]; } } while (($cl = get_parent_class($cl)) !== FALSE); foreach (class_implements($class) as $cl) { $cl = strtolower($cl); if (isset($l[$cl])) { return $l[''][$class] = $l[$cl]; } } return $l[''][$class] = FALSE; } /** * Returns property value. Do not call directly. * * @param string property name * @return mixed property value * @throws LogicException if the property is not defined. */ public function & __get($name) { $class = get_class($this); if ($name === '') { throw new LogicException("Cannot read a class '$class' property without name."); } // property getter support $name[0] = $name[0] & "\xDF"; // case-sensitive checking, capitalize first character $m = 'get' . $name; if (self::hasAccessor($class, $m)) { // ampersands: // - uses & __get() because declaration should be forward compatible (e.g. with Nette\Web\Html) // - doesn't call & $this->$m because user could bypass property setter by: $x = & $obj->property; $x = 'new value'; $val = $this->$m(); return $val; } $m = 'is' . $name; if (self::hasAccessor($class, $m)) { $val = $this->$m(); return $val; } $name = func_get_arg(0); throw new LogicException("Cannot read an undeclared property $class::\$$name."); } /** * Sets value of a property. Do not call directly. * * @param string property name * @param mixed property value * @return void * @throws LogicException if the property is not defined or is read-only */ public function __set($name, $value) { $class = get_class($this); if ($name === '') { throw new LogicException("Cannot assign to a class '$class' property without name."); } // property setter support $name[0] = $name[0] & "\xDF"; // case-sensitive checking, capitalize first character if (self::hasAccessor($class, 'get' . $name) || self::hasAccessor($class, 'is' . $name)) { $m = 'set' . $name; if (self::hasAccessor($class, $m)) { $this->$m($value); return; } else { $name = func_get_arg(0); throw new LogicException("Cannot assign to a read-only property $class::\$$name."); } } $name = func_get_arg(0); throw new LogicException("Cannot assign to an undeclared property $class::\$$name."); } /** * Is property defined? * * @param string property name * @return bool */ public function __isset($name) { $name[0] = $name[0] & "\xDF"; return $name !== '' && self::hasAccessor(get_class($this), 'get' . $name); } /** * Access to undeclared property. * * @param string property name * @return void * @throws LogicException */ public function __unset($name) { $class = get_class($this); throw new LogicException("Cannot unset the property $class::\$$name."); } /** * Has property an accessor? * * @param string class name * @param string method name * @return bool */ private static function hasAccessor($c, $m) { static $cache; if (!isset($cache[$c])) { // get_class_methods returns private, protected and public methods of Object (doesn't matter) // and ONLY PUBLIC methods of descendants (perfect!) // but returns static methods too (nothing doing...) // and is much faster than reflection // (works good since 5.0.4) $cache[$c] = array_flip(get_class_methods($c)); } return isset($cache[$c][$m]); } } Texy-2.3.0/src/Texy/TexyParser.php100777 0 0 15413 12172334252 12150 0texy; } } /** * Parser for block structures. */ class TexyBlockParser extends TexyParser { /** @var string */ private $text; /** @var int */ private $offset; /** @var bool */ private $indented; /** * @param Texy * @param TexyHtml */ public function __construct(Texy $texy, TexyHtml $element, $indented) { $this->texy = $texy; $this->element = $element; $this->indented = (bool) $indented; $this->patterns = $texy->getBlockPatterns(); } public function isIndented() { return $this->indented; } // match current line against RE. // if succesfull, increments current position and returns TRUE public function next($pattern, & $matches) { if ($this->offset > strlen($this->text)) { return FALSE; } $matches = NULL; $ok = preg_match( $pattern . 'Am', // anchored & multiline $this->text, $matches, PREG_OFFSET_CAPTURE, $this->offset ); if ($ok) { $this->offset += strlen($matches[0][0]) + 1; // 1 = "\n" foreach ($matches as $key => $value) { $matches[$key] = $value[0]; } } return $ok; } public function moveBackward($linesCount = 1) { while (--$this->offset > 0) { if ($this->text{ $this->offset-1 } === "\n") { $linesCount--; if ($linesCount < 1) { break; } } } $this->offset = max($this->offset, 0); } public static function cmp($a, $b) { if ($a[0] === $b[0]) { return $a[3] < $b[3] ? -1 : 1; } if ($a[0] < $b[0]) { return -1; } return 1; } /** * @param string * @return void */ public function parse($text) { $tx = $this->texy; $tx->invokeHandlers('beforeBlockParse', array($this, & $text)); // parser initialization $this->text = $text; $this->offset = 0; // parse loop $matches = array(); $priority = 0; foreach ($this->patterns as $name => $pattern) { preg_match_all( $pattern['pattern'], $text, $ms, PREG_OFFSET_CAPTURE | PREG_SET_ORDER ); foreach ($ms as $m) { $offset = $m[0][1]; foreach ($m as $k => $v) { $m[$k] = $v[0]; } $matches[] = array($offset, $name, $m, $priority); } $priority++; } unset($name, $pattern, $ms, $m, $k, $v); usort($matches, array(__CLASS__, 'cmp')); // generates strict error in PHP 5.1.2 $matches[] = array(strlen($text), NULL, NULL); // terminal cap // process loop $el = $this->element; $cursor = 0; do { do { list($mOffset, $mName, $mMatches) = $matches[$cursor]; $cursor++; if ($mName === NULL || $mOffset >= $this->offset) { break; } } while (1); // between-matches content if ($mOffset > $this->offset) { $s = trim(substr($text, $this->offset, $mOffset - $this->offset)); if ($s !== '') { $tx->paragraphModule->process($this, $s, $el); } } if ($mName === NULL) { break; // finito } $this->offset = $mOffset + strlen($mMatches[0]) + 1; // 1 = \n $res = call_user_func_array( $this->patterns[$mName]['handler'], array($this, $mMatches, $mName) ); if ($res === FALSE || $this->offset <= $mOffset) { // module rejects text // asi by se nemelo stat, rozdeli generic block $this->offset = $mOffset; // turn offset back continue; } elseif ($res instanceof TexyHtml) { $el->insert(NULL, $res); } elseif (is_string($res)) { $el->insert(NULL, $res); } } while (1); } } /** * Parser for single line structures. */ class TexyLineParser extends TexyParser { /** @var bool */ public $again; /** * @param Texy * @param TexyHtml */ public function __construct(Texy $texy, TexyHtml $element) { $this->texy = $texy; $this->element = $element; $this->patterns = $texy->getLinePatterns(); } /** * @param string * @return void */ public function parse($text) { $tx = $this->texy; // initialization $pl = $this->patterns; if (!$pl) { // nothing to do $this->element->insert(NULL, $text); return; } $offset = 0; $names = array_keys($pl); $arrMatches = $arrOffset = array(); foreach ($names as $name) { $arrOffset[$name] = -1; } // parse loop do { $min = NULL; $minOffset = strlen($text); foreach ($names as $index => $name) { if ($arrOffset[$name] < $offset) { $delta = ($arrOffset[$name] === -2) ? 1 : 0; if ($offset + $delta > strlen($text)) { unset($names[$index]); continue; } elseif (preg_match($pl[$name]['pattern'], $text, $arrMatches[$name], PREG_OFFSET_CAPTURE, $offset + $delta) ) { $m = & $arrMatches[$name]; if (!strlen($m[0][0])) { continue; } $arrOffset[$name] = $m[0][1]; foreach ($m as $keyx => $value) { $m[$keyx] = $value[0]; } } else { // try next time? if (!$pl[$name]['again'] || !preg_match($pl[$name]['again'], $text, $foo, NULL, $offset + $delta)) { unset($names[$index]); } continue; } } // if if ($arrOffset[$name] < $minOffset) { $minOffset = $arrOffset[$name]; $min = $name; } } // foreach if ($min === NULL) { break; } $px = $pl[$min]; $offset = $start = $arrOffset[$min]; $this->again = FALSE; $res = call_user_func_array( $px['handler'], array($this, $arrMatches[$min], $min) ); if ($res instanceof TexyHtml) { $res = $res->toString($tx); } elseif ($res === FALSE) { $arrOffset[$min] = -2; continue; } $len = strlen($arrMatches[$min][0]); $text = substr_replace( $text, (string) $res, $start, $len ); $delta = strlen($res) - $len; foreach ($names as $name) { if ($arrOffset[$name] < $start + $len) { $arrOffset[$name] = -1; } else { $arrOffset[$name] += $delta; } } if ($this->again) { $arrOffset[$min] = -2; } else { $arrOffset[$min] = -1; $offset += strlen($res); } } while (1); $this->element->insert(NULL, $text); } } Texy-2.3.0/src/Texy/TexyPatterns.php100777 0 0 3124 12172334252 12470 0 const MODIFIER_H = '(?: *+(?<= |^)\\.((?:\\([^)\\n]++\\)|\\[[^\\]\\n]++\\]|\\{[^}\\n]++\\}|<>|>|=|<){1,4}?))'; // modifier .(title)[class]{style}<>^ const MODIFIER_HV = '(?: *+(?<= |^)\\.((?:\\([^)\\n]++\\)|\\[[^\\]\\n]++\\]|\\{[^}\\n]++\\}|<>|>|=|<|\\^|\\-|\\_){1,5}?))'; // images [* urls .(title)[class]{style} >] '\[\* *+([^\n'.MARK.']{1,1000})'.MODIFIER.'? *+(\*|(?|<)\]' const IMAGE = '\[\* *+([^\n\x14-\x1F]{1,1000})(?: *+(?<= |^)\\.((?:\\([^)\\n]++\\)|\\[[^\\]\\n]++\\]|\\{[^}\\n]++\\}){1,3}?))? *+(\*|(?|<)\]'; // links, url - doesn't end by :).,!? const LINK_URL = '(?:\[[^\]\n]++\]|(?!\[)[^\s\x14-\x1F]{0,1000}?[^:);,.!?\s\x14-\x1F])'; // any url - doesn't end by :).,!? } Texy-2.3.0/src/Texy/TexyUtf.php100777 0 0 7403 12172334252 11432 0 charset table self::$xlat = & self::$xlatCache[strtolower($encoding)]; if (!self::$xlat) { for ($i = 128; $i<256; $i++) { $ch = @iconv($encoding, 'UTF-8//IGNORE', chr($i)); // intentionally @ if ($ch) { self::$xlat[$ch] = chr($i); } } } // convert return preg_replace_callback('#[\x80-\x{FFFF}]#u', array(__CLASS__, 'cb'), $s); } /** * Callback; converts UTF-8 to HTML entity OR character in dest encoding. */ private static function cb($m) { $m = $m[0]; if (isset(self::$xlat[$m])) { return self::$xlat[$m]; } $ch1 = ord($m[0]); $ch2 = ord($m[1]); if (($ch2 >> 6) !== 2) { return ''; } if (($ch1 & 0xE0) === 0xC0) { return '&#' . ((($ch1 & 0x1F) << 6) + ($ch2 & 0x3F)) . ';'; } if (($ch1 & 0xF0) === 0xE0) { $ch3 = ord($m[2]); if (($ch3 >> 6) !== 2) { return ''; } return '&#' . ((($ch1 & 0xF) << 12) + (($ch2 & 0x3F) << 06) + (($ch3 & 0x3F))) . ';'; } return ''; } } Texy-2.3.0/syntax.texy100777 0 0 15151 12171641710 10050 0This file is not updated! ************************* Texy! is sexy! ************** Texy! is text-to-HTML formatter and converter library. It allows you to write structured documents without knowledge or using of HTML language. You write documents in humane easy-to-read plain text format and Texy! converts it to structurally and valid (X)HTML code. Texy! is one of the **most complex** formatters. Its possibilities covers images, links (anchors), nested lists, tables and has full support for CSS((Cascade Style Sheet)). Headers ======= === Low Level Header ==== Paragraphs ========== Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Curabitur turpis enim, placerat tincidunt, tincidunt ac, fringilla et, mauris. still the same paragraph still the same paragraph, but wrapped with element `
` and second wrapped line Centered by modifier .<> Colored by modifier .{color:blue} Horizontal lines ================ four horizontal lines, written in four ways: --- *** Code ==== /---code php function reImage($matches) { $content = $matches[1]; $align = $matches[5]; $href = $matches[6]; } \--- Syntax can be highlighted by third party module Disable Texy ============ /---html example: **this is not strong** \--- /---text example: **this is not strong** \--- Div === /---div .[header] content of div \--- nested ------ /---div .[header] ## This is a header. /---div nested div \--- Texy! is sexy! \--- BlockQuote ========== > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. > > 640 K should be enough for everyone >:http://www.microsoft.com nested ------ > ## This is a header. > > > This is nested blockquote. > > Back to the first level. Links ===== - Look at "homepage":http://texy.info. - Do you know "La Trine":[http://www.latrine.cz/]? - This picture [* image.gif *]:www.texy.info is clickable using references ---------------- [homepage]: http://texy.info/ Texy! .(this is homepage) [email]: me@example.com - Look at [homepage] - My address is [email] - What about "this":[homepage] site? Images ====== [* image.gif *] in paragraph ------------ [* image.gif <] Left-aligned image. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Curabitur turpis enim, placerat tincidunt, tincidunt ac, fringilla et, mauris. [* image.gif >] Right-aligned image. Curabitur quam justo, hendrerit a, mattis ut, ultrices at, dui. In mollis. Ut pellentesque erat vehicula nunc. Ut ultricies. Nulla nunc velit, consequat vel, laoreet in, blandit et, eros. with on-mouse-over event ------------------------ [* image.gif | image-over.gif *] with description ---------------- [* image.gif *] *** This is description under image (or photo) using references ---------------- What a beautiful logo [*picture*] :-) [*picture*]: image.gif .(alternative text) with modifiers -------------- [* image.gif .{width:100px} *] Phrases ======= Formatting: *emphasis*, **strong emphasis** and ***stronger emphasis*** Quotes: >>Lorem Ipsum Dolores<<:www.lipsum.com Cite: As ~~Frank Borland~~ said, ... And other: superscript m^2 vs. subscript O_2 ++inserted text++ vs. --deleted text-- Code: `
` with modifiers -------------- **strong and green .{color:green}** like Hulk Direct HTML tags ================ This is strong text. List ==== * Red * Green * Blue or - Red - Green - Blue ordered ------- 1. Bird 2. McHale 3. Parish 1) Bird 2) McHale 3) Parish a) Bird b) McHale c) Parish A) Bird B) McHale C) Parish I. Bird II. McHale III. Parish nested ------ a) Bird I. Bird - Red - Green - Blue II. McHale III. Parish b) McHale c) Parish 1) Bird 2) McHale 3) Parish definition list --------------- Concert Divoky Bill: - term: 9. 12. 2004 - place: Hala Vodova, Brno - price: 260 CZK and with modifiers: .{color:red} PHP: .{color:blue} - PHP: Hypertext Preprocessor .{color:green} - A scripting language - Personal Home Pages Modifiers ========= These types of modifiers are available: - (title) descriptive, used as attribute title or alt (for images) - [class1 class2 #id] determine class and/or ID - {class:blue} direct CSS style - horizontal alignment: - left < - right > - center <> - justify = - vertical alignment: (for tables) - top ^ - middle - - bottom _ Typography ============== - very long words division (with respect for language rules). Example: antidisestablishmentarianism - clickable emails and URL www.davidgrudl.com, david@grudl.com (emails are obfuscated against spambots) - "national" 'typographic' quotes - divider vs. dash: 10-15 vs. north-west - en-dash: one -- two - dimension sign 10 x 20 - nonbreakable spaces in phone numbers +420 776 552 046 - acronym NATO((North Atlantic Treaty Organisation)) - abbr "et al."((and others)) - quickCorrects like this(TM) this(R) or this(C) - arrows <-- and --> and <--> ; - ellipsis... - preserve HTML entities - and many others :-) Tables ====== | first col | second col | third col | Adam | Eva | Franta with header ----------- |----------------------------- | First Name | Last Name | Age |---------------------------- | Jesus | Christ | 33 | Cecilie | Svobodova | 74 or using * |* First Name | Jesus | Cecilie |* Last Name | Christ | Svobodova |* Age | 33 | 74 colspans -------- notice the double || |----------------------------- | Name || Age |---------------------------- | Jesus | Christ | 33 | Cecilie | Svobodova | 74 rowspans -------- |----------------------------- | First Name | Last Name | Age |---------------------------- | Bill | Gates | 50 |^ | Clinton | 52 | Jim | Beam | 70 modifiers --------- .{color:red} | first col | second col .>| third col | .{font-style:italic} | Adam | Eva .{color: blue}| Franta | Copyright ========= Texy! (C) "David Grudl":http://davidgrudl.com, 2004-2009