Skip to content

Commit

Permalink
Merge pull request #40 from oat-sa/legacy-assessmentSectionRef-in-tes…
Browse files Browse the repository at this point in the history
…tPart

AssessmentSectionRef resolution support.
  • Loading branch information
Jérôme Bogaerts authored Jul 27, 2016
2 parents 358d20e + ccced41 commit f406c42
Show file tree
Hide file tree
Showing 13 changed files with 212 additions and 16 deletions.
2 changes: 1 addition & 1 deletion qtism/data/AssessmentSectionCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
* @author Jérôme Bogaerts <[email protected]>
*
*/
class AssessmentSectionCollection extends QtiIdentifiableCollection {
class AssessmentSectionCollection extends SectionPartCollection {

/**
* Check if $value is an AssessmentSection object.
Expand Down
7 changes: 6 additions & 1 deletion qtism/data/QtiComponentIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,11 @@ public function rewind() {
$root = $this->getRootComponent();
$this->pushOnTrail($root, $root->getComponents());

$hasTrail = false;
$foundClass = false;

while(count($this->getTrail()) > 0) {
$hasTrail = true;
$trailEntry = $this->popFromTrail();

$this->setValid(true);
Expand All @@ -309,11 +313,12 @@ public function rewind() {
$this->pushOnTrail($this->getCurrentComponent(), $this->getCurrentComponent()->getComponents());

if (empty($classes) === true || in_array($this->getCurrentComponent()->getQtiClassName(), $classes) === true) {
$foundClass = true;
break;
}
}

if (count($this->getTrail()) === 0) {
if (count($this->getTrail()) === 0 && (($hasTrail && !$foundClass) || (!$hasTrail))) {
$this->setValid(false);
$this->setCurrentComponent(null);
$this->setCurrentContainer(null);
Expand Down
26 changes: 17 additions & 9 deletions qtism/data/TestPart.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class TestPart extends QtiComponent implements QtiIdentifiable {
*
* The items contained in each testPart are arranged into sections and sub-sections.
*
* @var AssessmentSectionCollection
* @var SectionPartCollection
* @qtism-bean-property
*/
private $assessmentSections;
Expand All @@ -141,12 +141,12 @@ class TestPart extends QtiComponent implements QtiIdentifiable {
* Create a new instance of TestPart.
*
* @param string $identifier A QTI Identifier;
* @param AssessmentSectionCollection $assessmentSections A collection of AssessmentSection objects.
* @param @param SectionPartCollection $assessmentSections A collection of AssessmentSection or AssessmentSectionRef objects objects.
* @param int $navigationMode A value of the NavigationMode enumeration.
* @param int $submissionMode A value of the SubmissionMode enumeration.
* @throws InvalidArgumentException If an argument has the wrong type or format.
*/
public function __construct($identifier, AssessmentSectionCollection $assessmentSections, $navigationMode = NavigationMode::LINEAR, $submissionMode = SubmissionMode::INDIVIDUAL) {
public function __construct($identifier, SectionPartCollection $assessmentSections, $navigationMode = NavigationMode::LINEAR, $submissionMode = SubmissionMode::INDIVIDUAL) {
$this->setObservers(new SplObjectStorage());

$this->setIdentifier($identifier);
Expand Down Expand Up @@ -329,22 +329,30 @@ public function hasTimeLimits() {
}

/**
* Set the AssessmentSection that are part of this Test Part.
* Get the AssessmentSections and/or AssessmentSectionRefs that are part of this Test Part.
*
* @return AssessmentSectionCollection A collection of AssessmentSection object.
* @return \qtism\data\SectionPartCollection A collection of AssessmentSection and/or AssessmentSectionRef objects.
*/
public function getAssessmentSections() {
return $this->assessmentSections;
}

/**
* Set the AssessmentSection that are part of this Test Part.
* Set the AssessmentSections and/or AssessmentSectionRefs that are part of this Test Part.
*
* @param AssessmentSectionCollection $assessmentSections A collection of AssessmentSection objects.
* @throws InvalidArgumentException If $assessmentSections is an empty collection.
* @param SectionPartCollection $assessmentSections A collection of AssessmentSection and/or AssessmentSectionRef objects.
* @throws \InvalidArgumentException If $assessmentSections is an empty collection or contains something else than AssessmentSection and/or AssessmentSectionRef objects.
*/
public function setAssessmentSections(AssessmentSectionCollection $assessmentSections) {
public function setAssessmentSections(SectionPartCollection $assessmentSections) {
if (count($assessmentSections) > 0) {
// Check that we have only AssessmentSection and/ord AssessmentSectionRef objects.
foreach ($assessmentSections as $assessmentSection) {
if (!$assessmentSection instanceof AssessmentSection && !$assessmentSection instanceof AssessmentSectionRef) {
$msg = "A TestPart contain only contain AssessmentSection or AssessmentSectionRef objects.";
throw new InvalidArgumentException($msg);
}
}

$this->assessmentSections = $assessmentSections;
}
else {
Expand Down
50 changes: 50 additions & 0 deletions qtism/data/storage/xml/XmlDocument.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
use qtism\data\QtiDocument;
use qtism\data\storage\xml\marshalling\MarshallerFactory;
use qtism\data\AssessmentTest;
use qtism\data\AssessmentSectionRef;
use qtism\data\TestPart;
use qtism\data\content\Flow;
use qtism\data\storage\xml\marshalling\Marshaller;
use qtism\data\storage\xml\marshalling\UnmarshallingException;
Expand Down Expand Up @@ -354,6 +356,54 @@ public function xInclude($validate = false) {
throw new LogicException($msg);
}
}

public function includeAssessmentSectionRefs($validate = false)
{
if (($root = $this->getDocumentComponent()) !== null) {

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

$count = count($root->getComponentsByClassName('assessmentSectionRef'));
while ($count > 0) {
$iterator = new QtiComponentIterator($root, array('assessmentSectionRef'));
foreach ($iterator as $assessmentSectionRef) {
$parent = $iterator->parent();
$href = $assessmentSectionRef->getHref();

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

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

foreach ($sectionRoot->getComponentsByClassName(array('assessmentSectionRef', 'assessmentItemRef')) as $sectionPart) {
$newBasePath = Url::ltrim(str_replace($basePath, '', $href));
$pathinfo = pathinfo($newBasePath);
$newHref = $pathinfo['dirname'] . '/' . Url::ltrim($sectionPart->getHref());
$sectionPart->setHref($newHref);
}

if ($parent instanceof TestPart) {
$collection = $parent->getAssessmentSections();
} else {
$collection = $parent->getSectionParts();
}

$collection->detach($assessmentSectionRef);
$collection->attach($sectionRoot);
}
}

$count = count($root->getComponentsByClassName('assessmentSectionRef'));
}
} else {
$msg = "Cannot resolve assessmentSectionRefs before loading any file.";
throw new LogicException($msg);
}
}

/**
* Decorate the root element of the XmlAssessmentDocument with the appropriate
Expand Down
6 changes: 3 additions & 3 deletions qtism/data/storage/xml/marshalling/TestPartMarshaller.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
use qtism\data\TestPart;
use qtism\data\TestFeedbackCollection;
use qtism\data\ItemSessionControl;
use qtism\data\AssessmentSectionCollection;
use qtism\data\SectionPartCollection;
use qtism\data\rules\PreConditionCollection;
use qtism\data\rules\BranchRuleCollection;
use qtism\data\TimeLimits;
Expand Down Expand Up @@ -97,8 +97,8 @@ protected function unmarshall(DOMElement $element) {
// We do not use the regular DOMElement::getElementsByTagName method
// because it is recursive. We only want the first level elements with
// tagname = 'assessmentSection'.
$assessmentSectionElts = self::getChildElementsByTagName($element, 'assessmentSection');
$assessmentSections = new AssessmentSectionCollection();
$assessmentSectionElts = self::getChildElementsByTagName($element, array('assessmentSection', 'assessmentSectionRef'));
$assessmentSections = new SectionPartCollection();
foreach ($assessmentSectionElts as $sectElt) {
$marshaller = $this->getMarshallerFactory()->createMarshaller($sectElt);
$assessmentSections[] = $marshaller->unmarshall($sectElt);
Expand Down
15 changes: 14 additions & 1 deletion test/qtism/data/QtiComponentIteratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,17 @@ public function testClassSelection() {

$this->assertEquals(7, $i);
}
}

public function testOneChildComponents() {
$baseValues = new ExpressionCollection();
$baseValues[] = new BaseValue(BaseType::FLOAT, 0.5);
$sum = new Sum($baseValues);
$iterator = new QtiComponentIterator($sum);

$iterations = 0;
foreach ($iterator as $k => $i) {
$iterations++;
}
$this->assertEquals(1, $iterations);
}
}
43 changes: 42 additions & 1 deletion test/qtism/data/storage/xml/XmlAssessmentTestDocumentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,49 @@ public function testItemSessionControls() {
$this->assertInstanceOf('qtism\\data\\TestPart', $p02);
$this->assertEquals(4, $p02->getItemSessionControl()->getMaxAttempts());
}

public function testAssessmentSectionRefsInTestParts() {
$doc = new XmlDocument();
$doc->load(self::samplesDir() . 'custom/tests/nested_assessment_section_refs/test_definition/test.xml', true);

$testParts = $doc->getDocumentComponent()->getTestParts();
$this->assertTrue(isset($testParts['T01']));

$sectionParts = $testParts['T01']->getAssessmentSections();
$this->assertTrue(isset($sectionParts['SR01']));
$this->assertInstanceOf('qtism\\data\\AssessmentSectionRef', $sectionParts['SR01']);
}

public function testIncludeAssessmentSectionRefsInTestParts() {
$doc = new XmlDocument();
$doc->load(self::samplesDir() . 'custom/tests/nested_assessment_section_refs/test_definition/test.xml', true);
$doc->includeAssessmentSectionRefs();

$root = $doc->getDocumentComponent();

$testParts = $root->getTestParts();
$this->assertTrue(isset($testParts['T01']));

// Check that assessmentSectionRef 'SR01' has been resolved.
$sectionParts = $testParts['T01']->getAssessmentSections();

$this->assertTrue(isset($sectionParts['S01']));
$this->assertFalse(isset($sectionParts['SR01']));
$this->assertTrue(isset($sectionParts['S01']->getSectionParts()['S02']));

// Check that the final assessmentSection contains the assessmentItemRefs.
$assessmentItemRefs = $sectionParts['S01']->getSectionParts()['S02']->getSectionParts();
$this->assertEquals(3, count($assessmentItemRefs));

$this->assertInstanceOf('qtism\\data\\AssessmentItemRef', $assessmentItemRefs['Q01']);
$this->assertEquals('../sections/../sections/../items/question1.xml', $assessmentItemRefs['Q01']->getHref());
$this->assertInstanceOf('qtism\\data\\AssessmentItemRef', $assessmentItemRefs['Q02']);
$this->assertEquals('../sections/../sections/../items/question2.xml', $assessmentItemRefs['Q02']->getHref());
$this->assertInstanceOf('qtism\\data\\AssessmentItemRef', $assessmentItemRefs['Q03']);
$this->assertEquals('../sections/../sections/../items/question3.xml', $assessmentItemRefs['Q03']->getHref());
}

private static function decorateUri($uri) {
return dirname(__FILE__) . '/../../../../samples/ims/tests/' . $uri;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<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_v2p1p1.xsd"
identifier="question1" title="Question 1" adaptive="false" timeDependent="false">
<responseDeclaration identifier="RESPONSE" cardinality="single" baseType="identifier">
<correctResponse>
<value>ChoiceA</value>
</correctResponse>
</responseDeclaration>
<outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float"/>
<itemBody>
<choiceInteraction responseIdentifier="RESPONSE" shuffle="true" maxChoices="0">
<prompt>What is the correct response?</prompt>
<simpleChoice identifier="ChoiceA" fixed="false">Choice A</simpleChoice>
<simpleChoice identifier="ChoiceB" fixed="false">Choice B</simpleChoice>
<simpleChoice identifier="ChoiceC" fixed="false">Choice C</simpleChoice>
</choiceInteraction>
</itemBody>
<responseProcessing template="http://www.imsglobal.org/question/qti_v2p1/rptemplates/match_correct"/>
</assessmentItem>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<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_v2p1p1.xsd"
identifier="question2" title="Question 2" adaptive="false" timeDependent="false">
<responseDeclaration identifier="RESPONSE" cardinality="single" baseType="identifier">
<correctResponse>
<value>ChoiceB</value>
</correctResponse>
</responseDeclaration>
<outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float"/>
<itemBody>
<choiceInteraction responseIdentifier="RESPONSE" shuffle="true" maxChoices="0">
<prompt>What is the correct response?</prompt>
<simpleChoice identifier="ChoiceA" fixed="false">Choice A</simpleChoice>
<simpleChoice identifier="ChoiceB" fixed="false">Choice B</simpleChoice>
<simpleChoice identifier="ChoiceC" fixed="false">Choice C</simpleChoice>
</choiceInteraction>
</itemBody>
<responseProcessing template="http://www.imsglobal.org/question/qti_v2p1/rptemplates/match_correct"/>
</assessmentItem>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<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_v2p1p1.xsd"
identifier="question3" title="Question 3" adaptive="false" timeDependent="false">
<responseDeclaration identifier="RESPONSE" cardinality="single" baseType="identifier">
<correctResponse>
<value>ChoiceC</value>
</correctResponse>
</responseDeclaration>
<outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float"/>
<itemBody>
<choiceInteraction responseIdentifier="RESPONSE" shuffle="true" maxChoices="0">
<prompt>What is the correct response?</prompt>
<simpleChoice identifier="ChoiceA" fixed="false">Choice A</simpleChoice>
<simpleChoice identifier="ChoiceB" fixed="false">Choice B</simpleChoice>
<simpleChoice identifier="ChoiceC" fixed="false">Choice C</simpleChoice>
</choiceInteraction>
</itemBody>
<responseProcessing template="http://www.imsglobal.org/question/qti_v2p1/rptemplates/match_correct"/>
</assessmentItem>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<assessmentSection identifier="S01" title="Section 1" visible="true" fixed="false" keepTogether="false" 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_v2p1p1.xsd" xmlns="http://www.imsglobal.org/xsd/imsqti_v2p1">
<assessmentSectionRef identifier="SR02" href="../sections/section_ref2.xml" />
</assessmentSection>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<assessmentSection identifier="S02" title="Section 2" visible="true" fixed="false" keepTogether="false" 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_v2p1p1.xsd" xmlns="http://www.imsglobal.org/xsd/imsqti_v2p1">
<assessmentItemRef identifier="Q01" href="../items/question1.xml" required="false" fixed="false" />
<assessmentItemRef identifier="Q02" href="../items/question2.xml" required="false" fixed="false" />
<assessmentItemRef identifier="Q03" href="../items/question3.xml" required="false" fixed="false" />
</assessmentSection>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<assessmentTest title="Nested Assessment Section References" identifier="nested_assessment_section_refs" 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_v2p1p1.xsd" xmlns="http://www.imsglobal.org/xsd/imsqti_v2p1">
<testPart navigationMode="nonlinear" identifier="T01" submissionMode="individual">
<assessmentSectionRef identifier="SR01" href="../sections/section_ref1.xml" />
</testPart>
</assessmentTest>

0 comments on commit f406c42

Please sign in to comment.