diff --git a/Attributes/NFor.php b/Attributes/NFor.php index 661ad30..05eafdd 100644 --- a/Attributes/NFor.php +++ b/Attributes/NFor.php @@ -55,16 +55,11 @@ private function generateCurrentContext(int $iterator):void } private function sequentialContext(string $key, array $value): void { - $finalInner = []; - foreach ($value as $existingKey => $existingValue){ - $finalInner = [ - $existingKey => $existingValue - ]; - if(count($this->currentDeclaration)>1){ - $finalInner[$this->currentDeclaration[0]] = $key; - } + + if(count($this->currentDeclaration)>1){ + $this->contextData[$this->currentDeclaration[0]] = $key; } - $this->contextData[end($this->currentDeclaration)] = $finalInner; + $this->contextData[end($this->currentDeclaration)] = $value; } private function assocContext(string $key, mixed $value):void { @@ -88,7 +83,6 @@ function __invoke(\DOMAttr &$attr, $contextData = []): void for ($i = 0; $i < $iterations; $i++) { $this->generateCurrentContext($i); - $newDoc = new Interpreter($attr->ownerDocument->saveHTML($clone), $this->contextData, true); $html = $newDoc->asHtml(); if(!empty(trim($html))){ diff --git a/Interpreter.php b/Interpreter.php index 48beb6d..fdb553f 100644 --- a/Interpreter.php +++ b/Interpreter.php @@ -80,7 +80,7 @@ private function getFragmentCompletions(): array */ private function ensureEncoding(string $html): string { - if(!$this->skipEncoding && !strpos($html, '')){ + if(!$this->skipEncoding && !str_contains($html, '')){ $partial = $this->getFragmentCompletions(); $this->isFragment = true; $html = $partial[0] .$html . $partial[1]; @@ -115,17 +115,24 @@ function stepThrough(DOMNodeList $nodes): void // attributes? if($child->hasAttributes()){ $this->handleAttributes($child); - // attribute functions? } // IS delimiter? - if(Constants::delimiterIsTag() && $child->tagName === substr(Constants::getDelimiter()[0],1,-1) && isset($this->flatData[trim($child->textContent)])){ - $child->nodeValue = $this->flatData[trim($child->textContent)]; - } + $this->handleDelimiterIsTag($child); + } if($child instanceof DOMText && trim($child->nodeValue) !== ''){ $this->handleTextNode($child); } - $this->stepThrough($child->childNodes); + if($child->hasChildNodes()){ + $this->stepThrough($child->childNodes); + } + } + } + + function handleDelimiterIsTag(DOMElement $node): void + { + if(Constants::delimiterIsTag() && $node->tagName === substr(Constants::getDelimiter()[0],1,-1) && isset($this->flatData[trim($node->textContent)])){ + $node->nodeValue = $this->flatData[trim($node->textContent)]; } } @@ -136,9 +143,24 @@ function stepThrough(DOMNodeList $nodes): void function handleTextNode(DOMText $node): void { // readDelimiter - $node->nodeValue = $this->readDelimiter($node->nodeValue); - // handle functions - $this->handleFunctions($node); + $givenValue = $this->readDelimiter($node->nodeValue); + if($givenValue !== strip_tags($givenValue)){ + $this->appendAsFragment($node, $givenValue); + } else { + $node->nodeValue = $this->readDelimiter($node->nodeValue); + // handle functions + $this->handleFunctions($node); + } + + } + + private function appendAsFragment(DOMText $parentNode, string $htmlPartial): void + { + $subDoc = new Interpreter($htmlPartial, $this->contextData); + $fragment = $this->doc->createDocumentFragment(); + $fragment->appendXML($subDoc->asHtml()); + $parentNode->nodeValue = ''; + $parentNode->parentNode->appendChild($fragment); } /** @@ -152,14 +174,18 @@ function handleFunctions(DOMText $element): void $pattern = "/({$delimiter[0]}.*)*$function\(([^)]*)\)(.*{$delimiter[1]})*/"; $hit = preg_match_all($pattern, $element->nodeValue, $matches, PREG_SET_ORDER); if($hit){ - foreach($matches as $match){ - if(!empty($match[2]) && array_key_exists($match[2],$this->flatData)){ - $element->nodeValue = str_replace($match[0], $closure($this->flatData[$match[2]]), $element->nodeValue); - } elseif (empty($match[2])){ - $element->nodeValue = str_replace($match[0], $closure(), $element->nodeValue); - } - } + $this->executeFunction($closure, $matches, $element); + } + } + } + private function executeFunction(callable $callable, $matches, $element): void + { + foreach($matches as $match){ + if(!empty($match[2]) && array_key_exists($match[2],$this->flatData)){ + $element->nodeValue = str_replace($match[0], $callable($this->flatData[$match[2]]), $element->nodeValue); + } elseif (empty($match[2])){ + $element->nodeValue = str_replace($match[0], $callable(), $element->nodeValue); } } } @@ -206,6 +232,7 @@ function readDelimiter(string $string): string if($found){ $string = $this->replaceVariables($matches, $string); + } return $string; } @@ -218,7 +245,7 @@ function readDelimiter(string $string): string private function replaceVariables(array $matches, string $content): string { foreach ($matches as $pair){ - if(isset($this->flatData[trim($pair[1])])){ + if(array_key_exists(trim($pair[1]), $this->flatData)){ $content = str_replace($pair[0], $this->flatData[trim($pair[1])], $content); } } diff --git a/README.md b/README.md index 7b88e38..2aff348 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ echo Template::embrace('

{{test}}

',['test'=>'Hello World']); - [Custom Delimiter](#custom-delimiter) - [Custom Attributes](#custom-attributes) - [OOP](#oop) +- [Tips](#tips) ## Templating @@ -283,7 +284,7 @@ echo Template::embraceFromFile('/main.html', $translations) So far, we have used a purely static approach. However, the "Template" methods are merely facades resulting in the initialization of the Interpreter. If you need more control over what is happening, or it better fits your situation, you are welcome to use it directly: -````php +```php $html = file_get_contents(__DIR__ . '/test.html); $contextData = [ @@ -304,4 +305,68 @@ $templating->parse(); echo $templating->asHtml(); +``` + +## Tips + +There are a few things that are logical, but not obvious: + +### Parents on loops + +Due to processing in hierarchy, many internal parsing operations can cause race-conditions. +Imagine the following HTML: +```html +// ['items' => ['one','two'], 'name' => 'John'] +
+

{{item}} {{name}}

+

{{name}}

+
+``` +In this scenario, the parser would first hit the attribute `n-for` +and add p-tags to the parent node (here div). `n-for` now takes control of this parent and +interprets the children. As the context gets reevaluated in every loop, but the second p-tag is not iterated, +the resulting output would be: +```html +
+

{{name}}

+

one John

+

two John

+
+``` +It is therefore recommended to use **one** distinct parent when using attribute methods: +```html +// ['items' => ['one','two'], 'name' => 'John'] +
+

{{item}} {{name}}

+
+
+

{{name}}

+
+``` + +### Flat array properties + +The interpreter "flattens" the given context-array +in order to allow for the dot-notation. In this process generic values are added: +```html +// ['items' => ['one','two'], 'deepAssoc' => ['name' => 'Tim']] +

{{items}}

+

{{items.0}}

+

{{items.length}}

+

{{deepAssoc.name}}

+``` +output: +```html +

Array

+

one

+

2

+

Tim

+``` +If needed, you can use this in logic: +```html +
+ +
``` \ No newline at end of file diff --git a/composer.json b/composer.json index 272407c..e29a677 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "neoan3-apps/template", "description": "PHP DOMDocument-based minimal template engine", - "version": "2.0.0", + "version": "2.0.1", "license": "MIT", "autoload": { "psr-4": { diff --git a/tests/playground.html b/tests/playground.html index e173002..140f46c 100644 --- a/tests/playground.html +++ b/tests/playground.html @@ -5,12 +5,15 @@ Title -

{{headline(items.length)}}

- -

{{umlaut}}

-

{{some}}

-
I don't want to see this
+
+

{{headline(items.length)}}

+ +

{{umlaut}}

+

{{some}}

+
I don't want to see this
+
+ \ No newline at end of file