/** * 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 ($matches = TexyRegexp::match($cmd, '#^([a-z_][a-z0-9_-]*)\\s*(?:\\(([^()]*)\\)|:(.*))$#iu')) { $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)); }
/** * @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 $mod = NULL; if ($mx = TexyRegexp::match($s, '#' . TexyPatterns::MODIFIER_H . '(?=\\n|\\z)#sUm', TexyRegexp::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); } } }
/** * @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 = 0; if ($arrOffset[$name] === -2) { do { $delta++; } while (isset($text[$offset + $delta]) && $text[$offset + $delta] >= "€" && $text[$offset + $delta] < "À"); } if ($offset + $delta > strlen($text)) { unset($names[$index]); continue; } elseif ($arrMatches[$name] = TexyRegexp::match($text, $pl[$name]['pattern'], TexyRegexp::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'] || !TexyRegexp::match($text, $pl[$name]['again'], 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); }
/** * 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; }
/** * 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 ($matches = TexyRegexp::match($content[0], '#^(.*) (\\d+|\\?) *(X|x) *(\\d+|\\?) *()$#U')) { $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; }
/** * 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; }