/** * Static factory. * @param string element name (or NULL) * @param array|string element's attributes (or textual content) * @return Html */ public static function el($name = NULL, $attrs = NULL) { $el = new static(); $parts = explode(' ', $name, 2); $el->setName($parts[0]); if (is_array($attrs)) { $el->attrs = $attrs; } elseif ($attrs !== NULL) { $el->setText($attrs); } if (isset($parts[1])) { foreach (Nette\String::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\\2|\\s))?#i') as $m) { $el->attrs[$m[1]] = isset($m[3]) ? $m[3] : TRUE; } } return $el; }
/** * Process <texy>...</texy> elements. * @param string * @return string */ public static function texyElements($s) { return String::replace($s, '#<texy([^>]*)>(.*?)</texy>#s', function ($m) { list(, $mAttrs, $mContent) = $m; // parse attributes $attrs = array(); if ($mAttrs) { foreach (String::matchAll($mAttrs, '#([a-z0-9:-]+)\\s*(?:=\\s*(\'[^\']*\'|"[^"]*"|[^\'"\\s]+))?()#isu') as $m) { $key = strtolower($m[1]); $val = $m[2]; if ($val == NULL) { $attrs[$key] = TRUE; } elseif ($val[0] === '\'' || $val[0] === '"') { $attrs[$key] = html_entity_decode(substr($val, 1, -1), ENT_QUOTES, 'UTF-8'); } else { $attrs[$key] = html_entity_decode($val, ENT_QUOTES, 'UTF-8'); } } } return TemplateFilters::$texy->process($m[2]); }); }
function tokenize($input) { $this->input = $input; if ($this->names) { $this->tokens = String::matchAll($input, $this->re); $len = 0; foreach ($this->tokens as & $match) { $name = NULL; for ($i = 1; $i < count($this->names); $i++) { if (!isset($match[$i])) { break; } elseif ($match[$i] != NULL) { $name = $this->names[$i - 1]; break; } } $match = array($match[0], $name); $len += strlen($match[0]); } if ($len !== strlen($input)) { $errorOffset = $len; } } else { $this->tokens = String::split($input, $this->re, PREG_SPLIT_NO_EMPTY); if ($this->tokens && !String::match(end($this->tokens), $this->re)) { $tmp = String::split($this->input, $this->re, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); list(, $errorOffset) = end($tmp); } } if (isset($errorOffset)) { $line = $errorOffset ? substr_count($this->input, "\n", 0, $errorOffset) + 1 : 1; $col = $errorOffset - strrpos(substr($this->input, 0, $errorOffset), "\n") + 1; $token = str_replace("\n", '\n', substr($input, $errorOffset, 10)); throw new TokenizerException("Unexpected '$token' on line $line, column $col."); } return $this; }
/** * Parses phpDoc comment. * @param string * @return array */ private static function parseComment($comment) { static $tokens = array('true' => TRUE, 'false' => FALSE, 'null' => NULL, '' => TRUE); $matches = String::matchAll(trim($comment, '/*'), '~ (?<=\\s)@(' . self::RE_IDENTIFIER . ')[ \\t]* ## annotation ( \\((?>' . self::RE_STRING . '|[^\'")@]+)+\\)| ## (value) [^(@\\r\\n][^@\\r\\n]*|) ## value ~xi'); $res = array(); foreach ($matches as $match) { list(, $name, $value) = $match; if (substr($value, 0, 1) === '(') { $items = array(); $key = ''; $val = TRUE; $value[0] = ','; while ($m = String::match($value, '#\\s*,\\s*(?>(' . self::RE_IDENTIFIER . ')\\s*=\\s*)?(' . self::RE_STRING . '|[^\'"),\\s][^\'"),]*)#A')) { $value = substr($value, strlen($m[0])); list(, $key, $val) = $m; if ($val[0] === "'" || $val[0] === '"') { $val = substr($val, 1, -1); } elseif (is_numeric($val)) { $val = 1 * $val; } else { $lval = strtolower($val); $val = array_key_exists($lval, $tokens) ? $tokens[$lval] : $val; } if ($key === '') { $items[] = $val; } else { $items[$key] = $val; } } $value = count($items) < 2 && $key === '' ? $val : $items; } else { $value = trim($value); if (is_numeric($value)) { $value = 1 * $value; } else { $lval = strtolower($value); $value = array_key_exists($lval, $tokens) ? $tokens[$lval] : $value; } } $class = $name . 'Annotation'; if (class_exists($class)) { $res[$name][] = new $class(is_array($value) ? $value : array('value' => $value)); } else { $res[$name][] = is_array($value) ? new \ArrayObject($value, \ArrayObject::ARRAY_AS_PROPS) : $value; } } return $res; }
/** * Parse mask and array of default values; initializes object. * @param string * @param array * @return void */ private function setMask($mask, array $metadata) { $this->mask = $mask; // detect '//host/path' vs. '/abs. path' vs. 'relative path' if (substr($mask, 0, 2) === '//') { $this->type = self::HOST; } elseif (substr($mask, 0, 1) === '/') { $this->type = self::PATH; } else { $this->type = self::RELATIVE; } foreach ($metadata as $name => $meta) { if (!is_array($meta)) { $metadata[$name] = array(self::VALUE => $meta, 'fixity' => self::CONSTANT); } elseif (array_key_exists(self::VALUE, $meta)) { $metadata[$name]['fixity'] = self::CONSTANT; } } // PARSE MASK $parts = String::split($mask, '/<([^># ]+) *([^>#]*)(#?[^>\\[\\]]*)>|(\\[!?|\\]|\\s*\\?.*)/'); // <parameter-name [pattern] [#class]> or [ or ] or ?... $this->xlat = array(); $i = count($parts) - 1; // PARSE QUERY PART OF MASK if (isset($parts[$i - 1]) && substr(ltrim($parts[$i - 1]), 0, 1) === '?') { $matches = String::matchAll($parts[$i - 1], '/(?:([a-zA-Z0-9_.-]+)=)?<([^># ]+) *([^>#]*)(#?[^>]*)>/'); // name=<parameter-name [pattern][#class]> foreach ($matches as $match) { list(, $param, $name, $pattern, $class) = $match; // $pattern is not used if ($class !== '') { if (!isset(self::$styles[$class])) { throw new \InvalidStateException("Parameter '{$name}' has '{$class}' flag, but Route::\$styles['{$class}'] is not set."); } $meta = self::$styles[$class]; } elseif (isset(self::$styles['?' . $name])) { $meta = self::$styles['?' . $name]; } else { $meta = self::$styles['?#']; } if (isset($metadata[$name])) { $meta = $metadata[$name] + $meta; } if (array_key_exists(self::VALUE, $meta)) { $meta['fixity'] = self::OPTIONAL; } unset($meta['pattern']); $meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? NULL : array_flip($meta[self::FILTER_TABLE]); $metadata[$name] = $meta; if ($param !== '') { $this->xlat[$name] = $param; } } $i -= 5; } $brackets = 0; // optional level $re = ''; $sequence = array(); $autoOptional = array(0, 0); // strlen($re), count($sequence) do { array_unshift($sequence, $parts[$i]); $re = preg_quote($parts[$i], '#') . $re; if ($i === 0) { break; } $i--; $part = $parts[$i]; // [ or ] if ($part === '[' || $part === ']' || $part === '[!') { $brackets += $part[0] === '[' ? -1 : 1; if ($brackets < 0) { throw new \InvalidArgumentException("Unexpected '{$part}' in mask '{$mask}'."); } array_unshift($sequence, $part); $re = ($part[0] === '[' ? '(?:' : ')?') . $re; $i -= 4; continue; } $class = $parts[$i]; $i--; // validation class $pattern = trim($parts[$i]); $i--; // validation condition (as regexp) $name = $parts[$i]; $i--; // parameter name array_unshift($sequence, $name); if ($name[0] === '?') { // "foo" parameter $re = '(?:' . preg_quote(substr($name, 1), '#') . '|' . $pattern . ')' . $re; $sequence[1] = substr($name, 1) . $sequence[1]; continue; } // check name (limitation by regexp) if (preg_match('#[^a-z0-9_-]#i', $name)) { throw new \InvalidArgumentException("Parameter name must be alphanumeric string due to limitations of PCRE, '{$name}' given."); } // pattern, condition & metadata if ($class !== '') { if (!isset(self::$styles[$class])) { throw new \InvalidStateException("Parameter '{$name}' has '{$class}' flag, but Route::\$styles['{$class}'] is not set."); } $meta = self::$styles[$class]; } elseif (isset(self::$styles[$name])) { $meta = self::$styles[$name]; } else { $meta = self::$styles['#']; } if (isset($metadata[$name])) { $meta = $metadata[$name] + $meta; } if ($pattern == '' && isset($meta[self::PATTERN])) { $pattern = $meta[self::PATTERN]; } $meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? NULL : array_flip($meta[self::FILTER_TABLE]); if (array_key_exists(self::VALUE, $meta)) { if (isset($meta['filterTable2'][$meta[self::VALUE]])) { $meta['defOut'] = $meta['filterTable2'][$meta[self::VALUE]]; } elseif (isset($meta[self::FILTER_OUT])) { $meta['defOut'] = call_user_func($meta[self::FILTER_OUT], $meta[self::VALUE]); } else { $meta['defOut'] = $meta[self::VALUE]; } } $meta[self::PATTERN] = "#(?:{$pattern})\$#A" . ($this->flags & self::CASE_SENSITIVE ? '' : 'iu'); // include in expression $re = '(?P<' . str_replace('-', '___', $name) . '>' . $pattern . ')' . $re; // str_replace is dirty trick to enable '-' in parameter name if ($brackets) { // is in brackets? if (!isset($meta[self::VALUE])) { $meta[self::VALUE] = $meta['defOut'] = NULL; } $meta['fixity'] = self::PATH_OPTIONAL; } elseif (isset($meta['fixity'])) { // auto-optional $re = '(?:' . substr_replace($re, ')?', strlen($re) - $autoOptional[0], 0); array_splice($sequence, count($sequence) - $autoOptional[1], 0, array(']', '')); array_unshift($sequence, '[', ''); $meta['fixity'] = self::PATH_OPTIONAL; } else { $autoOptional = array(strlen($re), count($sequence)); } $metadata[$name] = $meta; } while (TRUE); if ($brackets) { throw new \InvalidArgumentException("Missing closing ']' in mask '{$mask}'."); } $this->re = '#' . $re . '/?$#A' . ($this->flags & self::CASE_SENSITIVE ? '' : 'iu'); $this->metadata = $metadata; $this->sequence = $sequence; }
/** * Builds HTML content. * @return void */ protected function buildHtml() { if ($this->html instanceof Nette\Templates\ITemplate) { $this->html->mail = $this; if ($this->basePath === NULL && $this->html instanceof Nette\Templates\IFileTemplate) { $this->basePath = dirname($this->html->getFile()); } $this->html = $this->html->__toString(TRUE); } if ($this->basePath !== FALSE) { $cids = array(); $matches = String::matchAll($this->html, '#(src\s*=\s*|background\s*=\s*|url\()(["\'])(?![a-z]+:|[/\\#])(.+?)\\2#i', PREG_OFFSET_CAPTURE); foreach (array_reverse($matches) as $m) { $file = rtrim($this->basePath, '/\\') . '/' . $m[3][0]; $cid = isset($cids[$file]) ? $cids[$file] : $cids[$file] = substr($this->addEmbeddedFile($file)->getHeader("Content-ID"), 1, -1); $this->html = substr_replace($this->html, "{$m[1][0]}{$m[2][0]}cid:$cid{$m[2][0]}", $m[0][1], strlen($m[0][0])); } } if (!$this->getSubject() && $matches = String::match($this->html, '#<title>(.+?)</title>#is')) { $this->setSubject(html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8')); } }
/** * Applies modifiers. * @param string * @param string * @return string */ public static function formatModifiers($var, $modifiers) { if (!$modifiers) { return $var; } $tokens = String::matchAll($modifiers . '|', '~ ' . self::RE_STRING . '| ## single or double quoted string [^\'"|:,\\s]+| ## symbol [|:,] ## separator ~xs', PREG_PATTERN_ORDER); $inside = FALSE; $prev = ''; foreach ($tokens[0] as $token) { if ($token === '|' || $token === ':' || $token === ',') { if ($prev === '') { } elseif (!$inside) { if (!String::match($prev, '#^' . self::RE_IDENTIFIER . '$#')) { throw new \InvalidStateException("Modifier name must be alphanumeric string, '{$prev}' given."); } $var = "\$template->{$prev}({$var}"; $prev = ''; $inside = TRUE; } else { $var .= ', ' . self::formatString($prev); $prev = ''; } if ($token === '|' && $inside) { $var .= ')'; $inside = FALSE; } } else { $prev .= $token; } } return $var; }