diff --git a/src/qtism/data/storage/xml/XmlCompactDocument.php b/src/qtism/data/storage/xml/XmlCompactDocument.php
index dd3e3ce25..5e571dec0 100644
--- a/src/qtism/data/storage/xml/XmlCompactDocument.php
+++ b/src/qtism/data/storage/xml/XmlCompactDocument.php
@@ -274,6 +274,11 @@ protected static function resolveAssessmentItemRef(ExtendedAssessmentItemRef $co
$doc = new XmlDocument();
$doc->load($href);
+
+ // Resolve external documents.
+ $doc->xInclude();
+ $doc->resolveTemplateLocation();
+
$item = $doc->getDocumentComponent();
foreach ($item->getResponseDeclarations() as $resp) {
diff --git a/src/qtism/data/storage/xml/XmlDocument.php b/src/qtism/data/storage/xml/XmlDocument.php
index e953cb55a..c5273fcd7 100644
--- a/src/qtism/data/storage/xml/XmlDocument.php
+++ b/src/qtism/data/storage/xml/XmlDocument.php
@@ -26,6 +26,8 @@
use qtism\data\QtiComponentCollection;
use qtism\data\QtiComponentIterator;
use qtism\data\QtiDocument;
+use qtism\data\AssessmentItem;
+use qtism\data\processing\ResponseProcessing;
use qtism\data\storage\xml\marshalling\Qti20MarshallerFactory;
use qtism\data\storage\xml\marshalling\Qti21MarshallerFactory;
use qtism\data\storage\xml\marshalling\Qti211MarshallerFactory;
@@ -365,13 +367,13 @@ public function schemaValidate($filename = '')
* 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.
+ * @param boolean $validate Whether or not validate files being included. Default is false.
* @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) {
+ if (($root = $this->getDocumentComponent()) !== null) {
$baseUri = str_replace('\\', '/', $this->getDomDocument()->documentElement->baseURI);
$pathinfo = pathinfo($baseUri);
@@ -413,6 +415,49 @@ public function xInclude($validate = false) {
throw new LogicException($msg);
}
}
+
+ /**
+ * Resolve responseProcessing elements with template location.
+ *
+ * If the root element of the currently loaded QTI file is an assessmentItem element,
+ * this method will try to resolve responseProcessing fragments referenced by responseProcessing
+ * elements having a templateLocation attribute.
+ *
+ * @param boolean $validate Whether or not validate files being included. Default is false.
+ * @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 resolveTemplateLocation($validate = false)
+ {
+ if (($root = $this->getDocumentComponent()) !== null) {
+
+ if ($root instanceof AssessmentItem && ($responseProcessing = $root->getResponseProcessing()) !== null && ($templateLocation = $responseProcessing->getTemplateLocation()) !== '') {
+
+ if (Url::isRelative($templateLocation) === true) {
+
+ $baseUri = str_replace('\\', '/', $this->getDomDocument()->documentElement->baseURI);
+ $pathinfo = pathinfo($baseUri);
+ $basePath = $pathinfo['dirname'];
+ $templateLocation = Url::rtrim($basePath) . '/' . Url::ltrim($templateLocation);
+
+ $doc = new XmlDocument();
+ $doc->load($templateLocation, $validate);
+
+ $newResponseProcessing = $doc->getDocumentComponent();
+
+ if ($newResponseProcessing instanceof ResponseProcessing) {
+ $root->setResponseProcessing($newResponseProcessing);
+ } else {
+ $msg = "The template at location '${templateLocation}' is not a document containing a QTI responseProcessing element.";
+ throw new XmlStorageException($msg, XmlStorageException::RESOLUTION);
+ }
+ }
+ }
+ } else {
+ $msg = "Cannot resolve template location loading any file.";
+ throw new LogicException($msg);
+ }
+ }
/**
* Decorate the root element of the XmlAssessmentDocument with the appropriate
diff --git a/test/qtismtest/data/storage/xml/XmlDocumentTemplateLocationTest.php b/test/qtismtest/data/storage/xml/XmlDocumentTemplateLocationTest.php
new file mode 100644
index 000000000..934caabfc
--- /dev/null
+++ b/test/qtismtest/data/storage/xml/XmlDocumentTemplateLocationTest.php
@@ -0,0 +1,55 @@
+load(self::samplesDir() . 'custom/items/template_location/template_location_item.xml', true);
+
+ $responseProcessings = $doc->getDocumentComponent()->getComponentsByClassName('responseProcessing');
+ $this->assertEquals(1, count($responseProcessings));
+ $this->assertEquals('template_location_rp.xml', $responseProcessings[0]->getTemplateLocation());
+
+ $doc->resolveTemplateLocation(true);
+
+ $responseProcessings = $doc->getDocumentComponent()->getComponentsByClassName('responseProcessing');
+ $this->assertEquals(1, count($responseProcessings));
+ $this->assertEquals('http://www.imsglobal.org/question/qti_v2p1/rptemplates/match_correct', $responseProcessings[0]->getTemplate());
+ }
+
+ public function testNotLoaded() {
+ $doc = new XmlDocument();
+
+ $this->setExpectedException('\\LogicException', 'Cannot resolve template location loading any file.');
+ $doc->resolveTemplateLocation();
+ }
+
+ public function testWrongTarget() {
+ $doc = new XmlDocument();
+ $doc->load(self::samplesDir() . 'custom/items/template_location/template_location_item_wrong_target.xml', true);
+
+ $this->setExpectedException('qtism\\data\\storage\\xml\\XmlStorageException');
+ $doc->resolveTemplateLocation();
+ }
+
+ public function testInvalidTargetNoValidation() {
+ $doc = new XmlDocument();
+ $doc->load(self::samplesDir() . 'custom/items/template_location/template_location_item_invalid_target.xml', true);
+
+ $this->setExpectedException('qtism\\data\\storage\\xml\\XmlStorageException', "'responseProcessingZ' components are not supported in QTI version '2.1.0'.", XmlStorageException::VERSION);
+ $doc->resolveTemplateLocation();
+ }
+
+ public function testInvalidTargetValidation() {
+ $doc = new XmlDocument();
+ $doc->load(self::samplesDir() . 'custom/items/template_location/template_location_item_invalid_target.xml', true);
+
+ $this->setExpectedException('qtism\\data\\storage\\xml\\XmlStorageException', null, XmlStorageException::XSD_VALIDATION);
+ $doc->resolveTemplateLocation(true);
+ }
+}
diff --git a/test/samples/custom/items/template_location/template_location_item.xml b/test/samples/custom/items/template_location/template_location_item.xml
new file mode 100644
index 000000000..2b672a3d9
--- /dev/null
+++ b/test/samples/custom/items/template_location/template_location_item.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ ChoiceA
+
+
+
+
+ 0
+
+
+
+
+ Who is the inventor of the telephone?
+ Alexander Graham Bell
+ Henri Owen Tudor
+ Bill Gates
+
+
+
+
diff --git a/test/samples/custom/items/template_location/template_location_item_invalid_target.xml b/test/samples/custom/items/template_location/template_location_item_invalid_target.xml
new file mode 100644
index 000000000..9023aa545
--- /dev/null
+++ b/test/samples/custom/items/template_location/template_location_item_invalid_target.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ ChoiceA
+
+
+
+
+ 0
+
+
+
+
+ Who is the inventor of the telephone?
+ Alexander Graham Bell
+ Henri Owen Tudor
+ Bill Gates
+
+
+
+
diff --git a/test/samples/custom/items/template_location/template_location_item_wrong_target.xml b/test/samples/custom/items/template_location/template_location_item_wrong_target.xml
new file mode 100644
index 000000000..d170d6024
--- /dev/null
+++ b/test/samples/custom/items/template_location/template_location_item_wrong_target.xml
@@ -0,0 +1,26 @@
+
+
+
+
+ ChoiceA
+
+
+
+
+ 0
+
+
+
+
+ Who is the inventor of the telephone?
+ Alexander Graham Bell
+ Henri Owen Tudor
+ Bill Gates
+
+
+
+
+
diff --git a/test/samples/custom/items/template_location/template_location_rp.xml b/test/samples/custom/items/template_location/template_location_rp.xml
new file mode 100644
index 000000000..64d5ea856
--- /dev/null
+++ b/test/samples/custom/items/template_location/template_location_rp.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/test/samples/custom/items/template_location/template_location_rp_invalid.xml b/test/samples/custom/items/template_location/template_location_rp_invalid.xml
new file mode 100644
index 000000000..513c0e3af
--- /dev/null
+++ b/test/samples/custom/items/template_location/template_location_rp_invalid.xml
@@ -0,0 +1,3 @@
+
+
+