1
|
|
|
<?php |
2
|
|
|
/* @description Transformation Style Sheets - Revolutionising PHP templating * |
3
|
|
|
* @author Tom Butler [email protected] * |
4
|
|
|
* @copyright 2015 Tom Butler <[email protected]> | https://r.je/ * |
5
|
|
|
* @license http://www.opensource.org/licenses/bsd-license.php BSD License * |
6
|
|
|
* @version 1.0 */ |
7
|
|
|
namespace Transphporm; |
8
|
|
|
/** Builds a Transphorm instance from the 3 constituent parts. XML template string, TSS string and data */ |
9
|
|
|
class Builder { |
10
|
|
|
private $template; |
11
|
|
|
private $tss; |
12
|
|
|
private $baseDir; |
13
|
|
|
private $cache; |
14
|
|
|
private $time; |
15
|
|
|
private $modules = []; |
16
|
|
|
private $defaultModules = [ |
17
|
|
|
'\\Transphporm\\Module\\Basics', |
18
|
|
|
'\\Transphporm\\Module\\Pseudo', |
19
|
|
|
'\\Transphporm\\Module\\Format' |
20
|
|
|
]; |
21
|
|
|
|
22
|
|
|
public function __construct($template, $tss = '', $modules = null) { |
23
|
|
|
$this->template = $template; |
24
|
|
|
$this->tss = $tss; |
25
|
|
|
$this->cache = new Cache(new \ArrayObject()); |
26
|
|
|
|
27
|
|
|
$modules = is_array($modules) ? $modules : $this->defaultModules; |
28
|
|
|
foreach ($modules as $module) $this->loadModule(new $module); |
29
|
|
|
} |
30
|
|
|
|
31
|
|
|
//Allow setting the time used by Transphporm for caching. This is for testing purposes |
32
|
|
|
//Would be better if PHP allowed setting the script clock, but this is the simplest way of overriding it |
33
|
|
|
public function setTime($time) { |
34
|
|
|
$this->time = $time; |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
public function loadModule(Module $module) { |
38
|
|
|
$this->modules[get_class($module)] = $module; |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
public function output($data = null, $document = false) { |
42
|
|
|
$headers = []; |
43
|
|
|
|
44
|
|
|
$data = new Hook\DataFunction(new \SplObjectStorage(), $data, $this->baseDir); |
45
|
|
|
$featureSet = new FeatureSet($data, new Hook\Formatter(), $headers); |
46
|
|
|
|
47
|
|
|
foreach ($this->modules as $module) $module->load($featureSet); |
48
|
|
|
//$locale = $this->getLocale(); |
|
|
|
|
49
|
|
|
|
50
|
|
|
$cachedOutput = $this->loadTemplate(); |
51
|
|
|
//To be a valid XML document it must have a root element, automatically wrap it in <template> to ensure it does |
52
|
|
|
$template = new Template($this->isValidDoc($cachedOutput['body']) ? str_ireplace('<!doctype', '<!DOCTYPE', $cachedOutput['body']) : '<template>' . $cachedOutput['body'] . '</template>' ); |
53
|
|
|
|
54
|
|
|
$this->processRules($template, $data, $featureSet); |
55
|
|
|
|
56
|
|
|
$result = ['body' => $template->output($document), 'headers' => array_merge($cachedOutput['headers'], $headers)]; |
57
|
|
|
$this->cache->write($this->template, $result); |
58
|
|
|
$result['body'] = $this->doPostProcessing($template)->output($document); |
59
|
|
|
|
60
|
|
|
return (object) $result; |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
private function processRules($template, $data, $featureSet) { |
64
|
|
|
$valueParser = new Parser\Value($data); |
65
|
|
|
foreach ($this->getRules($template, $valueParser) as $rule) { |
66
|
|
|
if ($rule->shouldRun($this->time)) $this->executeTssRule($rule, $template, $valueParser, $featureSet); |
67
|
|
|
} |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
//Add a postprocessing hook. This cleans up anything transphporm has added to the markup which needs to be removed |
71
|
|
|
private function doPostProcessing($template) { |
72
|
|
|
$template->addHook('//*[@transphporm]', new Hook\PostProcess()); |
73
|
|
|
return $template; |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
//Process a TSS rule e.g. `ul li {content: "foo"; format: bar} |
77
|
|
|
private function executeTssRule($rule, $template, $valueParser, $featureSet) { |
78
|
|
|
$rule->touch(); |
79
|
|
|
$pseudoMatcher = new Hook\PseudoMatcher($rule->pseudo); |
|
|
|
|
80
|
|
|
$pseudoMatcher = $featureSet->createPseudoMatcher($rule->pseudo); |
81
|
|
|
|
82
|
|
|
$hook = new Hook\PropertyHook($rule->properties, $pseudoMatcher, $valueParser); |
83
|
|
|
// foreach ($this->featureSetas $name => $property) $hook->registerProperty($name, $property); |
|
|
|
|
84
|
|
|
$featureSet->loadProperties($hook); |
85
|
|
|
$template->addHook($rule->query, $hook); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
//Load a template, firstly check if it's a file or a valid string |
89
|
|
|
private function loadTemplate() { |
90
|
|
|
if (trim($this->template)[0] !== '<') { |
91
|
|
|
$xml = $this->cache->load($this->template, filemtime($this->template)); |
92
|
|
|
return $xml ? $xml : ['body' => file_get_contents($this->template), 'headers' => []]; |
93
|
|
|
} |
94
|
|
|
else return ['body' => $this->template, 'headers' => []]; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
//Load the TSS rules either from a file or as a string |
98
|
|
|
//N.b. only files can be cached |
99
|
|
|
private function getRules($template, $valueParser) { |
100
|
|
|
if (is_file($this->tss)) { |
101
|
|
|
$this->baseDir = dirname(realpath($this->tss)) . DIRECTORY_SEPARATOR; |
102
|
|
|
//The cache for the key: the filename and template prefix |
103
|
|
|
//Each template may have a different prefix which changes the parsed TSS, |
104
|
|
|
//Because of this the cache needs to be generated for each template prefix. |
105
|
|
|
$key = $this->tss . $template->getPrefix() . $this->baseDir; |
106
|
|
|
//Try to load the cached rules, if not set in the cache (or expired) parse the supplied sheet |
107
|
|
|
$rules = $this->cache->load($key, filemtime($this->tss)); |
108
|
|
|
if (!$rules) return $this->cache->write($key, (new Parser\Sheet(file_get_contents($this->tss), $this->baseDir, $valueParser, $template->getPrefix()))->parse()); |
109
|
|
|
else return $rules; |
110
|
|
|
} |
111
|
|
|
else return (new Parser\Sheet($this->tss, $this->baseDir, $valueParser, $template->getPrefix()))->parse(); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
public function setCache(\ArrayAccess $cache) { |
115
|
|
|
$this->cache = new Cache($cache); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
private function isValidDoc($xml) { |
119
|
|
|
return strpos($xml, '<!') === 0 || strpos($xml, '<?') === 0; |
120
|
|
|
} |
121
|
|
|
} |
122
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.