/** * Static factory. * @param string element name (or NULL) * @param array|string element's attributes or plain text content * @return self */ 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 (Strings::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\\2|\\s))?#i') as $m) { $el->attrs[$m[1]] = isset($m[3]) ? $m[3] : TRUE; } } return $el; }
/** * Parse search query. * * @param string * @return array associative array with keys 'query' and 'tags' */ public function parseQuery($input) { // normalize input $input = Strings::lower(Strings::trim($input)); $input = Strings::replace($input, '/\\s+/', ' '); // extract tags $matches = Strings::matchAll($input, '/(?<=^|\\s)tag:\\s*(?<tag_list>[\\pL\\d_-]+(?:\\s*,\\s*(?&tag_list))?)/u'); $tags = array(); $query = $input; foreach ($matches as $m) { $tmp = Strings::split($m['tag_list'], '/\\s*,\\s*/'); $tmp = array_map('Nette\\Utils\\Strings::webalize', $tmp); $tmp = array_unique($tmp); $tags = array_merge($tags, $tmp); $query = str_replace($m[0], '', $query); } $query = Strings::trim($query) ?: null; return array('query' => $query, 'tags' => $tags); }
public function proccessTweetForm($form, array $values) { $text = $values['tweet']; $matches = Strings::matchAll($text, '~#([a-z0-9-]++)~'); $tags = array_column($matches, 1); $user = $this->users->getById($this->userId); $tweet = new Model\Tweet(); $tweet->user = $user; $tweet->text = $values['tweet']; $existingTags = $this->tags->findBy(['name' => $tags])->fetchPairs('name'); $newTags = array_diff($tags, array_keys($existingTags)); foreach ($newTags as $tag) { $tweet->tags->add(new Model\Tag($tag)); } foreach ($existingTags as $tag) { $tweet->tags->add($tag); } $this->tweets->persist($tweet); $this->tweets->flush(); $this->redirect('this'); }
/** * Tokenize string. * @param string * @return array */ public function tokenize($input) { $this->input = $input; if ($this->types) { $this->tokens = Strings::matchAll($input, $this->re); $len = 0; $count = count($this->types); $line = 1; foreach ($this->tokens as &$match) { $type = NULL; for ($i = 1; $i <= $count; $i++) { if (!isset($match[$i])) { break; } elseif ($match[$i] != NULL) { $type = $this->types[$i - 1]; break; } } $match = self::createToken($match[0], $type, $line); $len += strlen($match['value']); $line += substr_count($match['value'], "\n"); } if ($len !== strlen($input)) { $errorOffset = $len; } } else { $this->tokens = Strings::split($input, $this->re, PREG_SPLIT_NO_EMPTY); if ($this->tokens && !Strings::match(end($this->tokens), $this->re)) { $tmp = Strings::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->tokens; }
/** * Sets HTML body. * @param string * @param mixed base-path or FALSE to disable parsing * @return MandrillMessage provides a fluent interface */ public function setHtmlBody($html, $basePath = NULL) { if ($html instanceof ITemplate) { //$html->mail = $this; if ($basePath === NULL && $html instanceof IFileTemplate) { $basePath = dirname($html->getFile()); } $html = $html->__toString(TRUE); } if ($basePath !== FALSE) { $cids = array(); $matches = Strings::matchAll($html, '#(src\\s*=\\s*|background\\s*=\\s*|url\\()(["\'])(?![a-z]+:|[/\\#])(.+?)\\2#i', PREG_OFFSET_CAPTURE); foreach (array_reverse($matches) as $m) { $file = rtrim($basePath, '/\\') . '/' . $m[3][0]; if (!isset($cids[$file])) { $cids[$file] = substr($this->addEmbeddedFile($file)->getHeader("Content-ID"), 1, -1); } $html = substr_replace($html, "{$m[1][0]}{$m[2][0]}cid:{$cids[$file]}{$m[2][0]}", $m[0][1], strlen($m[0][0])); } } $this->mandrillMessage['html'] = $html; return $this; }
/** * Tokenize string. * @param string * @return array */ public function tokenize($input) { if ($this->types) { $tokens = Strings::matchAll($input, $this->re); $len = 0; $count = count($this->types); foreach ($tokens as &$match) { $type = NULL; for ($i = 1; $i <= $count; $i++) { if (!isset($match[$i])) { break; } elseif ($match[$i] != NULL) { $type = $this->types[$i - 1]; break; } } $match = array(self::VALUE => $match[0], self::OFFSET => $len, self::TYPE => $type); $len += strlen($match[self::VALUE]); } if ($len !== strlen($input)) { $errorOffset = $len; } } else { $tokens = Strings::split($input, $this->re, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); $last = end($tokens); if ($tokens && !Strings::match($last[0], $this->re)) { $errorOffset = $last[1]; } } if (isset($errorOffset)) { list($line, $col) = $this->getCoordinates($input, $errorOffset); $token = str_replace("\n", '\\n', substr($input, $errorOffset, 10)); throw new TokenizerException("Unexpected '{$token}' on line {$line}, column {$col}."); } return $tokens; }
/** * @param Table $table */ protected function analyseColumns(Table $table) { $tableName = $table->getName(); // Analyse columns $columns = $this->driver->getColumns($tableName); foreach ($columns as $key => $col) { $column = new Column(); $column->setName($col['name']); $column->setNullable($col['nullable']); $column->setType(Helpers::columnType($col['nativetype'])); $column->setDefault($col['default']); $column->setOnUpdate(Strings::contains($col['vendor']['Extra'], 'on update')); // Analyse ENUM if ($col['nativetype'] === ColumnTypes::NATIVE_TYPE_ENUM) { $enum = Strings::matchAll($col['vendor']['Type'], ColumnTypes::NATIVE_REGEX_ENUM, PREG_PATTERN_ORDER); if ($enum) { $column->setEnum($enum[1]); $column->setType(ColumnTypes::TYPE_ENUM); $column->setSubType(Helpers::columnType($col['nativetype'])); } } $table->addColumn($column); } }
/** * Sets HTML body. * @param string * @param mixed base-path * @return self */ public function setHtmlBody($html, $basePath = NULL) { if ($basePath === NULL && ($html instanceof Nette\Templating\IFileTemplate || $html instanceof Nette\Application\UI\ITemplate)) { $basePath = dirname($html->getFile()); $bc = TRUE; } $html = (string) $html; if ($basePath) { $cids = array(); $matches = Strings::matchAll($html, '#(src\\s*=\\s*|background\\s*=\\s*|url\\()(["\']?)(?![a-z]+:|[/\\#])([^"\')\\s]+)#i', PREG_OFFSET_CAPTURE); if ($matches && isset($bc)) { trigger_error(__METHOD__ . '() missing second argument with image base path.', E_USER_WARNING); } foreach (array_reverse($matches) as $m) { $file = rtrim($basePath, '/\\') . '/' . urldecode($m[3][0]); if (!isset($cids[$file])) { $cids[$file] = substr($this->addEmbeddedFile($file)->getHeader('Content-ID'), 1, -1); } $html = substr_replace($html, "{$m[1][0]}{$m[2][0]}cid:{$cids[$file]}", $m[0][1], strlen($m[0][0])); } } if ($this->getSubject() == NULL) { // intentionally == $html = Strings::replace($html, '#<title>(.+?)</title>#is', function ($m) use(&$title) { $title = $m[1]; }); $this->setSubject(html_entity_decode($title, ENT_QUOTES, 'UTF-8')); } $this->html = ltrim(str_replace("\r", '', $html), "\n"); if ($this->getBody() == NULL && $html != NULL) { // intentionally == $this->setBody($this->buildText($html)); } return $this; }
/** * Sets HTML body. * @param string|Nette\Templating\ITemplate * @param mixed base-path or FALSE to disable parsing * @return self */ public function setHtmlBody($html, $basePath = NULL) { if ($html instanceof Nette\Templating\ITemplate || $html instanceof Nette\Application\UI\ITemplate) { $html->mail = $this; if ($basePath === NULL && ($html instanceof Nette\Templating\IFileTemplate || $html instanceof Nette\Application\UI\ITemplate)) { $basePath = dirname($html->getFile()); } $html = $html->__toString(TRUE); } if ($basePath !== FALSE) { $cids = array(); $matches = Strings::matchAll($html, '#(src\\s*=\\s*|background\\s*=\\s*|url\\()(["\']?)(?![a-z]+:|[/\\#])([^"\')\\s]+)#i', PREG_OFFSET_CAPTURE); foreach (array_reverse($matches) as $m) { $file = rtrim($basePath, '/\\') . '/' . $m[3][0]; if (!isset($cids[$file])) { $cids[$file] = substr($this->addEmbeddedFile($file)->getHeader('Content-ID'), 1, -1); } $html = substr_replace($html, "{$m[1][0]}{$m[2][0]}cid:{$cids[$file]}", $m[0][1], strlen($m[0][0])); } } $this->html = $html; if ($this->getSubject() == NULL && ($matches = Strings::match($html, '#<title>(.+?)</title>#is'))) { // intentionally == $this->setSubject(html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8')); } if ($this->getBody() == NULL && $html != NULL) { // intentionally == $this->setBody($this->buildText($html)); } return $this; }
/** * Logging navigation requested * * @param string $requestLine */ private function logRequest($requestLine) { $requestLine = Strings::replace($requestLine, '[Navigation requested: ]'); $requestLine = Strings::trim($requestLine); $matches = Strings::matchAll($requestLine, '~ ([^=]+)=([^,]+),?~'); $request = []; foreach ($matches as $match) { $match[2] = $match[2] === 'true' ? true : ($match[2] === 'false' ? false : $match[2]); $request[$match[1]] = $match[2]; } $this->requests[] = $request; }
/** * Builds HTML content. * @return void */ protected function buildHtml() { if ($this->html instanceof Nette\Templating\ITemplate) { $this->html->mail = $this; if ($this->basePath === NULL && $this->html instanceof Nette\Templating\IFileTemplate) { $this->basePath = dirname($this->html->getFile()); } $this->html = $this->html->__toString(TRUE); } if ($this->basePath !== FALSE) { $cids = array(); $matches = Strings::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]; if (!isset($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:{$cids[$file]}{$m[2][0]}", $m[0][1], strlen($m[0][0])); } } if (!$this->getSubject() && ($matches = Strings::match($this->html, '#<title>(.+?)</title>#is'))) { $this->setSubject(html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8')); } }
public static function matchAll($subject, $pattern, $flags = 0, $offset = 0) { return Nette\Utils\Strings::matchAll($subject, $pattern, $flags, $offset); }
/** * 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 (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; }
/** * @param \SplFileInfo $file */ private function processPresenter($file) { $stream = fopen("safe://" . $file->getRealPath(), 'r'); $content = fread($stream, filesize("safe://" . $file->getRealPath())); fclose($stream); $module = String::match($content, '~(^|;)\\s*namespace (?P<name>[A-z0-9_-]+)Module;~m'); $module = $module['name']; $presenter = String::match($content, '~(^|;)\\s*class (?P<name>[A-z0-9_-]+)Presenter(\\s|$)~m'); if ($presenter === NULL || $presenter['name'] === 'Error') { return FALSE; } $presenter = $presenter['name']; $actions = array(); foreach (String::matchAll($content, '~function (action|render)(?P<name>[A-z0-9_-]+)(\\s|\\()~') as $action) { $action = lcfirst($action['name']); if (array_search($action, $actions) === FALSE) { $actions[] = $action; } } return array($module, $presenter, $actions); }
/** * Parses phpDoc comment. * @param string * @return array */ private static function parseComment($comment) { static $tokens = array('true' => TRUE, 'false' => FALSE, 'null' => NULL, '' => TRUE); $res = array(); $comment = preg_replace('#^\\s*\\*\\s?#ms', '', trim($comment, '/*')); $parts = preg_split('#^\\s*(?=@' . self::RE_IDENTIFIER . ')#m', $comment, 2); $description = trim($parts[0]); if ($description !== '') { $res['description'] = array($description); } $matches = Strings::matchAll(isset($parts[1]) ? $parts[1] : '', '~ (?<=\\s|^)@(' . self::RE_IDENTIFIER . ')[ \\t]* ## annotation ( \\((?>' . self::RE_STRING . '|[^\'")@]+)+\\)| ## (value) [^(@\\r\\n][^@\\r\\n]*|) ## value ~xi'); foreach ($matches as $match) { list(, $name, $value) = $match; if (substr($value, 0, 1) === '(') { $items = array(); $key = ''; $val = TRUE; $value[0] = ','; while ($m = Strings::match($value, '#\\s*,\\s*(?>(' . self::RE_IDENTIFIER . ')\\s*=\\s*)?(' . self::RE_STRING . '|[^\'"),\\s][^\'"),]*)#A')) { $value = substr($value, strlen($m[0])); list(, $key, $val) = $m; $val = rtrim($val); 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 (self::$useAnnotationClasses && class_exists($class)) { $res[$name][] = new $class(is_array($value) ? $value : array('value' => $value)); } else { $res[$name][] = is_array($value) ? Nette\ArrayHash::from($value) : $value; } } return $res; }
/** * Sets HTML body. * @param string * @param mixed base-path * @return self */ public function setHtmlBody($html, $basePath = NULL) { $html = (string) $html; if ($basePath) { $cids = []; $matches = Strings::matchAll($html, '# (<img[^<>]*\\s src\\s*=\\s* |<body[^<>]*\\s background\\s*=\\s* |<[^<>]+\\s style\\s*=\\s* ["\'][^"\'>]+[:\\s] url\\( |<style[^>]*>[^<]+ [:\\s] url\\() (["\']?)(?![a-z]+:|[/\\#])([^"\'>)\\s]+) |\\[\\[ ([\\w()+./@~-]+) \\]\\] #ix', PREG_OFFSET_CAPTURE); foreach (array_reverse($matches) as $m) { $file = rtrim($basePath, '/\\') . '/' . (isset($m[4]) ? $m[4][0] : urldecode($m[3][0])); if (!isset($cids[$file])) { $cids[$file] = substr($this->addEmbeddedFile($file)->getHeader('Content-ID'), 1, -1); } $html = substr_replace($html, "{$m[1][0]}{$m[2][0]}cid:{$cids[$file]}", $m[0][1], strlen($m[0][0])); } } if ($this->getSubject() == NULL) { // intentionally == $html = Strings::replace($html, '#<title>(.+?)</title>#is', function ($m) use(&$title) { $title = $m[1]; }); $this->setSubject(html_entity_decode($title, ENT_QUOTES, 'UTF-8')); } $this->html = ltrim(str_replace("\r", '', $html), "\n"); if ($this->getBody() == NULL && $html != NULL) { // intentionally == $this->setBody($this->buildText($html)); } return $this; }
/** * Expands cookie header "Set-Cookie" * user_time=1327581075; expires=Sat, 25-Feb-2012 12:31:15 GMT; path=/ * to array * * @param string $cookie * * @return array|NULL */ public static function readCookie($cookie) { if (!($m = Strings::matchAll($cookie, '~(?P<name>[^;=\\s]+)(?:=(?P<value>[^;]*))?~i'))) { return NULL; } $first = array_shift($m); $cookie = array('name' => urldecode($first['name']), 'value' => urldecode($first['value'])); foreach ($m as $found) { $cookie[$found['name']] = !empty($found['value']) ? $found['value'] : TRUE; } return $cookie; }
/** * Converts raw TXT to internal CSV */ private function raw2csv() { $verifier = new Verifier(); $lines = preg_split('/[\\r\\n]+/', $this->_sourceData); foreach ($lines as $line) { // if it's not a blank line, and it's not the header row if ($line == '' || Strings::length($line) < 10 || Strings::startsWith($line, 'SEPA Country') || Strings::startsWith($line, 'Name of country')) { continue; } // assigned fields to named variables list($countryName, $countryCode, $domesticExample, $bban, $bbanStructure, $bbanLength, $bbanBiPosition, $bbanBiLength, $bbanBiExample, $bbanExample, $iban, $ibanStructure, $ibanLength, $ibanElectronicExample, $ibanPrintExample, $countrySepa, $contactDetails) = array_map(function ($item) { return trim(trim($item, '"'), ' '); }, explode("\t", $line)); // sanitise $countryCode = Strings::upper(substr($countryCode, 0, 2)); // sanitise comments away $bbanStructure = Strings::replace($bbanStructure, '/[:;]/'); // errors seen in Germany, Hungary entries $ibanStructure = Strings::replace($ibanStructure, '/, .*$/'); // duplicates for FO, GL seen in DK $ibanElectronicExample = Strings::replace($ibanElectronicExample, '/, .*$/'); // duplicates for FO, GL seen in DK switch ($countryCode) { case 'MU': $ibanElectronicExample = str_replace(' ', '', $ibanElectronicExample); // MU example has a spurious space break; case 'CZ': $ibanElectronicExample = Strings::replace($ibanElectronicExample, '/ \\{10,}+$/'); // extra example for CZ $ibanPrintExample = Strings::replace($ibanPrintExample, '/^(CZ.. .... .... .... .... ....).*$/', '$1'); // extra example break; case 'FI': // remove additional example $ibanElectronicExample = Strings::replace($ibanElectronicExample, '/ or .*$/'); // fix bban example to remove verbosity and match domestic example $bban = '12345600000785'; break; } $ibanPrintExample = Strings::replace($ibanPrintExample, '/, .*$/'); // calculate $bban_regex from $bban_structure $bbanRegex = $this->swift2regex($bbanStructure); // calculate $iban_regex from $iban_structure $ibanRegex = $this->swift2regex($ibanStructure); // calculate numeric $bban_length $bbanLength = Strings::replace($bbanLength, '/[^\\d]/'); // calculate numeric $iban_length $ibanLength = Strings::replace($ibanLength, '/[^\\d]/'); /* * calculate bban_bankid_<start|stop>_offset * .... First we have to parse the freetext $bban_bi_position, eg: * Bank Identifier 1-3, Branch Identifier * Position 1-2 * Positions 1-2 * Positions 1-3 * Positions 1-3 ;Branch is not available * Positions 1-3, Branch identifier * Positions 1-3, Branch identifier positions * Positions 1-4 * Positions 1-4, Branch identifier * Positions 1-4, Branch identifier positions * Positions 1-5 * Positions 1-5 (positions 1-2 bank identifier; positions 3-5 branch identifier). In case of payment institutions Positions 1-5, Branch identifier positions * Positions 1-6, Branch identifier positions * Positions 1-6. First two digits of bank identifier indicate the bank or banking group (For example, 1 or 2 for Nordea, 31 for Handelsbanken, 5 for cooperative banks etc) * Positions 1-7 * Positions 1-8 * Positions 2-6, Branch identifier positions * positions 1-3, Branch identifier positions * * ... our algorithm is as follows: * - find all <digit>-<digit> tokens */ $matches = Strings::matchAll($bbanBiPosition, '/(\\d)-(\\d\\d?)/', PREG_PATTERN_ORDER); // - discard overlaps ({1-5,1-2,3-5} becomes {1-2,3-5}) $tmptokens = []; for ($j = 0; $j < count($matches[0]); $j++) { $from = $matches[1][$j]; $to = $matches[2][$j]; // (if we don't yet have a match starting here, or it goes further, // overwrite the match-from-this-position record) if (!isset($tmptokens[$from]) || $to < $tmptokens[$from]) { $tmptokens[$from] = $to; } } unset($matches); // done // - assume the token starting from position 1 is the bank identifier // (or, if it does not exist, the token starting from position 2) $bbanBankidStartOffset = 0; // decrement 1 on assignment if (isset($tmptokens[1])) { $bbanBankidStopOffset = $tmptokens[1] - 1; // decrement 1 on assignment unset($tmptokens[1]); } else { $bbanBankidStopOffset = $tmptokens[2] - 1; // decrement 1 on assignment unset($tmptokens[2]); } // - assume any subsequent token, if present, is the branch identifier. $tmpkeys = array_keys($tmptokens); $start = array_shift($tmpkeys); unset($tmpkeys); //done $bbanBranchidStartOffset = $bbanBranchidStopOffset = ''; if ($start != '') { // we have a branch identifier! $bbanBranchidStartOffset = $start - 1; $bbanBranchidStopOffset = $tmptokens[$start] - 1; } else { /* * (note: this codepath occurs for around two thirds of all records) * we have not yet found a branch identifier. HOWEVER, we can analyse the * structure of the BBAN to determine whether there is more than one * remaining non-tiny field (tiny fields on the end of a BBAN typically * being checksums) and, if so, assume that the first/shorter one is the branch identifier. */ $reducedBbanStructure = Strings::replace($bbanStructure, '/^\\d+![nac]/'); $tokens = $this->swiftTokenize($reducedBbanStructure, TRUE); // discard any tokens of length 1 or 2 for ($t = 0; $t < count($tokens[0]); $t++) { $tokens['discarded'][$t] = $tokens[1][$t] < 3 ? 1 : 0; } // interesting fields are those that are not discarded... $interestingFieldCount = !isset($tokens['discarded']) ? count($tokens[0]) : count($tokens[0]) - count($tokens['discarded']); // ...if we have at least two of them, there's a branchid-type field if ($interestingFieldCount >= 2) { // now loop through until we assign the branchid start offset // (this occurs just after the first non-discarded field) $found = FALSE; for ($f = 0; !$found && $f < count($tokens[0]); $f++) { // if this is a non-discarded token, of >2 length... if ((!isset($tokens['discarded'][$f]) || $tokens['discarded'][$f] != 1) && $tokens[1][$f] > 2) { // ... then assign. $preOffset = $bbanBankidStopOffset + 1; // this is the offset before we reduced the structure to remove the bankid field $bbanBranchidStartOffset = $preOffset + $tokens['offset'][$f]; $bbanBranchidStopOffset = $preOffset + $tokens['offset'][$f] + $tokens[1][$f] - 1; // decrement by one on assignment $found = TRUE; } } } } /* * calculate 1=Yes, 0=No for $country_sepa * note: This is buggy due to the free inclusion of random text by the registry publishers. * Notably it requires modification for places like Finland and Portugal where these comments are known to exist. */ $countrySepa = Strings::lower($countrySepa) == 'yes'; // set registry edition $registryEdition = date('Y-m-d'); // now prepare generate our registry lines... $toGenerate = [$countryCode => $countryName]; switch ($countryCode) { case 'DK': $toGenerate = ['DK' => $countryName, 'FO' => 'Faroe Islands', 'GL' => 'Greenland']; break; case 'FR': $toGenerate = ['FR' => $countryName, 'BL' => 'Saint Barthelemy', 'GF' => 'French Guyana', 'GP' => 'Guadelope', 'MF' => 'Saint Martin (French Part)', 'QM' => 'Martinique', 'RE' => 'Reunion', 'PF' => 'French Polynesia', 'TF' => 'French Sounthern Territories', 'YT' => 'Mayotte', 'NC' => 'New Caledonia', 'PM' => 'Saint Pierre et Miquelon', 'WF' => 'Wallis and Futuna Islands']; break; } // output loop foreach ($toGenerate as $countryCode => $countryName) { $ibanElectronicExample = $verifier->setChecksum($countryCode . substr($ibanElectronicExample, 2)); $ibanStructure = $countryCode . substr($ibanStructure, 2); $ibanRegex = '^' . $countryCode . substr($ibanRegex, 3); $this->_csvData[] = [$countryCode, $countryName, $domesticExample, $bbanExample, $bbanStructure, $bbanRegex, $bbanLength, $ibanElectronicExample, $ibanStructure, $ibanRegex, $ibanLength, $bbanBankidStartOffset, $bbanBankidStopOffset, $bbanBranchidStartOffset, $bbanBranchidStopOffset, $registryEdition, $countrySepa]; } } }
/** * 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 // <parameter-name[=default] [pattern] [#class]> or [ or ] or ?... $parts = Strings::split($mask, '/<([^>#= ]+)(=[^># ]*)? *([^>#]*)(#?[^>\\[\\]]*)>|(\\[!?|\\]|\\s*\\?.*)/'); $this->xlat = array(); $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][#class]> $matches = Strings::matchAll($parts[$i - 1], '/(?:([a-zA-Z0-9_.-]+)=)?<([^># ]+) *([^>#]*)(#?[^>]*)>/'); foreach ($matches as $match) { list(, $param, $name, $pattern, $class) = $match; // $pattern is not used if ($class !== '') { if (!isset(static::$styles[$class])) { throw new Nette\InvalidStateException("Parameter '{$name}' has '{$class}' flag, but Route::\$styles['{$class}'] is not set."); } $meta = static::$styles[$class]; } elseif (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 -= 6; } // PARSE PATH PART OF MASK $brackets = 0; // optional level $re = ''; $sequence = array(); $autoOptional = TRUE; 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 Nette\InvalidArgumentException("Unexpected '{$part}' in mask '{$mask}'."); } array_unshift($sequence, $part); $re = ($part[0] === '[' ? '(?:' : ')?') . $re; $i -= 5; continue; } $class = $parts[$i]; $i--; // validation class $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; } // check name (limitation by regexp) if (preg_match('#[^a-z0-9_-]#i', $name)) { throw new Nette\InvalidArgumentException("Parameter name must be alphanumeric string due to limitations of PCRE, '{$name}' given."); } // pattern, condition & metadata if ($class !== '') { if (!isset(static::$styles[$class])) { throw new Nette\InvalidStateException("Parameter '{$name}' has '{$class}' flag, but Route::\$styles['{$class}'] is not set."); } $meta = static::$styles[$class]; } elseif (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" . ($this->flags & self::CASE_SENSITIVE ? '' : 'iu'); // include in expression $re = '(?P<' . str_replace('-', '___', $name) . '>(?U)' . $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 (!$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 closing ']' in mask '{$mask}'."); } $this->re = '#' . $re . '/?\\z#A' . ($this->flags & self::CASE_SENSITIVE ? '' : 'iu'); $this->metadata = $metadata; $this->sequence = $sequence; }
public static function matchAll($re, $str, $flags = 0, $offset = 0) { return parent::matchAll($str, $re, $flags, $offset); }