public function fromXML(SimpleXMLElement $xml) { $data = array("itemtype" => $this->itemType(), "title" => (string) $xml["title"], "stimulus" => qti_get_stimulus($xml->itemBody)); // check for a div with the item class name $itembodycontainer = null; foreach ($xml->itemBody->div as $div) { if (!isset($div["class"]) || (string) $div["class"] != "eqiat-mcr") { continue; } // get elements from here $itembodycontainer = $div; break; } // if there was none, get elements from itemBody if (is_null($itembodycontainer)) { $itembodycontainer = $xml->itemBody; } // there is one choiceInteraction if (count($itembodycontainer->choiceInteraction) != 1) { return 0; } // there is one responseDeclaration if (count($xml->responseDeclaration) != 1) { return 0; } // check cardinality is as expected if ($this->itemType() == "multipleResponse" && (string) $xml->responseDeclaration["cardinality"] != "multiple") { return 0; } if ($this->itemType() == "multipleChoice" && (string) $xml->responseDeclaration["cardinality"] != "single") { return 0; } // multiple choice must have maxchoices 1 and one correct response value if ($this->itemType() == "multipleChoice") { if (!isset($itembodycontainer->choiceInteraction["maxChoices"]) || (string) $itembodycontainer->choiceInteraction["maxChoices"] != "1") { return 0; } if (!isset($xml->responseDeclaration->correctResponse)) { return 0; } if (count($xml->responseDeclaration->correctResponse->value) != 1) { return 0; } } // get shuffle value $shuffle = isset($itembodycontainer->choiceInteraction["shuffle"]) && (string) $itembodycontainer->choiceInteraction["shuffle"] == "true"; if ($shuffle) { $data["shuffle"] = "on"; } // there is at least one option if (count($itembodycontainer->choiceInteraction->simpleChoice) == 0) { return 0; } // collect options and their identifiers $o = 0; $options = array(); foreach ($itembodycontainer->choiceInteraction->simpleChoice as $sc) { $options[] = (string) $sc["identifier"]; $data["option_{$o}_optiontext"] = (string) $sc; if ($shuffle && isset($sc["fixed"]) && (string) $sc["fixed"] == "true") { $data["option_{$o}_fixed"] = "on"; } $o++; } // check correct response makes sense; collect correct responses $correct = array(); if (count($xml->responseDeclaration->correctResponse) > 0) { foreach ($xml->responseDeclaration->correctResponse->value as $value) { $pos = array_search((string) $value, $options); if ($pos === false) { return 0; } $correct[] = $pos; } } if ($this->itemType() == "multipleChoice") { $data["correct"] = "option_" . $correct[0]; } else { foreach ($correct as $o) { $data["option_{$o}_correct"] = "on"; } } // get max and min choices if ($this->itemType() == "multipleResponse") { $data["maxchoices"] = isset($itembodycontainer->choiceInteraction["maxChoices"]) ? (string) $itembodycontainer->choiceInteraction["maxChoices"] : "0"; $data["minchoices"] = isset($itembodycontainer->choiceInteraction["minChoices"]) ? (string) $itembodycontainer->choiceInteraction["minChoices"] : "0"; } // get prompt $data["prompt"] = (string) $itembodycontainer->choiceInteraction->prompt; // not checking this properly at all but if modalFeedback elements exist // which look about right, use them for feedback foreach ($xml->modalFeedback as $mf) { $id = explode("_", (string) $mf["identifier"]); if (count($id) == 3 && $id[2] < count($options) && (string) $mf["outcomeIdentifier"] == "FEEDBACK") { $data["feedback"] = true; if ((string) $mf["showHide"] == "show") { $data["option_{$id[2]}_feedback_chosen"] = xml_remove_wrapper_element($mf->asXML()); } else { $data["option_{$id[2]}_feedback_unchosen"] = xml_remove_wrapper_element($mf->asXML()); } } } // get min score foreach ($xml->responseProcessing->responseCondition as $rc) { if (count($rc->responseIf) != 1) { continue; } if (count($rc->responseIf->lt) != 1) { continue; } $ltchildren = $rc->responseIf->lt->children(); if ($ltchildren[0]->getName() != "variable" || (string) $ltchildren[0]["identifier"] != "SCORE") { continue; } if ($ltchildren[1]->getName() != "baseValue") { continue; } $val = (string) $ltchildren[1]; if (count($rc->responseIf->setOutcomeValue) != 1) { continue; } if ((string) $rc->responseIf->setOutcomeValue["identifier"] != "SCORE") { continue; } if (count($rc->responseIf->setOutcomeValue->baseValue) != 1) { continue; } if ((string) $rc->responseIf->setOutcomeValue->baseValue != $val) { continue; } $data["minscore"] = $val; } // get max score foreach ($xml->responseProcessing->responseCondition as $rc) { if (count($rc->responseIf) != 1) { continue; } if (count($rc->responseIf->gt) != 1) { continue; } $gtchildren = $rc->responseIf->gt->children(); if ($gtchildren[0]->getName() != "variable" || (string) $gtchildren[0]["identifier"] != "SCORE") { continue; } if ($gtchildren[1]->getName() != "baseValue") { continue; } $val = (string) $gtchildren[1]; if (count($rc->responseIf->setOutcomeValue) != 1) { continue; } if ((string) $rc->responseIf->setOutcomeValue["identifier"] != "SCORE") { continue; } if (count($rc->responseIf->setOutcomeValue->baseValue) != 1) { continue; } if ((string) $rc->responseIf->setOutcomeValue->baseValue != $val) { continue; } $data["maxscore"] = $val; } // check response conditions for incrementing score if a particular // response is given $data["scoring"] = "exact"; foreach ($xml->responseProcessing->responseCondition as $rc) { if (count($rc->responseIf) != 1) { continue; } if (count($rc->responseIf->member) != 1) { continue; } $mchildren = $rc->responseIf->member->children(); if ($mchildren[0]->getName() != "baseValue" || (string) $mchildren[0]["baseType"] != "identifier") { continue; } $oid = (string) $mchildren[0]; if ($mchildren[1]->getName() != "variable" || (string) $mchildren[1]["identifier"] != "RESPONSE") { continue; } if (count($rc->responseIf->setOutcomeValue) != 1) { continue; } if ((string) $rc->responseIf->setOutcomeValue["identifier"] != "SCORE") { continue; } if (count($rc->responseIf->setOutcomeValue->sum) != 1) { continue; } if (count($rc->responseIf->setOutcomeValue->sum->children()) != 2) { continue; } if (count($rc->responseIf->setOutcomeValue->sum->variable) != 1 || (string) $rc->responseIf->setOutcomeValue->sum->variable["identifier"] != "SCORE") { continue; } if (count($rc->responseIf->setOutcomeValue->sum->baseValue) != 1) { continue; } $val = (string) $rc->responseIf->setOutcomeValue->sum->baseValue; $pos = array_search((string) $oid, $options); if ($pos === false) { continue; } $data["option_{$pos}_score"] = $val; $data["scoring"] = "cumulative"; } // see if it is in fact cumulative if ($data["scoring"] == "cumulative" && (!isset($data["minscore"]) || $data["minscore"] != 0 || isset($data["maxscore"]))) { $data["scoring"] = "custom"; } if ($data["scoring"] == "cumulative") { foreach ($options as $o => $option) { if (!isset($data["option_{$o}_score"]) || $data["option_{$o}_score"] != 1 && $data["option_{$o}_score"] != -1) { $data["scoring"] = "custom"; break; } } } // happy with that -- set data property and identifier $this->data = $data; $this->setQTIID((string) $xml["identifier"]); // multiple response with one correct answer is less than ideal if ($this->itemType() == "multipleresponse" && count($options) == 1) { return 192; } return 255; }
public function fromXML(SimpleXMLElement $xml) { $data = array("itemtype" => $this->itemType(), "title" => (string) $xml["title"], "stimulus" => qti_get_stimulus($xml->itemBody)); // check for a div with the item class name $itembodycontainer = null; foreach ($xml->itemBody->div as $div) { if (!isset($div["class"]) || (string) $div["class"] != "eqiat-qm") { continue; } // get elements from here $itembodycontainer = $div; break; } // if there was none, get elements from itemBody if (is_null($itembodycontainer)) { $itembodycontainer = $xml->itemBody; } // count the choiceInteractions $questioncount = count($itembodycontainer->choiceInteraction); // no good if there are no questions if ($questioncount == 0) { return 0; } // ensure there are the same number of responseDeclarations if (count($xml->responseDeclaration) != $questioncount) { return 0; } // ensure there are the same number of responseConditions if (count($xml->responseProcessing->responseCondition) != $questioncount) { return 0; } // ensure some stuff for each question $q = 0; foreach ($itembodycontainer->choiceInteraction as $ci) { // candidate can only choose one answer if ((string) $ci["maxChoices"] != "1") { return 0; } // there are two possible answers if (count($ci->simpleChoice) != 2) { return 0; } // answers are true and false $answers = array(null, null); foreach ($ci->simpleChoice as $sc) { if (strtolower((string) $sc) == "false") { $answers[0] = (string) $sc["identifier"]; } else { if (strtolower((string) $sc) == "true") { $answers[1] = (string) $sc["identifier"]; } else { return 0; } } } // check some responseDeclaration things $declarationsfound = 0; foreach ($xml->responseDeclaration as $rd) { if ((string) $rd["identifier"] != (string) $ci["responseIdentifier"]) { continue; } $declarationsfound++; // has a correct response if (!isset($rd->correctResponse)) { return 0; } // has one correct response if (count($rd->correctResponse->value) != 1) { return 0; } // the correct response is one of the options $answer = array_search((string) $rd->correctResponse->value, $answers); if ($answer === false) { return 0; } // add answer to data $data["question_{$q}_answer"] = $answer ? true : false; } // there was a good responseDeclaration for this question if ($declarationsfound != 1) { return 0; } // add prompt to data $data["question_{$q}_prompt"] = (string) $ci->prompt; $q++; } // happy with that -- set data property and identifier $this->data = $data; $this->setQTIID((string) $xml["identifier"]); // rather strange question matrix if it's only one question if ($questioncount == 1) { return 127; } return 255; }
public function fromXML(SimpleXMLElement $xml) { $data = array("itemtype" => $this->itemType(), "title" => (string) $xml["title"], "stimulus" => qti_get_stimulus($xml->itemBody)); // check for a div with the item class name $itembodycontainer = null; foreach ($xml->itemBody->div as $div) { if (!isset($div["class"]) || (string) $div["class"] != "eqiat-te") { continue; } // get elements from here $itembodycontainer = $div; break; } // if there was none, get elements from itemBody if (is_null($itembodycontainer)) { $itembodycontainer = $xml->itemBody; } // get the text body and remove it from the tree $tb = null; foreach ($itembodycontainer->children() as $child) { if ($child->getName() == "div" && isset($child["class"]) && (string) $child["class"] == "textentrytextbody") { $tb = dom_import_simplexml($child); $tb->parentNode->removeChild($tb); break; } } if (is_null($tb)) { return 0; } // get text body fragments $fragments = self::getTextEntryInteractionsAndText($tb); $data["textbody"] = ""; $gaps = array(); // fail if there was a problem with a textEntryInteraction (for example // it doesn't have a responseDeclaration id associated) if ($fragments === false) { return 0; } // go through the textbody fragments, match them to responseDeclarations // and collect responses foreach ($fragments as $fid => $fragment) { if (!preg_match('%^\\[.*\\]$%', $fragment)) { $data["textbody"] .= $fragment; continue; } // we have a textEntryInteraction id -- look for a matching // responseDeclaration $rdi = substr($fragment, 1, -1); $rd = null; foreach ($xml->responseDeclaration as $d) { if ((string) $d["identifier"] == $rdi) { $rd = $d; break; } } if (is_null($rd)) { return 0; } // get all the responses for this gap and their scores if (!isset($rd->mapping)) { return 0; } // fail if the default score isn't 0 if ((string) $rd->mapping["defaultValue"] !== "0") { return 0; } // fail if there are no responses if (!isset($rd->mapping->mapEntry)) { return 0; } // collect responses and their scores and find the best response $gaps[$fid] = array(); foreach ($rd->mapping->mapEntry as $me) { $gaps[$fid][(string) $me["mapKey"]] = (string) $me["mappedValue"]; } // sort responses by descending score arsort($gaps[$fid]); // add the plaintext gap to the textbody string $data["textbody"] .= "[" . key($gaps[$fid]) . "]"; } // turn HTML paragraphs and line breaks into plaintext newlines $data["textbody"] = preg_replace(array('%\\s*</p>\\s*<p>\\s*%', '%\\s*</?p>\\s*%', '%\\s*<br\\s*/?>\\s*%'), array("\n\n", "", "\n"), trim($data["textbody"])); // fail if there are no gaps if (count($gaps) == 0) { return 0; } // add responses and their scores to data $g = 0; foreach ($gaps as $responses) { $r = 0; foreach ($responses as $response => $score) { $data["gap_{$g}_response_{$r}"] = $response; $data["gap_{$g}_response_{$r}_score"] = $score; $r++; } $g++; } // happy with that -- set data property and identifier $this->data = $data; $this->setQTIID((string) $xml["identifier"]); return 255; }
public function fromXML(SimpleXMLElement $xml) { $data = array("itemtype" => $this->itemType(), "title" => (string) $xml["title"], "stimulus" => qti_get_stimulus($xml->itemBody)); // check for a div with the item class name $itembodycontainer = null; foreach ($xml->itemBody->div as $div) { if (!isset($div["class"]) || (string) $div["class"] != "eqiat-emi") { continue; } // get elements from here $itembodycontainer = $div; break; } // if there was none, get elements from itemBody if (is_null($itembodycontainer)) { $itembodycontainer = $xml->itemBody; } // count the choiceInteractions $questioncount = count($itembodycontainer->choiceInteraction); // no good if there are no questions if ($questioncount == 0) { return 0; } // ensure there are the same number of responseDeclarations if (count($xml->responseDeclaration) != $questioncount) { return 0; } // ensure there are the same number of responseConditions if (count($xml->responseProcessing->responseCondition) != $questioncount) { return 0; } // check the stimulus for the options and collect them $options = array(); foreach ($itembodycontainer->ol as $ol) { if (!isset($ol["class"]) || (string) $ol["class"] != "emioptions") { continue; } if (count($ol->li) < 2) { return 0; } foreach ($ol->li as $listitem) { $options[] = (string) $listitem; } break; } if (empty($options)) { // check for table for backwards compatibility foreach ($itembodycontainer->table as $table) { if (!isset($table["class"]) || (string) $table["class"] != "emioptions") { continue; } if (count($table->tbody) != 1 || count($table->tbody->tr) < 2) { return 0; } foreach ($table->tbody->tr as $row) { if (count($row->td) != 1) { return 0; } $options[] = (string) $row->td; } break; } } if (empty($options)) { return 0; } // add options to data foreach ($options as $k => $option) { $data["option_{$k}_optiontext"] = $option; } // ensure some stuff for each question $q = 0; foreach ($itembodycontainer->choiceInteraction as $ci) { // questions are multiple response so fail if maxChoices is 1. don't // care about minChoices if ((string) $ci["maxChoices"] == "1") { return 0; } // there are the right number of choices if (count($ci->simpleChoice) != count($options)) { return 0; } // answers are ascending single letters; collect their identifiers $i = 0; $answers = array(); foreach ($ci->simpleChoice as $sc) { if (strtolower((string) $sc) != chr(ord("a") + $i)) { return 0; } $answers[] = (string) $sc["identifier"]; $i++; } // check some responseDeclaration things $declarationsfound = 0; foreach ($xml->responseDeclaration as $rd) { if ((string) $rd["identifier"] != (string) $ci["responseIdentifier"]) { continue; } $declarationsfound++; if (count($rd->correctResponse)) { // the correct response values are some of the options; // collect them $correct = array(); foreach ($rd->correctResponse->value as $value) { $answer = array_search((string) $value, $answers); if ($answer === false) { return 0; } $correct[] = $answer; } // add answers to data foreach ($correct as $o) { $data["question_{$q}_option_{$o}_correct"] = "on"; } } // else an empty response is correct -- nothing to check } // there was a good responseDeclaration for this question if ($declarationsfound != 1) { return 0; } // add prompt to data $data["question_{$q}_prompt"] = (string) $ci->prompt; $q++; } // happy with that -- set data property and identifier $this->data = $data; $this->setQTIID((string) $xml["identifier"]); // rather strange extended matching item if it's only one question if ($questioncount == 1) { return 127; } return 255; }