Exception:
Array
(
    [message] => Class "DOMDocument" not found
    [file] => /home/www/vvveb/www/system/vtpl/vtpl.php
    [line_no] => 201
    [line] => 		$this->document = new DOMDocument();	 // <==

    [lines] => Array
        (
            [0] => 

            [1] => 		$this->selector         = $selector;

            [2] => 		$this->componentId      = $componentId;

            [3] => 		$this->componentContent = $componentContent;

            [4] => 

            [5] => 		//libxml_disable_entity_loader();

            [6] => 

            [7] => 		$this->document = new DOMDocument();	 // <==

            [8] => 		$this->legacy   = true;

            [9] => 

            [10] => 		$this->configureDomDocument($this->document);

            [11] => 	}

            [12] => 

            [13] => 	/**

        )

    [trace] => #0 /home/www/vvveb/www/system/core/view.php(168): Vtpl->__construct()
#1 /home/www/vvveb/www/system/core/view.php(190): Vvveb\System\Core\View->__construct()
#2 /home/www/vvveb/www/plugins/insert-scripts/plugin.php(64): Vvveb\System\Core\View::getInstance()
#3 /home/www/vvveb/www/plugins/insert-scripts/plugin.php(80): InsertScriptsPlugin->app()
#4 /home/www/vvveb/www/plugins/insert-scripts/plugin.php(85): InsertScriptsPlugin->__construct()
#5 /home/www/vvveb/www/system/extensions/plugins.php(354): include('...')
#6 /home/www/vvveb/www/system/core/startup.php(512): Vvveb\System\Extensions\Plugins::loadPlugins()
#7 /home/www/vvveb/www/index.php(170): Vvveb\System\Core\start()
#8 /home/www/vvveb/www/public/index.php(28): include('...')
#9 {main}
    [code] => Array
        (
            [0] =>  

            [2] => /**

            [3] =>  * Vvveb

            [4] =>  *

            [5] =>  * Copyright (C) 2022  Ziadin Givan

            [6] =>  *

            [7] =>  * This program is free software: you can redistribute it and/or modify

            [8] =>  * it under the terms of the GNU Affero General Public License as

            [9] =>  * published by the Free Software Foundation, either version 3 of the

            [10] =>  * License, or (at your option) any later version.

            [11] =>  *

            [12] =>  * This program is distributed in the hope that it will be useful,

            [13] =>  * but WITHOUT ANY WARRANTY; without even the implied warranty of

            [14] =>  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

            [15] =>  * GNU Affero General Public License for more details.

            [16] =>  *

            [17] =>  * You should have received a copy of the GNU Affero General Public License

            [18] =>  * along with this program.  If not, see .

            [19] =>  *

            [20] =>  */

            [21] => 

            [22] => //set debug to true to show the vtpl console log

            [23] => defined('VTPL_DEBUG') || define('VTPL_DEBUG', false);

            [24] => 

            [25] => defined('VTPL_HTML_MINIFY') || define('VTPL_HTML_MINIFY', false);

            [26] => define('VTPL_JS_MINIFY', false);

            [27] => define('VTPL_PHP_MINIFY', false);

            [28] => define('VTPL_CLEAN_COMP_OPT', false);

            [29] => 

            [30] => define('VTPL_DEBUG_SHOW_XPATH', true);

            [31] => define('VTPL_DONT_ALLOW_PHP', true); //don't set to false unless ABSOLUTELY NECESSARY!

            [32] => 

            [33] => // PHP placeholder constants - used to embed PHP inside the DOM without triggering parsing.

            [34] => // These are replaced with real php tags in cleanHtml().

            [35] => define('VTPL_PHP_CDATA_OPEN', '<_script language="php"> define('VTPL_PHP_CDATA_CLOSE', ']]>');

            [37] => define('VTPL_PHP_OPEN', '<_script language="php">');

            [38] => define('VTPL_PHP_CLOSE', '');

            [39] => 

            [40] => include 'macros.php';

            [41] => 

            [42] => include 'debug.php';

            [43] => 

            [44] => /**

            [45] =>  * Vtpl template engine — compiles `.tpl` selector commands against plain-HTML documents.

            [46] =>  *

            [47] =>  * See each property declaration below for individual types.

            [48] =>  */

            [49] => #[\AllowDynamicProperties]

            [50] => class Vtpl {

            [51] => 	/** @var string */

            [52] => 	private string $template = '';

            [53] => 

            [54] => 	/** @var array */

            [55] => 	private array $templatePath = [];

            [56] => 

            [57] => 	/** @var string */

            [58] => 	private string $htmlPath = '';

            [59] => 

            [60] => 	/** @var string */

            [61] => 	private string $relativePath = '';

            [62] => 

            [63] => 	/** @var DOMDocument */

            [64] => 	private Dom\HTMLDocument|DOMDocument $document;

            [65] => 

            [66] => 	/** @var bool */

            [67] => 	private bool $legacy = true;

            [68] => 

            [69] => 	/** @var ?DOMXPath */

            [70] => 	private ?DOMXPath $xpath = null;

            [71] => 

            [72] => 	/** @var string */

            [73] => 	private string $extension = 'tpl';

            [74] => 

            [75] => 	/** @var string */

            [76] => 	private string $translationFunction = '\Vvveb\__';

            [77] => 

            [78] => 	/** @var bool */

            [79] => 	private bool $removeComments = true;

            [80] => 

            [81] => 	/** @var bool */

            [82] => 	private bool $removeVattrs = false;

            [83] => 

            [84] => 	/** @var bool */

            [85] => 	private bool $minifyHtml = VTPL_HTML_MINIFY;

            [86] => 

            [87] => 	/** @var string */

            [88] => 	private string $documentType = 'html';

            [89] => 

            [90] => 	/** @var bool */

            [91] => 	private bool $checkSyntax = true;

            [92] => 

            [93] => 	/** @var ?string */

            [94] => 	private ?string $selector = null;

            [95] => 

            [96] => 	/** @var array|null */

            [97] => 	private ?array $selectors = null;

            [98] => 

            [99] => 	/** @var array|null */

            [100] => 	private ?array $replaceConstants = null;

            [101] => 

            [102] => 	/** @var ?int */

            [103] => 	private ?int $componentId = null;

            [104] => 

            [105] => 	/** @var ?string */

            [106] => 	private ?string $componentContent = null;

            [107] => 

            [108] => 	/** @var array|null */

            [109] => 	private ?array $froms = null;

            [110] => 

            [111] => 	/** @var array */

            [112] => 	private array $constants = [];

            [113] => 

            [114] => 	/** @var array */

            [115] => 	private array $variables = [];

            [116] => 

            [117] => 	/** @var array */

            [118] => 	private array $strings = [];

            [119] => 

            [120] => 	/** @var array */

            [121] => 	private array $scripts = [];

            [122] => 

            [123] => 	/** @var VtplDebug */

            [124] => 	private VtplDebug $debug;

            [125] => 

            [126] => 	/** @var array */

            [127] => 	private array $phpCode = [];

            [128] => 

            [129] => 	/** @var array */

            [130] => 	private array $modifiers = ['outerHTML', 'text', 'before', 'after', 'append', 'prepend', 'deleteAllButFirst', 'deleteAllButFirstChild', 'delete', 'if_exists', 'hide', 'addClass', 'removeClass'];

            [131] => 

            [132] => 	/** @var array */

            [133] => 	private array $variableFilters =

            [134] => 		[

            [135] => 			'capitalize'        => 'ucfirst($$0)',

            [136] => 			'html_to_text'      => 'Vvveb\htmlToText($$0)',

            [137] => 			'cdata'             => 'CDATA_START . ( $$0 ). CDATA_END',

            [138] => 			'friendly_date'     => 'Vvveb\friendlyDate($$0)',

            [139] => 			'friendly_number'   => 'Vvveb\parseQuantity($$0)',

            [140] => 			'human_readable'    => 'Vvveb\humanReadable($$0)',

            [141] => 			'date'              => 'date($$1,is_int($$0) ? $$0 : strtotime($$0))',

            [142] => 			'truncate'          => 'substr($$0, 0, $$1)',

            [143] => 			'truncate_words'    => 'Vvveb\truncateWords($$0,$$1)',

            [144] => 			'replace'           => 'str_replace($$1, $$2, $$0)',

            [145] => 			'uppercase'         => 'strtoupper($$0)',

            [146] => 			'lowercase'         => 'strtolower($$0)',

            [147] => 			'append'            => '$$1 . $$0',

            [148] => 			'prepend'           => '$$0 . $$1',

            [149] => 			'strip_html'        => 'strip_tags($$0)',

            [150] => 			'strip_newlines'    => 'str_replace("\n",\' \', $$0)',

            [151] => 			'mod'               => ['tag', 'if (@++$_modc_@@__VTPL_rand()__@@ % (int)$$1 === (int)$$2) {', 'if (@++$_modc_@@__VTPL_rand()__@@ % (int)$$1 === (int)$$2) {'],

            [152] => 			'mod_class'         => ['class', ''],

            [153] => 			'conditional_class' => ['class', ''],

            [154] => 			'iteration_class'   => ['class', ''],

            [155] => 			'number_format'     => 'number_format($$0, $$1, $$2, $$3)',

            [156] => 			'only_decimals'     => 'substr($$0, (($_strpos = strrpos($$0, \'.\')) !== false)?$_strpos + 1:-100, ($_strpos !== false)?10:false)',

            [157] => 			'without_decimals'  => 'substr($$0, 0, strrpos($$0, \'.\'))',

            [158] => 		];

            [159] => 

            [160] => 	/** @var int */

            [161] => 	private int $attributesIndex = 0;

            [162] => 

            [163] => 	/** @var int */

            [164] => 	private int $newAttributesIndex = 0;

            [165] => 

            [166] => 	/** @var array */

            [167] => 	private array $attributes = [];

            [168] => 

            [169] => 	/** @var array */

            [170] => 	private array $newAttributes = [];

            [171] => 

            [172] => 	/** @var bool */

            [173] => 	private bool $externalElements = false;

            [174] => 

            [175] => 	/** @var bool */

            [176] => 	public bool $escapeInline = false;

            [177] => 

            [178] => 	/** @var array */

            [179] => 	private array $prefix = [];

            [180] => 

            [181] => 	/**

            [182] => 	 * @param ?string $selector

            [183] => 	 * @param ?int    $componentId

            [184] => 	 * @param ?string $componentContent

            [185] => 	 */

            [186] => 	public function __construct(?string $selector = null, ?int $componentId = null, ?string $componentContent = null) {

            [187] => 		$this->templatePath = [];

            [188] => 

            [189] => 		$this->debug = new VtplDebug();

            [190] => 

            [191] => 		if (VTPL_DEBUG) {

            [192] => 			$this->debug->enable(true);

            [193] => 		}

            [194] => 

            [195] => 		$this->selector         = $selector;

            [196] => 		$this->componentId      = $componentId;

            [197] => 		$this->componentContent = $componentContent;

            [198] => 

            [199] => 		//libxml_disable_entity_loader();

            [200] => 

            [201] => 		$this->document = new DOMDocument();	 // <==

            [202] => 		$this->legacy   = true;

            [203] => 

            [204] => 		$this->configureDomDocument($this->document);

            [205] => 	}

            [206] => 

            [207] => 	/**

            [208] => 	 * Apply standard configuration options to a DOMDocument instance.

            [209] => 	 *

            [210] => 	 * @param DOMDocument $doc

            [211] => 	 *

            [212] => 	 * @return void

            [213] => 	 */

            [214] => 	private function configureDomDocument(Dom\HTMLDocument|DOMDocument $doc): void {

            [215] => 		if ($this->legacy) {

            [216] => 			$doc->preserveWhiteSpace  = true;

            [217] => 			$doc->recover             = true;

            [218] => 			$doc->strictErrorChecking = false;

            [219] => 			$doc->substituteEntities  = false;

            [220] => 			$doc->formatOutput        = false;

            [221] => 			$doc->resolveExternals    = false;

            [222] => 			$doc->validateOnParse     = false;

            [223] => 			$doc->xmlStandalone       = true;

            [224] => 		}

            [225] => 	}

            [226] => 

            [227] => 	/**

            [228] => 	 * @param bool $flag

            [229] => 	 *

            [230] => 	 * @return void

            [231] => 	 */

            [232] => 	public function removeVattrs(bool $flag = true): void {

            [233] => 		$this->removeVattrs = $flag;

            [234] => 	}

            [235] => 

            [236] => 	/**

            [237] => 	 * @param bool $flag

            [238] => 	 *

            [239] => 	 * @return void

            [240] => 	 */

            [241] => 	public function setMinifyHtml(bool $flag = true): void {

            [242] => 		$this->minifyHtml = $flag;

            [243] => 	}

            [244] => 

            [245] => 	/**

            [246] => 	 * @return string

            [247] => 	 */

            [248] => 	public function getDocumentType(): string {

            [249] => 		return $this->documentType;

            [250] => 	}

            [251] => 

            [252] => 	/**

            [253] => 	 * @return Dom\Document|DOMDocument

            [254] => 	 */

            [255] => 	public function getDocument(): Dom\Document|DOMDocument {

            [256] => 		return $this->document;

            [257] => 	}

            [258] => 

            [259] => 	/**

            [260] => 	 * @param ?string      $selector

            [261] => 	 * @param false|string $command

            [262] => 	 *

            [263] => 	 * @return void

            [264] => 	 */

            [265] => 	public function addCommand(?string $selector, false|string $command = false): void {

            [266] => 		if ($selector) {

            [267] => 			$this->template .= "\n $selector";

            [268] => 

            [269] => 			if ($command) {

            [270] => 				//if not php code then quote string

            [271] => 				if (!str_starts_with($command, ' 					$command = "'$command'";

            [273] => 				}

            [274] => 

            [275] => 				$this->template .= " = $command\n";

            [276] => 			} else {

            [277] => 				$this->template .= "\n";

            [278] => 			}

            [279] => 		}

            [280] => 	}

            [281] => 

            [282] => 	/**

            [283] => 	 * @param string $path

            [284] => 	 *

            [285] => 	 * @return void

            [286] => 	 */

            [287] => 	public function setHtmlPath(string $path): void {

            [288] => 		$this->htmlPath = $path;

            [289] => 	}

            [290] => 

            [291] => 	/**

            [292] => 	 * @param string $path

            [293] => 	 *

            [294] => 	 * @return void

            [295] => 	 */

            [296] => 	public function setRelativePath(string $path): void {

            [297] => 		$this->relativePath = $path;

            [298] => 	}

            [299] => 

            [300] => 	/**

            [301] => 	 * @param string $path

            [302] => 	 *

            [303] => 	 * @return void

            [304] => 	 */

            [305] => 	public function addTemplatePath(string $path): void {

            [306] => 		if ($path) {

            [307] => 			$this->templatePath[] = $path;

            [308] => 		}

            [309] => 	}

            [310] => 

            [311] => 	/**

            [312] => 	 * @param string $templateFile

            [313] => 	 * @param bool   $before

            [314] => 	 *

            [315] => 	 * @return void

            [316] => 	 */

            [317] => 	public function loadTemplateFile(string $templateFile, bool $before = false): void {

            [318] => 		if (file_exists($templateFile)) {

            [319] => 			if ($before) {

            [320] => 				$this->template  = file_get_contents($templateFile) . "\n\n" . $this->template;

            [321] => 			} else {

            [322] => 				$this->template .= "\n\n" . file_get_contents($templateFile);

            [323] => 			}

            [324] => 		}

            [325] => 	}

            [326] => 

            [327] => 	/**

            [328] => 	 * @param string       $templateFile

            [329] => 	 * @param false|string $extra

            [330] => 	 *

            [331] => 	 * @return ?bool

            [332] => 	 */

            [333] => 	public function loadTemplateFileFromPath(string $templateFile, false|string $extra = false): ?bool {

            [334] => 		$path = '';

            [335] => 

            [336] => 		foreach ($this->templatePath as $path) {

            [337] => 			$this->debug->log('LOAD', $path . $templateFile);

            [338] => 

            [339] => 			if (!file_exists($path . $templateFile)) {

            [340] => 				$this->debug->log('LOAD', '!EXISTS' . $path . $templateFile);

            [341] => 

            [342] => 				continue;

            [343] => 			}

            [344] => 

            [345] => 			//load from the first matching path only (paths are priority ordered,

            [346] => 			//same semantics as the import loader); loading from every path would

            [347] => 			//duplicate content when a theme overrides a base template

            [348] => 			$this->template .= file_get_contents($path . $templateFile);

            [349] => 

            [350] => 			break;

            [351] => 		}

            [352] => 

            [353] => 		if ($extra) {

            [354] => 			$this->template .= $extra;

            [355] => 		}

            [356] => 

            [357] => 		if (!$this->template) {

            [358] => 			$this->debug->log('LOAD', 'EMPTY' . $path . $templateFile);

            [359] => 

            [360] => 			return false;

            [361] => 		}

            [362] => 

            [363] => 		return true;

            [364] => 	}

            [365] => 

            [366] => 	/**

            [367] => 	 * @return void

            [368] => 	 */

            [369] => 	private function processPhpcode(): void {

            [370] => 		// Placeholders, replace variables, php code etc with placeholders.

            [371] => 		//$this->template = preg_replace('@\/\/[^\n\r]+?(?:\*\)|[\n\r])@','', $this->template);

            [372] => 		preg_match_all('/(?/s', $this->template, $phpCode);

            [373] => 

            [374] => 		$patternsPhp     = [];

            [375] => 		$placeholdersPhp = [];

            [376] => 

            [377] => 		for ($i = 0; $i < count($phpCode[1]); $i++) {

            [378] => 			$patternsPhp[]     = '/' . preg_quote($phpCode[0][$i], '/') . '/';

            [379] => 			$placeholdersPhp[] = "replace_php_code-{$i}";

            [380] => 			// double backslashes must be escaped if we want to use them in the replacement argument

            [381] => 			$phpCode[0][$i] = str_replace('\\\\', '\\\\\\\\', $phpCode[1][$i]);

            [382] => 		}

            [383] => 

            [384] => 		if ($placeholdersPhp) {

            [385] => 			$this->template = preg_replace($patternsPhp, $placeholdersPhp, $this->template);

            [386] => 		}

            [387] => 

            [388] => 		$this->phpCode   = $phpCode[0];

            [389] => 	}

            [390] => 

            [391] => 	/**

            [392] => 	 * @return void

            [393] => 	 */

            [394] => 	private function processFroms(): void {

            [395] => 		// Froms - from(index.html|#element)

            [396] => 		preg_match_all('/from\(([^\|]+)\|(.+)\)/', $this->template, $froms);

            [397] => 		$patternsFroms     = [];

            [398] => 		$placeholdersFroms = [];

            [399] => 

            [400] => 		for ($i = 0; $i < count($froms[1]); $i++) {

            [401] => 			$patternsFroms[]     = '/' . preg_quote($froms[0][$i], '/') . '/';

            [402] => 			$placeholdersFroms[] = "replace_from-{$i}\n";

            [403] => 			// double backslashes must be escaped if we want to use them in the replacement argument

            [404] => 			$froms[0][$i] = str_replace('\\\\', '\\\\\\\\', $froms[1][$i]);

            [405] => 		}

            [406] => 

            [407] => 		if ($froms[0]) {

            [408] => 			$this->template = preg_replace($patternsFroms, $placeholdersFroms, $this->template);

            [409] => 		}

            [410] => 

            [411] => 		$this->froms     = $froms;

            [412] => 	}

            [413] => 

            [414] => 	/**

            [415] => 	 * @return void

            [416] => 	 */

            [417] => 	private function processStrings(): void {

            [418] => 		// strings

            [419] => 		//single quote

            [420] => 		preg_match_all("/=\\s+'[^'\\\r\n]*(?:\\.[^'\\\r\n]*)*'(?!\\])/s", $this->template, $stringsSingle);

            [421] => 		//doube quote

            [422] => 		preg_match_all('/=\s+"[^"\\\r\n]*(?:\.[^"\\\r\n]*)*"(?!\])/s', $this->template, $strings);

            [423] => 

            [424] => 		$strings             = array_values(array_unique($strings[0]));

            [425] => 		$stringsSingle       = array_values(array_unique($stringsSingle[0]));

            [426] => 		$strings             = array_merge((array)$strings, (array)$stringsSingle);

            [427] => 		$patternsStrings     = [];

            [428] => 		$placeholdersStrings = [];

            [429] => 

            [430] => 		for ($i = 0; $i < count($strings); $i++) {

            [431] => 			$string                = trim($strings[$i], '= ');

            [432] => 			$patternsStrings[]     = '/' . preg_quote($string, '/') . '/';

            [433] => 			$placeholdersStrings[] = "replace_string-{$i}";

            [434] => 			// double backslashes must be escaped if we want to use them in the replacement argument

            [435] => 			$strings[$i] = str_replace('\\\\', '\\\\\\\\', $string);

            [436] => 		}

            [437] => 

            [438] => 		if ($strings) {

            [439] => 			$this->template = preg_replace($patternsStrings, $placeholdersStrings, $this->template);

            [440] => 		}

            [441] => 

            [442] => 		$this->strings   = $strings;

            [443] => 	}

            [444] => 

            [445] => 	/**

            [446] => 	 * @return void

            [447] => 	 */

            [448] => 	private function processVariables(): void {

            [449] => 		preg_match_all('/(?template, $variables);

            [450] => 

            [451] => 		$patternsVariables     = [];

            [452] => 		$placeholdersVariables = [];

            [453] => 		$variables[1]          = array_unique($variables[1]);

            [454] => 		usort($variables[1], fn ($a, $b) => strlen($b) <=> strlen($a));

            [455] => 

            [456] => 		for ($i = 0; $i < count($variables[1]); $i++) {

            [457] => 			$patternsVariables[]     = '/' . preg_quote($variables[1][$i], '/') . '/';

            [458] => 			$placeholdersVariables[] = "replace_variable-{$i}";

            [459] => 			// double backslashes must be escaped if we want to use them in the replacement argument

            [460] => 		}

            [461] => 

            [462] => 		$this->template = preg_replace($patternsVariables, $placeholdersVariables, $this->template);

            [463] => 

            [464] => 		$this->variables = $variables[1];

            [465] => 	}

            [466] => 

            [467] => 	/**

            [468] => 	 * Process the raw template commands: expand imports, extract php/variable/

            [469] => 	 * from/string placeholders into lookup tables, then parse selector lines.

            [470] => 	 */

            [471] => 	private function processTemplateFile(): void {

            [472] => 		if (!$this->template) {

            [473] => 			return;

            [474] => 		}

            [475] => 

            [476] => 		$this->expandImports();

            [477] => 		$this->processPhpcode();

            [478] => 		$this->processVariables();

            [479] => 		$this->processFroms();

            [480] => 		$this->processStrings();

            [481] => 

            [482] => 		$this->parseSelectorLines();

            [483] => 	}

            [484] => 

            [485] => 	/**

            [486] => 	 * Expand import(file.tpl) directives by inlining the referenced files,

            [487] => 	 * repeating until no imports remain (imports may nest).

            [488] => 	 */

            [489] => 	private function expandImports(): void {

            [490] => 		$foundImports = true;

            [491] => 		//expand imports

            [492] => 		while ($foundImports) {

            [493] => 			$foundImports = preg_match_all("/import\\(([^\\&%'`\\@{}~!#\\(\\)&\\^\\+,=\\[\\]]*?\\.{$this->extension})\\,?(.+)?\\);?/", $this->template, $imports);

            [494] => 

            [495] => 			for ($i = 0; $i < count($imports[0]); $i++) {

            [496] => 				$content       = '';

            [497] => 				$importContent = '';

            [498] => 

            [499] => 				foreach ($this->templatePath as $path) {

            [500] => 					$import = $imports[1][$i];

            [501] => 

            [502] => 					if (str_starts_with($import, '/plugins/')) {

            [503] => 						$importFile = DIR_PLUGINS . substr($import, 9);

            [504] => 					} else {

            [505] => 						if ($import[0] === '/') {

            [506] => 							$importFile = DIR_ROOT . $import;

            [507] => 						} else {

            [508] => 							$importFile = $path . $import;

            [509] => 						}

            [510] => 					}

            [511] => 

            [512] => 					if (str_contains($importFile, '*')) {

            [513] => 						$files = glob($importFile);

            [514] => 					} else {

            [515] => 						$files = [$importFile];

            [516] => 					}

            [517] => 

            [518] => 					$found = false;

            [519] => 

            [520] => 					foreach ($files as $importFile) {

            [521] => 						if (file_exists($importFile)) {

            [522] => 							$parameter = trim($imports[2][$i]);

            [523] => 							$this->debug->log('LOAD', $importFile);

            [524] => 

            [525] => 							if (!empty($parameter)) {

            [526] => 								//if php array then parameter is a template variables list

            [527] => 								if ($parameter[0] === '{'/* || $parameter[0] == '['*/) {

            [528] => 									$importContent = file_get_contents($importFile);

            [529] => 

            [530] => 									//process template

            [531] => 									$templateParams = json_decode($parameter, true);

            [532] => 

            [533] => 									foreach ($templateParams as $name => $value) {

            [534] => 										$importContent = str_replace('{{' . $name . '}}', $value, $importContent);

            [535] => 									}

            [536] => 								} else {

            [537] => 									//parameter is a css selector to check if the element exists and conditionally include template

            [538] => 									$elements = $this->query($parameter);

            [539] => 									if ($elements && $elements->length) {

            [540] => 										//found, load template below

            [541] => 										$importContent = file_get_contents($importFile);

            [542] => 									} else {

            [543] => 										//not found, replace import with nothing

            [544] => 										$this->template = str_replace($imports[0][$i], '', $this->template);

            [545] => 

            [546] => 										continue;

            [547] => 									}

            [548] => 								}

            [549] => 							} else {

            [550] => 								$importContent = file_get_contents($importFile);

            [551] => 							}

            [552] => 

            [553] => 							//remove comments

            [554] => 							$importContent = preg_replace("/(? 							$content .= $importContent . "\n";

            [556] => 

            [557] => 							$found = true;

            [558] => 						} else {

            [559] => 							$this->debug->log('VTPL_IMPORT_FILE_NOT_EXIST', $importFile);

            [560] => 						}

            [561] => 					}

            [562] => 

            [563] => 					if ($found) {

            [564] => 						break;

            [565] => 					}

            [566] => 				}

            [567] => 

            [568] => 				$this->template = str_replace($imports[0][$i], $content, $this->template);

            [569] => 			}

            [570] => 		}

            [571] => 	}

            [572] => 

            [573] => 	/**

            [574] => 	 * Normalize template whitespace/comments and split the remaining command

            [575] => 	 * text into selector lines ("selector = value" pairs or bare selectors).

            [576] => 	 */

            [577] => 	private function parseSelectorLines(): void {

            [578] => 		//remove comments

            [579] => 		$this->template = preg_replace("/(?template);

            [580] => 		$this->template = preg_replace('/\n+/', "\n", $this->template);

            [581] => 		$this->template = preg_replace('/(?<=\=)\s*\n/', '', $this->template);

            [582] => 

            [583] => 		$this->template = str_replace("\n\n", "\n", trim($this->template));

            [584] => 		$lines          = explode("\n", $this->template);

            [585] => 

            [586] => 		foreach ($lines as $line) {

            [587] => 			$matches = [];

            [588] => 			//check if "=" exists for pair

            [589] => 			if (preg_match('/(.*?)(=)\s*(replace_.*|true|false);?/s', $line, $matches) && $matches[1]) {

            [590] => 				$this->selectors[] = [trim($matches[1]), trim($matches[3])];

            [591] => 			} else {

            [592] => 				//single command, no pair

            [593] => 				$line = trim($line);

            [594] => 

            [595] => 				if ($line) {

            [596] => 					$this->selectors[] = [$line];

            [597] => 				}

            [598] => 			}

            [599] => 		}

            [600] => 	}

            [601] => 

            [602] => 	/**

            [603] => 	 * Convert a CSS-selector into an xPath-query.

            [604] => 	 *

            [605] => 	 * @param string $selector The CSS-selector

            [606] => 	 *

            [607] => 	 * @return string

            [608] => 	 */

            [609] => 	public function cssToXpath(string $selector): string {

            [610] => 		$selector = (string)$selector;

            [611] => 

            [612] => 		//protect quoted literals so later rules (brackets/class/id) can't mangle

            [613] => 		//attribute values like name="settings[frontend]" — placeholders carry no

            [614] => 		//special css characters and are restored after the rule table runs

            [615] => 		$quotedLiterals = [];

            [616] => 		$selector       = preg_replace_callback('/"[^"]*"/', function(array $m) use (&$quotedLiterals): string {

            [617] => 			$key                  = '__QT' . count($quotedLiterals) . '__';

            [618] => 			$quotedLiterals[$key] = substr($m[0], 1, -1);

            [619] => 

            [620] => 			return '"' . $key . '"';

            [621] => 		}, $selector);

            [622] => 

            [623] => 		//convert , to | union operator to allow multiple queries

            [624] => 		$selector = str_replace(',', '|', $selector);

            [625] => 

            [626] => 		$cssSelector = [

            [627] => 			// E > F: Matches any F element that is a child of an element E

            [628] => 			'/\s*>\s*/',	// /

            [629] => 

            [630] => 			// E + F: Matches any F element immediately preceded by an element

            [631] => 			'/\s+\+\s+/',	// /following-sibling::*[1]/self::

            [632] => 

            [633] => 			// E F: Matches any F element that is a descendant of an E element

            [634] => 			'/([\w\*="\[\]#._-])\s+([\w\*="\[\]#._-])/', 	// \1//\2'  //'/([a-zA-Z\*="\[\]#._-])\s+([a-zA-Z\*#._-])/',

            [635] => 

            [636] => 			// E:first-child: Matches element E when E is the first child of its parent

            [637] => 			'/([a-z#\.]\w*):first-child/',	// *[1]/self::\1

            [638] => 

            [639] => 			// E:nth-child() Matches the nth child element

            [640] => 			'/([a-z#\.]\w*):nth-child\((\d+)\)/',	// *[\2]/self::\1

            [641] => 

            [642] => 			// E:first: Matches the first element from the set

            [643] => 			'/([a-z#\.]\w*):first/',	// \1[1]

            [644] => 

            [645] => 			// E:nth(2): Matches the nth element from the set

            [646] => 			'/([a-z#\.]\w*):nth\((\d+)\)/',		// \1[\2]

            [647] => 

            [648] => 			// E[foo=warning]: Matches any E element whose unquoted "foo" attribute value equals "warning"

            [649] => 			// bracketed value first (name=settings[enabled] — invalid css but common in templates),

            [650] => 			// so the plain unquoted rule can't stop at the inner ']'

            [651] => 			'/([a-z]\w*|\*)\[([a-z][\w\-_]*)\=([\w\-\.]+)\[([\w\-_]+)\]\]/',	// \1[ contains( concat( " ", @\2, " " ), concat( " ", "\3[\4]", " " ) ) ]

            [652] => 			'/\[([a-z][\w\-_]*)\=([\w\-\.]+)\[([\w\-_]+)\]\]/',	//	*[ contains( concat( " ", @\1, " " ), concat( " ", "\2[\3]", " " ) ) ]

            [653] => 			'/([a-z]\w*|\*)\[([a-z][\w\-_]*)\=([^\s\]"]+)\]/',	// \1[ contains( concat( " ", @\2, " " ), concat( " ", "\3", " " ) ) ]

            [654] => 

            [655] => 			// E[foo="warning"]: Matches any E element whose "foo" attribute value is exactly equal to "warning"

            [656] => 			'/([a-z]\w*)\[([a-z][\w\-_]*)\="([^"]*)"\]/',	// \1[ contains( concat( " ", @\2, " " ), concat( " ", "\3", " " ) ) ]

            [657] => 

            [658] => 			// E[foo]: Matches any E element with the "foo" attribute set (whatever the value)

            [659] => 			'/(? 

            [661] => 			// E[!foo]: Matches any E element without the "foo" attribute set

            [662] => 			'/([a-z]\w*)\[!([a-z][\w\-_]*)\]/',		//	\1 [ not(@\2) ]

            [663] => 

            [664] => 			// [foo=warning]: Matches any element whose unquoted "foo" attribute value equals "warning"

            [665] => 			'/\[([a-z][\w\-_]*)\=([^\s\]"]+)\]/',	//	*[ contains( concat( " ", @\1, " " ), concat( " ", "\2", " " ) ) ]

            [666] => 

            [667] => 			// [foo="warning"]: Matches any element whose "foo" attribute value is exactly equal to "warning"

            [668] => 			'/\[([a-z][\w\-_]*)\=\"(.*)\"\]/',	//	*[@\1 = "\2"]

            [669] => 

            [670] => 			// element[foo*="warning"]: Matches any element whose "foo" attribute value contains the string "warning"

            [671] => 			'/([a-z]\w*|\*)\[([a-z][\w\-_]*)\*\=\"([^"]+)\"\]/',	// \1[ contains( @\2, "\3" ) ]

            [672] => 

            [673] => 			// [foo*="warning"]: Matches any element whose "foo" attribute value contains the string "warning" and has other attributes

            [674] => 			'/(?<=\])\[([a-z][\w\-_]*)\*\=\"([^"]+)\"\]/',		//	[contains(@\1,"\2")]

            [675] => 

            [676] => 			// [foo*="warning"]: Matches any element whose "foo" attribute value contains the string "warning"

            [677] => 			'/\[([a-z][\w\-_]*)\*\=\"([^"]+)\"\]/',		//	[ contains( @\1, "\2" ) ]

            [678] => 

            [679] => 			// [foo^="warning"]: Matches any element whose "foo" attribute value begins with the string "warning"

            [680] => 			'/\[([a-z][\w_\-]*)\^\=\"([^"]+)\"\]/',		//	[starts-with(@\1,"\2")]

            [681] => 

            [682] => 			// [foo$="warning"]: Matches any element whose "foo" attribute value ends  with the string "warning"

            [683] => 			'/\[([a-z][\w_\-]*)\$\=\"([^"]+)\"\]/',		// [ends-with(@\1,"\2")]

            [684] => 

            [685] => 			// [foo][baz]: Matches any element with the "foo" attribute set (whatever the value)

            [686] => 			'/(?<=\])(? 

            [688] => 			// [foo]: Matches any element with the "foo" attribute set (whatever the value)

            [689] => 			'/(? 

            [691] => 			// [!foo]: Matches any element without the "foo" attribute set (whatever the value)

            [692] => 			'/\[!([a-z][\w_\-]*)\]/',	//	*[ not(@\1) ]

            [693] => 

            [694] => 			// element[foo*]: Matches any element that starts with "foo" attribute (whatever the value)

            [695] => 			'/(\w+)\[([a-z][\w\-]*)\*\]/',	//	\1 [ @*[starts-with(name(), "\2")] ]

            [696] => 

            [697] => 			// [foo*]: Matches any element that starts with "foo" attribute (whatever the value) and has other attributes

            [698] => 			'/(?<=\])\[([a-z][\w\-]*)\*\]/',	//	[ @*[starts-with(name(), "\1")] ]

            [699] => 

            [700] => 			// [foo*]: Matches any element that starts with "foo" attribute (whatever the value) and is a single attribute

            [701] => 			'/(? 

            [703] => 			// div.warn*: HTML only. The same as DIV[class*="warning"]

            [704] => 			'/([a-z]\w*|\*)\.([a-z][\w\-_]*)\*/',	//	\1[ contains( concat( " ", @class, " " ), concat( " ", "\2") ) ]

            [705] => 

            [706] => 			// div.warning: HTML only. The same as DIV[class~="warning"]

            [707] => 			'/([a-z]\w*|\*)\.([a-z][\w\-_]*)+/',	//	\1[ contains( concat( " ", @class, " " ), concat( " ", "\2", " " ) ) ]

            [708] => 

            [709] => 			// .warn*: HTML only. The same as [class*="warning"]

            [710] => 			'/\.([a-z][\w\-\_]*)\*/',	//	*[ contains( concat( " ", @class, " " ), concat( " ", "\1") ) ]

            [711] => 

            [712] => 			// .warning: HTML only. The same as [class~="warning"]  and has other attributes

            [713] => 			'/(?<=\])\.([a-z][\w\-\_]*)+/',	//	*[ contains( concat( " ", @class, " " ), concat( " ", "\1", " " ) ) ]

            [714] => 

            [715] => 			// .warning: HTML only. The same as [class~="warning"]

            [716] => 			'/\.([a-z][\w\-\_]*)+/',	//	*[ contains( concat( " ", @class, " " ), concat( " ", "\1", " " ) ) ]

            [717] => 

            [718] => 			// E#myid: Matches any E element with id-attribute equal to "myid"

            [719] => 			'/([a-z]\w*)\#([a-z][\w\-_]*)/',	//	\1[ @id = "\2" ]

            [720] => 

            [721] => 			// #myid: Matches any E element with id-attribute equal to "myid"

            [722] => 			'/\#([a-z][\w\-_]*)/',		//	*[ @id = "\1" ]

            [723] => 

            [724] => 			// negation pseudo selector

            [725] => 			'/\:not\(([^\)]+?)\)/', //	:not(\1)

            [726] => 

            [727] => 			// empty pseudo selector

            [728] => 			'/\:empty/', //:empty

            [729] => 		];

            [730] => 

            [731] => 		$xpathQuery = [

            [732] => 			'/', //element > child

            [733] => 			'/following-sibling::*[1]/self::', // element + precedent

            [734] => 			'\1//\2', //element descendent

            [735] => 			'*[1]/self::\1', //element:first-child

            [736] => 			'*[\2]/self::\1', //element:nth-child(2)

            [737] => 			'\1[1]', //element:first

            [738] => 			'\1[\2]', //element:nth(2)

            [739] => 			'\1[ contains( concat( " ", @\2, " " ), concat( " ", "\3[\4]", " " ) ) ]', //element[attribute=bwarningacketed unquoted]

            [740] => 			'*[ contains( concat( " ", @\1, " " ), concat( " ", "\2[\3]", " " ) ) ]', //[attribute=bracketed unquoted]

            [741] => 			'\1[ contains( concat( " ", @\2, " " ), concat( " ", "\3", " " ) ) ]', //element[attribute=string unquoted]

            [742] => 			'\1[ contains( concat( " ", @\2, " " ), concat( " ", "\3", " " ) ) ]', //element[attribute="string"]

            [743] => 			'\1 [ @\2 ]', //element[attribute]

            [744] => 			'\1 [ not(@\2) ]', //element[!attribute]

            [745] => 			'*[ contains( concat( " ", @\1, " " ), concat( " ", "\2", " " ) ) ]', //[foo=warning unquoted]

            [746] => 			'*[@\1 = "\2"]', //[foo="warning"]

            [747] => 			'\1[ contains( @\2, "\3" ) ]', //element[foo*="warning"]

            [748] => 			'[contains(@\1,"\2")]', //[foo*="warning"]

            [749] => 			'[ contains( @\1, "\2" ) ]', //[foo*="warning"]

            [750] => 			'[starts-with(@\1,"\2")]', //[foo^="warning"]

            [751] => 			'[ends-with(@\1,"\2")]', //[foo$="warning"]

            [752] => 			'[ @\1 ]', //[attribute][attribute]

            [753] => 			'*[ @\1 ]', //[attribute]

            [754] => 			'[ not(@\1) ]', //[!attribute]

            [755] => 			'\1 [ @*[starts-with(name(), "\2")] ]', //element[attr*]

            [756] => 			'[ @*[starts-with(name(), "\1")] ]', //[attr*] - attribute with other attributes

            [757] => 			'*[ @*[starts-with(name(), "\1")] ]', //[attr*] - single attribute

            [758] => 			'\1[ contains( concat( " ", @class, " " ), concat( " ", "\2") ) ]', //element[class*="string"]

            [759] => 			'\1[ contains( concat( " ", @class, " " ), concat( " ", "\2", " " ) ) ]', //element[class~="string"]

            [760] => 			'*[ contains( concat( " ", @class, " " ), concat( " ", "\1") ) ]', //[class*="string"]

            [761] => 			'[ contains( concat( " ", @class, " " ), concat( " ", "\1", " " ) ) ]', //element[class~="string"]

            [762] => 			'*[ contains( concat( " ", @class, " " ), concat( " ", "\1", " " ) ) ]', //element[class~="string"]

            [763] => 			'\1[ @id = "\2" ]', //element#id

            [764] => 			'*[ @id = "\1" ]', //#id

            [765] => 			//'(\1 except \2)', //negation pseudo selector

            [766] => 			'[not(self::\1)]', //negation pseudo selector

            [767] => 			'[not(text())]', //empty pseudo selector

            [768] => 		];

            [769] => 

            [770] => 		$result = (string)'//' . preg_replace($cssSelector, $xpathQuery, $selector);

            [771] => 		$result = strtr($result, $quotedLiterals);

            [772] => 		$this->debug->log('CSS_XPATH_TRANSFORM', $result);

            [773] => 

            [774] => 		return $result;

            [775] => 	}

            [776] => 

            [777] => 	/**

            [778] => 	 * @param string $selector

            [779] => 	 *

            [780] => 	 * @return Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|false

            [781] => 	 */

            [782] => 	public function query(string $selector): Dom\NodeList|DOMNodeList|false {

            [783] => 		if ($this->legacy) {

            [784] => 			$xpath    = $this->cssToXpath($selector);

            [785] => 			$result   = $this->xpath->query($xpath);

            [786] => 		} else {

            [787] => 			//missing css support for

            [788] => 			//[data-v-menu-item-*] => [attribute-name^="data-v-menu-item-"]

            [789] => 			$result = $this->document->querySelectorAll($selector);

            [790] => 		}

            [791] => 

            [792] => 		return $result;

            [793] => 	}

            [794] => 

            [795] => 	/**

            [796] => 	 * @param string                                                   $modifier

            [797] => 	 * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|false|null $elements

            [798] => 	 * @param array|string                           $val

            [799] => 	 *

            [800] => 	 * @return void

            [801] => 	 */

            [802] => 	private function processElements(string $modifier, null|Dom\NodeList|DOMNodeList|false $elements, array|string $val): void {

            [803] => 		//no match (or invalid xpath expression) - nothing to modify

            [804] => 		if (!$elements || !$elements->length) {

            [805] => 			return;

            [806] => 		}

            [807] => 

            [808] => 		switch ($modifier) {

            [809] => 			case 'deleteAllButFirstChild':

            [810] => 				$this->deleteAllButFirstChild($elements);

            [811] => 

            [812] => 				break;

            [813] => 

            [814] => 			case 'deleteAllButFirst':

            [815] => 				$this->deleteAllButFirst($elements);

            [816] => 

            [817] => 				break;

            [818] => 

            [819] => 			case 'outerHTML':

            [820] => 				$this->outerHTML($elements, $val);

            [821] => 

            [822] => 				break;

            [823] => 

            [824] => 			case 'text':

            [825] => 			case 'innerText':

            [826] => 				$this->innerText($elements, $val);

            [827] => 

            [828] => 				break;

            [829] => 

            [830] => 			case 'before':

            [831] => 				$this->insertBefore($elements, $val);

            [832] => 

            [833] => 				break;

            [834] => 

            [835] => 			case 'after':

            [836] => 				$this->insertAfter($elements, $val);

            [837] => 

            [838] => 				break;

            [839] => 

            [840] => 			case 'append':

            [841] => 				$this->append($elements, $val);

            [842] => 

            [843] => 				break;

            [844] => 

            [845] => 			case 'prepend':

            [846] => 				$this->prepend($elements, $val);

            [847] => 

            [848] => 				break;

            [849] => 

            [850] => 			case 'delete':

            [851] => 				$this->delete($elements);

            [852] => 

            [853] => 				break;

            [854] => 

            [855] => 			case 'if_exists':

            [856] => 				$this->ifExists($elements, $val);

            [857] => 

            [858] => 				break;

            [859] => 

            [860] => 			case 'hide':

            [861] => 				$this->hide($elements, $val);

            [862] => 

            [863] => 				break;

            [864] => 

            [865] => 			case 'addClass':

            [866] => 				$this->addClass($elements, $val);

            [867] => 

            [868] => 				break;

            [869] => 

            [870] => 			case 'removeClass':

            [871] => 				$this->removeClass($elements, $val);

            [872] => 

            [873] => 				break;

            [874] => 

            [875] => 			case 'addNewAttribute':

            [876] => 				$this->addNewAttribute($elements, $val);

            [877] => 

            [878] => 				break;

            [879] => 

            [880] => 			case 'removeAttribute':

            [881] => 				$this->removeAttribute($elements, $val);

            [882] => 

            [883] => 				break;

            [884] => 

            [885] => 			case '':

            [886] => 				$this->innerHTML($elements, $val);

            [887] => 

            [888] => 				break;

            [889] => 

            [890] => 			default:

            [891] => 				$this->setAttribute($elements, $modifier, $val);

            [892] => 		}

            [893] => 	}

            [894] => 

            [895] => 	/**

            [896] => 	 * @return void

            [897] => 	 */

            [898] => 	/**

            [899] => 	 * Expands comma-separated selector lines ("a, b|mod = value") into one

            [900] => 	 * selector entry per selector, preserving the modifier and value.

            [901] => 	 */

            [902] => 	private function expandCommaSelectors(): void {

            [903] => 		$count = count($this->selectors);

            [904] => 		for ($i = 0; $i < $count; $i++) {

            [905] => 			$data = $this->selectors[$i];

            [906] => 			//$data[0] = selector

            [907] => 			if (str_contains($data[0], ',')) {

            [908] => 				$selectorComponents       = explode('|', $data[0]);

            [909] => 				$selector                 = $selectorComponents[0];

            [910] => 				$modifier                 = (isset($selectorComponents[1])) ? trim($selectorComponents[1]) : '';

            [911] => 

            [912] => 				$selectors    = explode(',', $selector);

            [913] => 				$newSelectors = [];

            [914] => 				foreach ($selectors as $selector) {

            [915] => 					$newSelectors[] = [trim($selector) . ($modifier ? '|' . $modifier : ''), $data[1] ?? ''];

            [916] => 				}

            [917] => 

            [918] => 				$newCount = count($newSelectors) - 1;

            [919] => 

            [920] => 				array_splice($this->selectors, $i, 1, $newSelectors);

            [921] => 				$count += $newCount;

            [922] => 				$i += $newCount;

            [923] => 			}

            [924] => 		}

            [925] => 	}

            [926] => 

            [927] => 	private function processTemplate(): void {

            [928] => 		if (isset($this->selectors)) {

            [929] => 			//check for multiple selectors

            [930] => 

            [931] => 			$this->expandCommaSelectors();

            [932] => 

            [933] => 			foreach ($this->selectors as &$data) {

            [934] => 				$selector                 = $data[0];

            [935] => 				$selectorComponents       = explode('|', $selector);

            [936] => 				$selector                 = $selectorComponents[0];

            [937] => 				$modifier                 = (isset($selectorComponents[1])) ? trim($selectorComponents[1]) : '';

            [938] => 				$value                    = (isset($data[1])) ? $data[1] : '';

            [939] => 				$this->externalElements   = false;

            [940] => 

            [941] => 				//enable disable debugging

            [942] => 				if (!$selector) {

            [943] => 					continue;

            [944] => 				}

            [945] => 				$isConstant = false;

            [946] => 

            [947] => 				if (str_contains($selector, '@@_CONSTANT_')) {

            [948] => 					$isConstant = true;

            [949] => 				}

            [950] => 

            [951] => 				$prefix   = &$this->prefix;

            [952] => 				$isPrefix = false;

            [953] => 

            [954] => 				/**

            [955] => 				get all set prefix and save values

            [956] => 				@my-prefix = #selector .class[attribute]

            [957] => 				 */

            [958] => 				$selector = preg_replace_callback(

            [959] => 					'/^@([a-zA-Z][a-zA-Z0-9_-]+)(?![a-zA-Z0-9_-])\s+=\s+(.+)$/',

            [960] => 					function($matches) use (&$prefix, &$isPrefix) {

            [961] => 						$name  = $matches[1];

            [962] => 						$value = $matches[2];

            [963] => 

            [964] => 						$prefix[$name] = $value;

            [965] => 						$isPrefix      = true;

            [966] => 

            [967] => 						return trim($matches[0]);

            [968] => 					},

            [969] => 					$selector

            [970] => 				);

            [971] => 

            [972] => 				//if the line is only for set prefix then skip further processing

            [973] => 				if ($isPrefix) {

            [974] => 					continue;

            [975] => 				}

            [976] => 				//replace all prefix with actual values

            [977] => 				$selector = preg_replace_callback(

            [978] => 					'/@([a-zA-Z][a-zA-Z0-9_-]+)(?![a-zA-Z0-9_-])/',

            [979] => 					function($matches) use (&$prefix) {

            [980] => 						$name = $matches[1];

            [981] => 

            [982] => 						if (isset($prefix[$name])) {

            [983] => 							return $prefix[$name];

            [984] => 						}

            [985] => 

            [986] => 						return trim($matches[0]);

            [987] => 					},

            [988] => 					$selector

            [989] => 				);

            [990] => 

            [991] => 				$this->debug->log('SELECTOR', $selector);

            [992] => 

            [993] => 				$val = $value;

            [994] => 

            [995] => 				if ($selector === 'debug') {

            [996] => 					$this->debug->enable(($value === 'true') ? true : false);

            [997] => 				} else {

            [998] => 					$valueElements = explode('-', $value);

            [999] => 

            [1000] => 					switch ($valueElements[0]) {

            [1001] => 						case 'replace_string':

            [1002] => 							$val   = $value;

            [1003] => 							$index = (int)$valueElements[1];

            [1004] => 

            [1005] => 							if (isset($this->strings[$index])) {

            [1006] => 								$val = trim($this->strings[$index], '"\'');

            [1007] => 								$this->debug->log('SELECTOR_STRING', $this->strings[$index]);

            [1008] => 							} else {

            [1009] => 								$this->debug->log('SELECTOR_STRING', "$index not found for $value");

            [1010] => 							}

            [1011] => 

            [1012] => 							break;

            [1013] => 

            [1014] => 						case 'replace_php_code':

            [1015] => 							$phpCode = $this->phpCode[(int)$valueElements[1]] ?? '';

            [1016] => 							/*

            [1017] => 							if (VTPL_PHP_MINIFY === true) {

            [1018] => 								$phpCode = $this->minifyPhp($phpCode);

            [1019] => 							}

            [1020] => 							*/

            [1021] => 

            [1022] => 							if ($modifier && !in_array($modifier, $this->modifiers)) {

            [1023] => 								$val = '<_script language="php">minifyPhp($phpCode) . ']]>';

            [1024] => 							} else {

            [1025] => 								if ($modifier === 'if_exists' || $modifier === 'hide') {

            [1026] => 									//these modifiers take a boolean expression — strip a

            [1027] => 									//statement wrapper (echo/print + trailing ;) so ifExists()

            [1028] => 									//never wraps statements in isset()

            [1029] => 									$expr = trim($phpCode);

            [1030] => 									$expr = preg_replace('/^\s*(?:echo|print)\s+/s', '', $expr) ?? $expr;

            [1031] => 									$expr = preg_replace('/\s*;\s*$/s', '', $expr) ?? $expr;

            [1032] => 									$val  = str_contains($expr, ';') ? "({$phpCode})" : "({$expr})";

            [1033] => 								} else {

            [1034] => 									$val = VTPL_PHP_CDATA_OPEN . $this->minifyPhp($phpCode) . VTPL_PHP_CDATA_CLOSE;

            [1035] => 								}

            [1036] => 							}

            [1037] => 							$this->debug->log('SELECTOR_PHP', $phpCode);

            [1038] => 

            [1039] => 							break;

            [1040] => 

            [1041] => 						case 'replace_variable':

            [1042] => 							$variable = $this->variables[(int)$valueElements[1]] ?? '';

            [1043] => 							//variable chains keep the classic isset() guard; composite

            [1044] => 							//expressions ($x ?? '', ternaries, calls …) are evaluated

            [1045] => 							//directly — isset() is invalid on expressions (php fatal)

            [1046] => 							$isChain  = (bool)preg_match('/^\$[a-zA-Z_][a-zA-Z0-9_]*(?:\s*(?:->\s*\w+|\[(?:\'[^\']*\'|"[^"]*"|[^\[\]]*)\]))*$/s', trim($variable));

            [1047] => 							$evalExpr = VTPL_PHP_CDATA_OPEN . '$_vtpl_v = (' . $variable . '); if ($_vtpl_v !== null && $_vtpl_v !== \'\') echo htmlspecialchars((string) $_vtpl_v);' . VTPL_PHP_CDATA_CLOSE;

            [1048] => 							if ($modifier) {

            [1049] => 								if ($modifier === 'if_exists' || $modifier === 'hide') {

            [1050] => 									$val = $variable;

            [1051] => 								} elseif (!in_array($modifier, $this->modifiers) || $modifier === 'addClass') {

            [1052] => 									$val = $isChain

            [1053] => 										? VTPL_PHP_CDATA_OPEN . 'if (isset(' . $variable . ')) echo htmlspecialchars(' . $variable . ');' . VTPL_PHP_CDATA_CLOSE

            [1054] => 										: $evalExpr;

            [1055] => 								}

            [1056] => 							} else {

            [1057] => 								if ($isChain) {

            [1058] => 									$echo = $isConstant ? 'echo ' . $variable : 'echo htmlspecialchars(' . $variable . ')';

            [1059] => 									$val  = VTPL_PHP_CDATA_OPEN . 'if (isset(' . $variable . ')) ' . $echo . ';' . VTPL_PHP_CDATA_CLOSE;

            [1060] => 								} else {

            [1061] => 									$val = $evalExpr;

            [1062] => 								}

            [1063] => 							}

            [1064] => 

            [1065] => 							$this->debug->log('SELECTOR_VARIABLE', $variable);

            [1066] => 

            [1067] => 							break;

            [1068] => 

            [1069] => 						case 'replace_from':

            [1070] => 							$this->externalElements   = true;

            [1071] => 							$val                      = $valueElements;

            [1072] => 

            [1073] => 							break;

            [1074] => 					}

            [1075] => 

            [1076] => 					if ($isConstant) {

            [1077] => 						$this->constants[$selector] = $val;

            [1078] => 

            [1079] => 						continue;

            [1080] => 					}

            [1081] => 

            [1082] => 					$elements      = $this->query($selector);

            [1083] => 

            [1084] => 					if (!$elements) {

            [1085] => 						$this->debug->log(0, ' [empty]');

            [1086] => 					} else {

            [1087] => 						if ($elements->length === 0) {

            [1088] => 							$this->debug->log(0, ' [0 elements]');

            [1089] => 						} else {

            [1090] => 							$this->debug->log(0, " [{$elements->length} elements]");

            [1091] => 						}

            [1092] => 					}

            [1093] => 

            [1094] => 					$this->debug->log('SELECTOR_VARIABLE', $selector . ' - ' . $modifier);

            [1095] => 

            [1096] => 					$this->processElements($modifier, $elements, $val);

            [1097] => 				}

            [1098] => 			}

            [1099] => 		}

            [1100] => 	}

            [1101] => 

            [1102] => 	/**

            [1103] => 	 * Replace $$N placeholders in a filter command string with option values.

            [1104] => 	 * Non-zero indices are quoted as string literals.

            [1105] => 	 *

            [1106] => 	 * @param string                  $command

            [1107] => 	 * @param array $options

            [1108] => 	 *

            [1109] => 	 * @return string

            [1110] => 	 */

            [1111] => 	private function replaceFilterVars(string $command, array &$options): string {

            [1112] => 		return preg_replace_callback(

            [1113] => 			'@\$\$(\d+)@',

            [1114] => 			function($matches) use (&$options) {

            [1115] => 				$index = (int)$matches[1];

            [1116] => 

            [1117] => 				if ($index > 0) {

            [1118] => 					$options[$index] = '\'' . trim($options[$index], '\'') . '\'';

            [1119] => 				}

            [1120] => 

            [1121] => 				return $options[$index];

            [1122] => 			},

            [1123] => 			$command

            [1124] => 		);

            [1125] => 	}

            [1126] => 

            [1127] => 	/*

            [1128] => 	 Process data-filter-* attributes defined in filters array.

            [1129] => 	 Process attributes with json and transforms them to php arrays ex: data-my-data='{var:"value"}'

            [1130] => 	 Process macro definitions like @@macro Mymacro('var1', 'var2') Mymacro must be a function defined with name vtplMymacro and must accept two variables like in definition

            [1131] => 	 Process json path, for a node with data-v-myjson='{var:{subvar1:val1, subvar2:val2}}' @@myjson.var.subvar1@@ will return val1

            [1132] => 	 */

            [1133] => 

            [1134] => 	/**

            [1135] => 	 * @param string                  $value

            [1136] => 	 * @param Dom\HTMLElement|DOMNode &$node

            [1137] => 	 *

            [1138] => 	 * @return string

            [1139] => 	 */

            [1140] => 	private function processAttributeFilters(string $value, Dom\HTMLElement|DOMNode &$node): string {

            [1141] => 		/*

            [1142] => 		filters

            [1143] => 		search for filters and their options

            [1144] => 		@filter_([^ :$]+):?(\'[^\']+\'|[^ $]+)?@ old

            [1145] => 		 */

            [1146] => 		$filters = [];

            [1147] => 		$length  = $node->attributes->length;

            [1148] => 

            [1149] => 		for ($i = 0; $i < $length; $i++) {

            [1150] => 			if ($item = $node->attributes->item($i)) {

            [1151] => 				$name = $item->name;

            [1152] => 

            [1153] => 				if (str_contains($name, 'data-filter')) {

            [1154] => 					$name           = str_replace('data-filter-', '', $name);

            [1155] => 					$filters[$name] = $item->value;

            [1156] => 					//$node->removeAttribute($item->name);

            [1157] => 				}

            [1158] => 			}

            [1159] => 		}

            [1160] => 

            [1161] => 		//if ($class && preg_match_all('@filter_([^ :$]+)(:\'[^\']+\'|:[^ $]+)*@', $class, $matches, PREG_SET_ORDER) > 0)

            [1162] => 		if ($filters) {

            [1163] => 			$chain = '_$variable';

            [1164] => 

            [1165] => 			foreach ($filters as $name => $options) {

            [1166] => 				if ($options) {

            [1167] => 					//string is json

            [1168] => 					if ($options[0] === '{'/* || $options[0] == '['*/) {

            [1169] => 						$options = json_decode($options, false);

            [1170] => 					} else {

            [1171] => 						$options = [$options];

            [1172] => 					}

            [1173] => 				} else {

            [1174] => 					$options = [];

            [1175] => 				}

            [1176] => 

            [1177] => 				//clean up, remove filter from attribute

            [1178] => 				if (isset($this->variableFilters[$name])) {

            [1179] => 					$type     = '';

            [1180] => 					$commands = $this->variableFilters[$name];

            [1181] => 

            [1182] => 					if (is_array($commands)) {

            [1183] => 						$type = $commands[0];

            [1184] => 						unset($commands[0]);

            [1185] => 					} else {

            [1186] => 						$commands = [1 => $commands];

            [1187] => 					}

            [1188] => 

            [1189] => 					if (!$commands) {

            [1190] => 						continue;

            [1191] => 					}

            [1192] => 

            [1193] => 					foreach ($commands as &$command) {

            [1194] => 						$commandVariableCount = preg_match_all('@\$\$[1-9]+@', $command);

            [1195] => 

            [1196] => 						//if different parameter number then don't add filter to filter chain

            [1197] => 						if ($commandVariableCount != count($options)) {

            [1198] => 							$this->debug->log('warning', 'Invalid number of options for filter ' . $name . ' for "' . $name . '"');

            [1199] => 

            [1200] => 							continue 2;

            [1201] => 						}

            [1202] => 

            [1203] => 						//run php functions if any

            [1204] => 						$command = preg_replace_callback(

            [1205] => 							'/@@__VTPL_([^_]+)__@@/',

            [1206] => 							fn ($matches) => eval('return ' . $matches[1] . ';'),

            [1207] => 							$command

            [1208] => 						);

            [1209] => 					}

            [1210] => 

            [1211] => 					if ($type === 'class') {

            [1212] => 						$command = $this->replaceFilterVars($command, $options);

            [1213] => 						$this->addNodeClass($node, trim($commands[1], '\''), false);

            [1214] => 					} else {

            [1215] => 						if ($type === 'tag') {

            [1216] => 							$command  = $this->replaceFilterVars($command, $options);

            [1217] => 							$openTag  = $this->replaceFilterVars($commands[1], $options);

            [1218] => 							$closeTag = $this->replaceFilterVars($commands[2], $options);

            [1219] => 

            [1220] => 							$nodeList = [$node]; //only one node and the methods accepts multiple nodes

            [1221] => 							$this->tagWrap($nodeList, $openTag, $closeTag);

            [1222] => 						} else {

            [1223] => 							if (is_array($options)) {

            [1224] => 								array_unshift($options, $chain);

            [1225] => 							} else {

            [1226] => 								$options[] = $chain;

            [1227] => 							}

            [1228] => 

            [1229] => 							$chain = $this->replaceFilterVars($commands[1], $options);

            [1230] => 						}

            [1231] => 					}

            [1232] => 				} else {

            [1233] => 					$this->debug->log('warning', 'Unknown filter ' . $name . ' for "' . $name . '"');

            [1234] => 				}

            [1235] => 			}

            [1236] => 

            [1237] => 			preg_match('@echo htmlspecialchars\(([^)]+)\)@', $value, $variable);

            [1238] => 

            [1239] => 			if ($variable) {

            [1240] => 				$chain = str_replace('_$variable', $variable[1], $chain);

            [1241] => 				$value = str_replace($variable[0], 'echo htmlspecialchars(' . $chain . ')', $value);

            [1242] => 			} else {

            [1243] => 				preg_match('@echo\(([^)]+)\)@', $value, $variable);

            [1244] => 

            [1245] => 				if ($variable) {

            [1246] => 					$chain = str_replace('_$variable', $variable[1], $chain);

            [1247] => 					$value = str_replace($variable[0], 'echo(' . $chain . ')', $value);

            [1248] => 				}

            [1249] => 			}

            [1250] => 		}

            [1251] => 

            [1252] => 		return $value;

            [1253] => 	}

            [1254] => 

            [1255] => 	/*

            [1256] => 	 Replace placeholders in values

            [1257] => 	 Ex;

            [1258] => 	 // @@__innerText__@@ will be replaced with the text already present in the tag that has data-v-product-name attribute

            [1259] => 	 [data-v-product-name] = 

            [1260] => 

            [1261] => 	 Available placeholders:

            [1262] => 	 @@__innerText__@@            - inner html of the node

            [1263] => 	 @@__innerText__@@            - inner text of the node

            [1264] => 	 @@__my-attribute__@@         - value of my-attribute of current node

            [1265] => 	 @@__data-v-plugin-(.+)__@@   - run regex and return first match \1 for example for data-v-plugin-name it will return `name`

            [1266] => 	 @@__my-*:my-(*)__@@ - get attribute name that starts with 'my-' and run the regex after ':' used to extract attribute name from current node

            [1267] => 

            [1268] => 	 Process data-filter-* attributes defined in filters array.

            [1269] => 	 Process attributes with json and transforms them to php arrays ex: data-my-data='{var:"value"}'

            [1270] => 	 Process macro definitions like @@macro Mymacro('var1', 'var2') Mymacro must be a function defined with name vtplMymacro and must accept two variables like in definition

            [1271] => 	 Process json path, for a node with data-v-myjson='{var:{subvar1:val1, subvar2:val2}}' @@myjson.var.subvar1@@ will return val1

            [1272] => 	 */

            [1273] => 

            [1274] => 	/**

            [1275] => 	 * Expand a JSON-looking value ({...}) to PHP array syntax via var_export.

            [1276] => 	 *

            [1277] => 	 * Shared by the @@__innerText__@@, @@__innerHTML__@@ and attribute

            [1278] => 	 * placeholder expansions in processAttributeConstants().

            [1279] => 	 *

            [1280] => 	 * @param string|null $value

            [1281] => 	 *

            [1282] => 	 * @return string|null

            [1283] => 	 */

            [1284] => 	private function expandJsonValue(?string $value): ?string {

            [1285] => 		if (isset($value[0]) && $value[0] === '{') {

            [1286] => 			$value = var_export(json_decode($value, true), true);

            [1287] => 		}

            [1288] => 

            [1289] => 		return $value;

            [1290] => 	}

            [1291] => 

            [1292] => 	/**

            [1293] => 	 * @param string                  $value

            [1294] => 	 * @param Dom\HTMLElement|DOMNode $node

            [1295] => 	 *

            [1296] => 	 * @return string

            [1297] => 	 */

            [1298] => 	private function processAttributeConstants(string $value, Dom\HTMLElement|DOMNode $node): string {

            [1299] => 		$value = preg_replace_callback(

            [1300] => 			'/@@__innerText__@@/',

            [1301] => 			function($matches) use ($node) {

            [1302] => 				$value = $this->innerText([$node]);

            [1303] => 

            [1304] => 				$value = $this->expandJsonValue($value);

            [1305] => 

            [1306] => 				$this->debug->log('VTPL_ATTRIBUTE', 'VALUE ' . $value);

            [1307] => 

            [1308] => 				return trim($value);

            [1309] => 			},

            [1310] => 			$value

            [1311] => 		);

            [1312] => 

            [1313] => 		$value = preg_replace_callback(

            [1314] => 			'/@@__innerHTML__@@/',

            [1315] => 			function($matches) use ($node) {

            [1316] => 				$value = $this->innerHTML([$node]);

            [1317] => 

            [1318] => 				$value = $this->expandJsonValue($value);

            [1319] => 

            [1320] => 				$this->debug->log('VTPL_ATTRIBUTE', 'VALUE ' . $value);

            [1321] => 

            [1322] => 				return $value;

            [1323] => 			},

            [1324] => 			$value

            [1325] => 		);

            [1326] => 

            [1327] => 		//attribute value

            [1328] => 		$value = preg_replace_callback(

            [1329] => 			'/@@__([\.a-zA-Z*_-]+)__@@/',

            [1330] => 			function($matches) use ($node) {

            [1331] => 				$attributeName = $matches[1];

            [1332] => 

            [1333] => 				if (str_contains($attributeName, '*')) {

            [1334] => 					//wildcard attribute

            [1335] => 					$attributeName = str_replace('*', '', $attributeName);

            [1336] => 

            [1337] => 					$value = '';

            [1338] => 

            [1339] => 					foreach ($node->attributes as $attribute) {

            [1340] => 						if (str_contains($attribute->name, $attributeName)) {

            [1341] => 							$value = $attribute->value;

            [1342] => 						}

            [1343] => 					}

            [1344] => 				} else {

            [1345] => 					$value = $node->getAttribute($matches[1]);

            [1346] => 				}

            [1347] => 

            [1348] => 				$value = $this->expandJsonValue($value);

            [1349] => 

            [1350] => 				$this->debug->log('VTPL_ATTRIBUTE', 'VALUE ' . $value);

            [1351] => 

            [1352] => 				return $value;

            [1353] => 			},

            [1354] => 			$value

            [1355] => 		);

            [1356] => 

            [1357] => 		//run regex on attribute name @@__data-v-product-*:data-v-product-(*)__@@

            [1358] => 		$value = preg_replace_callback(

            [1359] => 			'/@@__([*a-zA-Z_-]+):([a-zA-Z-_\]\[\\\+\(\)\,\.\+\^:*]+)__@@/',

            [1360] => 			function($matches) use ($node) {

            [1361] => 				$attrib = $matches[1];

            [1362] => 				$regex  = $matches[2];

            [1363] => 				$this->debug->log('VTPL_ATTRIBUTE', 'ATTRIB NAME ' . $attrib);

            [1364] => 				$this->debug->log('VTPL_ATTRIBUTE', 'REGEX  ' . $regex);

            [1365] => 				$value = $node->getAttribute($attrib);

            [1366] => 				$this->debug->log('VTPL_ATTRIBUTE', 'ATTRIB VALUE  ' . $value);

            [1367] => 

            [1368] => 				foreach ($node->attributes as $name => $attrNode) {

            [1369] => 					if (preg_match('@' . $regex . '@', $value, $_match)) {

            [1370] => 						//$value = \Vvveb\System\filter('@[0-9a-zA-Z_\-\.\#\/]+@', $_match[1], 500);

            [1371] => 						$value = $_match[1];

            [1372] => 						$this->debug->log('VTPL_ATTRIBUTE', 'MATCH ' . $_match[1]);

            [1373] => 					} else {

            [1374] => 						$this->debug->log('VTPL_ATTRIBUTE', 'NO MATCH  ' . $regex . ' - ' . $attrib . ' - ' . $attrNode->name);

            [1375] => 					}

            [1376] => 				}

            [1377] => 

            [1378] => 				return $value;

            [1379] => 			},

            [1380] => 			$value

            [1381] => 		);

            [1382] => 

            [1383] => 		//attribute name ex @@__data-v-plugin-(.+)__@@

            [1384] => 		$value = preg_replace_callback(

            [1385] => 			'/@@__(.+?)__@@/',

            [1386] => 			function($matches) use ($node) {

            [1387] => 				$value = $node->getAttribute($matches[1]);

            [1388] => 				$this->debug->log('VTPL_ATTRIBUTE', 'ATTRIB NAME ' . $matches[1]);

            [1389] => 				//expand shorthand expression (*) to regex ([a-zA-Z_0-9-]+)

            [1390] => 				$regex = str_replace('(*)', '([a-zA-Z_0-9-\.]+)', $matches[1]);

            [1391] => 

            [1392] => 				foreach ($node->attributes as $name => $attrNode) {

            [1393] => 					if (preg_match("@$regex@", $name, $_match)) {

            [1394] => 						//$value = \Vvveb\System\filter('@[0-9a-zA-Z_\-\.\#\/]+@', $_match[1], 500);

            [1395] => 						$value = $_match[1] ?? null;

            [1396] => 						$this->debug->log('VTPL_ATTRIBUTE', 'MATCH  ' . $value);

            [1397] => 					} else {

            [1398] => 						$this->debug->log('VTPL_ATTRIBUTE', 'NO MATCH ');

            [1399] => 					}

            [1400] => 				}

            [1401] => 

            [1402] => 				return $value;

            [1403] => 			},

            [1404] => 			$value

            [1405] => 		);

            [1406] => 

            [1407] => 		$json = [];

            [1408] => 

            [1409] => 		//check if attribute value is json string

            [1410] => 		if ($node->hasAttributes()) {

            [1411] => 			foreach ($node->attributes as $attr) {

            [1412] => 				$name = str_replace('data-v-', '', $attr->nodeName);

            [1413] => 				$val  = $attr->nodeValue;

            [1414] => 

            [1415] => 				if ($val && ($val[0] === '{'/* || $val[0] == '['*/)) {

            [1416] => 					$json[$name] = json_decode($val, true);

            [1417] => 				}

            [1418] => 			}

            [1419] => 		}

            [1420] => 

            [1421] => 		$value = preg_replace_callback(

            [1422] => 			'/@@([\.a-zA-Z_-]+)@@/m',

            [1423] => 			function($matches) use ($node, $json) {

            [1424] => 				$attrib = var_export(\Vvveb\arrayPath($json, $matches[1]), true);

            [1425] => 				$this->debug->log('VTPL_ATTRIBUTE', 'JSON NAME ' . $attrib);

            [1426] => 				$value = $node->getAttribute($attrib);

            [1427] => 				$this->debug->log('VTPL_ATTRIBUTE', 'ATTRIB VALUE ' . $value);

            [1428] => 

            [1429] => 				return $attrib;

            [1430] => 			},

            [1431] => 			$value

            [1432] => 		);

            [1433] => 

            [1434] => 		//macros, compile time function calls

            [1435] => 		$value = preg_replace_callback(

            [1436] => 			'/@@macro ([a-z_A-Z]+)\((.+?)\)@@/',

            [1437] => 			function($matches) use (&$node) {

            [1438] => 				$function = 'vtpl' . $matches[1];

            [1439] => 				//$parameters = preg_split('@\'?\s*,\s*\'?@', $matches[2]);

            [1440] => 

            [1441] => 				if (function_exists($function)) {

            [1442] => 					preg_match_all('@"(.*?)",?@i', $matches[2], $parameters, PREG_SET_ORDER);

            [1443] => 					//add node as first parameter to allow macros to alter node if needed

            [1444] => 					$params[] = &$this;

            [1445] => 					$params[] = &$node;

            [1446] => 

            [1447] => 					foreach ($parameters as $param) {

            [1448] => 						$params[] = trim($param[1]);

            [1449] => 					}

            [1450] => 

            [1451] => 					// $params_exp = var_export($params, true);

            [1452] => 					return $function(...$params);

            [1453] => 				}

            [1454] => 

            [1455] => 				return $matches[0];

            [1456] => 			},

            [1457] => 			$value

            [1458] => 		);

            [1459] => 

            [1460] => 		return $this->processAttributeFilters($value, $node);

            [1461] => 	}

            [1462] => 

            [1463] => 	/**

            [1464] => 	 * @param Dom\HTMLElement|DOMNode &$node

            [1465] => 	 *

            [1466] => 	 * @return void

            [1467] => 	 */

            [1468] => 	/**

            [1469] => 	 * Detach a node from the document after emptying it.

            [1470] => 	 *

            [1471] => 	 * Shared by the delete* modifiers; the parent check makes it safe to

            [1472] => 	 * call on already-detached nodes.

            [1473] => 	 *

            [1474] => 	 * @param Dom\HTMLElement|DOMNode &$node

            [1475] => 	 *

            [1476] => 	 * @return void

            [1477] => 	 */

            [1478] => 	private function removeNode(Dom\HTMLElement|DOMNode &$node): void {

            [1479] => 		$this->removeChildren($node);

            [1480] => 

            [1481] => 		if ($node->parentNode) {

            [1482] => 			$node->parentNode->removeChild($node);

            [1483] => 		}

            [1484] => 	}

            [1485] => 

            [1486] => 	/**

            [1487] => 	 * @param Dom\HTMLElement|DOMNode &$node

            [1488] => 	 *

            [1489] => 	 * @return void

            [1490] => 	 */

            [1491] => 	private function removeChildren(Dom\HTMLElement|DOMNode &$node): void {

            [1492] => 		while ($node->firstChild) {

            [1493] => 			$node->removeChild($node->firstChild);

            [1494] => 		}

            [1495] => 	}

            [1496] => 

            [1497] => 	/**

            [1498] => 	 * Set a node's value based on its tag type.

            [1499] => 	 * - input → value attribute

            [1500] => 	 * - iframe/video/audio/source → src attribute

            [1501] => 	 * - form → action attribute

            [1502] => 	 * - default → innerHTML via document fragment

            [1503] => 	 *

            [1504] => 	 * @param DOMNode $node

            [1505] => 	 * @param string  $html

            [1506] => 	 *

            [1507] => 	 * @return void

            [1508] => 	 */

            [1509] => 	private function setNodeValueByType(Dom\HTMLElement|DOMNode $node, string $html): void {

            [1510] => 		switch ($node->nodeName) {

            [1511] => 			case 'input':

            [1512] => 				$this->setAttribute($node, 'value', $html);

            [1513] => 

            [1514] => 				break;

            [1515] => 

            [1516] => 			case 'iframe':

            [1517] => 			case 'video':

            [1518] => 			case 'audio':

            [1519] => 			case 'source':

            [1520] => 				$this->setAttribute($node, 'src', $html);

            [1521] => 

            [1522] => 				break;

            [1523] => 

            [1524] => 			case 'form':

            [1525] => 				$this->setAttribute($node, 'action', $html);

            [1526] => 

            [1527] => 				break;

            [1528] => 

            [1529] => 			default:

            [1530] => 				$this->removeChildren($node);

            [1531] => 				$f = $this->createXmlFragment($this->processAttributeConstants($html, $node));

            [1532] => 				if ($f) {

            [1533] => 					$node->appendChild($f);

            [1534] => 				}

            [1535] => 		}

            [1536] => 	}

            [1537] => 

            [1538] => 	/**

            [1539] => 	 * @param array|Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode> $nodeList

            [1540] => 	 * @param false|string                                                          $html

            [1541] => 	 *

            [1542] => 	 * @return string|null

            [1543] => 	 */

            [1544] => 	private function innerHTML(array|Dom\NodeList|DOMNodeList $nodeList, false|string $html = false): ?string {

            [1545] => 		if ($nodeList) {

            [1546] => 			foreach ($nodeList as $node) {

            [1547] => 				if ($html === false) {

            [1548] => 					$doc = new DOMDocument();

            [1549] => 

            [1550] => 					foreach ($node->childNodes as $child) {

            [1551] => 						$doc->appendChild($doc->importNode($child, true));

            [1552] => 					}

            [1553] => 

            [1554] => 					return $this->saveDocument($doc);

            [1555] => 				} else {

            [1556] => 					if ($html === '') {

            [1557] => 						continue;

            [1558] => 					}

            [1559] => 

            [1560] => 					if ($this->externalElements) {

            [1561] => 						$result = $this->loadFromExternalHtml($html, $node);

            [1562] => 						$this->removeChildren($node);

            [1563] => 

            [1564] => 						foreach ($result as $externalNode) {

            [1565] => 							$importedNode = $this->document->importNode($externalNode, true);

            [1566] => 							$node->appendChild($importedNode);

            [1567] => 						}

            [1568] => 					} else {

            [1569] => 						$this->setNodeValueByType($node, $html);

            [1570] => 					}

            [1571] => 				}

            [1572] => 			}

            [1573] => 		}

            [1574] => 

            [1575] => 		return null;

            [1576] => 	}

            [1577] => 

            [1578] => 	/**

            [1579] => 	 * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList

            [1580] => 	 * @param array|string|null                $html

            [1581] => 	 *

            [1582] => 	 * @return void

            [1583] => 	 */

            [1584] => 	private function outerHTML(null|Dom\NodeList|DOMNodeList &$nodeList, null|array|string $html = null): void {

            [1585] => 		foreach ($nodeList as $node) {

            [1586] => 			if ($html === '') {

            [1587] => 				continue;

            [1588] => 			}

            [1589] => 

            [1590] => 			if ($this->externalElements) {

            [1591] => 				$result = $this->loadFromExternalHtml($html, $node);

            [1592] => 

            [1593] => 				$parent = $node->parentNode;

            [1594] => 				$count  = 0;

            [1595] => 

            [1596] => 				if ($result) {

            [1597] => 					foreach ($result as $externalNode) {

            [1598] => 						$importedNode = $this->document->importNode($externalNode, true);

            [1599] => 

            [1600] => 						if ($parent) {

            [1601] => 							if ($count) {

            [1602] => 								$parent->appendChild($importedNode);

            [1603] => 							} else {

            [1604] => 								$parent->replaceChild($importedNode, $node);

            [1605] => 							}

            [1606] => 							$node = $importedNode;

            [1607] => 							$count++;

            [1608] => 						}

            [1609] => 					}

            [1610] => 				}

            [1611] => 			} else {

            [1612] => 				$f = $this->createXmlFragment($this->processAttributeConstants($html, $node));

            [1613] => 				if ($f) {

            [1614] => 					$node->parentNode->replaceChild($f, $node);

            [1615] => 				}

            [1616] => 			}

            [1617] => 		}

            [1618] => 	}

            [1619] => 

            [1620] => 	/**

            [1621] => 	 * @param list|Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null $nodeList

            [1622] => 	 * @param string|null                                                                      $text

            [1623] => 	 *

            [1624] => 	 * @return string|null

            [1625] => 	 */

            [1626] => 	private function innerText(array|Dom\NodeList|DOMNodeList|null $nodeList, ?string $text = null): ?string {

            [1627] => 		foreach ($nodeList as $node) {

            [1628] => 			if ($text === null) {

            [1629] => 				return $node->nodeValue;

            [1630] => 			} else {

            [1631] => 				if ($node->hasChildNodes()) {

            [1632] => 					foreach ($node->childNodes as $childNode) {

            [1633] => 						$value = trim($childNode->nodeValue);

            [1634] => 

            [1635] => 						if ($childNode->nodeType === XML_TEXT_NODE && $value !== '') {

            [1636] => 							$f = $this->createXmlFragment($this->processAttributeConstants($text, $node));

            [1637] => 

            [1638] => 							if ($f) {

            [1639] => 								$node->replaceChild($f, $childNode);

            [1640] => 							}

            [1641] => 

            [1642] => 							break;

            [1643] => 						}

            [1644] => 					}

            [1645] => 				} else {

            [1646] => 					$this->setNodeValueByType($node, $text);

            [1647] => 				}

            [1648] => 			}

            [1649] => 		}

            [1650] => 

            [1651] => 		return null;

            [1652] => 	}

            [1653] => 

            [1654] => 	/**

            [1655] => 	 * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList

            [1656] => 	 * @param false|string                                       $variable

            [1657] => 	 *

            [1658] => 	 * @return void

            [1659] => 	 */

            [1660] => 	private function ifExists(null|Dom\NodeList|DOMNodeList &$nodeList, false|string $variable = false): void {

            [1661] => 		if ($variable === '') {

            [1662] => 			return;

            [1663] => 		}

            [1664] => 

            [1665] => 		foreach ($nodeList as $node) {

            [1666] => 			$condition = $this->processAttributeConstants($variable, $node);

            [1667] => 

            [1668] => 			//only variable/property chains may go through isset() — boolean

            [1669] => 			//expressions (empty(), &&, calls …) are used as-is, keeping their

            [1670] => 			//'!' intact (blanket str_replace would corrupt them)

            [1671] => 			$probe = trim($condition, " \t\n\r()");

            [1672] => 			if (preg_match('/^!?\s*\$[a-zA-Z_][a-zA-Z0-9_]*(?:->\w+|\[(?:\'[^\']*\'|"[^"]*"|\d+|[\w\$]+)\])*$/', $probe)) {

            [1673] => 				$isset = str_replace('!', '', $condition);

            [1674] => 				$guard = "isset($isset) && $condition";

            [1675] => 			} else {

            [1676] => 				$guard = $condition;

            [1677] => 			}

            [1678] => 

            [1679] => 			//before

            [1680] => 			$f = $this->createXmlFragment(VTPL_PHP_CDATA_OPEN . "if ($guard) {" . VTPL_PHP_CDATA_CLOSE);

            [1681] => 			if ($f) {

            [1682] => 				$node->parentNode->insertBefore($f, $node);

            [1683] => 			}

            [1684] => 

            [1685] => 			//after

            [1686] => 			$f = $this->createXmlFragment(VTPL_PHP_OPEN . '}' . VTPL_PHP_CLOSE);

            [1687] => 			if ($f) {

            [1688] => 				$node->parentNode->insertBefore($f, $node->nextSibling);

            [1689] => 			}

            [1690] => 		}

            [1691] => 	}

            [1692] => 

            [1693] => 	/**

            [1694] => 	 * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList

            [1695] => 	 * @param false|string                                       $variable

            [1696] => 	 *

            [1697] => 	 * @return void

            [1698] => 	 */

            [1699] => 	private function hide(null|Dom\NodeList|DOMNodeList &$nodeList, false|string $variable = false): void {

            [1700] => 		if ($variable) {

            [1701] => 			$variable = '!' . $variable;

            [1702] => 		}

            [1703] => 

            [1704] => 		$this->ifExists($nodeList, $variable);

            [1705] => 	}

            [1706] => 

            [1707] => 	/**

            [1708] => 	 * @param list|Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList

            [1709] => 	 * @param false|string                                                                     $open

            [1710] => 	 * @param false|string                                                                     $close

            [1711] => 	 *

            [1712] => 	 * @return bool

            [1713] => 	 */

            [1714] => 	private function tagWrap(array|Dom\NodeList|DOMNodeList|null &$nodeList, false|string $open = false, false|string $close = false): bool {

            [1715] => 		if ($open === '' || $close === '') {

            [1716] => 			return false;

            [1717] => 		}

            [1718] => 		$openStart = VTPL_PHP_CDATA_OPEN . $open . VTPL_PHP_CDATA_CLOSE;

            [1719] => 		$openEnd   = VTPL_PHP_OPEN . '}' . VTPL_PHP_CLOSE;

            [1720] => 

            [1721] => 		$closeStart = VTPL_PHP_CDATA_OPEN . $close . VTPL_PHP_CDATA_CLOSE;

            [1722] => 		$closeEnd   = VTPL_PHP_OPEN . '}' . VTPL_PHP_CLOSE;

            [1723] => 

            [1724] => 		foreach ($nodeList as $node) {

            [1725] => 			//before start

            [1726] => 			$f = $this->createXmlFragment($this->processAttributeConstants($openStart, $node));

            [1727] => 			if ($f) {

            [1728] => 				$node->parentNode->insertBefore($f, $node);

            [1729] => 			}

            [1730] => 

            [1731] => 			//before end

            [1732] => 			$f = $this->createXmlFragment($this->processAttributeConstants($openEnd, $node));

            [1733] => 

            [1734] => 			if ($node->hasChildNodes()) {

            [1735] => 				if ($f) {

            [1736] => 					$node->insertBefore($f, $node->firstChild);

            [1737] => 				}

            [1738] => 			} else {

            [1739] => 				if ($f) {

            [1740] => 					$node->appendChild($f);

            [1741] => 				}

            [1742] => 			}

            [1743] => 

            [1744] => 			//after start

            [1745] => 			$f = $this->createXmlFragment($this->processAttributeConstants($closeStart, $node));

            [1746] => 			if ($f) {

            [1747] => 				$node->appendChild($f);

            [1748] => 			}

            [1749] => 

            [1750] => 			//after end

            [1751] => 			$f = $this->createXmlFragment($this->processAttributeConstants($closeEnd, $node));

            [1752] => 			if ($f) {

            [1753] => 				$node->parentNode->insertBefore($f, $node->nextSibling);

            [1754] => 			}

            [1755] => 		}

            [1756] => 

            [1757] => 		return true;

            [1758] => 	}

            [1759] => 

            [1760] => 	/**

            [1761] => 	 * Resolve external elements for insert/append operations.

            [1762] => 	 * If the from() reference is @_SELF_@, queries the current document;

            [1763] => 	 * otherwise loads from an external HTML file.

            [1764] => 	 *

            [1765] => 	 * @param array|string $html

            [1766] => 	 * @param DOMNode                        $node

            [1767] => 	 *

            [1768] => 	 * @return Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|false

            [1769] => 	 */

            [1770] => 	private function resolveExternalElements(array|string $html, Dom\HTMLElement|DOMNode $node): Dom\NodeList|DOMNodeList|false {

            [1771] => 		if ($this->froms && $this->froms[0][(int)$html[1]] === '@_SELF_@') {

            [1772] => 			$selector = $this->froms[2][(int)$html[1]];

            [1773] => 

            [1774] => 			return $this->query($selector);

            [1775] => 		}

            [1776] => 

            [1777] => 		return $this->loadFromExternalHtml($html, $node);

            [1778] => 	}

            [1779] => 

            [1780] => 	/**

            [1781] => 	 * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList

            [1782] => 	 * @param array|string                     $html

            [1783] => 	 *

            [1784] => 	 * @return bool

            [1785] => 	 */

            [1786] => 	private function insertBefore(null|Dom\NodeList|DOMNodeList &$nodeList, array|string $html): bool {

            [1787] => 		if ($html === '') {

            [1788] => 			return false;

            [1789] => 		}

            [1790] => 

            [1791] => 		if ($nodeList) {

            [1792] => 			foreach ($nodeList as $node) {

            [1793] => 				if ($this->externalElements) {

            [1794] => 					$result = $this->resolveExternalElements($html, $node);

            [1795] => 

            [1796] => 					if (!$result) {

            [1797] => 						continue;

            [1798] => 					}

            [1799] => 					foreach ($result as $externalNode) {

            [1800] => 						$importedNode = $this->document->importNode($externalNode, true);

            [1801] => 						$node->parentNode->insertBefore($importedNode, $node);

            [1802] => 					}

            [1803] => 				} else {

            [1804] => 					$f = $this->createXmlFragment($this->processAttributeConstants($html, $node));

            [1805] => 					if ($f) {

            [1806] => 						$node->parentNode->insertBefore($f, $node);

            [1807] => 					}

            [1808] => 				}

            [1809] => 			}

            [1810] => 

            [1811] => 			return true;

            [1812] => 		}

            [1813] => 

            [1814] => 		return false;

            [1815] => 	}

            [1816] => 

            [1817] => 	/**

            [1818] => 	 * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList

            [1819] => 	 * @param array|string                     $html

            [1820] => 	 *

            [1821] => 	 * @return bool

            [1822] => 	 */

            [1823] => 	private function insertAfter(null|Dom\NodeList|DOMNodeList &$nodeList, array|string $html): bool {

            [1824] => 		if ($html === '') {

            [1825] => 			return false;

            [1826] => 		}

            [1827] => 

            [1828] => 		if ($nodeList) {

            [1829] => 			foreach ($nodeList as $node) {

            [1830] => 				if ($this->externalElements) {

            [1831] => 					$result = $this->resolveExternalElements($html, $node);

            [1832] => 

            [1833] => 					if (!$result) {

            [1834] => 						continue;

            [1835] => 					}

            [1836] => 					foreach ($result as $externalNode) {

            [1837] => 						$importedNode = $this->document->importNode($externalNode, true);

            [1838] => 

            [1839] => 						//importNode returns the same node for nodes already in this document;

            [1840] => 						//when the external element is $node's own nextSibling, inserting it

            [1841] => 						//before itself throws "Cannot add newnode as the previous sibling of refnode".

            [1842] => 						//In that case it is already in the correct position, so skip the move.

            [1843] => 						if ($importedNode === $node->nextSibling) {

            [1844] => 							continue;

            [1845] => 						}

            [1846] => 						$node->parentNode->insertBefore($importedNode, $node->nextSibling);

            [1847] => 					}

            [1848] => 				} else {

            [1849] => 					$f = $this->createXmlFragment($this->processAttributeConstants($html, $node));

            [1850] => 					if ($f) {

            [1851] => 						$node->parentNode->insertBefore($f, $node->nextSibling);

            [1852] => 					}

            [1853] => 				}

            [1854] => 			}

            [1855] => 

            [1856] => 			return true;

            [1857] => 		}

            [1858] => 

            [1859] => 		return false;

            [1860] => 	}

            [1861] => 

            [1862] => 	/**

            [1863] => 	 * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList

            [1864] => 	 * @param array|string                     $html

            [1865] => 	 *

            [1866] => 	 * @return bool

            [1867] => 	 */

            [1868] => 	private function append(null|Dom\NodeList|DOMNodeList &$nodeList, array|string $html): bool {

            [1869] => 		if ($html === '') {

            [1870] => 			return false;

            [1871] => 		}

            [1872] => 

            [1873] => 		if ($nodeList) {

            [1874] => 			foreach ($nodeList as $node) {

            [1875] => 				if ($this->externalElements) {

            [1876] => 					$result = $this->resolveExternalElements($html, $node);

            [1877] => 

            [1878] => 					if (!$result) {

            [1879] => 						continue;

            [1880] => 					}

            [1881] => 					foreach ($result as $externalNode) {

            [1882] => 						$importedNode = $this->document->importNode($externalNode, true);

            [1883] => 						$node->appendChild($importedNode);

            [1884] => 					}

            [1885] => 				} else {

            [1886] => 					$f = $this->createXmlFragment($this->processAttributeConstants($html, $node));

            [1887] => 					if ($f) {

            [1888] => 						$node->appendChild($f);

            [1889] => 					}

            [1890] => 				}

            [1891] => 			}

            [1892] => 

            [1893] => 			return true;

            [1894] => 		}

            [1895] => 

            [1896] => 		return false;

            [1897] => 	}

            [1898] => 

            [1899] => 	/**

            [1900] => 	 * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList

            [1901] => 	 * @param array|string                     $html

            [1902] => 	 *

            [1903] => 	 * @return bool

            [1904] => 	 */

            [1905] => 	private function prepend(null|Dom\NodeList|DOMNodeList &$nodeList, array|string $html): bool {

            [1906] => 		if (!$html) {

            [1907] => 			return false;

            [1908] => 		}

            [1909] => 

            [1910] => 		if ($nodeList) {

            [1911] => 			foreach ($nodeList as $node) {

            [1912] => 				if ($this->externalElements) {

            [1913] => 					$result = $this->resolveExternalElements($html, $node);

            [1914] => 

            [1915] => 					if (!$result) {

            [1916] => 						continue;

            [1917] => 					}

            [1918] => 

            [1919] => 					foreach ($result as $externalNode) {

            [1920] => 						$importedNode = $this->document->importNode($externalNode, true);

            [1921] => 

            [1922] => 						if ($importedNode === $node->firstChild) {

            [1923] => 							//self-reference already in position, avoid inserting before itself

            [1924] => 							continue;

            [1925] => 						}

            [1926] => 

            [1927] => 						if ($node->firstChild) {

            [1928] => 							// $ref has an immediate sibling : insert newnode before this one

            [1929] => 							$node->insertBefore($importedNode, $node->firstChild);

            [1930] => 						} else {

            [1931] => 							// $ref has no sibling next to him : insert newnode as last child of his parent

            [1932] => 							$node->appendChild($importedNode);

            [1933] => 						}

            [1934] => 					}

            [1935] => 				} else {

            [1936] => 					$f = $this->createXmlFragment($this->processAttributeConstants($html, $node));

            [1937] => 

            [1938] => 					if ($node->firstChild) {

            [1939] => 						// $ref has an immediate sibling : insert newnode before this one

            [1940] => 						if ($f) {

            [1941] => 							$node->insertBefore($f, $node->firstChild);

            [1942] => 						}

            [1943] => 					} else {

            [1944] => 						// $ref has no sibling next to him : insert newnode as last child of his parent

            [1945] => 						if ($f) {

            [1946] => 							$node->appendChild($f);

            [1947] => 						}

            [1948] => 					}

            [1949] => 				}

            [1950] => 			}

            [1951] => 

            [1952] => 			return true;

            [1953] => 		}

            [1954] => 

            [1955] => 		return false;

            [1956] => 	}

            [1957] => 

            [1958] => 	/**

            [1959] => 	 * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList

            [1960] => 	 *

            [1961] => 	 * @return void

            [1962] => 	 */

            [1963] => 	private function deleteAllButFirst(null|Dom\NodeList|DOMNodeList &$nodeList): void {

            [1964] => 		$first = true;

            [1965] => 

            [1966] => 		if ($nodeList) {

            [1967] => 			foreach ($nodeList as $node) {

            [1968] => 				if (!$first) {

            [1969] => 					$this->removeNode($node);

            [1970] => 				}

            [1971] => 				$first = false;

            [1972] => 			}

            [1973] => 		}

            [1974] => 	}

            [1975] => 

            [1976] => 	/**

            [1977] => 	 * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList

            [1978] => 	 *

            [1979] => 	 * @return void

            [1980] => 	 */

            [1981] => 	private function deleteAllButFirstChild(null|Dom\NodeList|DOMNodeList &$nodeList): void {

            [1982] => 		$parents = [];

            [1983] => 

            [1984] => 		if ($nodeList) {

            [1985] => 			foreach ($nodeList as $node) {

            [1986] => 				if (in_array($node->parentNode, $parents, true)) {

            [1987] => 					$this->removeNode($node);

            [1988] => 				}

            [1989] => 

            [1990] => 				if ($node->parentNode) {

            [1991] => 					$parents[] = $node->parentNode;

            [1992] => 				}

            [1993] => 			}

            [1994] => 		}

            [1995] => 	}

            [1996] => 

            [1997] => 	/**

            [1998] => 	 * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList

            [1999] => 	 *

            [2000] => 	 * @return void

            [2001] => 	 */

            [2002] => 	private function delete(null|Dom\NodeList|DOMNodeList &$nodeList): void {

            [2003] => 		if ($nodeList) {

            [2004] => 			foreach ($nodeList as $node) {

            [2005] => 				$this->removeNode($node);

            [2006] => 			}

            [2007] => 		}

            [2008] => 	}

            [2009] => 

            [2010] => 	/**

            [2011] => 	 * @param Dom\HTMLElement|DOMNode $node

            [2012] => 	 * @param string                  $attribute

            [2013] => 	 * @param string                  $val

            [2014] => 	 *

            [2015] => 	 * @return void

            [2016] => 	 */

            [2017] => 	private function setNodeAttribute(Dom\HTMLElement|DOMNode $node, string $attribute, string $val): void {

            [2018] => 		if (!$val) {

            [2019] => 			return;

            [2020] => 		}

            [2021] => 		$value = $this->processAttributeConstants($val, $node);

            [2022] => 		//if the attribute value has no php in it add it directly

            [2023] => 		if ($value) {

            [2024] => 			if (!str_contains($value, VTPL_PHP_OPEN)) {

            [2025] => 				$node->setAttribute($attribute, $value);

            [2026] => 			} else {

            [2027] => 				$this->attributes[++$this->attributesIndex] = $value;

            [2028] => 				$node->setAttribute($attribute, "@@__VTPL__ATTRIBUTE_PLACEHOLDER__{$this->attributesIndex}@@");

            [2029] => 			}

            [2030] => 		}

            [2031] => 	}

            [2032] => 

            [2033] => 	/**

            [2034] => 	 * @param Dom\HTMLElement|DOMNode|Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode> &$nodeList

            [2035] => 	 * @param string                                                                $attribute

            [2036] => 	 * @param string                                                                $val

            [2037] => 	 *

            [2038] => 	 * @return void

            [2039] => 	 */

            [2040] => 	private function setAttribute(Dom\HTMLElement|Dom\NodeList|DOMNode|DOMNodeList &$nodeList, string $attribute, string $val): void {

            [2041] => 		if (is_a($nodeList, 'DOMNodeList') || is_a($nodeList, 'Dom\NodeList')) {

            [2042] => 			if ($nodeList->length > 0) {

            [2043] => 				foreach ($nodeList as $node) {

            [2044] => 					/*			$attr = new DOMAttr($attribute);

            [2045] => 								$attr->value = $val;

            [2046] => 								$node->setAttributeNodeNS($attr);*/

            [2047] => 

            [2048] => 					$this->setNodeAttribute($node, $attribute, $val);

            [2049] => 				}

            [2050] => 			}

            [2051] => 		} else {

            [2052] => 			$this->setNodeAttribute($nodeList, $attribute, $val);

            [2053] => 		}

            [2054] => 	}

            [2055] => 

            [2056] => 	/**

            [2057] => 	 * @param Dom\HTMLElement|DOMNode &$node

            [2058] => 	 * @param string                  $val

            [2059] => 	 * @param bool                    $processConstants

            [2060] => 	 *

            [2061] => 	 * @return void

            [2062] => 	 */

            [2063] => 	private function addNodeClass(Dom\HTMLElement|DOMNode &$node, string $val, bool $processConstants = true): void {

            [2064] => 		if ($processConstants) {

            [2065] => 			$val =  $this->processAttributeConstants($val, $node);

            [2066] => 		}

            [2067] => 

            [2068] => 		$this->attributes[++$this->attributesIndex] = $val;

            [2069] => 		$node->setAttribute('class', $node->getAttribute('class') . " @@__VTPL__ATTRIBUTE_PLACEHOLDER__{$this->attributesIndex}@@");

            [2070] => 	}

            [2071] => 

            [2072] => 	/**

            [2073] => 	 * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode> $nodeList

            [2074] => 	 * @param string                                        $val

            [2075] => 	 *

            [2076] => 	 * @return void

            [2077] => 	 */

            [2078] => 	private function addClass(Dom\NodeList|DOMNodeList $nodeList, string $val): void {

            [2079] => 		if (is_a($nodeList, 'DOMNodeList')) {

            [2080] => 			if ($nodeList->length > 0) {

            [2081] => 				foreach ($nodeList as $node) {

            [2082] => 					$this->addNodeClass($node, $val);

            [2083] => 				}

            [2084] => 			}

            [2085] => 		}

            [2086] => 	}

            [2087] => 

            [2088] => 	/**

            [2089] => 	 * @param Dom\HTMLElement|DOMNode &$node

            [2090] => 	 * @param string                  $val

            [2091] => 	 *

            [2092] => 	 * @return void

            [2093] => 	 */

            [2094] => 	public function addNodeNewAttribute(Dom\HTMLElement|DOMNode &$node, string $val): void {

            [2095] => 		$this->newAttributes[++$this->newAttributesIndex] = $this->processAttributeConstants($val, $node);

            [2096] => 		$node->setAttribute("__VTPL__NEW_ATTRIBUTE_PLACEHOLDER__{$this->newAttributesIndex}", '');

            [2097] => 	}

            [2098] => 

            [2099] => 	/**

            [2100] => 	 * @param array|Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode> &$nodeList

            [2101] => 	 * @param string                                                                $val

            [2102] => 	 *

            [2103] => 	 * @return void

            [2104] => 	 */

            [2105] => 	public function addNewAttribute(array|Dom\NodeList|DOMNodeList &$nodeList, string $val): void {

            [2106] => 		if ($nodeList->length > 0) {

            [2107] => 			foreach ($nodeList as $node) {

            [2108] => 				$this->addNodeNewAttribute($node, $val);

            [2109] => 			}

            [2110] => 		}

            [2111] => 	}

            [2112] => 

            [2113] => 	/**

            [2114] => 	 * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode> $nodeList

            [2115] => 	 * @param string                                        $val

            [2116] => 	 *

            [2117] => 	 * @return void

            [2118] => 	 */

            [2119] => 	private function removeAttribute(Dom\NodeList|DOMNodeList $nodeList, string $val): void {

            [2120] => 		if ($nodeList->length > 0) {

            [2121] => 			foreach ($nodeList as $node) {

            [2122] => 				$node->removeAttribute($val);

            [2123] => 			}

            [2124] => 		}

            [2125] => 	}

            [2126] => 

            [2127] => 	/**

            [2128] => 	 * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode> $nodeList

            [2129] => 	 * @param string                                        $val

            [2130] => 	 *

            [2131] => 	 * @return void

            [2132] => 	 */

            [2133] => 	private function removeClass(Dom\NodeList|DOMNodeList $nodeList, string $val): void {

            [2134] => 		if ($nodeList->length > 0) {

            [2135] => 			foreach ($nodeList as $node) {

            [2136] => 				$class = $node->getAttribute('class');

            [2137] => 				$class = str_replace($val, '', $class);

            [2138] => 				$node->setAttribute('class', $class);

            [2139] => 			}

            [2140] => 		}

            [2141] => 	}

            [2142] => 

            [2143] => 	/**

            [2144] => 	 * @param array|string $val

            [2145] => 	 * @param DOMNode                        $node

            [2146] => 	 *

            [2147] => 	 * @return Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|false

            [2148] => 	 */

            [2149] => 	private function loadFromExternalHtml(array|string $val, Dom\HTMLElement|DOMNode $node): Dom\NodeList|DOMNodeList|false {

            [2150] => 		$filename = $this->froms[0][(int)$val[1]]; //external html file

            [2151] => 		$selector = $this->froms[2][(int)$val[1]];

            [2152] => 		//load specified selector if available otherwise load html with the same selector

            [2153] => 

            [2154] => 		$filename = $this->processAttributeConstants($filename, $node);

            [2155] => 		$selector = $this->processAttributeConstants($selector, $node);

            [2156] => 

            [2157] => 		if (str_starts_with($filename, '/plugins/') || str_starts_with($filename, PUBLIC_PATH)) {

            [2158] => 			$filename = DIR_ROOT . $filename;

            [2159] => 		} elseif ($filename[0] !== '/') {

            [2160] => 			$filename = $this->htmlPath . $filename;

            [2161] => 		}

            [2162] => 

            [2163] => 		$this->debug->log('SELECTOR_FROM', $filename);

            [2164] => 

            [2165] => 		if (!($html = @file_get_contents($filename))) {

            [2166] => 			Vvveb\logError("can't load html $filename");

            [2167] => 			$this->debug->log('LOAD', 'EXTERNAL ERROR ' . $filename . ' ' . $selector);

            [2168] => 

            [2169] => 			return false;

            [2170] => 		}

            [2171] => 

            [2172] => 		$this->debug->log('LOAD', $filename . ' SELECTOR ' . $selector);

            [2173] => 

            [2174] => 		if (VTPL_DONT_ALLOW_PHP) {

            [2175] => 			$html = $this->removePhp($html);

            [2176] => 		}

            [2177] => 

            [2178] => 		if ($this->minifyHtml === true) {

            [2179] => 			$html = $this->minifyHtml($html);

            [2180] => 		}

            [2181] => 

            [2182] => 		$document = $this->loadDomFromString($html);

            [2183] => 

            [2184] => 		if ($selector) {

            [2185] => 			if ($this->legacy) {

            [2186] => 				$xpath         = new DOMXPath($document);

            [2187] => 				$xpathSelector = $this->cssToXpath($selector);

            [2188] => 				$elements      = $xpath->query($xpathSelector);

            [2189] => 			} else {

            [2190] => 				$elements = $document->querySelectorAll($selector);

            [2191] => 			}

            [2192] => 		} else {

            [2193] => 			$elements = $document->childNodes;

            [2194] => 		}

            [2195] => 		//$elements   = $this->query($selector);

            [2196] => 

            [2197] => 		return $elements;

            [2198] => 	}

            [2199] => 

            [2200] => 	/**

            [2201] => 	 * Processes a node's attributes during the multi-language pass: rewrites

            [2202] => 	 * relative media urls against the theme path, removes data-v-* attributes

            [2203] => 	 * when configured, converts {$variable} attribute values to php echoes and

            [2204] => 	 * wraps translatable placeholder/title values with the translation function.

            [2205] => 	 *

            [2206] => 	 * @param Dom\HTMLElement|DOMNode $node

            [2207] => 	 *

            [2208] => 	 * @return void

            [2209] => 	 */

            [2210] => 	private function processNodeAttributes(Dom\Element|Dom\HTMLElement|DOMNode $node): void {

            [2211] => 		if (!$node->hasAttributes()) {

            [2212] => 			return;

            [2213] => 		}

            [2214] => 

            [2215] => 		$publicLen = strlen(PUBLIC_PATH);

            [2216] => 

            [2217] => 		foreach ($node->attributes as $name => $attrNode) {

            [2218] => 			$value = $attrNode->nodeValue;

            [2219] => 			$name  = $attrNode->nodeName;

            [2220] => 

            [2221] => 			//set relative media to theme folder

            [2222] => 			if ($this->relativePath) {

            [2223] => 				if (($name === 'src' || $name === 'href' || $name === 'srcset') && $node->nodeName !== 'A') {

            [2224] => 					if ($value && $value[0] !== '#'

            [2225] => 						&& $value[0] !== '@'

            [2226] => 						&& !str_starts_with($value, '//')

            [2227] => 							&& !str_starts_with($value, '/feed/')

            [2228] => 							&& (!str_contains($value, ':'))) {

            [2229] => 						if ($value[0] === '/') {

            [2230] => 							if (str_starts_with($value, PUBLIC_PATH)) {

            [2231] => 								$value = substr($value, $publicLen);

            [2232] => 							} else {

            [2233] => 								$value = substr($value, 1);

            [2234] => 							}

            [2235] => 

            [2236] => 							$this->setNodeAttribute($node, $name, PUBLIC_PATH . $value);

            [2237] => 						} else {

            [2238] => 							$this->setNodeAttribute($node, $name, $this->relativePath . $value);

            [2239] => 						}

            [2240] => 					}

            [2241] => 				}

            [2242] => 

            [2243] => 				if ($name === 'style' && str_contains($value, 'url(')) {

            [2244] => 					$value = preg_replace_callback(

            [2245] => 						'/url\(([^\)]+)\)/',

            [2246] => 						function($matches) use ($publicLen) {

            [2247] => 							$url = trim($matches[1], ' \'"');

            [2248] => 

            [2249] => 							if (str_starts_with($url, 'http')) {

            [2250] => 								return 'url(' . $url . ')';

            [2251] => 							}

            [2252] => 

            [2253] => 							if ($url[0] === '/') {

            [2254] => 								if (str_starts_with($url, PUBLIC_PATH)) {

            [2255] => 									$url = substr($url, $publicLen);

            [2256] => 								} else {

            [2257] => 									$url = substr($url, 1);

            [2258] => 								}

            [2259] => 

            [2260] => 								$url = PUBLIC_PATH . $url;

            [2261] => 							} else {

            [2262] => 								$url = $this->relativePath . $url;

            [2263] => 							}

            [2264] => 

            [2265] => 							return 'url(' . $url . ')';

            [2266] => 						},

            [2267] => 						$value

            [2268] => 					);

            [2269] => 

            [2270] => 					$this->setNodeAttribute($node, $name, $value);

            [2271] => 				}

            [2272] => 			}

            [2273] => 

            [2274] => 			if ($this->removeVattrs && str_starts_with($name, 'data-v-')) {

            [2275] => 				$node->removeAttribute($name);

            [2276] => 

            [2277] => 				continue;

            [2278] => 			} else {

            [2279] => 				if (str_contains($value, '$')) {

            [2280] => 					$value = preg_replace_callback(

            [2281] => 						'/{(\$[a-z][\w\.>\-]+)}/i',

            [2282] => 						function($matches) {

            [2283] => 							$value = $matches[1];

            [2284] => 

            [2285] => 							if (str_starts_with($value, '$this')) {

            [2286] => 								$value = str_replace('$this.', '$this->', $value);

            [2287] => 							}

            [2288] => 							$value   = Vvveb\dotToArrayKey($value);

            [2289] => 

            [2290] => 							return VTPL_PHP_CDATA_OPEN . 'if (isset(' . $value . ')) echo htmlspecialchars(' . $value . ');' . VTPL_PHP_CDATA_CLOSE;

            [2291] => 						},

            [2292] => 						$value

            [2293] => 					);

            [2294] => 

            [2295] => 					$this->setNodeAttribute($node, $name, $value);

            [2296] => 				}

            [2297] => 			}

            [2298] => 

            [2299] => 			if (($name === 'placeholder' || $name === 'title'/* || $name == 'alt'*/) && ($value && $value[0] !== '@' && $value[0] !== '<')) {

            [2300] => 				$trimmed = trim($value);

            [2301] => 

            [2302] => 				if ($trimmed) {

            [2303] => 					$php     = VTPL_PHP_CDATA_OPEN . 'echo ' . $this->translationFunction . '(\'' . addcslashes($trimmed, "'") . '\');' . VTPL_PHP_CDATA_CLOSE;

            [2304] => 					$this->setNodeAttribute($node, $name, $php);

            [2305] => 				}

            [2306] => 			}

            [2307] => 			/*

            [2308] => 			if (strpos($name, 'data-v-') === 0) {

            [2309] => 				$node->removeAttribute($name);

            [2310] => 			}*/

            [2311] => 		}

            [2312] => 	}

            [2313] => 

            [2314] => 	/**

            [2315] => 	 * @param Dom\HTMLElement|DOMNode $currentNode

            [2316] => 	 *

            [2317] => 	 * @return void

            [2318] => 	 */

            [2319] => 	private function setMultiLanguageText(Dom\Node|DOMNode $currentNode): void {

            [2320] => 		if ($currentNode->childNodes->length === 0) {

            [2321] => 			return;

            [2322] => 		}

            [2323] => 

            [2324] => 		$length    = $currentNode->childNodes->length;

            [2325] => 		$publicLen = strlen(PUBLIC_PATH);

            [2326] => 		for ($i = 0; $i < $length; $i++) {

            [2327] => 			$node = $currentNode->childNodes[$i];

            [2328] => 			if (!$node) {

            [2329] => 				continue;

            [2330] => 			}

            [2331] => 			//strip comments

            [2332] => 			if ($node->nodeType === XML_COMMENT_NODE && $this->removeComments) {

            [2333] => 				$node->parentNode->removeChild($node);

            [2334] => 				$i--;

            [2335] => 				continue;

            [2336] => 			}

            [2337] => 

            [2338] => 			//process media urls, data-v-* attrs, {$variables} and translatable placeholders

            [2339] => 			if (property_exists($node, 'attributes') || (method_exists($node, 'hasAttributes') && $node->hasAttributes())) {

            [2340] => 				$this->processNodeAttributes($node);

            [2341] => 			}

            [2342] => 

            [2343] => 			$tagName = isset($node->parentNode->tagName) ? strtolower($node->parentNode->tagName) : false;

            [2344] => 

            [2345] => 			if ($node && $node->nodeType === XML_TEXT_NODE

            [2346] => 				&& (!$tagName || ($tagName !== 'textarea' && $tagName !== '_script' && $tagName !== 'script'

            [2347] => 					&& $tagName !== 'code' && $tagName !== 'iframe' && $tagName !== 'html'

            [2348] => 					&& $tagName !== 'noscript'))) {

            [2349] => 				if (isset($node->wholeText)) {

            [2350] => 					$text = $node->wholeText;

            [2351] => 				} else {

            [2352] => 					$text = $node->textContent;

            [2353] => 				}

            [2354] => 

            [2355] => 				$text    = \Vvveb\stripExtraSpaces($text);

            [2356] => 				$trimmed = trim($text);

            [2357] => 

            [2358] => 				//skip untranslatable text

            [2359] => 				if (strlen($trimmed) < 2 //too small

            [2360] => 					|| str_starts_with($trimmed, '{$') //starts with variable

            [2361] => 					|| str_starts_with($trimmed, 'http') //is url

            [2362] => 					|| str_contains($trimmed, '{$') //string has variable

            [2363] => 					|| str_contains($trimmed, ' 					|| (is_numeric($trimmed[0]))

            [2365] => 				) {

            [2366] => 					continue;

            [2367] => 				}

            [2368] => 

            [2369] => 				$before = $currentNode->childNodes->length;

            [2370] => 

            [2371] => 				//don't translate long texts

            [2372] => 				if (strlen($trimmed) < 1024) {

            [2373] => 					if ($this->legacy) {

            [2374] => 						$c = $this->document->createCDATASection('echo ' . $this->translationFunction . '(\'' . addcslashes($trimmed, "'") . '\');');

            [2375] => 					} else {

            [2376] => 						$c = $this->document->createTextNode('echo ' . $this->translationFunction . '(\'' . addcslashes($trimmed, "'") . '\');');

            [2377] => 					}

            [2378] => 					$f = $this->document->createElement('_script');

            [2379] => 					$f->setAttribute('language', 'php');

            [2380] => 					$f->appendChild($c);

            [2381] => 					$node->parentNode->replaceChild($f, $node);

            [2382] => 

            [2383] => 					//add previously trimmed space around text

            [2384] => 					$space   = str_replace($trimmed, '|', $text);

            [2385] => 					$explode = explode('|', $space);

            [2386] => 					//space before text

            [2387] => 					if ($explode[0]) {

            [2388] => 						$s = $this->document->createTextNode($explode[0]);

            [2389] => 						$f->parentNode->insertBefore($s, $f);

            [2390] => 					}

            [2391] => 					//space after text

            [2392] => 					if (isset($explode[1]) && $explode[1]) {

            [2393] => 						$e = $this->document->createTextNode($explode[1]);

            [2394] => 						//php 8

            [2395] => 						if (isset($f->nextElementSibling)) {

            [2396] => 							//add before next element

            [2397] => 							$f->parentNode->insertBefore($e, $f->nextSibling);

            [2398] => 						} else {

            [2399] => 							if ($f->nextSibling) {

            [2400] => 								//add before next element

            [2401] => 								$f->parentNode->insertBefore($e, $f->nextSibling);

            [2402] => 							} else {

            [2403] => 								//add as last element

            [2404] => 								$f->parentNode->appendChild($e);

            [2405] => 							}

            [2406] => 						}

            [2407] => 					}

            [2408] => 				}

            [2409] => 

            [2410] => 				$diff = $currentNode->childNodes->length - $before;

            [2411] => 				$length += $diff;

            [2412] => 			} else {

            [2413] => 				$this->setMultiLanguageText($node);

            [2414] => 			}

            [2415] => 		}

            [2416] => 	}

            [2417] => 

            [2418] => 	/**

            [2419] => 	 * @param string $php

            [2420] => 	 *

            [2421] => 	 * @return string

            [2422] => 	 */

            [2423] => 	private function minifyPhp(string $php): string {

            [2424] => 		if (function_exists('token_get_all')) {

            [2425] => 			//tokenize so // and /* inside strings (e.g. "https://host") are not stripped as comments

            [2426] => 			$tokens = token_get_all(' 			$clean  = '';

            [2428] => 			foreach ($tokens as $token) {

            [2429] => 				if (is_array($token) && ($token[0] === T_DOC_COMMENT || ($token[0] === T_COMMENT && !str_starts_with($token[1], '#')))) {

            [2430] => 					continue;

            [2431] => 				}

            [2432] => 				$clean .= is_array($token) ? $token[1] : $token;

            [2433] => 			}

            [2434] => 			$php = preg_replace('/^<\?php\s*/', '', $clean, 1);

            [2435] => 		} else {

            [2436] => 			//php comments outside strings /* */

            [2437] => 			$php = preg_replace('/(? 

            [2439] => 			//php comments outside strings //

            [2440] => 			$php = preg_replace('@(\/\/)(?=(?:[^"\']|["\'][^"\']*["\'])*$)[^\n]*@s', '', $php);

            [2441] => 		}

            [2442] => 

            [2443] => 		//repeating spaces

            [2444] => 		$php = preg_replace('/\s+/', ' ', $php);

            [2445] => 

            [2446] => 		//repeating end lines

            [2447] => 		return preg_replace('/\n+/', '', $php);

            [2448] => 	}

            [2449] => 

            [2450] => 	/**

            [2451] => 	 * @param string $html

            [2452] => 	 *

            [2453] => 	 * @return string

            [2454] => 	 */

            [2455] => 	private function removePhp(string $html): string {

            [2456] => 		//hack, php allows different opening and closing tags

            [2457] => 		return preg_replace('@(<\?php|<\? |<\?=|<\s*script\s*language\s*=\s*"\s*php\s*"\s*>|<%[^%]*%>).*?(\?>|<\s*/\s*script\s*>|%>)@sm', '', $html);

            [2458] => 	}

            [2459] => 

            [2460] => 	/**

            [2461] => 	 * @param string $html

            [2462] => 	 *

            [2463] => 	 * @return string

            [2464] => 	 */

            [2465] => 	private function minifyHtml(string $html): string {

            [2466] => 		//html comments but keep ie conditionals

            [2467] => 		$html = preg_replace('//sm', '', $html);

            [2468] => 		$html = str_replace('