예제 #1
0
 /**
  * Process the PatternMatch.
  *
  * @return boolean|null A single boolean with a value of true if the sub-expression matches the pattern and false if it does not. If the sub-expression is NULL, the the operator results in NULL.
  * @throws \qtism\runtime\expressions\operators\OperatorProcessingException
  */
 public function process()
 {
     $operands = $this->getOperands();
     if ($operands->containsNull() === true) {
         return null;
     }
     if ($operands->exclusivelySingle() === false) {
         $msg = "The PatternMatch operator only accepts operands with a single cardinality.";
         throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY);
     }
     if ($operands->exclusivelyString() === false) {
         $msg = "The PatternMatch operator only accepts operands with a string baseType.";
         throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE);
     }
     // XML schema always implicitly anchors the entire regular expression
     // because there is no carret (^) nor dollar ($) signs.
     // see http://www.regular-expressions.info/xml.html
     $rawPattern = $this->getExpression()->getPattern();
     $pattern = OperatorUtils::escapeSymbols($rawPattern, array('$', '^'));
     $pattern = OperatorUtils::pregAddDelimiter('^' . $pattern . '$');
     // XSD regexp always case-sensitive (nothing to do), dot matches white-spaces (use PCRE_DOTALL).
     $pattern .= 's';
     $result = @preg_match($pattern, $operands[0]->getValue());
     if ($result === 1) {
         return new Boolean(true);
     } elseif ($result === 0) {
         return new Boolean(false);
     } else {
         $error = preg_last_error();
         $errorType = 'PCRE Engine compilation error';
         switch ($error) {
             case PREG_INTERNAL_ERROR:
                 $errorType = "PCRE Engine internal error";
                 break;
             case PREG_BACKTRACK_LIMIT_ERROR:
                 $errorType = "PCRE Engine backtrack limit exceeded";
                 break;
             case PREG_RECURSION_LIMIT_ERROR:
                 $errorType = "PCRE Engine recursion limit exceeded";
                 break;
             case PREG_BAD_UTF8_ERROR:
                 $errorType = "PCRE Engine malformed UTF-8";
                 break;
             case PREG_BAD_UTF8_OFFSET_ERROR:
                 $errorType = "PCRE Engine UTF-8 offset error";
                 break;
         }
         $msg = "An internal error occured while processing the regular expression '{$rawPattern}': {$errorType}.";
         throw new OperatorProcessingException($msg, $this, OperatorProcessingException::RUNTIME_ERROR);
     }
 }
예제 #2
0
 /**
  * Create the OperatorProcessor relevant to the given $expression.
  *
  * @param \qtism\data\expressions\Expression $expression The Operator object you want to get the processor.
  * @param \qtism\runtime\expressions\operators\OperandsCollection $operands The operands to be involved in the Operator object.
  * @return \qtism\runtime\expressions\operators\OperatorProcessor An OperatorProcessor object ready to process $expression.
  * @throws \qtism\runtime\expressions\operators\OperatorProcessingException If the $operands count is not compliant with the Operator object to be processed.
  * @throws \InvalidArgumentException If $expression is not an Operator object.
  * @throws \RuntimeException If no relevant OperatorProcessor is found for the given $expression.
  */
 public function createProcessor(QtiComponent $expression, OperandsCollection $operands = null)
 {
     if ($expression instanceof Operator) {
         if ($expression instanceof CustomOperator) {
             // QTI custom operator. Try to load an autoloaded class by using
             // its class attribute value.
             if ($expression->hasClass() === true) {
                 $className = Utils::customOperatorClassToPhpClass($expression->getClass());
                 if (class_exists($className) === true) {
                     return new $className($expression, $operands);
                 } else {
                     $msg = "No custom operator implementation found for class '" . $expression->getClass() . "'.";
                     throw new RuntimeException($msg);
                 }
             } else {
                 $msg = "Only custom operators with a 'class' attribute value can be processed.";
                 throw new RuntimeException($msg);
             }
         } else {
             // QTI built-in operator.
             $qtiClassName = ucfirst($expression->getQtiClassName());
             $nsPackage = 'qtism\\runtime\\expressions\\operators\\';
             $className = $nsPackage . $qtiClassName . 'Processor';
             if (class_exists($className)) {
                 if (is_null($operands) === true) {
                     $operands = new OperandsCollection();
                 }
                 return new $className($expression, $operands);
             } else {
                 $msg = "No dedicated OperatorProcessor class found for QTI operator '{$qtiClassName}'.";
                 throw new RuntimeException($msg);
             }
         }
     } else {
         $msg = "The OperatorProcessorFactory only accepts to create processors for Operator objects.";
         throw new InvalidArgumentException($msg);
     }
 }
