/** * @see \qtism\runtime\common\Processable::process() */ public function process() { $expression = $this->getExpression(); return RuntimeUtils::valueToRuntime($expression->getValue(), $expression->getBaseType()); }
/** * Create a Container object from a Data Model ValueCollection object. * * @param \qtism\data\state\ValueCollection $valueCollection A collection of qtism\data\state\Value objects. * @return \qtism\common\collections\Container A Container object populated with the values found in $valueCollection. * @throws \InvalidArgumentException If a value from $valueCollection is not compliant with the QTI Runtime Model or the container type. */ public static function createFromDataModel(ValueCollection $valueCollection) { $container = new static(); foreach ($valueCollection as $value) { $container[] = RuntimeUtils::valueToRuntime($value->getValue(), $value->getBaseType()); } return $container; }
/** * Create a QTI Runtime value from Data Model ValueCollection * * @param \qtism\data\state\ValueCollection $valueCollection A collection of qtism\data\state\Value objects. * @param integer $baseType The baseType the Value objects in the ValueCollection must respect. * @param integer $cardinality The cardinality the Value objects in the ValueCollection must respect. * @throws \UnexpectedValueException If $baseType or/and $cardinality are not respected by the Value objects in the ValueCollection. * @return mixed The resulting QTI Runtime value (primitive or container depending on baseType/cardinality). */ protected static function dataModelValuesToRuntime(ValueCollection $valueCollection, $baseType, $cardinality) { // Cardinality? // -> Single? Multiple? Ordered? Record? if ($cardinality === Cardinality::SINGLE) { // We should find a single value in the DefaultValue's values. if (count($valueCollection) == 1) { $dataModelValue = RuntimeUtils::valueToRuntime($valueCollection[0]->getValue(), $baseType); return $dataModelValue; } else { // The Data Model is in an inconsistent state. // This should be handled by the Data Model but // I prefer to be defensive. $msg = "A Data Model VariableDeclaration with 'single' cardinality must contain a single value, "; $msg .= count($valueCollection) . " value(s) found."; throw new UnexpectedValueException($msg); } } else { // Multiple|Ordered|Record, use a container. $container = null; try { // Create the appropriate Container object. $className = ucfirst(Cardinality::getNameByConstant($cardinality)) . 'Container'; $nsClassName = 'qtism\\runtime\\common\\' . $className; $callback = array($nsClassName, 'createFromDataModel'); $container = call_user_func_array($callback, array($valueCollection, $baseType)); return $container; // return container. } catch (InvalidArgumentException $e) { $msg = "The default value found in the Data Model Variable Declaration is not consistent. "; $msg .= "The values must have a baseType compliant with the baseType of the VariableDeclaration."; $msg .= "If the VariableDeclaration's cardinality is 'record', make sure the values it contains have "; $msg .= "fieldIdentifiers."; throw new UnexpectedValueException($msg, 0, $e); } } }
/** * Process the LookupOutcomeValue rule. * * A RuleProcessingException will be thrown if: * * * The outcome variable to set does not exist. * * The variable to set is not an OutcomeVariable * * The outcome variable's baseType does not match the baseType of the affected value (the result of the bound expression). * * The outcome variable's declaration has no associated lookup table. * * The variable's declaration contains a matchTable but the result of the bound expression is not an integer. * * The variable's declaration contains an interpolationTable but the result of the bound expression is not an integer, nor a float. * * There is no associated table in the variable's declaration. * * An error occurs during the processing of the related expression. * * @throws \qtism\runtime\rules\RuleProcessingException If one of the error described above arise. */ public function process() { $state = $this->getState(); $rule = $this->getRule(); $identifier = $rule->getIdentifier(); $var = $state->getVariable($identifier); if (is_null($var) === true) { $msg = "The variable to set '{$identifier}' does not exist in the current state."; throw new RuleProcessingException($msg, $this, RuleProcessingException::NONEXISTENT_VARIABLE); } elseif (!$var instanceof OutcomeVariable) { $msg = "The variable to set '{$identifier}' is not an OutcomeVariable."; throw new RuleProcessingException($msg, $this, RuleProcessingException::WRONG_VARIABLE_TYPE); } $expression = $rule->getExpression(); $expressionEngine = new ExpressionEngine($expression, $state); try { $val = $expressionEngine->process(); // Let's lookup the associated table. $table = $var->getLookupTable(); if (is_null($table) === true) { $msg = "No lookupTable in declaration of variable '{$identifier}'."; throw new RuleProcessingException($msg, $this, RuleProcessingException::LOGIC_ERROR); } // $targetVal = The value that will be set to the target variable. // // As per specs: // The default outcome value to be used when no matching table // entry is found. If omitted, the NULL value is used. $targetVal = $table->getDefaultValue(); if ($table instanceof InterpolationTable) { if (!$val instanceof Float && !$val instanceof Integer && !$val instanceof Duration) { $msg = "The value of variable '{$identifier}' must be integer, float or duration when used with an interpolationTable"; throw new RuleProcessingException($msg, $this, RuleProcessingException::LOGIC_ERROR); } foreach ($table->getInterpolationTableEntries() as $entry) { $lowerBound = $entry->getSourceValue(); $includeBoundary = $entry->doesIncludeBoundary(); if ($includeBoundary === true && $val->getValue() <= $lowerBound) { $targetVal = $entry->getTargetValue(); break; } elseif ($includeBoundary === false && $val->getValue() < $lowerBound) { $targetVal = $entry->getTargetValue(); break; } } } else { // $table instanceof MatchTable if (!$val instanceof Integer) { $msg = "The value of the variable '{$identifier}' must be integer when used with a matchTable."; throw new RuleProcessingException($msg, $this, RuleProcessingException::LOGIC_ERROR); } foreach ($table->getMatchTableEntries() as $entry) { if ($entry->getSourceValue() === $val->getValue()) { $targetVal = $entry->getTargetValue(); break; } } } // assign target value try { $finalVal = RuntimeUtils::valueToRuntime($targetVal, $var->getBaseType()); $state[$identifier] = $finalVal; } catch (InvalidArgumentException $e) { // $targetVal's baseType not compliant with target variable's baseType. $msg = "The looked up value's baseType is not compliant with the baseType of variable '{$identifier}'."; throw new RuleProcessingException($msg, $this, RuleProcessingException::RUNTIME_ERROR); } } catch (ExpressionProcessingException $e) { $msg = "An error occured while processing the expression bound to the lookupOutcomeValue rule."; throw new RuleProcessingException($msg, $this, RuleProcessingException::RUNTIME_ERROR, $e); } }
/** * Create a RecordContainer object from a Data Model ValueCollection object. * * @param \qtism\data\state\ValueCollection $valueCollection A collection of qtism\data\state\Value objects. * @return \qtism\runtime\common\RecordContainer A Container object populated with the values found in $valueCollection. * @throws \InvalidArgumentException If a value from $valueCollection is not compliant with the QTI Runtime Model or the container type or if a value has no fieldIdentifier. */ public static function createFromDataModel(ValueCollection $valueCollection) { $container = new static(); foreach ($valueCollection as $value) { $fieldIdentifier = $value->getFieldIdentifier(); if (!empty($fieldIdentifier)) { $container[$value->getFieldIdentifier()] = RuntimeUtils::valueToRuntime($value->getValue(), $value->getBaseType()); } else { $msg = "Cannot include qtism\\data\\state\\Value '" . $value->getValue() . "' in the RecordContainer "; $msg .= "because it has no fieldIdentifier specified."; throw new InvalidArgumentException($msg); } } return $container; }
/** * Read a record field value from the current binary stream. A record field is * composed of a key string and a value. * * @return array An array where the value at index 0 is the key string and index 1 is the value. * @throws QtiBinaryStreamAccessException */ public function readRecordField($isNull = false) { try { $key = $this->readString(); if ($isNull === false) { $baseType = $this->readTinyInt(); $toCall = 'read' . ucfirst(BaseType::getNameByConstant($baseType)); $value = Utils::valueToRuntime(call_user_func(array($this, $toCall)), $baseType); } else { $value = null; } return array($key, $value); } catch (BinaryStreamAccessException $e) { $msg = "An error occured while reading a Record Field."; throw new QtiBinaryStreamAccessException($msg, $this, QtiBinaryStreamAccessException::RECORDFIELD, $e); } }