Skip to content

Commit 271b3a6

Browse files
committed
Loader::load() detects content-type WIP
1 parent b6c2bdf commit 271b3a6

File tree

5 files changed

+164
-8
lines changed

5 files changed

+164
-8
lines changed

src/Latte/Engine.php

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public function compile(string $name): string
130130
$source = $this->loadCompatible($name);
131131

132132
try {
133-
$node = $this->parse($source->content);
133+
$node = $this->parse($source);
134134
$this->applyPasses($node);
135135
$compiled = $this->generate($node, $this->getTemplateClass($name), $source->sourceName);
136136

@@ -154,10 +154,11 @@ public function compile(string $name): string
154154
/**
155155
* Parses template to AST node.
156156
*/
157-
public function parse(string $template): TemplateNode
157+
public function parse(LoadedContent|string $source): TemplateNode
158158
{
159+
$source = is_string($source) ? new LoadedContent($source) : $source;
159160
$parser = new Compiler\TemplateParser;
160-
$parser->getLexer()->setSyntax($this->syntax);
161+
$parser->getLexer()->setSyntax($source->static ? 'off' : $this->syntax);
161162
$parser->strict = $this->strictParsing;
162163

163164
foreach ($this->extensions as $extension) {
@@ -166,9 +167,9 @@ public function parse(string $template): TemplateNode
166167
}
167168

168169
return $parser
169-
->setContentType($this->contentType)
170+
->setContentType($source->contentType ?? $this->contentType)
170171
->setPolicy($this->getPolicy(effective: true))
171-
->parse($template);
172+
->parse($source->content);
172173
}
173174

174175

@@ -230,8 +231,8 @@ private function loadTemplate(string $name): string
230231
$compiled = $this->compile($name);
231232
if (@eval(substr($compiled, 5)) === false) { // @ is escalated to exception, substr removes <?php
232233
throw new CompileException(
233-
'Error in template: ' . error_get_last()['message'],
234-
new SourceReference("$name (compiled)", code: $compiled),
234+
'Error in compiled template: ' . error_get_last()['message'],
235+
new SourceReference($this->loadCompat($name)->sourceName, code: $compiled), // TODO
235236
);
236237
}
237238
}

src/Latte/LoadedContent.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ class LoadedContent
1818
{
1919
public function __construct(
2020
public readonly string $content,
21+
public readonly ?string $contentType = null,
2122
public readonly ?string $sourceName = null,
23+
public readonly bool $static = false,
2224
) {
2325
}
2426
}

src/Latte/Loaders/FileLoader.php

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
namespace Latte\Loaders;
1111

1212
use Latte;
13+
use Latte\ContentType;
1314
use function array_pop, end, explode, file_get_contents, implode, is_file, preg_match, str_starts_with, strtr, time, touch;
1415
use const DIRECTORY_SEPARATOR;
1516

@@ -38,7 +39,13 @@ public function load(string $file): Latte\LoadedContent
3839
throw new Latte\TemplateNotFoundException("Missing template file '$path'.");
3940
}
4041

41-
return new Latte\LoadedContent(file_get_contents($path), sourceName: $path);
42+
[$contentType, $static] = $this->detectContentType($file);
43+
return new Latte\LoadedContent(
44+
file_get_contents($path),
45+
contentType: $contentType,
46+
sourceName: $path,
47+
static: $static,
48+
);
4249
}
4350

4451

@@ -78,4 +85,24 @@ protected static function normalizePath(string $path): string
7885

