Example #1
0
 /**
  * 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));
 }
 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;
 }
Example #5
0
 /**
  * @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);
 }
Example #6
0
 /**
  * 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;
 }
 /**
  * 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;
 }
Example #11
0
 /**
  * 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;
 }
Example #12
0
 /**
  * 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;
 }