Skip to content

Commit

Permalink
Merge pull request #14 from sroehrl/interpreter
Browse files Browse the repository at this point in the history
BUGFIX: sequential array in nFor
  • Loading branch information
sroehrl authored Aug 15, 2022
2 parents 9519601 + 69390bd commit c20b03c
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 36 deletions.
14 changes: 4 additions & 10 deletions Attributes/NFor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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))){
Expand Down
61 changes: 44 additions & 17 deletions Interpreter.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ private function getFragmentCompletions(): array
*/
private function ensureEncoding(string $html): string
{
if(!$this->skipEncoding && !strpos($html, '<!DOCTYPE html>')){
if(!$this->skipEncoding && !str_contains($html, '<!DOCTYPE html>')){
$partial = $this->getFragmentCompletions();
$this->isFragment = true;
$html = $partial[0] .$html . $partial[1];
Expand Down Expand Up @@ -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)];
}
}

Expand All @@ -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);
}

/**
Expand All @@ -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);
}
}
}
Expand Down Expand Up @@ -206,6 +232,7 @@ function readDelimiter(string $string): string

if($found){
$string = $this->replaceVariables($matches, $string);

}
return $string;
}
Expand All @@ -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);
}
}
Expand Down
67 changes: 66 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ echo Template::embrace('<h1>{{test}}</h1>',['test'=>'Hello World']);
- [Custom Delimiter](#custom-delimiter)
- [Custom Attributes](#custom-attributes)
- [OOP](#oop)
- [Tips](#tips)


## Templating
Expand Down Expand Up @@ -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 = [
Expand All @@ -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']
<div>
<p n-for="items as item">{{item}} {{name}}</p>
<p>{{name}}</p>
</div>
```
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
<div>
<p>{{name}}</p>
<p>one John</p>
<p>two John</p>
</div>
```
It is therefore recommended to use **one** distinct parent when using attribute methods:
```html
// ['items' => ['one','two'], 'name' => 'John']
<div>
<p n-for="items as item">{{item}} {{name}}</p>
</div>
<div>
<p>{{name}}</p>
</div>
```

### 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']]
<p>{{items}}</p>
<p>{{items.0}}</p>
<p>{{items.length}}</p>
<p>{{deepAssoc.name}}</p>
```
output:
```html
<p>Array</p>
<p>one</p>
<p>2</p>
<p>Tim</p>
```
If needed, you can use this in logic:
```html
<div n-if="items == 'Array'">
<ul>
<li n-for="items as item">{{item}}</li>
</ul>
</div>
```
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
17 changes: 10 additions & 7 deletions tests/playground.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
<title>Title</title>
</head>
<body>
<h3>{{headline(items.length)}}</h3>
<ul>
<li n-for="items as e => item" n-if="item !== 'two'">{{item}}{{myFunc(item)}}</li>
</ul>
<p>{{umlaut}}</p>
<p class="{{some}}-{{fake}}">{{some}}</p>
<div n-if="items.0 === 'öne' && !later ">I don't want to see this</div>
<section>
<h3>{{headline(items.length)}}</h3>
<ul>
<li n-for="items as e => item" n-if="item !== 'two'">{{item}}{{myFunc(item)}}</li>
</ul>
<p>{{umlaut}}</p>
<p class="{{some}}-{{fake}}">{{some}}</p>
<div n-if="items.0 === 'öne' && !later ">I don't want to see this</div>
</section>

</body>
</html>

0 comments on commit c20b03c

Please sign in to comment.