7986
return $m[1] . implode(DIRECTORY_SEPARATOR, $res);
8087
}
88+
89+
90+
/** @internal */
91+
public function detectContentType(string $file): array
92+
{
93+
[, $prev, $last] = preg_match('/(?:\.(\w+))?\.(\w+)$/', $file, $m) ? $m : null;
94+
$static = $last !== 'latte';
95+
return [
96+
match ($static ? $last : $prev) {
97+
'html' => ContentType::Html,
98+
'xml' => ContentType::Xml,
99+
'txt' => ContentType::Text,
100+
'js' => ContentType::JavaScript,
101+
'css' => ContentType::Css,
102+
'ical' => ContentType::ICal,
103+
default => null,
104+
},
105+
$static,
106+
];
107+
}
81108
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/**
4+
* Test: FileLoader & contentType
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Latte\ContentType;
10+
use Latte\Loaders\FileLoader;
11+
use Tester\Assert;
12+
13+
require __DIR__ . '/../bootstrap.php';
14+
15+
$loader = new FileLoader;
16+
17+
// not latte
18+
Assert::same([ContentType::Html, true], $loader->detectContentType('template.html'));
19+
Assert::same([null, true], $loader->detectContentType('template'));
20+
Assert::same([null, true], $loader->detectContentType('template.LATTE'));
21+
Assert::same([null, true], $loader->detectContentType('template.latte.x'));
22+
23+
// latte without content-type
24+
Assert::same([null, false], $loader->detectContentType('template.latte'));
25+
Assert::same([null, false], $loader->detectContentType('template.foo.latte'));
26+
27+
// content type extensions
28+
Assert::same([ContentType::Text, false], $loader->detectContentType('template.txt.latte'));
29+
Assert::same([ContentType::Html, false], $loader->detectContentType('template.html.latte'));
30+
Assert::same([ContentType::Xml, false], $loader->detectContentType('template.xml.latte'));
31+
Assert::same([ContentType::JavaScript, false], $loader->detectContentType('template.js.latte'));
32+
Assert::same([ContentType::Css, false], $loader->detectContentType('template.css.latte'));
33+
Assert::same([ContentType::ICal, false], $loader->detectContentType('template.ical.latte'));
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
/**
4+
* Test: comments HTML test
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Latte\ContentType;
10+
use Tester\Assert;
11+
12+
require __DIR__ . '/../bootstrap.php';
13+
14+
15+
class ContentTypeLoader implements Latte\Loader
16+
{
17+
private $contents = [
18+
'file.js' => '// JavaScript <img> {=latte} <script></script>',
19+
'file.txt' => '// text <img> {=latte} <script></script>',
20+
'file.html' => '// HTML <img> {=latte} <script></script>',
21+
];
22+
23+
24+
public function load(string $name): Latte\LoadedContent
25+
{
26+
if (!isset($this->contents[$name])) {
27+
return new Latte\LoadedContent($name);
28+
}
29+
[$contentType, $static] = (new Latte\Loaders\FileLoader)->detectContentType($name);
30+
return new Latte\LoadedContent(
31+
$this->contents[$name],
32+
contentType: $contentType,
33+
static: $static,
34+
);
35+
}
36+
37+
38+
public function getReferredName(string $name, string $referringName): string
39+
{
40+
return $name;
41+
}
42+
43+
44+
public function getUniqueId(string $name): string
45+
{
46+
return $name;
47+
}
48+
}
49+
50+
51+
$latte = new Latte\Engine;
52+
$latte->setLoader(new ContentTypeLoader);
53+
$latte->setAutoRefresh();
54+
55+
Assert::same(
56+
'// JavaScript &lt;img&gt; &#123;=latte} &lt;script&gt;&lt;/script&gt;',
57+
$latte->renderToString('{include file.js}'),
58+
);
59+
60+
Assert::same(
61+
'<div> // HTML <img> {=latte} <script></script> </div>',
62+
$latte->renderToString('<div> {include file.html} </div>'),
63+
);
64+
65+
Assert::same(
66+
'<div title="// HTML &#123;=latte} "></div>',
67+
$latte->renderToString('<div title="{include file.html}"></div>'),
68+
);
69+
70+
// <script>
71+
Assert::same(
72+
'<script> // JavaScript <img> {=latte} <script><\/script> </script>',
73+
$latte->renderToString('<script> {include file.js} </script>'),
74+
);
75+
76+
Assert::exception(
77+
fn() => $latte->renderToString('<script> {include file.txt} </script>'),
78+
Latte\RuntimeException::class,
79+
"Including 'file.txt' with content type TEXT into incompatible type HTML/RAW/JS.",
80+
);
81+
82+
Assert::exception(
83+
fn() => $latte->renderToString('<script> {include file.html} </script>'),
84+
Latte\RuntimeException::class,
85+
"Including 'file.html' with content type HTML into incompatible type HTML/RAW/JS.",
86+
);
87+
88+
// HTML to Text
89+
$latte->setContentType(ContentType::Text);
90+
Assert::same(
91+
'* // HTML {=latte} *',
92+
$latte->renderToString('* {include file.html} *'),
93+
);

0 commit comments

Comments
 (0)