예제 #3
0
 /**
  * Process the Gcd operator.
  *
  * @return integer The integer value equal in value to the greatest common divisor of the sub-expressions. If any of the sub-expressions is NULL, the result is NULL.
  * @throws \qtism\runtime\expressions\operators\OperatorProcessingException
  */
 public function process()
 {
     $operands = $this->getOperands();
     if ($operands->containsNull() === true) {
         return null;
     }
     if ($operands->anythingButRecord() === false) {
         $msg = "The Gcd operator only accepts operands with a cardinality of single, multiple or ordered.";
         throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY);
     }
     if ($operands->exclusivelyInteger() === false) {
         $msg = "The Gcd operator only accepts operands with an integer baseType.";
         throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE);
     }
     // Make a flat collection first.
     $flatCollection = new OperandsCollection();
     $zeroCount = 0;
     $valueCount = 0;
     foreach ($operands as $operand) {
         if ($operand instanceof QtiScalar) {
             $valueCount++;
             if ($operand->getValue() !== 0) {
                 $flatCollection[] = $operand;
             } else {
                 $zeroCount++;
             }
         } elseif ($operand->contains(null)) {
             // Container with at least one null value inside.
             // -> If any of the sub-expressions is null or not numeric, returns null.
             return null;
         } else {
             // Container with no null values.
             foreach ($operand as $o) {
                 $valueCount++;
                 if ($o->getValue() !== 0) {
                     $flatCollection[] = $o;
                 } else {
                     $zeroCount++;
                 }
             }
         }
     }
     if ($zeroCount === $valueCount) {
         // All arguments of gcd() are 0.
         return new QtiInteger(0);
     } else {
         $g = $flatCollection[0];
         $loopLimit = count($flatCollection) - 1;
         $i = 0;
         while ($i < $loopLimit) {
             $g = new QtiInteger(Utils::gcd($g->getValue(), $flatCollection[$i + 1]->getValue()));
             $i++;
         }
         return $g;
     }
 }
예제 #4
0
 /**
  * Process the Repeat operator.
  *
  * Note: NULL values are simply ignored. If all sub-expressions are NULL, NULL is
  * returned.
  *
  * @return \qtism\runtime\common\OrderedContainer An ordered container filled sequentially by evaluating each sub-expressions, repeated a 'numberRepeats' of times. NULL is returned if all sub-expressions are NULL or numberRepeats < 1.
  * @throws \qtism\runtime\expressions\operators\OperatorProcessingException
  */
 public function process()
 {
     $operands = $this->getOperands();
     // get the value of numberRepeats
     $expression = $this->getExpression();
     $numberRepeats = $expression->getNumberRepeats();
     if (gettype($numberRepeats) === 'string') {
         // Variable reference found.
         $state = $this->getState();
         $varName = Utils::sanitizeVariableRef($numberRepeats);
         $varValue = $state[$varName];
         if (is_null($varValue) === true) {
             $msg = "The variable with name '{$varName}' could not be resolved.";
             throw new OperatorProcessingException($msg, $this);
         } elseif ($varValue instanceof Integer) {
             $msg = "The variable with name '{$varName}' is not an integer value.";
             throw new OperatorProcessingException($msg, $this);
         }
         $numberRepeats = $varValue->getValue();
     }
     if ($numberRepeats < 1) {
         return null;
     }
     $result = null;
     for ($i = 0; $i < $numberRepeats; $i++) {
         $refType = null;
         foreach ($operands as $operand) {
             // If null, ignore
             if (is_null($operand) || $operand instanceof Container && $operand->isNull()) {
                 continue;
             }
             // Check cardinality.
             if ($operand->getCardinality() !== Cardinality::SINGLE && $operand->getCardinality() !== Cardinality::ORDERED) {
                 $msg = "The Repeat operator only accepts operands with a single or ordered cardinality.";
                 throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY);
             }
             // Check baseType.
             $currentType = RuntimeUtils::inferBaseType($operand);
             if ($refType !== null && $currentType !== $refType) {
                 $msg = "The Repeat operator only accepts operands with the same baseType.";
                 throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE);
             } elseif (is_null($result)) {
                 $refType = $currentType;
                 $result = new OrderedContainer($refType);
             }
             // Okay we are good...
             $operandCardinality = RuntimeUtils::inferCardinality($operand);
             if ($operandCardinality !== Cardinality::ORDERED) {
                 $operand = new OrderedContainer($currentType, array($operand));
             }
             foreach ($operand as $o) {
                 $result[] = $o instanceof QtiDatatype ? clone $o : $o;
             }
         }
     }
     if (isset($result) && $result->isNull() !== true) {
         return $result;
     } else {
         return null;
     }
 }
