/** * Process the Delete operator. * * @return \qtism\common\collections\Container A new container derived from the second sub-expression with all instances of the first sub-expression removed, or NULL if either sub-expression is considered to be NULL. * @throws \qtism\runtime\expressions\operators\OperatorProcessingException */ public function process() { $operands = $this->getOperands(); if ($operands->containsNull() === true) { return null; } if ($operands->sameBaseType() === false) { $msg = "The Delete operator only accepts operands with the same baseType."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE); } $operand1 = $operands[0]; if (RuntimeUtils::inferCardinality($operand1) !== Cardinality::SINGLE) { $msg = "The first operand of the Delete operator must have the single cardinality."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY); } $operand2 = $operands[1]; $cardinality = RuntimeUtils::inferCardinality($operand2); if ($cardinality !== Cardinality::MULTIPLE && $cardinality !== Cardinality::ORDERED) { $msg = "The second operand of the Delete operator must have a cardinality or multiple or ordered."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY); } $returnedBaseType = RuntimeUtils::inferBaseType($operand1); $returnValue = $cardinality === Cardinality::MULTIPLE ? new MultipleContainer($returnedBaseType) : new OrderedContainer($returnedBaseType); foreach ($operand2 as $value) { if ($value === $operand1 || $operand1 instanceof Comparable && $operand1->equals($value) === true) { // This is the same value, it will not be included in the returned value. continue; } else { $returnValue[] = $value; } } return $returnValue; }
/** * Process the current expression. * * @return \qtism\runtime\common\OrderedContainer|null An OrderedContainer object or NULL. * @throws \qtism\runtime\expressions\operators\OperatorProcessingException */ public function process() { $operands = $this->getOperands(); if (count($operands) === 0) { return null; } if ($operands->exclusivelySingleOrOrdered() === false) { $msg = "The Ordered operator only accepts operands with single or ordered cardinality."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE); } $refType = null; $returnValue = null; foreach ($operands as $operand) { if (is_null($operand) || $operand instanceof OrderedContainer && $operand->isNull()) { // As per specs, ignore. continue; } else { if ($refType !== null) { // A reference type as already been identifier. if (CommonUtils::inferBaseType($operand) === $refType) { // $operand can be added to $returnValue. static::appendValue($returnValue, $operand); } else { // baseType mismatch. $msg = "The Ordered operator only accepts values with a similar baseType."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE); } } elseif (($discoveryType = CommonUtils::inferBaseType($operand)) !== false) { // First value being identified as non-null. $refType = $discoveryType; $returnValue = new OrderedContainer($refType); static::appendValue($returnValue, $operand); } } } return $returnValue; }
/** * Processes all values of a record container and merge them into * a single string. * * @param RecordContainer $variable The record to process. * @return string All the key/values delimited by printedVariable->delimiter. Indicator between keys and values is defined by printedVariable->mappingIndicator. */ private function processRecord(Variable $variable) { $processedValues = array(); $baseType = $variable->getBaseType(); $mappingIndicator = $this->getComponent()->getMappingIndicator(); foreach ($variable->getValue() as $k => $v) { $processedValues[] = "{$k}{$mappingIndicator}" . $this->processValue(Utils::inferBaseType($v), $v); } return implode($this->getComponent()->getDelimiter(), $processedValues); }
/** * Process the EqualRounded operator. * * @return boolean|null A boolean with a value of true if the two expressions are numerically equal after rounding and false if they are not. If either sub-expression is NULL, the operator results in NULL. * @throws \qtism\runtime\expressions\operators\OperatorProcessingException */ public function process() { $operands = $this->getOperands(); if ($operands->containsNull()) { return null; } if ($operands->exclusivelySingle() === false) { $msg = "The EqualRounded operator only accepts operands with a single cardinality."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY); } if ($operands->exclusivelyNumeric() === false) { $msg = "The EqualRounded operator only accepts operands with an integer or float baseType."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE); } // delegate the rounding to the RoundTo operator. $expression = $this->getExpression(); $roundingMode = $expression->getRoundingMode(); $figures = $expression->getFigures(); if (gettype($figures) === 'string') { // Variable reference to deal with. $state = $this->getState(); $varName = Utils::sanitizeVariableRef($figures); $varValue = $state[$varName]; if (is_null($varValue) === true) { $msg = "The variable with name '{$varName}' could not be resolved."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::NONEXISTENT_VARIABLE); } elseif (!$varValue instanceof Integer) { $msg = "The variable with name '{$varName}' is not an integer."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_VARIABLE_BASETYPE); } $figures = $varValue->getValue(); } $rounded = new OperandsCollection(); // will contain the rounded operands. foreach ($operands as $operand) { $baseType = RuntimeUtils::inferBaseType($operand); $subExpression = new BaseValue($baseType, $operand); $roundToExpression = new RoundTo(new ExpressionCollection(array($subExpression)), $figures, $roundingMode); $roundToProcessor = new RoundToProcessor($roundToExpression, new OperandsCollection(array($operand))); try { $rounded[] = $roundToProcessor->process(); } catch (OperatorProcessingException $e) { $msg = "An error occured while rounding '{$operand}'."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::LOGIC_ERROR, $e); } } return new Boolean($rounded[0]->getValue() == $rounded[1]->getValue()); }
/** * @dataProvider inferBaseTypeProvider */ public function testInferBaseType($value, $expectedBaseType) { $this->assertTrue(Utils::inferBaseType($value) === $expectedBaseType); }
/** * Whether the collection is composed of values with the same baseType. * * * If any of the values has not the same baseType than other values in the collection, false is returned. * * If the OperandsCollection is an empty collection, false is returned. * * If the OperandsCollection contains a value considered to be null, false is returned. * * If the OperandsCollection is composed exclusively by non-null RecordContainer objects, true is returned. * * @return boolean */ public function sameBaseType() { $operandsCount = count($this); if ($operandsCount > 0 && !$this->containsNull()) { // take the first value of the collection as a referer. $refValue = $this[0]; $refType = RuntimeUtils::inferBaseType($refValue); for ($i = 1; $i < $operandsCount; $i++) { $value = $this[$i]; $testType = RuntimeUtils::inferBaseType($value); if ($testType !== $refType) { return false; } } // In any other case, return true. return true; } else { return false; } }
/** * 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; } }
/** * Write a record field value in the current binary stream. A record field is composed of a key string and a value. * * @param array $recordField An array where index 0 is the key string, and the index 1 is the value. * @throws QtiBinaryStreamAccessException */ public function writeRecordField(array $recordField, $isNull = false) { try { $this->writeBoolean($isNull); $key = $recordField[0]; $this->writeString($key); if ($isNull === false) { $value = $recordField[1]; $baseType = Utils::inferBaseType($value); $this->writeTinyInt($baseType); $toCall = 'write' . ucfirst(BaseType::getNameByConstant($baseType)); call_user_func(array($this, $toCall), $value instanceof Scalar ? $value->getValue() : $value); } } catch (BinaryStreamAccessException $e) { $msg = "An error occured while reading a Record Field."; throw new QtiBinaryStreamAccessException($msg, $this, QtiBinaryStreamAccessException::RECORDFIELD); } }
/** * Makes $value compliant with baseType $targetBaseType, if $value is compliant. Otherwise, * the original $value is returned. * * @param mixed $value A QTI Runtime compliant value. * @param integer $targetBaseType The target baseType. * @return mixed The juggled value if needed, otherwise the original value of $value. */ public static function juggle($value, $targetBaseType) { // A lot of people designing QTI items want to put float values // in integer baseType'd variables... So let's go for type juggling! $valueBaseType = RuntimeUtils::inferBaseType($value); if ($valueBaseType !== $targetBaseType && ($value instanceof MultipleContainer || $value instanceof OrderedContainer)) { $class = get_class($value); if ($valueBaseType === BaseType::FLOAT && $targetBaseType === BaseType::INTEGER) { $value = new $class($targetBaseType, self::floatArrayToInteger($value->getArrayCopy())); } else { if ($valueBaseType === BaseType::INTEGER && $targetBaseType === BaseType::FLOAT) { $value = new $class($targetBaseType, self::integerArrayToFloat($value->getArrayCopy())); } else { if ($valueBaseType === BaseType::IDENTIFIER && $targetBaseType === BaseType::STRING) { $value = new $class($targetBaseType, $value->getArrayCopy()); } else { if ($valueBaseType === BaseType::STRING && $targetBaseType === BaseType::IDENTIFIER) { $value = new $class($targetBaseType, $value->getArrayCopy()); } else { if ($valueBaseType === BaseType::URI && $targetBaseType === BaseType::STRING) { $value = new $class($targetBaseType, $value->getArrayCopy()); } else { if ($valueBaseType === BaseType::STRING && $targetBaseType === BaseType::URI) { $value = new $class($targetBaseType, $value->getArrayCopy()); } else { if ($valueBaseType === BaseType::URI && $targetBaseType === BaseType::IDENTIFIER) { $value = new $class($targetBaseType, $value->getArrayCopy()); } else { if ($valueBaseType === BaseType::IDENTIFIER && $targetBaseType === BaseType::URI) { $value = new $class($targetBaseType, $value->getArrayCopy()); } } } } } } } } } else { if ($valueBaseType !== $targetBaseType) { // Scalar value. if ($valueBaseType === BaseType::FLOAT && $targetBaseType === BaseType::INTEGER) { $value = intval($value); } else { if ($valueBaseType === BaseType::INTEGER && $targetBaseType === BaseType::FLOAT) { $value = floatval($value); } } } } return $value; }