Non puoi selezionare più di 25 argomenti
Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
215 righe
6.4 KiB
215 righe
6.4 KiB
<?php |
|
|
|
use lyquidity\xml\MS\XmlNamespaceManager; |
|
use lyquidity\xml\xpath\XPathNodeType; |
|
use lyquidity\XPath2\NodeProvider; |
|
use lyquidity\XPath2\XPath2Expression; |
|
use lyquidity\XPath2\DOM\DOMXPathNavigator; |
|
use lyquidity\XPath2\FalseValue; |
|
use lyquidity\XPath2\TrueValue; |
|
use lyquidity\XPath2\XPath2Exception; |
|
use SebastianBergmann\Type\FalseType; |
|
|
|
/** |
|
* Helper class to run XPath 2.0 queries on XML files |
|
*/ |
|
class XML_XPath20_Querier |
|
{ |
|
public $dom; // @var DOMDocument |
|
protected $nsMgr; // @var XmlNamespaceManager |
|
public $navigator; // @var DOMXPathNavigator |
|
protected $xpath10; // Runner di XPath 1.0 |
|
|
|
protected $code_cache; |
|
protected $code_cache_enabled = true; |
|
protected $result_cache; |
|
protected $result_cache_enabled = true; |
|
|
|
/** |
|
* Initialize the Querier with an existing DOM |
|
* |
|
* @param DOMDocument $dom |
|
*/ |
|
function __construct($dom, $enable_result_cache = true, $enable_code_cache = true) |
|
{ |
|
$this->dom = $dom; |
|
$this->xpath10 = new DOMXPath($this->dom); |
|
$this->result_cache_enabled = $enable_result_cache; |
|
$this->code_cache_enabled = $enable_code_cache; |
|
// Load the namespaces from the document. $node is a DOMNameSpaceNode so extract the prefix from the node name |
|
$this->nsMgr = new XmlNamespaceManager(); |
|
$xpath = new DOMXPath($this->dom); |
|
foreach ($xpath->query('namespace::*', $this->dom->documentElement) as $node) { |
|
$this->nsMgr->addNamespace(str_replace(array('xmlns', ':'), array('', ''), $node->nodeName), (string)$node->nodeValue); |
|
} |
|
// Create a navigator to navigate the document and move to root |
|
$this->navigator = new DOMXPathNavigator($this->dom, $this->nsMgr); |
|
$this->navigator->MoveToRoot(); |
|
} |
|
/** |
|
* Helper function to convert from (some) lyquidity types to native ones |
|
* |
|
* @param mixed $result |
|
* @return mixed |
|
*/ |
|
protected function processResult($result) |
|
{ |
|
if ($result instanceof FalseValue) |
|
return False; |
|
if ($result instanceof TrueValue) |
|
return True; |
|
return $result; |
|
} |
|
|
|
/** |
|
* Runs a XPath 2.0 query and returns an array of DomNodes |
|
* |
|
* @param String $query |
|
* @return array[DOMNode] |
|
*/ |
|
public function runQuery(String $query): array |
|
{ |
|
// Array di risultati |
|
$results = []; |
|
|
|
try { |
|
$xpath10results = @$this->xpath10->query($query); |
|
} catch (\Throwable $t) { |
|
$xpath10results = false; |
|
} |
|
|
|
if ($xpath10results !== false) { |
|
// Creiamo l'array di nodi |
|
foreach ($xpath10results as $node) { |
|
$results[] = $node; |
|
} |
|
} else { |
|
// Compile the XPath 2.0 |
|
$expression = null; |
|
if ($this->code_cache_enabled) { |
|
$expression = $this->code_cache[$query] ?? null; |
|
} |
|
if ($expression === null) { |
|
$expression = XPath2Expression::Compile($query, $this->nsMgr); |
|
if ($this->code_cache_enabled) |
|
$this->code_cache[$query] = $expression; |
|
} |
|
|
|
$provider = new NodeProvider($this->navigator); |
|
|
|
|
|
// Evaluate the expression |
|
$result = $expression->EvaluateWithVars($provider, null); |
|
if ($result) { |
|
// If there is a list of context then iterate over them. In this case $result will be ChildNodeIterator |
|
foreach ($result as |
|
/** @var DOMXPathNavigator $localNav */ |
|
$localNav) { |
|
$provider = new NodeProvider($localNav); |
|
$domNode = $localNav->getUnderlyingObject(); |
|
$results[] = $domNode; |
|
} |
|
} |
|
} |
|
|
|
// Return results |
|
return $results; |
|
} |
|
/** |
|
* Runs a XPath 2.0 query on a XPath 2.0 context query |
|
* |
|
* @param String $context_query |
|
* @param String $query |
|
* @return array |
|
*/ |
|
public function runQueryContext(String $context_query, String $query): array |
|
{ |
|
|
|
if ($this->result_cache_enabled && isset($this->result_cache[$context_query]) && isset($this->result_cache[$context_query][$query])) { |
|
return $this->result_cache[$context_query][$query]; |
|
} |
|
|
|
$results = []; |
|
$context = $this->runQuery($context_query); |
|
|
|
|
|
if (!empty($context)) { |
|
// If there is a list of context then iterate over them. In this case $result will be ChildNodeIterator |
|
foreach ($context as /** @var DOMNode $domNode */ $domNode) { |
|
$localNav = new DOMXPathNavigator($domNode); |
|
$provider = new NodeProvider($localNav); |
|
|
|
// Compile the XPath 2.0 |
|
$expression = null; |
|
if ($this->code_cache_enabled) { |
|
$expression = $this->code_cache[$query] ?? null; |
|
} |
|
if ($expression === null) { |
|
$expression = XPath2Expression::Compile($query, $this->nsMgr); |
|
if ($this->code_cache_enabled) |
|
$this->code_cache[$query] = $expression; |
|
} |
|
// Evaluate it in the current context |
|
$result = $expression->EvaluateWithVars($provider, null); |
|
// Organize the results in a simple to use array |
|
$results[] = $this->processResult($result); |
|
} |
|
} |
|
|
|
// Return results |
|
if ($this->result_cache_enabled) |
|
$this->result_cache[$context_query][$query] = $results; |
|
return $results; |
|
} |
|
|
|
public const XPATH_FAIL_EXCEPTION = 1; |
|
public const XPATH_FAIL_NO_RESULTS = 2; |
|
public const XPATH_FAIL_NOT_ENOUGH_RESULTS = 3; |
|
public const XPATH_FAIL_WRONG_TYPE = 4; |
|
/** |
|
* Will evaluate an XPath 2.0 condition to True/False and fail gracefully |
|
* |
|
* @param string $query |
|
* @param string||null $context If not null, manually provide a context to execute the query |
|
* @return array||null The value of the evaluation |
|
*/ |
|
public function evalBoolean($query, $context, $index, &$error): ?bool |
|
{ |
|
// Evaluate the condition |
|
try { |
|
$condition_results = $this->runQueryContext($context, $query); |
|
} catch (XPath2Exception $xpath_ex) { |
|
// TODO Un giorno magari le debugghiamo |
|
$error = self::XPATH_FAIL_EXCEPTION; |
|
return null; |
|
} catch (\Throwable $ex) { |
|
throw $ex; |
|
$error = self::XPATH_FAIL_EXCEPTION; |
|
return null; |
|
} |
|
// Get count of results |
|
$cr_count = count($condition_results); |
|
|
|
|
|
// No results |
|
if ($cr_count === 0) { |
|
$error = self::XPATH_FAIL_NO_RESULTS; |
|
return null; |
|
} |
|
|
|
if ($index >= $cr_count) { |
|
$error = self::XPATH_FAIL_NOT_ENOUGH_RESULTS; |
|
return null; |
|
} |
|
|
|
$condition_result = $condition_results[$index]; |
|
|
|
// Not a boolean |
|
if (!is_bool($condition_result)) { |
|
$error = self::XPATH_FAIL_WRONG_TYPE; |
|
throw new Exception("Wrong type, boolean expected."); |
|
return null; |
|
} |
|
return $condition_result; |
|
} |
|
}
|
|
|