205 lines
6.8 KiB
PHP
205 lines
6.8 KiB
PHP
<?php
|
|
namespace nur\data\template;
|
|
|
|
use nur\A;
|
|
use nur\b\io\IOException;
|
|
use nur\data\expr\SimpleContext;
|
|
use nur\func;
|
|
use nur\session;
|
|
use ZipArchive;
|
|
|
|
/**
|
|
* Class InterpTemplate: réimplémentation de l'ancienne classe interp
|
|
*/
|
|
class InterpTemplate extends SimpleContext implements ITemplate {
|
|
use TTemplate;
|
|
|
|
function __construct(?string $text=null, $data=null, $quote=true, bool $allowPattern=true) {
|
|
if ($data === null) $data = [];
|
|
parent::__construct($data);
|
|
$this->context = $this;
|
|
$this->setText($text, $data, $quote, $allowPattern);
|
|
}
|
|
|
|
/** @var string */
|
|
protected $text;
|
|
/** @var bool|array */
|
|
protected $quote;
|
|
/** @var bool */
|
|
protected $allowPattern;
|
|
|
|
function setText(?string $text, $data=null, $quote=true, bool $allowPattern=true): void {
|
|
$this->text = $text;
|
|
if ($data !== null) {
|
|
$this->data = A::with($data);
|
|
$this->quote = $quote;
|
|
$this->allowPattern = $allowPattern;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Traiter la valeur $value selon la valeur de $quote
|
|
*
|
|
* D'abord la transformer en chaine, puis:
|
|
* - si $quote===true, la mettre en échappement avec htmlspecialchars()
|
|
* - si $quote===false ou null, ne pas la mettre en échappement
|
|
* - sinon, ce doit être une fonction qui met la valeur en échappement
|
|
*/
|
|
private static final function quote($value, $quote) {
|
|
if (A::is_array($value)) $value = print_r(A::with($value), true);
|
|
elseif (!is_string($value)) $value = strval($value);
|
|
if ($quote === true) return htmlspecialchars($value);
|
|
else if ($quote === false || $quote === null) return $value;
|
|
else return func::call($quote, $value);
|
|
}
|
|
|
|
/**
|
|
* Traiter la valeur $value selon la valeur de $quote. Le traitement final est
|
|
* fait avec la méthode quote()
|
|
*
|
|
* - Si $quote est un tableau, alors $quote[$name] est la valeur utilisée pour
|
|
* décider comment traiter la valeur et sa valeur par défaut est true.
|
|
* - Sinon prendre la valeur $quote telle quelle
|
|
*/
|
|
private static final function quote_nv(string $name, $value, $quote) {
|
|
if (is_array($quote)) {
|
|
if (isset($quote[$name])) {
|
|
$value = self::quote($value, $quote[$name]);
|
|
} else {
|
|
# quoter par défaut quand on fournit un tableau
|
|
$value = self::quote($value, true);
|
|
}
|
|
} else {
|
|
$value = self::quote($value, $quote);
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Résoudre la valeur du nom $name
|
|
* - Si $name est de la forme '+keyp', prendre la valeur dans la configuration
|
|
* - Si $name est de la forme '*keyp', prendre la valeur dans la session
|
|
* - Sinon, $name est le chemin de clé dans le tableau $values
|
|
*/
|
|
private function resolve(string $name, ?array $values, $quote) {
|
|
switch (substr($name, 0, 1)) {
|
|
case "+":
|
|
return $this->getConfig(substr($name, 1));
|
|
case "*":
|
|
if (!session::started()) return "";
|
|
return $this->getSession(substr($name, 1));
|
|
default:
|
|
$value = A::pget_s($values, $name, "");
|
|
$value = self::quote_nv($name, $value, $quote);
|
|
return $value;
|
|
}
|
|
}
|
|
|
|
function apply() {
|
|
$text = $this->text;
|
|
$data = $this->data;
|
|
$quote = $this->quote;
|
|
## d'abord les remplacements complexes
|
|
if ($this->allowPattern) {
|
|
if ($data !== null) {
|
|
$names = array_keys($data);
|
|
$name_keys_pattern = '('.implode("|", $names).')((?:\.[a-zA-Z0-9_]+)*)';
|
|
$or_name_pattern = '|(?:(?:'.implode("|", $names).')(?:\.[a-zA-Z0-9_]+)*)';
|
|
} else {
|
|
$name_keys_pattern = null;
|
|
$or_name_pattern = '';
|
|
}
|
|
# patterns conditionnels
|
|
# autoriser un niveau de variable complexe à l'intérieur de la condition, i.e {{if(cond){{var.value}}}}
|
|
$pattern = '/\{\{(if|unless)\(((?:[+*][a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*)'.$or_name_pattern.')\)((?:[^{}]*(?:\{\{[^{}]*\}\})?(?:\{[^{}]*\})?)*)\}\}/s';
|
|
$text = preg_replace_callback($pattern, function($matches) use($data, $quote) {
|
|
$cond = $this->resolve($matches[2], $data, $quote);
|
|
if ($matches[1] == "if") {
|
|
if ($cond) $value = $matches[3];
|
|
else return "";
|
|
} elseif ($matches[1] == "unless") {
|
|
if (!$cond) $value = $matches[3];
|
|
else return "";
|
|
} else {
|
|
return $matches[0];
|
|
}
|
|
$value = self::xml($value, $data, $quote);
|
|
return $value;
|
|
}, $text);
|
|
# valeurs de config
|
|
$pattern = '/\{\{(\+[a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*)\}\}/s';
|
|
$text = preg_replace_callback($pattern, function($matches) use($data, $quote) {
|
|
$name = $matches[1];
|
|
$value = $this->resolve($name, $data, $quote);
|
|
return $value;
|
|
}, $text);
|
|
# valeurs de la session
|
|
$pattern = '/\{\{(\*[a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*)\}\}/s';
|
|
$text = preg_replace_callback($pattern, function($matches) use($data, $quote) {
|
|
$name = $matches[1];
|
|
$value = $this->resolve($name, $data, $quote);
|
|
return $value;
|
|
}, $text);
|
|
if ($data !== null) {
|
|
# indirections
|
|
$pattern = '/\{\{'.$name_keys_pattern.'\}\}/s';
|
|
$text = preg_replace_callback($pattern, function($matches) use($data, $quote) {
|
|
$name = $matches[1];
|
|
$value = $this->resolve("$name$matches[2]", $data, $quote);
|
|
return $value;
|
|
}, $text);
|
|
}
|
|
}
|
|
if ($data !== null) {
|
|
## ensuite les remplacements simples
|
|
$froms = array();
|
|
$tos = array();
|
|
foreach ($data as $name => $value) {
|
|
$froms[] = "{".$name."}";
|
|
$tos[] = self::quote_nv($name, $value, $quote);
|
|
}
|
|
$text = str_replace($froms, $tos, $text);
|
|
}
|
|
return $text;
|
|
}
|
|
|
|
function xml(?string $text, $data=null, $quote=true, bool $allowPattern=true): string {
|
|
$this->setText($text, $data, $quote, $allowPattern);
|
|
return $this->apply();
|
|
}
|
|
|
|
/**
|
|
* Comme {@link xml()} mais les valeurs ne sont pas mises en échappement par
|
|
* défaut.
|
|
*/
|
|
function string(?string $text, $data=null): string {
|
|
return $this->xml($text, $data, false);
|
|
}
|
|
|
|
/** Comme {@link xml()} pour un fichier au format LibreOffice Writer */
|
|
function odt(string $file, $data=null, $quote=true): void {
|
|
$zip = new ZipArchive();
|
|
$error = $zip->open($file);
|
|
if ($error !== true) {
|
|
throw new IOException("$file: error $error occured on open");
|
|
}
|
|
$oldContent = $zip->getFromName("content.xml");
|
|
if ($oldContent !== false) {
|
|
$newContent = $this->xml($oldContent, $data, $quote);
|
|
if ($newContent !== $oldContent) {
|
|
$zip->deleteName("content.xml");
|
|
$zip->addFromString("content.xml", $newContent);
|
|
}
|
|
}
|
|
$oldStyles = $zip->getFromName("styles.xml");
|
|
if ($oldStyles !== false) {
|
|
$newStyles = $this->xml($oldStyles, null, $quote);
|
|
if ($newStyles != $oldStyles) {
|
|
$zip->deleteName("styles.xml");
|
|
$zip->addFromString("styles.xml", $newStyles);
|
|
}
|
|
}
|
|
$zip->close();
|
|
}
|
|
}
|