Skip to content

Commit

Permalink
More XInclude support in XmlDocument.
Browse files Browse the repository at this point in the history
Conflicts:
	src/qtism/data/content/AtomicInline.php
	src/qtism/data/storage/xml/XmlDocument.php
	src/qtism/data/storage/xml/marshalling/ImgMarshaller.php
	test/qtismtest/data/storage/xml/XmlAssessmentItemDocumentTest.php
  • Loading branch information
bugalot authored and jbout committed Jun 26, 2015
1 parent b129d73 commit d6835b9
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 22 deletions.
1 change: 1 addition & 0 deletions qtism/data/content/AtomicInline.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

namespace qtism\data\content;

use qtism\common\utils\Format;
use qtism\data\QtiComponentCollection;
use \InvalidArgumentException;

Expand Down
63 changes: 63 additions & 0 deletions qtism/data/storage/xml/XmlDocument.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,25 @@

namespace qtism\data\storage\xml;

use qtism\common\utils\Url;
use qtism\data\QtiComponentCollection;
use qtism\data\QtiComponentIterator;
use qtism\data\QtiDocument;
use qtism\data\storage\xml\marshalling\MarshallerFactory;
use qtism\data\AssessmentTest;
use qtism\data\content\Flow;
use qtism\data\storage\xml\marshalling\Marshaller;
use qtism\data\storage\xml\marshalling\UnmarshallingException;
use qtism\data\QtiComponent;
use qtism\data\storage\Utils as StorageUtils;
use qtism\data\storage\xml\Utils as XmlUtils;
use \ReflectionClass;
use \DOMDocument;
use \DOMElement;
use \DOMException;
use \RuntimeException;
use \InvalidArgumentException;
use \LogicException;

/**
* This class represents a QTI-XML Document.
Expand Down Expand Up @@ -292,6 +298,63 @@ public function schemaValidate($filename = '') {
}
}


/**
* Resolve include components.
*
* After the item has been loaded using the load or loadFromString method,
* the include components can be resolved by calling this method. Files will
* be included following the rules described by the XInclude specification.
*
* @param boolean $validate Whether or not validate files being included.
* @throws \LogicException If the method is called prior the load or loadFromString method was called.
* @throws \qtism\data\storage\xml\XmlStorageException If an error occured while parsing or validating files to be included.
*/
public function xInclude($validate = false) {

if (($root = $this->getDocumentComponent()) !== false) {

$baseUri = str_replace('\\', '/', $this->getDomDocument()->documentElement->baseURI);
$pathinfo = pathinfo($baseUri);
$basePath = $pathinfo['dirname'];

$iterator = new QtiComponentIterator($root, array('include'));
foreach ($iterator as $include) {
$parent = $iterator->parent();

// Is the parent something we can deal with for replacement?
$reflection = new ReflectionClass($parent);

if ($reflection->hasMethod('getContent') === true && $parent->getContent() instanceof QtiComponentCollection) {
$href = $include->getHref();

if (Url::isRelative($href) === true) {
$href = Url::rtrim($basePath) . '/' . Url::ltrim($href);

$doc = new XmlDocument();
$doc->load($href, $validate);
$includeRoot = $doc->getDocumentComponent();

if ($includeRoot instanceof Flow) {
// Derive xml:base...
$xmlBase = Url::ltrim(str_replace($basePath, '', $href));
$xmlBasePathInfo = pathinfo($xmlBase);

if ($xmlBasePathInfo['dirname'] !== '.') {
$includeRoot->setXmlBase($xmlBasePathInfo['dirname'] . '/');
}
}

$parent->getContent()->replace($include, $includeRoot);
}
}
}
} else {
$msg = "Cannot include fragments prior to loading any file.";
throw new LogicException($msg);
}
}

