TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Utility\String;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class StringFragment implements \Stringable
|
||||
{
|
||||
public readonly int $length;
|
||||
public readonly string $ident;
|
||||
|
||||
public static function raw(string $value): self
|
||||
{
|
||||
return new self($value, StringFragmentSplitter::TYPE_RAW);
|
||||
}
|
||||
|
||||
public static function expression(string $value): self
|
||||
{
|
||||
return new self($value, StringFragmentSplitter::TYPE_EXPRESSION);
|
||||
}
|
||||
|
||||
public function __construct(
|
||||
public readonly string $value,
|
||||
public readonly string $type
|
||||
) {
|
||||
if ($this->value === '') {
|
||||
throw new \LogicException('Value must not be empty', 1651671582);
|
||||
}
|
||||
$this->length = strlen($this->value);
|
||||
$this->ident = md5($this->type . '::' . ($this->value));
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Utility\String;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class StringFragmentCollection implements \Stringable, \Countable
|
||||
{
|
||||
/**
|
||||
* @var list<StringFragment>
|
||||
*/
|
||||
protected array $fragments;
|
||||
|
||||
/**
|
||||
* Length of all fragment strings
|
||||
*/
|
||||
protected int $length = 0;
|
||||
|
||||
public function __construct(StringFragment ...$fragments)
|
||||
{
|
||||
$lengths = array_map(static fn(StringFragment $fragment) => $fragment->length, $fragments);
|
||||
$this->length = array_sum($lengths);
|
||||
$this->fragments = $fragments;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return implode('', array_map('strval', $this->fragments));
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->fragments);
|
||||
}
|
||||
|
||||
public function with(StringFragment ...$fragments): self
|
||||
{
|
||||
$target = clone $this;
|
||||
foreach ($fragments as $fragment) {
|
||||
$target->length += $fragment->length;
|
||||
$target->fragments[] = $fragment;
|
||||
}
|
||||
return $target;
|
||||
}
|
||||
|
||||
public function withOnlyType(string $type): self
|
||||
{
|
||||
$fragments = array_filter(
|
||||
$this->fragments,
|
||||
static fn(StringFragment $item) => $item->type === $type
|
||||
);
|
||||
return new self(...$fragments);
|
||||
}
|
||||
|
||||
public function withoutType(string $type): self
|
||||
{
|
||||
$fragments = array_filter(
|
||||
$this->fragments,
|
||||
static fn(StringFragment $item) => $item->type !== $type
|
||||
);
|
||||
return new self(...$fragments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<StringFragment>
|
||||
*/
|
||||
public function getFragments(): array
|
||||
{
|
||||
return $this->fragments;
|
||||
}
|
||||
|
||||
public function getLength(): int
|
||||
{
|
||||
return $this->length;
|
||||
}
|
||||
|
||||
public function diff(self $other): self
|
||||
{
|
||||
$otherFragmentIdents = $other->getFragmentIdents();
|
||||
$differentFragments = array_filter(
|
||||
$this->fragments,
|
||||
static fn(StringFragment $item) => !in_array($item->ident, $otherFragmentIdents, true)
|
||||
);
|
||||
return new self(...$differentFragments);
|
||||
}
|
||||
|
||||
public function intersect(self $other): self
|
||||
{
|
||||
$otherFragmentIdents = $other->getFragmentIdents();
|
||||
$sameFragments = array_filter(
|
||||
$this->fragments,
|
||||
static fn(StringFragment $item) => in_array($item->ident, $otherFragmentIdents, true)
|
||||
);
|
||||
return new self(...$sameFragments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
protected function getFragmentIdents(): array
|
||||
{
|
||||
return array_map(static fn(StringFragment $item) => $item->ident, $this->fragments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Utility\String;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
readonly class StringFragmentPattern
|
||||
{
|
||||
public function __construct(
|
||||
public string $type,
|
||||
public string $pattern
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Compiles this string fragment pattern to a scoped PCRE pattern string.
|
||||
*/
|
||||
public function compilePattern(): string
|
||||
{
|
||||
return sprintf(
|
||||
'(?P<%s>%s)',
|
||||
$this->type . '_' . bin2hex(random_bytes(5)),
|
||||
$this->pattern
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Utility\String;
|
||||
|
||||
/**
|
||||
* Splits a string into RAW and EXPRESSION fragments.
|
||||
* EXPRESSION fragments are resolved by provided arbitrary regex pattern.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class StringFragmentSplitter
|
||||
{
|
||||
/**
|
||||
* Raw string literals
|
||||
*/
|
||||
public const TYPE_RAW = 'raw';
|
||||
|
||||
/**
|
||||
* Literals used as expression
|
||||
*/
|
||||
public const TYPE_EXPRESSION = 'expression';
|
||||
|
||||
/**
|
||||
* Returns `null` in case there have not been any pattern matches,
|
||||
* if omitted an array containing only `raw` fragments is returned
|
||||
*/
|
||||
public const FLAG_UNMATCHED_AS_NULL = 1;
|
||||
|
||||
/**
|
||||
* @var list<StringFragmentPattern>
|
||||
*/
|
||||
protected readonly array $patterns;
|
||||
|
||||
public function __construct(StringFragmentPattern ...$patterns)
|
||||
{
|
||||
$this->patterns = $patterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value to be split into `raw` and `expression` fragments
|
||||
* @param int $flags (optional) `FLAG_UNMATCHED_AS_NULL`
|
||||
*/
|
||||
public function split(string $value, int $flags = 0): ?StringFragmentCollection
|
||||
{
|
||||
$pattern = chr(1) . implode('|', $this->preparePatterns()) . chr(1);
|
||||
$options = PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE | PREG_SET_ORDER;
|
||||
if (!preg_match_all($pattern, $value, $matches, $options)) {
|
||||
if (($flags & self::FLAG_UNMATCHED_AS_NULL) === self::FLAG_UNMATCHED_AS_NULL) {
|
||||
return null;
|
||||
}
|
||||
return new StringFragmentCollection(StringFragment::raw($value));
|
||||
}
|
||||
|
||||
$collection = new StringFragmentCollection();
|
||||
foreach ($matches as $match) {
|
||||
// filters string keys (e.g. `expression_a1b2c3d4e5`) from matches, skips numeric indexes
|
||||
$types = array_filter(
|
||||
array_keys($match),
|
||||
static fn(int|string $type) => is_string($type) && $type !== ''
|
||||
);
|
||||
foreach ($types as $type) {
|
||||
$matchOffset = $match[$type][1];
|
||||
if ($matchOffset < 0) {
|
||||
continue;
|
||||
}
|
||||
$matchValue = $match[$type][0];
|
||||
// matches contain only pattern matches, but no raw string literals - by comparing the
|
||||
// position of the collection with the current offset, missing raw literals are synchronized
|
||||
if ($collection->getLength() < $matchOffset) {
|
||||
$gapValue = substr($value, $collection->getLength(), $matchOffset - $collection->getLength());
|
||||
$collection = $collection->with(StringFragment::raw($gapValue));
|
||||
}
|
||||
$collection = $collection->with(StringFragment::expression($matchValue));
|
||||
}
|
||||
}
|
||||
// synchronize missing raw string literals
|
||||
// (at the end of the given value after previous expression)
|
||||
if ($collection->getLength() < strlen($value)) {
|
||||
$gapValue = substr($value, $collection->getLength());
|
||||
$collection = $collection->with(StringFragment::raw($gapValue));
|
||||
}
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
protected function preparePatterns(): array
|
||||
{
|
||||
return array_map(
|
||||
static fn(StringFragmentPattern $pattern) => $pattern->compilePattern(),
|
||||
$this->patterns
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user