private function setMask($mask, array $metadata)
{
$this->mask = $mask;
// detect '//host/path' vs. '/abs. path' vs. 'relative path'
if (preg_match('#(?:(https?):)?(//.*)#A', $mask, $m)) {
$this->type = self::HOST;
list(, $this->scheme, $mask) = $m;
} 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] = $meta = [self::VALUE => $meta];
}
if (array_key_exists(self::VALUE, $meta)) {
if (is_scalar($meta[self::VALUE])) {
$metadata[$name][self::VALUE] = (string) $meta[self::VALUE];
}
$metadata[$name]['fixity'] = self::CONSTANT;
}
}
if (strpbrk($mask, '?<>[]') === FALSE) {
$this->re = '#' . preg_quote($mask, '#') . '/?\\z#A';
$this->sequence = [$mask];
$this->metadata = $metadata;
return;
}
// PARSE MASK
// <parameter-name[=default] [pattern]> or [ or ] or ?...
$parts = Strings::split($mask, '/<([^<>= ]+)(=[^<> ]*)? *([^<>]*)>|(\\[!?|\\]|\\s*\\?.*)/');
$this->xlat = [];
$i = count($parts) - 1;
// PARSE QUERY PART OF MASK
if (isset($parts[$i - 1]) && substr(ltrim($parts[$i - 1]), 0, 1) === '?') {
// name=<parameter-name [pattern]>
$matches = Strings::matchAll($parts[$i - 1], '/(?:([a-zA-Z0-9_.-]+)=)?<([^> ]+) *([^>]*)>/');
foreach ($matches as list(, $param, $name, $pattern)) {
// $pattern is not used
if (isset(static::$styles['?' . $name])) {
$meta = static::$styles['?' . $name];
} else {
$meta = static::$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;
}
// PARSE PATH PART OF MASK
$brackets = 0;
// optional level
$re = '';
$sequence = [];
$autoOptional = TRUE;
$aliases = [];
do {
$part = $parts[$i];
// part of path
if (strpbrk($part, '<>') !== FALSE) {
throw new Nette\InvalidArgumentException("Unexpected '{$part}' in mask '{$mask}'.");
}
array_unshift($sequence, $part);
$re = preg_quote($part, '#') . $re;
if ($i === 0) {
break;
}
$i--;
$part = $parts[$i];
// [ or ]
if ($part === '[' || $part === ']' || $part === '[!') {
$brackets += $part[0] === '[' ? -1 : 1;
if ($brackets < 0) {
throw new Nette\InvalidArgumentException("Unexpected '{$part}' in mask '{$mask}'.");
}
array_unshift($sequence, $part);
$re = ($part[0] === '[' ? '(?:' : ')?') . $re;
$i -= 4;
continue;
}
$pattern = trim($parts[$i]);
$i--;
// validation condition (as regexp)
$default = $parts[$i];
$i--;
// default value
$name = $parts[$i];
$i--;
// parameter name
array_unshift($sequence, $name);
if ($name[0] === '?') {
// "foo" parameter
$name = substr($name, 1);
$re = $pattern ? '(?:' . preg_quote($name, '#') . "|{$pattern}){$re}" : preg_quote($name, '#') . $re;
$sequence[1] = $name . $sequence[1];
continue;
}
// pattern, condition & metadata
if (isset(static::$styles[$name])) {
$meta = static::$styles[$name];
} else {
$meta = static::$styles['#'];
}
if (isset($metadata[$name])) {
$meta = $metadata[$name] + $meta;
}
if ($pattern == '' && isset($meta[self::PATTERN])) {
$pattern = $meta[self::PATTERN];
}
if ($default !== '') {
$meta[self::VALUE] = (string) substr($default, 1);
$meta['fixity'] = self::PATH_OPTIONAL;
}
$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})\\z#A";
// include in expression
$aliases['p' . $i] = $name;
$re = '(?P<p' . $i . '>(?U)' . $pattern . ')' . $re;
if ($brackets) {
// is in brackets?
if (!isset($meta[self::VALUE])) {
$meta[self::VALUE] = $meta['defOut'] = NULL;
}
$meta['fixity'] = self::PATH_OPTIONAL;
} elseif (!$autoOptional) {
unset($meta['fixity']);
} elseif (isset($meta['fixity'])) {
// auto-optional
$re = '(?:' . $re . ')?';
$meta['fixity'] = self::PATH_OPTIONAL;
} else {
$autoOptional = FALSE;
}
$metadata[$name] = $meta;
} while (TRUE);
if ($brackets) {
throw new Nette\InvalidArgumentException("Missing '[' in mask '{$mask}'.");
}
$this->aliases = $aliases;
$this->re = '#' . $re . '/?\\z#A';
$this->metadata = $metadata;
$this->sequence = $sequence;
}