/** * @see \qtism\data\storage\xml\marshalling\OperatorMarshaller::unmarshallChildrenKnown() */ protected function unmarshallChildrenKnown(DOMElement $element, QtiComponentCollection $children) { $object = new Equal($children); if (($toleranceMode = static::getDOMElementAttributeAs($element, 'toleranceMode')) !== null) { $toleranceMode = ToleranceMode::getConstantByName($toleranceMode); $object->setToleranceMode($toleranceMode); } if (($tolerance = static::getDOMElementAttributeAs($element, 'tolerance')) !== null) { $tolerance = explode(" ", $tolerance); if (count($tolerance) < 1) { $msg = "No 'tolerance' could be extracted from element '" . $element->localName . "'."; throw new UnmarshallingException($msg, $element); } elseif (count($tolerance) > 2) { $msg = "'tolerance' attribute not correctly formatted in element '" . $element->localName . "'."; throw new UnmarshallingException($msg, $element); } else { $finalTolerance = array(); foreach ($tolerance as $t) { $finalTolerance[] = Format::isFloat($t) ? floatval($t) : $t; } $object->setTolerance($finalTolerance); } } if (($includeLowerBound = static::getDOMElementAttributeAs($element, 'includeLowerBound', 'boolean')) !== null) { $object->setIncludeLowerBound($includeLowerBound); } if (($includeUpperBound = static::getDOMElementAttributeAs($element, 'includeUpperBound', 'boolean')) !== null) { $object->setIncludeUpperBound($includeUpperBound); } return $object; }
/** * Unmarshall a DOMElement object corresponding to a QTI weight element. * * @param DOMElement $element A DOMElement object. * @return QtiComponent A Weight object. * @throws UnmarshallingException If the mandatory attributes 'identifier' or 'value' are missing from $element but also if 'value' cannot be converted to a float value or 'identifier' is not a valid QTI Identifier. */ protected function unmarshall(DOMElement $element) { // identifier is a mandatory value. if (($identifier = static::getDOMElementAttributeAs($element, 'identifier', 'string')) !== null) { if (($value = static::getDOMElementAttributeAs($element, 'value', 'string')) !== null) { if (Format::isFloat($value)) { try { $object = new Weight($identifier, floatval($value)); return $object; } catch (InvalidArgumentException $e) { $msg = "The value of 'identifier' from element '" . $element->localName . "' is not a valid QTI Identifier."; throw new UnmarshallingException($msg, $element, $e); } } else { $msg = "The value of attribute 'value' from element '" . $element->localName . "' cannot be converted into a float."; throw new UnmarshallingException($msg, $element); } } else { $msg = "The mandatory attribute 'value' is missing from element '" . $element->localName . "'."; throw new UnmarshallingException($msg, $element); } } else { $msg = "The mandatory attribute 'identifier' is missing from element '" . $element->localName . "'."; throw new UnmarshallingException($msg, $element); } }
/** * Set the cite attribute's value. * * @param string $cite * @throws \InvalidArgumentException If $cite is not a valid URI. */ public function setCite($cite) { if (Format::isUri($cite) === true) { } else { $msg = "The 'cite' argument must be a valid URI, '" . $cite . "' given."; throw new InvalidArgumentException($msg); } }
/** * Set the href attribute. * * @param string $href A URI (Uniform Resource Identifier). * @throws InvalidArgumentException If $href is not a URI. */ public function setHref($href) { if (Format::isUri($href) === true) { $this->href = $href; } else { $msg = "The 'href' argument must be a URI, '" . $href . "' given."; throw new InvalidArgumentException($msg); } }
/** * Set the numberRepeats attribute. * * @param integer|string $numberRepeats An integer or a QTI variable reference. * @throws \InvalidArgumentException If $numberRepeats is not an integer nor a valid QTI variable reference. */ public function setNumberRepeats($numberRepeats) { if (is_int($numberRepeats) || gettype($numberRepeats) === 'string' && Format::isVariableRef($numberRepeats)) { $this->numberRepeats = $numberRepeats; } else { $msg = "The numberRepeats argument must be an integer or a variable reference, '" . gettype($numberRepeats) . "' given."; throw new InvalidArgumentException($msg); } }
/** * Set the identifier of the template variable to be set. * * @param string $identifier A valid QTI identifier. */ public function setIdentifier($identifier) { if (Format::isIdentifier($identifier, false) === true) { $this->identifier = $identifier; } else { $msg = "The 'identifier' argument must be a valid QTI identifier, '" . $identifier . "' given."; throw new InvalidArgumentException($msg); } }
/** * Set the assessment section identifier to match. * * @param string $sectionIdentifier A QTI Identifier. * @throws \InvalidArgumentException If $sectionIdentifier is not a valid QTI Identifier. */ public function setSectionIdentifier($sectionIdentifier) { if (Format::isIdentifier($sectionIdentifier) || empty($sectionIdentifier)) { $this->sectionIdentifier = $sectionIdentifier; } else { $msg = "'{$sectionIndentifier}' is not a valid QTI Identifier."; throw new InvalidArgumentException($msg); } }
/** * @see \qtism\data\content\Flow::setXmlBase() */ public function setXmlBase($xmlBase = '') { if (is_string($xmlBase) && (empty($xmlBase) || Format::isUri($xmlBase))) { $this->xmlBase = $xmlBase; } else { $msg = "The 'xmlBase' argument must be an empty string or a valid URI, '" . $xmlBase . "' given"; throw new InvalidArgumentException($msg); } }
/** * Set the fieldIdentifier attribute. * * @param string $fieldIdentifier A QTI Identifier. * @throws InvalidArgumentException If $fieldIdentifier is not a valid QTI Identifier. */ public function setFieldIdentifier($fieldIdentifier) { if (Format::isIdentifier($fieldIdentifier)) { $this->fieldIdentifier = $fieldIdentifier; } else { $msg = "'{$fieldIdentifier}' is not a valid QTI Identifier."; throw new InvalidArgumentException($msg); } }
/** * Set the n attribute. * * @param integer|string $n The index to lookup. It must be an integer or a variable reference. * @throws \InvalidArgumentException If $n is not an integer nor a variable reference. */ public function setN($n) { if (is_int($n) || gettype($n) === 'string' && Format::isVariableRef($n)) { $this->n = $n; } else { $msg = "The n attribute must be an integer or a variable reference, '" . gettype($n) . "' given."; throw new InvalidArgumentException($msg); } }
/** * Set the identifier of the associated mapping. * * @param string $identifier A QTI Identifier. * @throws \InvalidArgumentException If $identifier is not a valid QTI Identifier. */ public function setIdentifier($identifier) { if (Format::isIdentifier($identifier, false)) { $this->identifier = $identifier; } else { $msg = "{$identifier} is not a valid QTI Identifier."; throw new InvalidArgumentException($identifier); } }
/** * Check if $value is a valid QTI Identifier. * * @throws \InvalidArgumentException If $value is not a valid QTI Identifier. */ protected function checkType($value) { if (gettype($value) !== 'string') { $msg = "IdentifierCollection class only accept string values, '" . gettype($value) . "' given."; throw new InvalidArgumentException($msg); } elseif (!Format::isIdentifier($value)) { $msg = "IdentifierCollection class only accept valid QTI Identifiers, '{$value}' given."; throw new InvalidArgumentException($msg); } }
/** * Unmarshall a QTI repeat operator element into a Repeat object. * * @param DOMElement The repeat element to unmarshall. * @param QtiComponentCollection A collection containing the child Expression objects composing the Operator. * @return QtiComponent A Repeat object. * @throws UnmarshallingException */ protected function unmarshallChildrenKnown(DOMElement $element, QtiComponentCollection $children) { if (($numberRepeats = static::getDOMElementAttributeAs($element, 'numberRepeats')) !== null) { if (Format::isInteger($numberRepeats)) { $numberRepeats = intval($numberRepeats); } $object = new Repeat($children, $numberRepeats); return $object; } else { $msg = "The mandatory attribute 'numberRepeats' is missing from element '" . $element->localName . "'."; throw new UnmarshallingException($msg, $element); } }
/** * Unmarshall a QTI roundTo operator element into a RoundTo object. * * @param DOMElement The roundTo element to unmarshall. * @param QtiComponentCollection A collection containing the child Expression objects composing the Operator. * @return QtiComponent A RoundTo object. * @throws UnmarshallingException */ protected function unmarshallChildrenKnown(DOMElement $element, QtiComponentCollection $children) { if (($figures = static::getDOMElementAttributeAs($element, 'figures', 'string')) !== null) { if (!Format::isVariableRef($figures)) { $figures = intval($figures); } $object = new RoundTo($children, $figures); if (($roundingMode = static::getDOMElementAttributeAs($element, 'roundingMode')) !== null) { $object->setRoundingMode(RoundingMode::getConstantByName($roundingMode)); } return $object; } else { $msg = "The mandatory attribute 'figures' is missing from element '" . $element->localName . "'."; throw new UnmarshallingException($msg, $element); } }
/** * Unmarshall a DOMElement object corresponding to a QTI randomFloat element. * * @param \DOMElement $element A DOMElement object. * @return \qtism\data\QtiComponent A RandomFloat object. * @throws \qtism\data\storage\xml\marshalling\UnmarshallingException If the mandatory attributes min or max ar missing. */ protected function unmarshall(DOMElement $element) { // max attribute is mandatory. if (($max = static::getDOMElementAttributeAs($element, 'max')) !== null) { $max = Format::isVariableRef($max) ? $max : floatval($max); $object = new RandomFloat(0.0, $max); if (($min = static::getDOMElementAttributeAs($element, 'min')) !== null) { $min = Format::isVariableRef($min) ? $min : floatval($min); $object->setMin($min); } return $object; } else { $msg = "The mandatory attribute 'max' is missing from element '" . $element->localName . "'."; throw new UnmarshallingException($msg, $element); } }
public function convert(BaseQuestionType $questionType, $interactionIdentifier, $interactionLabel) { /** @var mcq $question */ $question = $questionType; // Build <choiceInteraction> $valueIdentifierMap = []; $simpleChoiceCollection = new SimpleChoiceCollection(); foreach ($question->get_options() as $index => $option) { /** @var mcq_options_item $option */ $choiceContent = new FlowStaticCollection(); foreach (QtiMarshallerUtil::unmarshallElement($option->get_label()) as $component) { $choiceContent->attach($component); } // Use option['value'] as choice `identifier` if it has the correct format, // Otherwise, generate a valid using index such `CHOICE_1`, `CHOICE_2`, etc $originalOptionValue = $option->get_value(); $choiceIdentifier = Format::isIdentifier($originalOptionValue, false) ? $originalOptionValue : 'CHOICE_' . $index; // Store this reference in a map $valueIdentifierMap[$originalOptionValue] = $choiceIdentifier; $choice = new SimpleChoice($choiceIdentifier); $choice->setContent($choiceContent); $simpleChoiceCollection->attach($choice); } // Build final interaction and its corresponding <responseDeclaration>, and its <responseProcessingTemplate> $interaction = new ChoiceInteraction($interactionIdentifier, $simpleChoiceCollection); $interaction->setLabel($interactionLabel); $interaction->setMinChoices(1); $interaction->setMaxChoices($question->get_multiple_responses() ? $simpleChoiceCollection->count() : 1); // Build the prompt $interaction->setPrompt($this->convertStimulusForPrompt($question->get_stimulus())); // Set shuffle options $interaction->setShuffle($question->get_shuffle_options() ? true : false); // Set the layout if ($question->get_ui_style() instanceof mcq_ui_style && $question->get_ui_style()->get_type() === 'horizontal' && intval($question->get_ui_style()->get_columns()) === count($question->get_options())) { $interaction->setOrientation(Orientation::HORIZONTAL); } else { $interaction->setOrientation(Orientation::VERTICAL); LogService::log('ui_style` is ignored and `choiceInteraction` is assumed and set as `vertical`'); } if (empty($question->get_validation())) { return [$interaction, null, null]; } $builder = new McqValidationBuilder($question->get_multiple_responses(), $valueIdentifierMap); list($responseDeclaration, $responseProcessing) = $builder->buildValidation($interactionIdentifier, $question->get_validation()); return [$interaction, $responseDeclaration, $responseProcessing]; }
/** * Unmarshall a DOMElement object corresponding to a QTI randomInteger element. * * @param DOMElement $element A DOMElement object. * @return QtiComponent A RandomInteger object. * @throws UnmarshallingException If the mandatory attributes 'min' or 'max' are missing from $element. */ protected function unmarshall(DOMElement $element) { if (($max = static::getDOMElementAttributeAs($element, 'max', 'string')) !== null) { $max = Format::isVariableRef($max) ? $max : intval($max); $object = new RandomInteger(0, $max); if (($step = static::getDOMElementAttributeAs($element, 'step')) !== null) { $object->setStep(abs(intval($step))); } if (($min = static::getDOMElementAttributeAs($element, 'min')) !== null) { $min = Format::isVariableRef($min) ? $min : intval($min); $object->setMin($min); } return $object; } else { $msg = "The mandatory attribute 'max' is missing from element '" . $element->localName . "'."; throw new UnmarshallingException($msg, $element); } }
public function convert(item $item, array $questions) { // Make sure we clean up the log LogService::flush(); // Try to build the identifier using item `reference` // Otherwise, generate an alternative identifier and store the original reference as `label` $itemReference = $item->get_reference(); $itemIdentifier = Format::isIdentifier($itemReference, false) ? $itemReference : 'ITEM_' . StringUtil::generateRandomString(12); if ($itemReference !== $itemIdentifier) { LogService::log("The item `reference` ({$itemReference}) is not a valid identifier, thus can not be used for `assessmentItem` identifier. " . "Replaced it with randomly generated `{$itemIdentifier}` and stored the original `reference` as `label` attribute"); } $builder = new AssessmentItemBuilder(); $assessmentItem = $builder->build($itemIdentifier, $itemReference, $questions, $item->get_content()); $xml = new XmlDocument(); $xml->setDocumentComponent($assessmentItem); // Flush out all the error messages stored in this static class, also ensure they are unique $messages = array_values(array_unique(LogService::flush())); return [$xml->saveToString(true), $messages]; }
/** * Get the categories of an item * * @param RdfResource $item the item * * @return string[] the list of categories */ public function getItemCategories(RdfResource $item) { $categories = []; foreach ($item->getTypes() as $class) { $eligibleProperties = $this->getElligibleProperties($class); $propertiesValues = $item->getPropertiesValues(array_keys($eligibleProperties)); foreach ($propertiesValues as $property => $propertyValues) { foreach ($propertyValues as $value) { if ($value instanceof RdfResource) { $sanitizedIdentifier = self::sanitizeCategoryName($value->getLabel()); } else { $sanitizedIdentifier = self::sanitizeCategoryName((string) $value); } if (Format::isIdentifier($sanitizedIdentifier)) { $categories[] = $sanitizedIdentifier; } } } } return $categories; }
/** * Unmarshall a QTI anyN operator element into an AnyN object. * * @param DOMElement The anyN element to unmarshall. * @param QtiComponentCollection A collection containing the child Expression objects composing the Operator. * @return QtiComponent An AnyN object. * @throws UnmarshallingException */ protected function unmarshallChildrenKnown(DOMElement $element, QtiComponentCollection $children) { if (($min = static::getDOMElementAttributeAs($element, 'min')) !== null) { if (Format::isInteger($min)) { $min = intval($min); } if (($max = static::getDOMElementAttributeAs($element, 'max')) !== null) { if (Format::isInteger($max)) { $max = intval($max); } $object = new AnyN($children, $min, $max); return $object; } else { $msg = "The mandatory attribute 'max' is missing from element '" . $element->localName . "'."; throw new UnmarshallingException($msg, $element); } } else { $msg = "The mandatory attribute 'min' is missing from element '" . $element->localName . "'."; throw new UnmarshallingException($msg, $element); } }
/** * Get the target identifier of the BranchRule. * * @param string $target A QTI Identifier. * @throws \InvalidArgumentException If $target is not a valid QTI Identifier. */ public function setTarget($target) { if (Format::isIdentifier($target)) { $this->target = $target; } else { $msg = "'Target' must be a valid QTI Identifier."; throw new InvalidArgumentException($msg); } }
/** * Set the optional response processing template location. An empty string ('') indicates * there is no template location description. * * @param string $templateLocaton The URI of the template location. * @throws \InvalidArgumentException If $templateLocation is not a valid URI nor an empty string. */ public function setTemplateLocation($templateLocation) { if (Format::isUri($templateLocation) === true || gettype($templateLocation) === 'string' && empty($templateLocation) === true) { $this->templateLocation = $templateLocation; } else { $msg = "The given templateLocation '{$templateLocation}' is not a valid URI."; throw new InvalidArgumentException($msg); } }
/** * Set the value of the max attribute. * * @param integer $max * @throws \InvalidArgumentException */ public function setMax($max) { if (is_int($max) || Format::isVariableRef($max)) { $this->max = $max; } else { $msg = "'Max' must be an integer, '" . gettype($max) . "' given."; throw new InvalidArgumentException($msg); } }
/** * Set the label of the body element. * * @param string $label A string of 256 characters maximum. * @throws \InvalidArgumentException If $label is not or a string or contains more than 256 characters. */ public function setLabel($label = '') { if (Format::isString256($label) === true) { $this->label = $label; } else { $msg = "The 'label' argument must be a string that does not exceed 256 characters."; throw new InvalidArgumentException($msg); } }
/** * If the interaction is bound to a numeric response variable, set the identifier of the response variable where the * plain text entered by the candidate will be stored. If $stringIdentifier is an empty string, it means that * there is no value for the stringIdentifier attribute. * * @param string $stringIdentifier A QTI Identifier or an empty string. * @throws InvalidArgumentException If $stringIdentifier is not a valid QTIIdentifier nor an empty string. */ public function setStringIdentifier($stringIdentifier) { if (Format::isIdentifier($stringIdentifier, false) === true || is_string($stringIdentifier) && empty($stringIdentifier) === true) { $this->stringIdentifier = $stringIdentifier; } else { $msg = "The 'stringIdentifier' argument must be a valid QTI identifier or an empty string, '" . $stringIdentifier . "' given."; throw new InvalidArgumentException($msg); } }
/** * Transform a custom operator class e.g. 'org.qtism.custom.explode' into a PHP * fully qualified class name e.g. 'org\qtism\custom\Explode'. * * @param string $class A custom operator class name where namespace separator is '.' (dot). * @return boolean|string A fully qualified PHP class name corresponding to $class or false if the transformation failed. */ public static function customOperatorClassToPhpClass($class) { if (is_string($class) === false) { return false; } else { if (Format::isIdentifier($class, false) === false) { return false; } } $class = strval($class); $tokens = explode('.', $class); if ($tokens === false) { return $tokens; } else { $tokenCount = count($tokens); if ($tokenCount <= 1) { return false; } // ucfirst on last token (i.e. The actual class name) $lastPosition = $tokenCount - 1; $lastToken = ucfirst($tokens[$lastPosition]); $tokens[$lastPosition] = $lastToken; return implode("\\", $tokens); } }
/** * Process a $value depending on its $baseType. * * @param integer $baseType The baseType of the value to process. * @param mixed $value A QTI Runtime compliant value. * @throws PrintedVariableProcessingException If the baseType is unknown. * @return string */ private function processValue($baseType, $value) { $printedVariable = $this->getComponent(); if ($value === null) { return 'null'; } if ($baseType === BaseType::INT_OR_IDENTIFIER) { $baseType = is_int($value) === true ? BaseType::INTEGER : BaseType::STRING; } else { if ($baseType === BaseType::IDENTIFIER || $baseType === BaseType::URI) { $baseType = BaseType::STRING; } else { if ($baseType === BaseType::FILE) { // @todo support baseType::FILE in PrintedVariable. $msg = "the 'file' BaseType is not supported yet by PrintedVariableEngine implementation."; throw new PrintedVariableProcessingException($msg, $this, PrintedVariableProcessingException::RUNTIME_ERROR); } } } if ($baseType === BaseType::STRING) { return $value->getValue(); } else { if ($baseType === BaseType::INTEGER || $baseType === BaseType::FLOAT) { $format = $printedVariable->getFormat(); if (empty($format) === false) { $format = Format::printfFormatIsoToPhp($format); return sprintf($format, $value->getValue()); } else { if ($baseType === BaseType::FLOAT && $printedVariable->mustPowerForm() === true) { return Format::scale10($value->getValue(), 'x'); } else { if ($baseType === BaseType::FLOAT) { return sprintf('%e', $value->getValue()); } else { // integer to string return '' . $value->getValue(); } } } } else { if ($baseType === BaseType::DURATION) { return '' . $value->getSeconds(true); } else { if ($baseType === BaseType::BOOLEAN) { return $value->getValue() === true ? 'true' : 'false'; } else { if ($baseType === BaseType::POINT || $baseType === BaseType::PAIR || $baseType === BaseType::DIRECTED_PAIR) { return $value->__toString(); } else { $msg = "Unknown value type."; throw new PrintedVariableProcessingException($msg, $this, PrintedVariableProcessingException::RUNTIME_ERROR); } } } } } }
/** * Set the template identifier of the choice. * * @param string $templateIdentifier An empty string if no identifier is provided or a QTI identifier. * @throws InvalidArgumentException If the given $templateIdentifier is not a valid QTI identifier. */ public function setTemplateIdentifier($templateIdentifier) { if (is_string($templateIdentifier) === true && empty($templateIdentifier) === true || Format::isIdentifier($templateIdentifier, false) === true) { $this->templateIdentifier = $templateIdentifier; } else { $msg = "The 'templateIdentifier' must be an empty string or a valid QTI identifier, '" . gettype($templateIdentifier) . "' given."; throw new InvalidArgumentException($msg); } }
/** * Get the hyper-text reference to the actual content of the TestFeedback. * * @param string $href */ public function setHref($href) { if (Format::isUri($href) === true) { $this->href = $href; } else { $msg = "'{$href}' is not a valid URI."; throw new InvalidArgumentException($msg); } }
/** * Set the hotspotLabel of the associableHotspot. * * @param string $hotspotLabel A string with at most 256 characters. * @throws \InvalidArgumentException If $hotspotLabel has more than 256 characters. */ public function setHotspotLabel($hotspotLabel) { if (Format::isString256($hotspotLabel) === true) { $this->hotspotLabel = $hotspotLabel; } else { $msg = "The 'hotspotLabel' argument must be a string value with at most 256 characters."; throw new InvalidArgumentException($msg); } }