diff --git a/src/Html/Renderables/Container.php b/src/Html/Renderables/Container.php index d45e8de..2ac16e3 100644 --- a/src/Html/Renderables/Container.php +++ b/src/Html/Renderables/Container.php @@ -15,7 +15,7 @@ final class Container implements TransformableRenderable /** * @param Renderable[] $Contents */ - public function __construct($Contents) + public function __construct($Contents = []) { $this->Contents = $Contents; } @@ -67,4 +67,18 @@ final class Container implements TransformableRenderable $this->Contents )); } + + public function replacingAll(string $search, Renderable $Replacement): Renderable + { + return new Container(\array_map( + function (Renderable $R) use ($search, $Replacement): Renderable { + if (! $R instanceof TransformableRenderable) { + return $R; + } + + return $R->replacingAll($search, $Replacement); + }, + $this->Contents + )); + } } diff --git a/src/Html/Renderables/Element.php b/src/Html/Renderables/Element.php index 3a41df6..2df3d53 100644 --- a/src/Html/Renderables/Element.php +++ b/src/Html/Renderables/Element.php @@ -214,4 +214,22 @@ final class Element implements TransformableRenderable $this->Contents )); } + + public function replacingAll(string $search, Renderable $Replacement): Renderable + { + if (! isset($this->Contents)) { + return $this; + } + + return new self($this->name, $this->attributes, \array_map( + function (Renderable $R) use ($search, $Replacement): Renderable { + if (! $R instanceof TransformableRenderable) { + return $R; + } + + return $R->replacingAll($search, $Replacement); + }, + $this->Contents + )); + } } diff --git a/src/Html/Renderables/Text.php b/src/Html/Renderables/Text.php index 54fb4d1..446121e 100644 --- a/src/Html/Renderables/Text.php +++ b/src/Html/Renderables/Text.php @@ -41,4 +41,50 @@ final class Text implements TransformableRenderable { return $Transform($this->text); } + + public function replacingAll(string $search, Renderable $Replacement): Renderable + { + $searchLen = \strlen($search); + + if ($searchLen < 1) { + return $this; + } + + $result = \preg_match_all( + '/\b'.\preg_quote($search, '/').'\b/', + $this->text, + $matches, + \PREG_OFFSET_CAPTURE + ); + + if (empty($result)) { + return $this; + } + + $lastEndPos = 0; + + $Container = new Container; + + foreach ($matches[0] as $match) { + $pos = $match[1]; + $endPos = $pos + $searchLen; + + if ($pos !== $lastEndPos) { + $Container = $Container->adding( + new Text(\substr($this->text, $lastEndPos, $pos - $lastEndPos)) + ); + } + + $Container = $Container->adding($Replacement); + $lastEndPos = $endPos; + } + + if (\strlen($this->text) -1 !== $lastEndPos) { + $Container = $Container->adding( + new Text(\substr($this->text, $lastEndPos)) + ); + } + + return $Container; + } } diff --git a/src/Html/TransformableRenderable.php b/src/Html/TransformableRenderable.php index 9497dcb..957288e 100644 --- a/src/Html/TransformableRenderable.php +++ b/src/Html/TransformableRenderable.php @@ -22,4 +22,10 @@ interface TransformableRenderable extends Renderable * @return Renderable */ public function transformingContent(\Closure $Transform): Renderable; + + /** + * Similar to transformingContent, but replace the string $search in text content + * with the renderable $Replacement and return the result. + */ + public function replacingAll(string $search, Renderable $Replacement): Renderable; }