/**
* Decorate the root element of the XmlAssessmentDocument with the appropriate
* namespaces and schema definition.
Expand Down
2 changes: 1 addition & 1 deletion qtism/data/storage/xml/marshalling/ImgMarshaller.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ protected function marshall(QtiComponent $component) {
}

if ($component->hasXmlBase() === true) {
self::setXmlBase($element, $component->setXmlBase());
self::setXmlBase($element, $component->getXmlBase());
}

self::fillElement($element, $component);
Expand Down
21 changes: 0 additions & 21 deletions test/qtism/data/storage/xml/XmlAssessmentDocumentQTIGuideTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -249,27 +249,6 @@ public function testWriteInteractionMixSachsen() {
$this->assertFalse(file_exists($file));
}

public function testLoadAndSaveXIncludeNsInTag() {
$doc = new XmlDocument();
$doc->load(self::samplesDir() . 'custom/items/xinclude/xinclude_ns_in_tag.xml', true);

$includes = $doc->getDocumentComponent()->getComponentsByClassName('include');
$this->assertEquals(1, count($includes));
$this->assertEquals('xinclude_ns_in_tag_content1.xml', $includes[0]->getHref());

$file = tempnam('/tmp', 'qsm');
$doc->save($file);

// Does it validate again?
$doc = new XmlDocument();
try {
$doc->load($file, true);
$this->assertTrue(true);
} catch (XmlStorageException $e) {
$this->assertFalse(true, "The document using xinclude should validate after being saved.");
}
}

private static function decorateUri($uri) {
return self::samplesDir() . 'ims/tests/' . $uri;
}
Expand Down
80 changes: 80 additions & 0 deletions test/qtism/data/storage/xml/XmlDocumentXIncludeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php
namespace qtismtest\data\storage\xml;

use qtism\data\storage\xml\XmlDocument;
use qtism\data\storage\xml\XmlStorageException;

class XmlDocumentXIncludeTest extends \QtiSmTestCase {

public function testLoadAndSaveXIncludeNsInTag() {
$doc = new XmlDocument();
$doc->load(self::samplesDir() . 'custom/items/xinclude/xinclude_ns_in_tag.xml', true);

$includes = $doc->getDocumentComponent()->getComponentsByClassName('include');
$this->assertEquals(1, count($includes));
$this->assertEquals('xinclude_ns_in_tag_content1.xml', $includes[0]->getHref());

$file = tempnam('/tmp', 'qsm');
$doc->save($file);

// Does it validate again?
$doc = new XmlDocument();
try {
$doc->load($file, true);
$this->assertTrue(true);
} catch (XmlStorageException $e) {
$this->assertFalse(true, "The document using xinclude should validate after being saved.");
}
}

/**
* @depends testLoadAndSaveXIncludeNsInTag
*/
public function testLoadAndResolveXIncludeSameBase() {
$doc = new XmlDocument();
$doc->load(self::samplesDir() . 'custom/items/xinclude/xinclude_ns_in_tag.xml', true);

// At this moment, includes are not resolved.
$includes = $doc->getDocumentComponent()->getComponentsByClassName('include');
$this->assertEquals(1, count($includes));
// So no img components can be found...
$imgs = $doc->getDocumentComponent()->getComponentsByClassName('img');
$this->assertEquals(0, count($imgs));

$doc->xInclude();

// Now they are!
$includes = $doc->getDocumentComponent()->getComponentsByClassName('include');
$this->assertEquals(0, count($includes));

// And we should find an img component then!
$imgs = $doc->getDocumentComponent()->getComponentsByClassName('img');
$this->assertEquals(1, count($imgs));

// Check that xml:base was appropriately resolved. In this case,
// no content for xml:base because 'xinclude_ns_in_tag_content1.xml' is in the
// same directory as the main xml file.
$this->assertEquals('', $imgs[0]->getXmlBase());
}

/**
* @depends testLoadAndResolveXIncludeSameBase
*/
public function testLoadAndResolveXIncludeDifferentBase() {
$doc = new XmlDocument();
$doc->load(self::samplesDir() . 'custom/items/xinclude/xinclude_ns_in_tag_subfolder.xml', true);
$doc->xInclude();

$includes = $doc->getDocumentComponent()->getComponentsByClassName('include');
$this->assertEquals(0, count($includes));

// And we should find an img component then!
$imgs = $doc->getDocumentComponent()->getComponentsByClassName('img');
$this->assertEquals(1, count($imgs));

// Check that xml:base was appropriately resolved. In this case,
// no content for xml:base because 'xinclude_ns_in_tag_content1.xml' is in the
// same directory as the main xml file.
$this->assertEquals('subfolder/', $imgs[0]->getXmlBase());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<img xmlns="http://www.imsglobal.org/xsd/imsqti_v2p1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_v2p1 http://www.imsglobal.org/xsd/qti/qtiv2p1/imsqti_v2p1.xsd" src="images/sign.png" alt="NEVER LEAVE LUGGAGE UNATTENDED"/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Thie example adapted from the PET Handbook, copyright University of Cambridge ESOL Examinations -->
<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_v2p1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_v2p1 http://www.imsglobal.org/xsd/qti/qtiv2p1/imsqti_v2p1.xsd"
identifier="choice" title="Unattended Luggage" adaptive="false" timeDependent="false">
<responseDeclaration identifier="RESPONSE" cardinality="single" baseType="identifier">
<correctResponse>
<value>ChoiceA</value>
</correctResponse>
</responseDeclaration>
<outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float">
<defaultValue>
<value>0</value>
</defaultValue>
</outcomeDeclaration>
<itemBody>
<p>Look at the text in the picture.</p>
<p>
<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="subfolder/xinclude_ns_in_tag_content1.xml"/>
</p>
<choiceInteraction responseIdentifier="RESPONSE" shuffle="false" maxChoices="1">
<prompt>What does it say?</prompt>
<simpleChoice identifier="ChoiceA">You must stay with your luggage at all times.</simpleChoice>
<simpleChoice identifier="ChoiceB">Do not let someone else look after your luggage.</simpleChoice>
<simpleChoice identifier="ChoiceC">Remember your luggage when you leave.</simpleChoice>
</choiceInteraction>
</itemBody>
<responseProcessing
template="http://www.imsglobal.org/question/qti_v2p1/rptemplates/match_correct"/>
</assessmentItem>

0 comments on commit d6835b9

Please sign in to comment.