예제 #5
0
 /**
  *
  * @return null, \qtism\common\datatypes\Float
  */
 protected function processPopSD()
 {
     $operands = $this->getOperands();
     $operand = $operands[0];
     $result = OperatorsUtils::standardDeviation(self::filterValues($operand->getArrayCopy()), false);
     return $result !== false ? new QtiFloat(floatval($result)) : null;
 }
예제 #6
0
 /**
  * @dataProvider invalidCustomOperatorClassToPhpClassProvider
  * 
  * @param string $customClass
  */
 public function testInvalidCustomOperatorClassToPhpClass($customClass)
 {
     $this->assertFalse(OperatorsUtils::customOperatorClassToPhpClass($customClass));
 }
예제 #7
0
 /**
  * Process the RoundTo operator.
  *
  * An OperatorProcessingException will be thrown if:
  *
  * * The given operand is not a numeric value.
  * * The cardinality of the operand is not single.
  * * The value of the 'figures' attribute comes from a templateVariable which does not exist or is not numeric or null.
  *
  * @return null|float A single float with the value nearest to that of the expression's value or NULL if the sub-expression is NaN.
  * @throws \qtism\runtime\expressions\operators\OperatorProcessingException
  */
 public function process()
 {
     $operands = $this->getOperands();
     $state = $this->getState();
     $operand = $operands[0];
     // If the value is null, return null.
     if ($operands->containsNull()) {
         return null;
     }
     if (!$operands->exclusivelySingle()) {
         $msg = "The RoundTo operator accepts 1 operand with single cardinality.";
         throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY);
     }
     // Accept only numerical operands.
     if (!$operands->exclusivelyNumeric()) {
         $msg = "The RoundTo operand accepts 1 operand with numerical baseType.";
         throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE);
     }
     // As per QTI 2.1 spec...
     if (is_nan($operand->getValue())) {
         return null;
     } elseif (is_infinite($operand->getValue())) {
         return $operand;
     }
     $roundingMode = $this->getExpression()->getRoundingMode();
     $figures = $this->getExpression()->getFigures();
     if (gettype($figures) === 'string') {
         // try to recover the value from the state.
         $figuresIdentifier = Utils::sanitizeVariableRef($figures);
         $figures = $state[$figuresIdentifier];
         if (is_null($figures)) {
             $msg = "The variable '{$figuresIdentifier}' used to set up the 'figures' attribute is null or nonexisting.";
             throw new OperatorProcessingException($msg, $this, OperatorProcessingException::NONEXISTENT_VARIABLE);
         } elseif (!$figures instanceof Integer) {
             $msg = "The variable '{$figuresIdentifier}' used to set up the 'figures' attribute is not an integer.";
             throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_VARIABLE_BASETYPE);
         }
         $figures = $figures->getValue();
     }
     if ($roundingMode === RoundingMode::SIGNIFICANT_FIGURES) {
         if ($figures <= 0) {
             // As per QTI 2.1 spec.
             $msg = "The 'figures' attribute must be a non-zero positive integer when mode 'significantFigures' is used, '{$figures}' given.";
             throw new OperatorProcessingException($msg, $this, OperatorProcessingException::LOGIC_ERROR);
         }
         if ($operand->getValue() == 0) {
             return new Float(0.0);
         }
         $d = ceil(log10($operand->getValue() < 0 ? -$operand->getValue() : $operand->getValue()));
         $power = $figures - intval($d);
         $magnitude = pow(10, $power);
         $shifted = round($operand->getValue() * $magnitude);
         return new Float(floatval($shifted / $magnitude));
     } else {
         // As per QTI 2.1 spec.
         if ($figures < 0) {
             $msg = "The 'figures' attribute must be a integer greater than or equal to zero when mode 'decimalPlaces' is used, '{$figures}' given.";
             throw new OperatorProcessingException($msg, $this);
         }
         return new Float(round($operand->getValue(), $figures));
     }
 }