/** * 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 = TexyRegexp::replace($text, '#^\\[\\*([^\\n]{1,100})\\*\\]:\\ +(.{1,1000})\\ *' . TexyPatterns::MODIFIER . '?\\s*()$#mUu', array($this, 'patternReferenceDef')); } }
public function postLine($text, $preserveSpaces = FALSE) { if (!$preserveSpaces) { $text = TexyRegexp::replace($text, '# {2,}#', ' '); } return TexyRegexp::replace($text, $this->pattern); }
/** * 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 = TexyRegexp::replace($text, '#^\\[([^\\[\\]\\#\\?\\*\\n]{1,100})\\]: ++(\\S{1,1000})(\\ .{1,1000})?' . TexyPatterns::MODIFIER . '?\\s*()$#mUu', array($this, 'patternReferenceDef')); } }
/** * 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 = TexyRegexp::replace($content, '#\\n +(?=\\S)#', "\r"); } else { $content = TexyRegexp::replace($content, '#\\n#', "\r"); } $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 <br /> if (strpos($content, "\r") !== FALSE) { $key = $tx->protect('<br />', Texy::CONTENT_REPLACED); $content = str_replace("\r", $key, $content); } } $content = strtr($content, "\r\n", ' '); $el->setText($content); return $el; }
/** * Single block pre-processing. * @param TexyBlockParser * @param string * @return void */ public function beforeBlockParse($parser, &$text) { // autoclose exclusive blocks $text = TexyRegexp::replace($text, '#^(/--++ *+(?!div|texysource).*)$((?:\\n.*+)*?)(?:\\n\\\\--.*$|(?=(\\n/--.*$)))#mi', "\$1\$2\n\\--"); }
/** * Finish invocation. * * @param TexyHandlerInvocation handler invocation * @param string * @return string */ public function solveComment($invocation, $content) { if (!$this->passComment) { return ''; } // sanitize comment $content = TexyRegexp::replace($content, '#-{2,}#', ' - '); $content = trim($content, '-'); return $this->texy->protect('<!--' . $content . '-->', Texy::CONTENT_MARKUP); }
public function postLine($text) { return TexyRegexp::replace($text, '#[^\\ \\n\\t\\x14\\x15\\x16\\x{2013}\\x{2014}\\x{ad}-]{' . $this->wordLimit . ',}#u', array($this, 'pattern')); }
/** * 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('\\|', "", $mContent); $mContent = TexyRegexp::replace($mContent, '#(\\[[^\\]]*)\\|#', "\$1"); // HACK: support for [..|..] foreach (explode('|', $mContent) as $cell) { $cell = strtr($cell, "", '|'); // rowSpan if (isset($prevRow[$col]) && ($lineMode || ($matches = TexyRegexp::match($cell, '#\\^\\ *$|\\*??(.*)\\ +\\^$#AU')))) { $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 $matches = TexyRegexp::match($cell, '#(\\*??)\\ *' . TexyPatterns::MODIFIER_HV . '??(.*)' . TexyPatterns::MODIFIER_HV . '?\\ *()$#AU'); if (!$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; }
/** * Outdents text block. * @param string * @return string */ public static final function outdent($s) { $s = trim($s, "\n"); $min = strlen($s); foreach (TexyRegexp::match($s, '#^ *\\S#m', TexyRegexp::ALL) as $m) { $min = min($min, strlen($m[0]) - 1); } if ($min) { $s = TexyRegexp::replace($s, "#^ {{$min}}#m", ''); } return $s; }
/** * Callback function: <tag> | </tag> | .... * @return string * @internal */ public 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 (array_intersect(array_keys($this->tagUsed, TRUE), $this->preserveSpaces)) { // inside pre & textarea preserve spaces $s = Texy::freezeSpaces($mText); } else { $s = TexyRegexp::replace($mText, '#[ \\n]+#', ' '); // 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 && !array_intersect(array_keys($this->tagUsed, TRUE), $this->preserveSpaces); if ($indent && $mTag === 'br') { // formatting exception return rtrim($s) . '<' . $mTag . $mAttr . ">\n" . str_repeat("\t", max(0, $this->space - 1)) . ""; } 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 = "" . '</' . $mTag . '>' . "\n" . str_repeat("\t", $this->space); $s .= "\n" . str_repeat("\t", $this->space++) . $open . ""; $indent = 1; } else { $close = '</' . $mTag . '>'; $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; }
/** * Outdents text block. * @param string * @return string */ public static final function outdent($s) { $s = trim($s, "\n"); $spaces = strspn($s, ' '); if ($spaces) { return TexyRegexp::replace($s, "#^ {1,{$spaces}}#m", ''); } return $s; }