Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for <sch:let> #3

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 73 additions & 8 deletions src/Schematron.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ class Schematron

/** @var array[prefix => URI] loaded from <sch:ns> */
protected $namespaces = array();

/** @var array[name => value] {@see self::findGlobalLets()} */
protected $globallets = array();

/** @var stdClass[] {@see self::findPatterns()} */
protected $patterns = array();
Expand Down Expand Up @@ -229,6 +232,9 @@ public function loadDom(DOMDocument $schema)

$this->loadSchemaBasics($schema);
$this->namespaces = $this->findNamespaces($schema);

$this->globallets = $this->findGlobalLets($schema);

$this->patterns = $this->findPatterns($schema);
if (!count($this->patterns) && !($this->options & self::ALLOW_EMPTY_SCHEMA)) {
throw new SchematronException('None <sch:pattern> found in schema.');
Expand Down Expand Up @@ -274,15 +280,36 @@ public function validate(DOMDocument $doc, $result = self::RESULT_SIMPLE, $phase
} else {
$activePatternKeys = array_keys($this->phases[$phase]);
}


$globallets = array();

foreach($this->globallets as $name => $value) {
$globallets[$name] = $xpath->evaluate("string($value)");
/* May be better ways to do this. When using the variable for string comparations in xpath it
* needs to be enclosed by ''.
*/
if(!is_numeric($globallets[$name])) {
$globallets[$name] = "'" . $globallets[$name] . "'";
}
}

$return = array();
foreach ($activePatternKeys as $patternKey) {
$pattern = $this->patterns[$patternKey];
foreach ($pattern->rules as $ruleKey => $rule) {
foreach ($xpath->queryContext($rule->context, $doc) as $currentNode) {
$lets = array();
foreach($rule->lets as $name => $value) {
$lets[$name] = $xpath->evaluate("string($value)", $currentNode);
if(!is_numeric($lets[$name])) {
$lets[$name] = "'" . $lets[$name] . "'";
}
}
foreach ($rule->statements as $statement) {
if ($statement->isAssert ^ $xpath->evaluate("boolean($statement->test)", $currentNode)) {
$message = $this->statementToMessage($statement->node, $xpath, $currentNode);
$test = call_user_func($this->getReplaceCb(), $statement->test, array_merge($lets, $globallets));
if ($statement->isAssert ^ $xpath->evaluate("boolean($test)", $currentNode)) {
$message = $this->statementToMessage($statement->node, $xpath, $currentNode,
array_merge($lets, $globallets));

switch ($result) {
case self::RESULT_EXCEPTION:
Expand Down Expand Up @@ -555,6 +582,8 @@ protected function loadSchemaBasics(DOMDocument $schema)





/**
* Search for all <sch:ns>.
* @return array[string prefix => string URI]
Expand All @@ -577,8 +606,6 @@ protected function findNamespaces(DOMDocument $schema)
return $namespaces;
}



/**
* Search for all <sch:pattern>. Abstract patterns are instantized.
* @return stdClass[]
Expand Down Expand Up @@ -741,8 +768,9 @@ protected function findRules(DOMElement $pattern)
$rules[] = (object) array(
'context' => $context,
'statements' => $statements = $this->findStatements($element, $abstracts),
'lets' => $this->findLets($element)
);

if (!count($statements) && !($this->options & self::ALLOW_EMPTY_RULE)) {
throw new SchematronException("Asserts nor reports not found for <$element->nodeName> on line {$element->getLineNo()}.");
}
Expand Down Expand Up @@ -802,7 +830,42 @@ protected function findStatements(DOMElement $rule, array $abstractRules = array
}
return $statements;
}

/**
* Search for all <sch:let> not specified inside a rule.
* @return array[string name => string value]
* @throws SchematronException
*/
protected function findGlobalLets(DOMDocument $schema)
{
$lets = $elements = array();
foreach ($this->xPath->query('//sch:let[not(parent::sch:rule)]', $schema) as $element) {
$name = Helpers::getAttribute($element, 'name');
$value = Helpers::getAttribute($element, 'value');
if (array_key_exists($name, $elements)) {
throw new SchematronException("Global variable '$name' on line {$element->getLineNo()} is alredy declared on line {$elements[$name]->getLineNo()}.");
}
$elements[$name] = $element;
$lets[$name] = $value;
}
return $lets;
}

/**
* Search for all <sch:let>
* @return stdClass[]
* @throws SchematronException
*/
protected function findLets(DOMElement $rule)
{
$lets = array();
foreach ($this->xPath->query('sch:let', $rule) as $node) {
$name = Helpers::getAttribute($node, 'name');
$value = Helpers::getAttribute($node, 'value');
$lets[$name] = $value;
}
return $lets;
}


/**
Expand Down Expand Up @@ -855,7 +918,7 @@ protected function findActives(DOMElement $phase)
* Expands <sch:name> and <sch:value-of> in assertion/report message.
* @return string
*/
protected function statementToMessage(DOMElement $stmt, SchematronXPath $xPath, DOMNode $current)
protected function statementToMessage(DOMElement $stmt, SchematronXPath $xPath, DOMNode $current, $lets = array())
{
$message = '';
foreach ($stmt->childNodes as $node) {
Expand All @@ -864,7 +927,9 @@ protected function statementToMessage(DOMElement $stmt, SchematronXPath $xPath,
$message .= $xPath->evaluate('name(' . Helpers::getAttribute($node, 'path', '') . ')', $current);

} elseif ($node->localName === 'value-of') {
$message .= $xPath->evaluate('string(' . Helpers::getAttribute($node, 'select') . ')', $current);
$selectValue = $test = call_user_func($this->getReplaceCb(), Helpers::getAttribute($node, 'select'),
$lets);
$message .= $xPath->evaluate('string(' . $selectValue . ')', $current);

} else {
/** @todo warning? */
Expand Down