/** * packItem implementation for QTI * @inheritdoc * @see {@link ItemPacker} * @throws InvalidArgumentException * @throws common_Exception */ public function packItem(core_kernel_classes_Resource $item, $lang = "") { $itemPack = null; $path = $this->getPath($item, $lang); $content = $this->getItemContent($path); //use the QtiParser to transform the QTI XML into an assoc array representation try { //load content $qtiParser = new QtiParser($content); //validate it $qtiParser->validate(); if (!$qtiParser->isValid()) { throw new common_Exception('Invalid QTI content : ' . $qtiParser->displayErrors(false)); } //parse $qtiItem = $qtiParser->load(); //then build the ItemPack from the parsed data if (!is_null($qtiItem)) { $itemPack = new ItemPack(self::$itemType, $qtiItem->toArray()); $itemPack->setAssetEncoders($this->getAssetEncoders()); $assetParser = new AssetParser($qtiItem, $path); $assetParser->setDeepParsing($this->isNestedResourcesInclusion()); foreach ($assetParser->extract($itemPack) as $type => $assets) { $itemPack->setAssets($type, $assets, $path); } } } catch (common_Exception $e) { throw new common_Exception('Unable to pack item ' . $item->getUri() . ' : ' . $e->getMessage()); } return $itemPack; }
/** * Validate and format (if possible) a QTI XML string * * @param string $qti * @return string * @throws QtiModelException */ public static function validateQtiXml($qti) { $returnValue = ''; // render and clean the xml $dom = new DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = true; $dom->preserveWhiteSpace = false; $dom->validateOnParse = false; if ($dom->loadXML($qti)) { $returnValue = $dom->saveXML(); //in debug mode, systematically check if the save QTI is standard compliant if (DEBUG_MODE) { $parserValidator = new Parser($returnValue); $parserValidator->validate(); if (!$parserValidator->isValid()) { common_Logger::w('Invalid QTI output: ' . PHP_EOL . ' ' . $parserValidator->displayErrors()); common_Logger::d(print_r(explode(PHP_EOL, $returnValue), true)); throw new QtiModelException('invalid QTI item XML ' . PHP_EOL . ' ' . $parserValidator->displayErrors()); } } } else { $parserValidator = new Parser($qti); $parserValidator->validate(); if (!$parserValidator->isValid()) { throw new QtiModelException('Wrong QTI item output format'); } } return (string) $returnValue; }
/** * Validate a QTI XML string. * * @param string $qti File path or XML string * @throws QtiModelException */ public static function validateQtiXml($qti) { $dom = self::loadQtiXml($qti); $returnValue = $dom->saveXML(); $parserValidator = new Parser($returnValue); $parserValidator->validate(); if (!$parserValidator->isValid()) { common_Logger::w('Invalid QTI output: ' . PHP_EOL . ' ' . $parserValidator->displayErrors()); throw new QtiModelException('invalid QTI item XML ' . PHP_EOL . ' ' . $parserValidator->displayErrors()); } }
public function getJson() { $returnValue = array('itemData' => null); $xml = file_get_contents('php://input'); Authoring::validateQtiXml($xml); $qtiParser = new QtiParser($xml); $item = $qtiParser->load(); if (!is_null($item)) { $returnValue['itemData'] = $item->toArray(); } else { throw new common_Exception('invalid qti xml'); } $this->returnJson($returnValue); }
/** * packItem implementation for QTI * @inheritdoc * @see {@link ItemPacker} * @throws InvalidArgumentException * @throws common_Exception */ public function packItem(core_kernel_classes_Resource $item, $lang, Directory $directory) { //use the QtiParser to transform the QTI XML into an assoc array representation $content = $this->getXmlByItem($item, $lang); //load content $qtiParser = new QtiParser($content); //validate it $qtiParser->validate(); if (!$qtiParser->isValid()) { throw new common_Exception('Invalid QTI content : ' . $qtiParser->displayErrors(false)); } //parse $qtiItem = $qtiParser->load(); return $this->packQtiItem($item, $lang, $qtiItem, $directory); }
/** * synchronise item label * @param ItemRdfUpdatedEvent $event */ public static function catchItemRdfUpdatedEvent(ItemRdfUpdatedEvent $event) { $rdfItem = new core_kernel_classes_Resource($event->getItemUri()); $type = $rdfItem->getProperty(TAO_ITEM_MODEL_PROPERTY); /*@var $directory \oat\oatbox\filesystem\Directory */ $directory = taoItems_models_classes_ItemsService::singleton()->getItemDirectory($rdfItem); $itemModel = $rdfItem->getPropertyValues($type); if ($directory->exists() && in_array(TAO_ITEM_MODEL_QTI, $itemModel)) { /* @var $file File */ $file = $directory->getFile(Service::QTI_ITEM_FILE); $qtiParser = new Parser($file->read()); $qtiItem = $qtiParser->load(); $label = mb_substr($rdfItem->getLabel(), 0, 256, 'UTF-8'); $qtiItem->setAttribute('label', $label); $file->put($qtiItem->toXML()); } }
protected function convertQtiFromV2p0ToV2p1($xml) { $returnValue = ''; $qtiParser = new Parser($xml); $qtiv2p1xsd = ROOT_PATH . 'taoQTI/models/classes/QTI/data/qtiv2p0/imsqti_v2p0.xsd'; $qtiParser->validate($qtiv2p1xsd); if ($qtiParser->isValid()) { $this->out('is a qti 2.0 item'); $item = $qtiParser->load(); if ($item instanceof Item) { $this->out('item loaded as QTI 2.1'); $returnValue = $item->toXML(); } } else { $this->out('does not seem to be a valid qti 2.0 item, attempt to load it anyway.'); $item = $qtiParser->load(); if ($item instanceof Item) { $this->out('Done! Item loaded as QTI 2.1'); $returnValue = $item->toXML(); } } return $returnValue; }
/** * test if alternative QTI profiles are managed correctly during parsing */ public function testParseAlternativeProfile() { $file = dirname(__FILE__) . '/samples/xml/qtiv2p1/alternativeProfiles/apip001.xml'; $qtiParser = new Parser($file); $qtiParser->validate(); if (!$qtiParser->isValid()) { $this->fail($qtiParser->displayErrors()); } $this->assertTrue($qtiParser->isValid()); $item = $qtiParser->load(); $this->assertInstanceOf('\\oat\\taoQtiItem\\model\\qti\\Item', $item); $xml = simplexml_load_string($item->toXML()); $this->assertEquals('http://www.imsglobal.org/xsd/apip/apipv1p0/qtiitem/imsqti_v2p1', $xml->getNamespaces()['']); $this->assertNotNull($xml->apipAccessibility); }
protected function createQtiItemModel($qtiFile, $validate = true) { $qtiXml = Authoring::sanitizeQtiXml($qtiFile); //validate the file to import $qtiParser = new Parser($qtiXml); if ($validate) { $qtiParser->validate(); if (!$qtiParser->isValid()) { $eStrs = array(); foreach ($qtiParser->getErrors() as $libXmlError) { $eStrs[] = __('QTI-XML error at line %1$d "%2$s".', $libXmlError['line'], str_replace('[LibXMLError] ', '', trim($libXmlError['message']))); } // Make sure there are no duplicate... $eStrs = array_unique($eStrs); // Add sub-report. throw new ValidationException($qtiFile, $eStrs); } } $qtiItem = $qtiParser->load(); if (!$qtiItem && count($qtiParser->getErrors())) { $errors = []; foreach ($qtiParser->getErrors() as $error) { $errors[] = $error['message']; } throw new ValidationException($qtiFile, $errors); } return $qtiItem; }
/** * test the building of item from all the samples */ public function _testSamples() { //check if samples are loaded foreach (glob(dirname(__FILE__) . '/samples/xml/qtiv2p1/*.xml') as $file) { $qtiParser = new Parser($file); $item = $qtiParser->load(); $this->assertTrue($qtiParser->isValid()); $this->assertNotNull($item); $this->assertInstanceOf('\\oat\\taoQtiItem\\model\\qti\\Item', $item); foreach ($item->getInteractions() as $interaction) { $this->assertInstanceOf('\\oat\\taoQtiItem\\model\\qti\\interaction\\Interaction', $interaction); if ($interaction instanceof MatchInteraction) { foreach ($interaction->getChoices(0) as $choice) { $this->assertInstanceOf('\\oat\\taoQtiItem\\model\\qti\\choice\\Choice', $choice); } foreach ($interaction->getChoices(1) as $choice) { $this->assertInstanceOf('\\oat\\taoQtiItem\\model\\qti\\choice\\Choice', $choice); } } else { foreach ($interaction->getChoices() as $choice) { $this->assertInstanceOf('\\\\oat\\taoQtiItem\\model\\qti\\choice\\Choice', $choice); } } } } }
/** * @param core_kernel_classes_Resource $item * @param string $lang * @param Directory $publicDirectory * @return qti\Item * @throws taoItems_models_classes_CompilationFailedException */ protected function retrieveAssets(core_kernel_classes_Resource $item, $lang, Directory $publicDirectory) { $qtiItem = Service::singleton()->getDataItemByRdfItem($item, $lang); $assetParser = new AssetParser($qtiItem, $publicDirectory); $assetParser->setGetSharedLibraries(false); $assetParser->setGetXinclude(false); $resolver = new ItemMediaResolver($item, $lang); $replacementList = array(); foreach ($assetParser->extract() as $type => $assets) { foreach ($assets as $assetUrl) { foreach (self::$BLACKLIST as $blacklist) { if (preg_match($blacklist, $assetUrl) === 1) { continue 2; } } $mediaAsset = $resolver->resolve($assetUrl); $mediaSource = $mediaAsset->getMediaSource(); $basename = $mediaSource->getBaseName($mediaAsset->getMediaIdentifier()); $replacement = $basename; $count = 0; while (in_array($replacement, $replacementList)) { $dot = strrpos($basename, '.'); $replacement = $dot !== false ? substr($basename, 0, $dot) . '_' . $count . substr($basename, $dot) : $basename . $count; $count++; } $replacementList[$assetUrl] = $replacement; $tmpfile = $mediaSource->download($mediaAsset->getMediaIdentifier()); $fh = fopen($tmpfile, 'r'); $publicDirectory->writeStream($lang . '/' . $replacement, $fh); fclose($fh); unlink($tmpfile); //$fileStream = $mediaSource->getFileStream($mediaAsset->getMediaIdentifier()); //$publicDirectory->writeStream($lang.'/'.$replacement, $fileStream); } } $dom = new \DOMDocument('1.0', 'UTF-8'); if ($dom->loadXML($qtiItem->toXml()) === true) { $xpath = new \DOMXPath($dom); $attributeNodes = $xpath->query('//@*'); foreach ($attributeNodes as $node) { if (isset($replacementList[$node->value])) { $node->value = $replacementList[$node->value]; } } $attributeNodes = $xpath->query("//*[local-name()='entry']") ?: []; unset($xpath); foreach ($attributeNodes as $node) { $node->nodeValue = strtr(htmlentities($node->nodeValue, ENT_XML1), $replacementList); } } else { throw new taoItems_models_classes_CompilationFailedException('Unable to load XML'); } $qtiParser = new Parser($dom->saveXML()); $assetRetrievedQtiItem = $qtiParser->load(); //loadxinclude $xincludeLoader = new XIncludeLoader($assetRetrievedQtiItem, $resolver); $xincludeLoader->load(true); return $assetRetrievedQtiItem; }
/** * Test that xincludes are correctly loaded into pci elements */ public function testLoadxincludeInPci() { $file = dirname(__FILE__) . '/samples/xml/qtiv2p1/xinclude/pci_include.xml'; $href1 = 'stimulus.xml'; $file1 = dirname(__FILE__) . '/samples/xml/qtiv2p1/xinclude/stimulus.xml'; $mediaSource1 = $this->prophesize('oat\\taoMediaManager\\model\\MediaSource'); $mediaSource1->download($href1)->willReturn($file1); $asset1 = $this->prophesize('oat\\tao\\model\\media\\MediaAsset'); $asset1->getMediaSource()->willReturn($mediaSource1->reveal()); $asset1->getMediaIdentifier()->willReturn($href1); $asset1Revealed = $asset1->reveal(); $resolver = $this->prophesize('oat\\taoItems\\model\\media\\ItemMediaResolver'); $resolver->resolve($href1)->willReturn($asset1Revealed); $this->assertEquals($file1, $asset1Revealed->getMediaSource()->download($asset1Revealed->getMediaIdentifier())); //load item model $qtiParser = new Parser($file); $item = $qtiParser->load(); $this->assertTrue($item instanceof Item); //find the unique pci in the sample $pci = null; foreach ($item->getComposingElements() as $element) { if ($element instanceof PortableCustomInteraction) { $pci = $element; break; } } $this->assertNotNull($pci); //check inital markup $markupXml = simplexml_load_string($pci->getMarkup()); $this->assertEquals(1, count($markupXml->xpath(".//*[name(.)='include']")), 'the pci markup has an include element'); $this->assertEquals(0, count($markupXml->xpath(".//*[name(.)='img']"))); $this->assertEquals(0, count($markupXml->xpath(".//*[name(.)='m:math']"))); //load xinclude $xincludeLoader = new XIncludeLoader($item, $resolver->reveal()); $xincludes = $xincludeLoader->load(); //check markup after loading $markupXml = simplexml_load_string($pci->getMarkup()); $this->assertEquals(1, count($xincludes)); $this->assertEquals(0, count($markupXml->xpath(".//*[name(.)='include']")), 'the include element has been replaced'); $this->assertEquals(1, count($markupXml->xpath(".//*[name(.)='img']"))); $this->assertEquals(1, count($markupXml->xpath(".//*[name(.)='m:math']"))); }
public function testFileParsingQtiPic() { $files = glob(dirname(__FILE__) . '/samples/xml/qtiv2p1/pic/*.xml'); //check if samples are loaded foreach ($files as $file) { $qtiParser = new Parser($file); $qtiParser->validate(); if (!$qtiParser->isValid()) { echo $qtiParser->displayErrors(); } $item = $qtiParser->load(); $this->assertInstanceOf('\\oat\\taoQtiItem\\model\\qti\\Item', $item); } }
/** * * @param core_kernel_classes_Resource $item * @param string $lang * @param string $destination * @return \oat\taoQtiItem\model\qti\Item */ protected function retrieveAssets(core_kernel_classes_Resource $item, $lang, $destination) { $xml = taoItems_models_classes_ItemsService::singleton()->getItemContent($item); $qtiParser = new Parser($xml); $qtiItem = $qtiParser->load(); $assetParser = new AssetParser($qtiItem); $assetParser->setGetSharedLibraries(false); $assetParser->setGetXinclude(false); $resolver = new ItemMediaResolver($item, $lang); foreach ($assetParser->extract() as $type => $assets) { foreach ($assets as $assetUrl) { foreach (self::$BLACKLIST as $blacklist) { if (preg_match($blacklist, $assetUrl) === 1) { continue 2; } } $mediaAsset = $resolver->resolve($assetUrl); $mediaSource = $mediaAsset->getMediaSource(); $srcPath = $mediaSource->download($mediaAsset->getMediaIdentifier()); $filename = \tao_helpers_File::getSafeFileName(ltrim($mediaAsset->getMediaIdentifier(), '/'), $destination); $replacement = $mediaAsset->getMediaIdentifier(); if (get_class($mediaSource) !== 'oat\\tao\\model\\media\\sourceStrategy\\HttpSource') { $fileInfo = $mediaSource->getFileInfo($mediaAsset->getMediaIdentifier()); $filename = $fileInfo['filePath']; if ($mediaAsset->getMediaIdentifier() !== $fileInfo['uri']) { $replacement = $filename; } } $destPath = ltrim($filename, '/'); tao_helpers_File::copy($srcPath, $destination . $destPath, false); $xml = str_replace($assetUrl, $replacement, $xml); } } $qtiParser = new Parser($xml); $assetRetrievedQtiItem = $qtiParser->load(); //loadxinclude $xincludeLoader = new XIncludeLoader($assetRetrievedQtiItem, $resolver); $xincludeLoader->load(true); return $assetRetrievedQtiItem; }
/** * test the building and exporting out the items * @dataProvider rpItemProvider */ public function testResponseProcessingToArray($name, $file, $expectation) { $qtiParser = new Parser($file); $item = $qtiParser->load(); $this->assertTrue($qtiParser->isValid()); $this->assertNotNull($item); $this->assertInstanceOf('\\oat\\taoQtiItem\\model\\qti\\Item', $item); $rp = $item->getResponseProcessing(); $data = $rp->toArray(); $this->assertFalse(empty($data)); $this->assertFalse(empty($data['responseRules'])); //compare the result with expectation $responseRules = json_decode($expectation, true); $this->assertEquals($data['responseRules'], $responseRules); }
/** * Load a QTI item from a qti file in parameter. * * @access public * @author Somsack Sipasseuth, <*****@*****.**> * @param string file * @return oat\taoQtiItem\model\qti\Item */ public function loadItemFromFile($file) { $returnValue = null; if (is_string($file) && !empty($file)) { //validate the file to import try { $qtiParser = new Parser($file); $qtiParser->validate(); if (!$qtiParser->isValid()) { throw new ParsingException($qtiParser->displayErrors()); } $returnValue = $qtiParser->load(); } catch (ParsingException $pe) { throw new ParsingException($pe->getMessage()); } catch (Exception $e) { throw new Exception("Unable to load file {$file} caused by {$e->getMessage()}"); } } return $returnValue; }
protected function createQtiItemModel($qtiFile, $validate = true) { $qtiXml = Authoring::sanitizeQtiXml($qtiFile); //validate the file to import $qtiParser = new Parser($qtiXml); if ($validate) { $basePath = common_ext_ExtensionsManager::singleton()->getExtensionById('taoQtiItem')->getDir(); $qtiParser->validate(); if (!$qtiParser->isValid()) { $failedValidation = true; $eStrs = array(); foreach ($qtiParser->getErrors() as $libXmlError) { $eStrs[] = __('QTI-XML error at line %1$d "%2$s".', $libXmlError['line'], str_replace('[LibXMLError] ', '', trim($libXmlError['message']))); } // Make sure there are no duplicate... $eStrs = array_unique($eStrs); // Add sub-report. throw new ValidationException($qtiFile, $eStrs); } } $qtiItem = $qtiParser->load(); return $qtiItem; }
/** * * @dataProvider templateDrivenRuleProvider */ public function testTemplateDrivenRules($file, $rpExpected) { $qtiParser = new Parser($file); $qtiParser->validate(); $this->assertTrue($qtiParser->isValid()); $item = $qtiParser->load(); $this->assertInstanceOf('\\oat\\taoQtiItem\\model\\qti\\Item', $item); $this->assertInstanceOf('\\oat\\taoQtiItem\\model\\qti\\response\\TemplatesDriven', $item->getResponseProcessing()); $rpJson = json_encode($item->getResponseProcessing()->toArray()['responseRules']); $this->assertEquals($rpExpected, $rpJson); }
public function testParseRpTemplateDriven() { /** * a rp using standard template will be parsed into a template driven rp (for authoring purpose) */ $file = dirname(__FILE__) . '/samples/xml/qtiv2p1/responseProcessing/template.xml'; $qtiParser = new Parser($file); $qtiParser->validate(); $this->assertTrue($qtiParser->isValid()); $item = $qtiParser->load(); $this->assertInstanceOf('\\oat\\taoQtiItem\\model\\qti\\Item', $item); $this->assertInstanceOf('\\oat\\taoQtiItem\\model\\qti\\response\\TemplatesDriven', $item->getResponseProcessing()); //check if the rp is serialized correctly $xml = simplexml_load_string($item->toXML()); $this->assertEquals('http://www.imsglobal.org/question/qti_v2p1/rptemplates/match_correct', (string) $xml->responseProcessing[0]['template']); /** * tao custom rp build using the tao "recognizable" response condition, with 2 interactions */ $file = dirname(__FILE__) . '/samples/xml/qtiv2p1/responseProcessing/templateDrivenMultiple.xml'; $qtiParser = new Parser($file); $qtiParser->validate(); $this->assertTrue($qtiParser->isValid()); $item = $qtiParser->load(); $this->assertInstanceOf('\\oat\\taoQtiItem\\model\\qti\\Item', $item); $this->assertInstanceOf('\\oat\\taoQtiItem\\model\\qti\\response\\TemplatesDriven', $item->getResponseProcessing()); //check if the rp is serialized correctly $xml = simplexml_load_string($item->toXML()); $this->assertEmpty((string) $xml->responseProcessing[0]['template']); /** * tao custom rp build using the tao "recognizable" response condition, with one interaction with the responseIdentifier RESPONSE_1 */ $file = dirname(__FILE__) . '/samples/xml/qtiv2p1/responseProcessing/templateDrivenSingle.xml'; $qtiParser = new Parser($file); $qtiParser->validate(); $this->assertTrue($qtiParser->isValid()); $item = $qtiParser->load(); $this->assertInstanceOf('\\oat\\taoQtiItem\\model\\qti\\Item', $item); $this->assertInstanceOf('\\oat\\taoQtiItem\\model\\qti\\response\\TemplatesDriven', $item->getResponseProcessing()); //check if the rp is serialized correctly $xml = simplexml_load_string($item->toXML()); $this->assertEmpty((string) $xml->responseProcessing[0]['template']); /** * tao custom rp build using the tao "recognizable" response condition, with one unique interaction that has the "right" responseIdentifier RESPONSE */ $file = dirname(__FILE__) . '/samples/xml/qtiv2p1/responseProcessing/templateDrivenSingleRESPONSE.xml'; $qtiParser = new Parser($file); $qtiParser->validate(); $this->assertTrue($qtiParser->isValid()); $item = $qtiParser->load(); $this->assertInstanceOf('\\oat\\taoQtiItem\\model\\qti\\Item', $item); $this->assertInstanceOf('\\oat\\taoQtiItem\\model\\qti\\response\\TemplatesDriven', $item->getResponseProcessing()); $xml = simplexml_load_string($item->toXML()); $this->assertEquals('http://www.imsglobal.org/question/qti_v2p1/rptemplates/map_response', (string) $xml->responseProcessing[0]['template']); }