Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/Level-2/Transphporm
Browse files Browse the repository at this point in the history
  • Loading branch information
solleer committed Sep 7, 2018
2 parents 04d6e08 + 975e786 commit 3feb6b7
Show file tree
Hide file tree
Showing 22 changed files with 469 additions and 220 deletions.
109 changes: 50 additions & 59 deletions README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
}
],
"require": {
"php": ">=5.6.0"
"php": ">=7.0.0"
},
"require-dev": {
"phpunit/phpunit": "^5.7.20"
},
"autoload": {
"classmap": ["src/"]
"psr-4": {"Transphporm\\": "src/"}
}
}
82 changes: 39 additions & 43 deletions src/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Builder {
private $modules = [];
private $config;
private $filePath;
private $cacheKey;
private $defaultModules = [
'\\Transphporm\\Module\\Basics',
'\\Transphporm\\Module\\Pseudo',
Expand All @@ -39,44 +40,55 @@ public function setTime($time) {
public function loadModule(Module $module) {
$this->modules[get_class($module)] = $module;
}

public function setLocale($locale) {
$format = new \Transphporm\Module\Format($locale);
$this->modules[get_class($format)] = $format;
}
$format = new \Transphporm\Module\Format($locale);
$this->modules[get_class($format)] = $format;
}

public function addPath($dir) {
$this->filePath->addPath($dir);
}

private function getSheetLoader() {
$tssRules = is_file($this->tss) ? new SheetLoader\TSSFile($this->tss, $this->filePath, $this->cache, $this->time) : new SheetLoader\TSSString($this->tss, $this->filePath);
return new SheetLoader\SheetLoader($this->cache, $this->filePath, $tssRules, $this->time);
}

public function output($data = null, $document = false) {
$headers = [];

$tssCache = $this->getSheetLoader();
$this->cacheKey = $tssCache->getCacheKey($data);
$result = $this->loadTemplate();
//If an update is required, run any rules that need to be run. Otherwise, return the result from cache
//without creating any further objects, loading a DomDocument, etc
if (empty($result['renderTime']) || $tssCache->updateRequired($data) === true) {
$template = $this->createAndProcessTemplate($data, $result['cache'], $headers);
$tssCache->processRules($template, $this->config);

$result = ['cache' => $template->output($document),
'renderTime' => time(),
'headers' => array_merge($result['headers'], $headers),
'body' => $this->doPostProcessing($template)->output($document)
];
$this->cache->write($tssCache->getCacheKey($data) . $this->template, $result);
}
unset($result['cache'], $result['renderTime']);
return (object) $result;
}

private function createAndProcessTemplate($data, $body, &$headers) {
$elementData = new \Transphporm\Hook\ElementData(new \SplObjectStorage(), $data);
$functionSet = new FunctionSet($elementData);

$cachedOutput = $this->loadTemplate();
//To be a valid XML document it must have a root element, automatically wrap it in <template> to ensure it does
$template = new Template($this->isValidDoc($cachedOutput['body']) ? str_ireplace('<!doctype', '<!DOCTYPE', $cachedOutput['body']) : '<template>' . $cachedOutput['body'] . '</template>' );
$template = new Template($this->isValidDoc($body) ? str_ireplace('<!doctype', '<!DOCTYPE', $body) : '<template>' . $body . '</template>' );

$valueParser = new Parser\Value($functionSet);
$this->config = new Config($functionSet, $valueParser, $elementData, new Hook\Formatter(), new Parser\CssToXpath($functionSet, $template->getPrefix(), md5($this->tss)), $this->filePath, $headers);

foreach ($this->modules as $module) $module->load($this->config);

$this->processRules($template, $this->config);

$result = ['body' => $template->output($document), 'headers' => array_merge($cachedOutput['headers'], $headers)];
$this->cache->write($this->template, $result);
$result['body'] = $this->doPostProcessing($template)->output($document);
return (object) $result;
}

private function processRules($template, $config) {
$rules = $this->getRules($template, $config);

foreach ($rules as $rule) {
if ($rule->shouldRun($this->time)) $this->executeTssRule($rule, $template, $config);
}
return $template;
}

//Add a postprocessing hook. This cleans up anything transphporm has added to the markup which needs to be removed
Expand All @@ -85,35 +97,19 @@ private function doPostProcessing($template) {
return $template;
}

//Process a TSS rule e.g. `ul li {content: "foo"; format: bar}
private function executeTssRule($rule, $template, $config) {
$rule->touch();

$pseudoMatcher = $config->createPseudoMatcher($rule->pseudo);
$hook = new Hook\PropertyHook($rule->properties, $config->getLine(), $rule->file, $rule->line, $pseudoMatcher, $config->getValueParser(), $config->getFunctionSet(), $config->getFilePath());
$config->loadProperties($hook);
$template->addHook($rule->query, $hook);
}

//Load a template, firstly check if it's a file or a valid string
private function loadTemplate() {
$result = ['body' => $this->template, 'headers' => []];
if (file_exists($this->template)) $result = $this->loadTemplateFromFile($this->template);
$result = ['cache' => $this->template, 'headers' => []];
if (strpos($this->template, "\n") === false && is_file($this->template)) $result = $this->loadTemplateFromFile($this->template);
return $result;
}

private function loadTemplateFromFile($file) {
$xml = $this->cache->load($this->template, filemtime($this->template));
return $xml ? $xml : ['body' => file_get_contents($this->template) ?: "", 'headers' => []];
$xml = $this->cache->load($this->cacheKey . $this->template, filemtime($this->template));
return $xml ? $xml : ['cache' => file_get_contents($this->template) ?: "", 'headers' => []];
}

//Load the TSS rules either from a file or as a string
//N.b. only files can be cached
private function getRules($template, $config) {
$cache = new TSSCache($this->cache, $template->getPrefix());
return (new Parser\Sheet($this->tss, $config->getCssToXpath(), $config->getValueParser(), $cache, $config->getFilePath()))->parse();
}

public function setCache(\ArrayAccess $cache) {
$this->cache = new Cache($cache);
}
Expand All @@ -123,7 +119,7 @@ private function isValidDoc($xml) {
}

public function __destruct() {
//Required hack as DomXPath can only register static functions clear, the statically stored instance to avoid memory leaks
//Required hack as DomXPath can only register static functions clear the statically stored instance to avoid memory leaks
if (isset($this->config)) $this->config->getCssToXpath()->cleanup();
}
}
2 changes: 1 addition & 1 deletion src/Cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public function __construct(\ArrayAccess $cache) {
}

public function write($key, $content) {
$this->cache[md5($key)] = ['content' => $content, 'timestamp' => time()];
$this->cache[md5($key)] = ['content' => $content, 'timestamp' => time()];
return $content;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Formatter/Locale/deDE.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
{
"thousands_separator": ".",
"decimal_separator": ",",
"currency": "",
Expand Down
3 changes: 2 additions & 1 deletion src/FunctionSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ public function __call($name, $args) {
if (isset($this->functions[$name])) {
return $this->functions[$name]->run($this->getArgs0($name, $args), $this->element);
}

return false;
}

private function getArgs0($name, $args) {
if (isset($this->functions[$name]) && !($this->functions[$name] instanceof TSSFunction\Data)) {
$tokens = $args[0];
if ($tokens->count() == 0) return [];
$parser = new \Transphporm\Parser\Value($this);
return $parser->parseTokens($tokens, $this->elementData->getData($this->element));
}
Expand Down
58 changes: 18 additions & 40 deletions src/Parser/Sheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,27 @@
namespace Transphporm\Parser;
/** Parses a .tss file into individual rules, each rule has a query e,g, `ul li` and a set of rules e.g. `display: none; bind: iteration(id);` */
class Sheet {
private $cache;
private $tss;
private $rules;
private $file;
private $valueParser;
private $xPath;
private $filePath;
private $import = [];
private $valueParser;
private $sheetLoader;
private $file;
private $rules;

public function __construct($tss, CssToXpath $xPath, Value $valueParser, \Transphporm\TSSCache $cache, \Transphporm\FilePath $filePath) {
$this->cache = $cache;
public function __construct($tss, CssToXpath $xPath, Value $valueParser, \Transphporm\FilePath $filePath, \Transphporm\SheetLoader\SheetLoader $sheetLoader, $file = null) {
$this->xPath = $xPath;
$this->valueParser = $valueParser;
$this->filePath = $filePath;
if (is_file($tss)) {
$this->file = $tss;
$this->rules = $this->cache->load($tss);
$this->filePath->addPath(dirname(realpath($tss)));
if (empty($this->rules)) $tss = file_get_contents($tss);
else return;
}
$this->sheetLoader = $sheetLoader;
$this->file = $file;
$this->tss = (new Tokenizer($tss))->getTokens();
}

public function parse($indexStart = 0) {
if (!empty($this->rules)) return $this->rules['rules'];
$rules = $this->parseTokens($indexStart);
usort($rules, [$this, 'sortRules']);
$this->checkError($rules);
//var_dump($rules);
return $this->cache->write($this->file, $rules, $this->import);
return $rules;
}

private function parseTokens($indexStart) {
Expand All @@ -46,7 +36,7 @@ private function parseTokens($indexStart) {
if ($processing = $this->processingInstructions($token, count($this->rules)+$indexStart)) {
$this->rules = array_merge($this->rules, $processing);
}
else if ($token['type'] !== Tokenizer::NEW_LINE) $this->addRules($token, $indexStart++);
else if (!in_array($token['type'], [Tokenizer::NEW_LINE, Tokenizer::AT_SIGN])) $this->addRules($token, $indexStart++);
}

return $this->rules;
Expand All @@ -57,7 +47,6 @@ private function addRules($token, $indexStart) {

$this->tss->skip(count($selector));
if (count($selector) === 0) return;

$newRules = $this->cssToRules($selector, count($this->rules)+$indexStart, $this->getProperties($this->tss->current()['value']), $token['line']);
$this->rules = $this->writeRule($this->rules, $newRules);
}
Expand All @@ -79,7 +68,6 @@ private function CssToRules($selector, $index, $properties, $line) {

private function writeRule($rules, $newRules) {
foreach ($newRules as $selector => $newRule) {

if (isset($rules[$selector])) {
$newRule->properties = array_merge($rules[$selector]->properties, $newRule->properties);
$newRule->index = $rules[$selector]->index;
Expand All @@ -94,34 +82,24 @@ private function processingInstructions($token, $indexStart) {
if ($token['type'] !== Tokenizer::AT_SIGN) return false;
$tokens = $this->tss->from(Tokenizer::AT_SIGN, false)->to(Tokenizer::SEMI_COLON, false);
$funcName = $tokens->from(Tokenizer::NAME, true)->read();
$args = $this->valueParser->parseTokens($tokens->from(Tokenizer::NAME));
$rules = $this->$funcName($args, $indexStart);

$funcToks = $tokens->from(Tokenizer::NAME);
$args = $this->valueParser->parseTokens($funcToks);
$rules = $this->$funcName($args, $indexStart, $funcToks);
$this->tss->skip(count($tokens)+2);

return $rules;
}

private function import($args, $indexStart) {
private function import($args, $indexStart, $tokens) {
$fileName = $this->filePath->getFilePath($args[0]);
$this->import[] = $fileName;
$sheet = new Sheet($fileName, $this->xPath, $this->valueParser, $this->cache, $this->filePath);
return $sheet->parse($indexStart);
}

private function sortRules($a, $b) {
//If they have the same depth, compare on index
if ($a->query === $b->query) return $this->sortPseudo($a, $b);
$this->sheetLoader->addImport($fileName);

if ($a->depth === $b->depth) $property = 'index';
else $property = 'depth';

return ($a->$property < $b->$property) ? -1 : 1;
$tssFile = new \Transphporm\SheetLoader\TSSString(file_get_contents($fileName), $this->filePath);
return $tssFile->getRules($this->xPath, $this->valueParser, $this->sheetLoader, $indexStart);
}


private function sortPseudo($a, $b) {
return count($a->pseudo) < count($b->pseudo) ? -1 :1;
private function cacheKey($args, $indexStart, $tokens) {
$this->sheetLoader->setCacheKey($tokens);
}

private function getProperties($tokens) {
Expand Down
2 changes: 1 addition & 1 deletion src/Parser/Tokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function rewind() {
}

public function add($token) {
if ($token instanceof Tokens) $this->tokens = array_merge($token->tokens);
if ($token instanceof Tokens) $this->tokens = array_merge($this->tokens, $token->tokens);
else $this->tokens[] = $token;
}

Expand Down
1 change: 0 additions & 1 deletion src/Parser/ValueResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ private function write($index, $value, $allowNull = false) {

//Postprocessing - replace values with null where allowed, or override a value at position
public function postProcess(ValueData $data, $val, $overrideVal, $allowNull) {
if ($this->getMode() !== Tokenizer::ARG) return;
foreach ($this->getResult() as $i => $value) {
if (is_scalar($value)) {
$val = ($overrideVal == $val) ? $data->read($value) : $overrideVal;
Expand Down
18 changes: 6 additions & 12 deletions src/Property/Content.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ public function run(array $values, \DomElement $element, array $rules, \Transphp
//Remove the current contents
$this->removeAllChildren($element);
//Now make a text node
if ($this->getContentMode($rules) === 'replace') $this->replaceContent($element, $values);
if ($this->getContentMode($rules) === 'replace') {
$contentReplace = new ContentReplace($this);
$contentReplace->replaceContent($element, $values);
}
else $this->appendContent($element, $values);
}
}
Expand All @@ -42,7 +45,7 @@ private function getContentMode($rules) {
public function addContentPseudo($name, ContentPseudo $contentPseudo) {
$this->contentPseudo[$name] = $contentPseudo;
}

private function processPseudo($value, $element, $pseudoMatcher) {
foreach ($this->contentPseudo as $pseudoName => $pseudoFunction) {
if ($pseudoMatcher->hasFunction($pseudoName)) {
Expand All @@ -67,8 +70,6 @@ public function getNode($node, $document) {
private function convertNode($node, $document) {
if ($node instanceof \DomElement || $node instanceof \DOMComment) {
$new = $document->importNode($node, true);
//Removing this might cause problems with caching...
//$new->setAttribute('transphporm', 'added');
}
else {
if ($node instanceof \DomText) $node = $node->nodeValue;
Expand All @@ -80,13 +81,6 @@ private function convertNode($node, $document) {
return $new;
}

private function replaceContent($element, $content) {
//If this rule was cached, the elements that were added last time need to be removed prior to running the rule again.
foreach ($this->getNode($content, $element->ownerDocument) as $node) {
$element->parentNode->insertBefore($node, $element);
}
$element->setAttribute('transphporm', 'remove');
}

private function appendContent($element, $content) {
foreach ($this->getNode($content, $element->ownerDocument) as $node) {
Expand All @@ -97,4 +91,4 @@ private function appendContent($element, $content) {
private function removeAllChildren($element) {
while ($element->hasChildNodes()) $element->removeChild($element->firstChild);
}
}
}
Loading

0 comments on commit 3feb6b7

Please sign in to comment.