Esempio n. 1
0
 function create_class($name)
 {
     $xmldiag = simplexml_load_string($this->load->view("xml/class_canvas", array(), TRUE));
     $xmlclass = simplexml_load_string($this->load->view("xml/class_class", array(), TRUE));
     $class_id = $this->gen_id("class");
     $item_id = $this->gen_id("item");
     $xmldiag->addAttribute("id", $item_id);
     $xmldiag->subject[0]->ref[0]->addAttribute("refid", $class_id);
     $xmldiag->matrix->val = $this->random_matrix();
     $xmlclass->name[0]->val[0] = $name;
     $xmlclass->addAttribute("id", $class_id);
     $xmlclass->package[0]->ref[0]->addAttribute("refid", $this->package_id);
     $xmlclass->presentation[0]->reflist[0]->ref[0]->addAttribute("refid", $item_id);
     simplexml_append($this->xml->Diagram[0]->canvas, $xmldiag);
     simplexml_append($this->xml, $xmlclass);
     $classlist =& $this->xml->xpath("//Class");
     $class =& $classlist[count($classlist) - 1];
     return $class;
 }
 protected function buildQTI($data = null)
 {
     if (!is_null($data)) {
         $this->data = $data;
     }
     if (empty($this->data)) {
         return false;
     }
     // container element and other metadata
     $ai = $this->initialXML();
     // response declaration
     $rd = $ai->addChild("responseDeclaration");
     $rd->addAttribute("identifier", "RESPONSE");
     $rd->addAttribute("cardinality", $this->itemType() == "multipleChoice" ? "single" : "multiple");
     $rd->addAttribute("baseType", "identifier");
     // correct response
     if ($this->itemType() == "multipleResponse") {
         // build array of correct responses
         $correct = array();
         if ($this->data["scoring"] == "custom") {
             // collect scores, sort
             $scores = array();
             for ($i = 0; array_key_exists("option_{$i}_optiontext", $this->data); $i++) {
                 $scores[$i] = floatval($this->data["option_{$i}_score"]);
             }
             arsort($scores, SORT_NUMERIC);
             $keys = array_keys($scores);
             // using max and min choices find the optimal response
             for ($i = 0; $i < (isset($this->data["minchoices"]) ? $this->data["minchoices"] : 0); $i++) {
                 $correct[] = $keys[$i];
             }
             for (; $i < count($scores); $i++) {
                 if (isset($this->data["maxchoices"]) && $this->data["maxchoices"] > 0 && $i >= $this->data["maxchoices"]) {
                     break;
                 }
                 //stop if we've used all the choices we're allowed
                 if ($scores[$keys[$i]] <= 0) {
                     break;
                 }
                 //stop if the next best choice doesn't improve the score
                 $correct[] = $keys[$i];
             }
         } else {
             for ($i = 0; array_key_exists("option_{$i}_optiontext", $this->data); $i++) {
                 if (isset($this->data["option_{$i}_correct"])) {
                     $correct[] = $i;
                 }
             }
         }
         // add correctResponse node only if any options are correct
         if (!empty($correct)) {
             $rd->addChild("correctResponse");
             foreach ($correct as $o) {
                 $rd->correctResponse->addChild("value", "option_{$o}");
             }
         }
     } else {
         $rd->addChild("correctResponse");
         $rd->correctResponse->addChild("value", $this->data["correct"]);
     }
     // feedback outcome declaration
     if (isset($this->data["feedback"])) {
         $od = $ai->addChild("outcomeDeclaration");
         $od->addAttribute("identifier", "FEEDBACK");
         $od->addAttribute("cardinality", "multiple");
         $od->addAttribute("baseType", "identifier");
     }
     // score outcome declaration
     $od = $ai->addChild("outcomeDeclaration");
     $od->addAttribute("identifier", "SCORE");
     $od->addAttribute("cardinality", "single");
     $od->addAttribute("baseType", $this->data["scoring"] == "custom" ? "float" : "integer");
     $od->addChild("defaultValue");
     $od->defaultValue->addChild("value", "0");
     // item body
     $ib = $ai->addChild("itemBody");
     // get stimulus and add to the XML tree
     if (isset($this->data["stimulus"]) && !empty($this->data["stimulus"])) {
         $this->data["stimulus"] = wrapindiv($this->data["stimulus"]);
         // parse it as XML
         $stimulus = stringtoxml($this->data["stimulus"], "stimulus");
         if (is_array($stimulus)) {
             // errors
             $this->errors[] = "Stimulus is not valid XML. It must not only be valid XML but valid QTI, which accepts a subset of XHTML. Details on specific issues follow:";
             $this->errors = array_merge($this->errors, $stimulus);
         } else {
             simplexml_append($ib, $stimulus);
         }
     }
     // div with class eqiat-mcr
     $d = $ib->addChild("div");
     $d->addAttribute("class", "eqiat-mcr");
     // choices
     $ci = $d->addChild("choiceInteraction");
     $ci->addAttribute("responseIdentifier", "RESPONSE");
     $ci->addAttribute("shuffle", isset($this->data["shuffle"]) ? "true" : "false");
     if ($this->itemType() == "multipleChoice") {
         $ci->addAttribute("maxChoices", "1");
     } else {
         if (isset($this->data["maxchoices"])) {
             $ci->addAttribute("maxChoices", $this->data["maxchoices"]);
         }
         if (isset($this->data["minchoices"])) {
             $ci->addAttribute("minChoices", $this->data["minchoices"]);
         }
     }
     if (isset($this->data["prompt"])) {
         $ci->addChild("prompt", $this->data["prompt"]);
     }
     for ($i = 0; array_key_exists("option_{$i}_optiontext", $this->data); $i++) {
         $sc = $ci->addChild("simpleChoice", $this->data["option_{$i}_optiontext"]);
         $sc->addAttribute("identifier", "option_{$i}");
         if (isset($this->data["shuffle"])) {
             $sc->addAttribute("fixed", isset($this->data["option_{$i}_fixed"]) ? "true" : "false");
         }
     }
     // response processing
     $rp = $ai->addChild("responseProcessing");
     // scoring logic
     if ($this->itemType() == "multipleChoice" || $this->data["scoring"] == "exact") {
         $rc = $rp->addChild("responseCondition");
         $ri = $rc->addChild("responseIf");
         if ($this->itemType() == "multipleResponse" && empty($correct)) {
             // multiple response in which the correct response is to tick no
             // boxes -- check number of responses is equal to zero
             $e = $ri->addChild("equal");
             $e->addAttribute("toleranceMode", "exact");
             $e->addChild("containerSize")->addChild("variable")->addAttribute("identifier", "RESPONSE");
             $e->addChild("baseValue", "0")->addAttribute("baseType", "integer");
         } else {
             // otherwise, we match responses to the correctResponse above
             $m = $ri->addChild("match");
             $m->addChild("variable")->addAttribute("identifier", "RESPONSE");
             $m->addChild("correct")->addAttribute("identifier", "RESPONSE");
         }
         $sov = $ri->addChild("setOutcomeValue");
         $sov->addAttribute("identifier", "SCORE");
         $sov->addChild("baseValue", "1")->addAttribute("baseType", "integer");
         $re = $rc->addChild("responseElse");
         $sov = $re->addChild("setOutcomeValue");
         $sov->addAttribute("identifier", "SCORE");
         $sov->addChild("baseValue", "0")->addAttribute("baseType", "integer");
     } else {
         // start with zero score
         $sov = $rp->addChild("setOutcomeValue");
         $sov->addAttribute("identifier", "SCORE");
         $sov->addChild("baseValue", "0")->addAttribute("baseType", $this->data["scoring"] == "custom" ? "float" : "integer");
         // add the option's score if it was chosen
         for ($i = 0; array_key_exists("option_{$i}_optiontext", $this->data); $i++) {
             if ($this->data["scoring"] == "custom" && $scores[$i] == 0) {
                 continue;
             }
             $rc = $rp->addChild("responseCondition");
             $ri = $rc->addChild("responseIf");
             $c = $ri->addChild("member");
             $c->addChild("baseValue", "option_{$i}")->addAttribute("baseType", "identifier");
             //if this
             $c->addChild("variable")->addAttribute("identifier", "RESPONSE");
             //is a member of this
             $sov = $ri->addChild("setOutcomeValue");
             //then do this
             $sov->addAttribute("identifier", "SCORE");
             $s = $sov->addChild("sum");
             $s->addChild("variable")->addAttribute("identifier", "SCORE");
             if ($this->data["scoring"] == "custom") {
                 $val = $scores[$i];
             } else {
                 if (isset($this->data["option_{$i}_correct"])) {
                     $val = 1;
                 } else {
                     $val = -1;
                 }
             }
             $s->addChild("baseValue", $val)->addAttribute("baseType", $this->data["scoring"] == "custom" ? "float" : "integer");
         }
         // minimum score
         if ($this->data["scoring"] == "cumulative" || $this->data["scoring"] == "custom" && is_numeric($this->data["minscore"])) {
             $rc = $rp->addChild("responseCondition");
             $ri = $rc->addChild("responseIf");
             $lt = $ri->addChild("lt");
             $lt->addChild("variable")->addAttribute("identifier", "SCORE");
             $lt->addChild("baseValue", $this->data["scoring"] == "custom" ? $this->data["minscore"] : 0)->addAttribute("baseType", $this->data["scoring"] == "custom" ? "float" : "integer");
             $sov = $ri->addChild("setOutcomeValue");
             $sov->addAttribute("identifier", "SCORE");
             $sov->addChild("baseValue", $this->data["scoring"] == "custom" ? $this->data["minscore"] : 0)->addAttribute("baseType", $this->data["scoring"] == "custom" ? "float" : "integer");
         }
         // maximum score
         if ($this->data["scoring"] == "custom" && is_numeric($this->data["maxscore"])) {
             $rc = $rp->addChild("responseCondition");
             $ri = $rc->addChild("responseIf");
             $gt = $ri->addChild("gt");
             $gt->addChild("variable")->addAttribute("identifier", "SCORE");
             $gt->addChild("baseValue", $this->data["maxscore"])->addAttribute("baseType", "float");
             $sov = $ri->addChild("setOutcomeValue");
             $sov->addAttribute("identifier", "SCORE");
             $sov->addChild("baseValue", $this->data["maxscore"])->addAttribute("baseType", "float");
         }
     }
     if (isset($this->data["feedback"])) {
         // initialize feedback var
         $sov = $rp->addChild("setOutcomeValue");
         $sov->addAttribute("identifier", "FEEDBACK");
         $sov->addChild("null");
         // feedback logic
         for ($i = 0; array_key_exists("option_{$i}_optiontext", $this->data); $i++) {
             $rc = $rp->addChild("responseCondition");
             $ri = $rc->addChild("responseIf");
             if ($this->itemType() == "multipleResponse") {
                 $c = $ri->addChild("member");
                 $c->addChild("baseValue", "option_{$i}")->addAttribute("baseType", "identifier");
                 //if this
                 $c->addChild("variable")->addAttribute("identifier", "RESPONSE");
                 //is a member of this
             } else {
                 $m = $ri->addChild("match");
                 $m->addChild("baseValue", "option_{$i}")->addAttribute("baseType", "identifier");
                 //if this
                 $m->addChild("variable")->addAttribute("identifier", "RESPONSE");
                 //is equal to this
             }
             $sov = $ri->addChild("setOutcomeValue");
             //then do this
             $sov->addAttribute("identifier", "FEEDBACK");
             $m = $sov->addChild("multiple");
             $m->addChild("variable")->addAttribute("identifier", "FEEDBACK");
             $m->addChild("baseValue", "feedback_option_{$i}")->addAttribute("baseType", "identifier");
         }
         // the feedback itself
         for ($i = 0; array_key_exists("option_{$i}_optiontext", $this->data); $i++) {
             if (isset($this->data["option_{$i}_feedback_chosen"]) && !empty($this->data["option_{$i}_feedback_chosen"])) {
                 $this->data["option_{$i}_feedback_chosen"] = wrapindiv($this->data["option_{$i}_feedback_chosen"]);
                 $mf = $ai->addChild("modalFeedback");
                 $mf->addAttribute("outcomeIdentifier", "FEEDBACK");
                 $mf->addAttribute("identifier", "feedback_option_{$i}");
                 $mf->addAttribute("showHide", "show");
                 // parse it as XML
                 $feedback = stringtoxml($this->data["option_{$i}_feedback_chosen"], "feedback");
                 if (is_array($feedback)) {
                     // errors
                     $this->errors[] = "Feedback is not valid XML. It must not only be valid XML but valid QTI, which accepts a subset of XHTML. Details on specific issues follow:";
                     $this->errors = array_merge($this->errors, $feedback);
                 } else {
                     simplexml_append($mf, $feedback);
                 }
             }
             if (isset($this->data["option_{$i}_feedback_unchosen"]) && !empty($this->data["option_{$i}_feedback_unchosen"])) {
                 $this->data["option_{$i}_feedback_unchosen"] = wrapindiv($this->data["option_{$i}_feedback_unchosen"]);
                 $mf = $ai->addChild("modalFeedback");
                 $mf->addAttribute("outcomeIdentifier", "FEEDBACK");
                 $mf->addAttribute("identifier", "feedback_option_{$i}");
                 $mf->addAttribute("showHide", "hide");
                 // parse it as XML
                 $feedback = stringtoxml($this->data["option_{$i}_feedback_unchosen"], "feedback");
                 if (is_array($feedback)) {
                     // errors
                     $this->errors[] = "Feedback is not valid XML. It must not only be valid XML but valid QTI, which accepts a subset of XHTML. Details on specific issues follow:";
                     $this->errors = array_merge($this->errors, $feedback);
                 } else {
                     simplexml_append($mf, $feedback);
                 }
             }
         }
     }
     if (!empty($this->errors)) {
         return false;
     }
     // validate the QTI
     validateQTI($ai, $this->errors, $this->warnings, $this->messages);
     if (!empty($this->errors)) {
         return false;
     }
     return $ai;
 }
Esempio n. 3
0
 public function buildQTI($data = null)
 {
     if (!is_null($data)) {
         $this->data = $data;
     }
     if (empty($this->data)) {
         return false;
     }
     // container element and other metadata
     $ai = $this->initialXML();
     // response declarations
     for ($g = 0; array_key_exists("gap_{$g}_response_0", $this->data); $g++) {
         $rd = $ai->addChild("responseDeclaration");
         $rd->addAttribute("identifier", "RESPONSE_gap_{$g}");
         $rd->addAttribute("cardinality", "single");
         $rd->addAttribute("baseType", "string");
         $m = $rd->addChild("mapping");
         $m->addAttribute("defaultValue", "0");
         for ($r = 0; array_key_exists("gap_{$g}_response_{$r}", $this->data); $r++) {
             $me = $m->addChild("mapEntry");
             $me->addAttribute("mapKey", $this->data["gap_{$g}_response_{$r}"]);
             $me->addAttribute("mappedValue", $this->data["gap_{$g}_response_{$r}_score"]);
         }
     }
     // outcome declaration
     $od = $ai->addChild("outcomeDeclaration");
     $od->addAttribute("identifier", "SCORE");
     $od->addAttribute("cardinality", "single");
     $od->addAttribute("baseType", "float");
     $od->addChild("defaultValue");
     $od->defaultValue->addChild("value", "0");
     // item body
     $ib = $ai->addChild("itemBody");
     // get stimulus and add to the XML tree
     if (isset($this->data["stimulus"]) && !empty($this->data["stimulus"])) {
         $this->data["stimulus"] = wrapindiv($this->data["stimulus"]);
         // parse it as XML
         $stimulus = stringtoxml($this->data["stimulus"], "stimulus");
         if (is_array($stimulus)) {
             // errors
             $this->errors[] = "Stimulus is not valid XML. It must not only be valid XML but valid QTI, which accepts a subset of XHTML. Details on specific issues follow:";
             $this->errors = array_merge($this->errors, $stimulus);
         } else {
             simplexml_append($ib, $stimulus);
         }
     }
     // div with class eqiat-te
     $d = $ib->addChild("div");
     $d->addAttribute("class", "eqiat-te");
     // body text
     $bt = $d->addChild("div");
     $bt->addAttribute("class", "textentrytextbody");
     $text = xmlspecialchars($this->data["textbody"]);
     $text = preg_replace('%\\n\\n+%', "</p><p>", $text);
     $text = preg_replace('%\\n%', "<br/>", $text);
     $text = "<p>" . $text . "</p>";
     $g = 0;
     $start = 0;
     while (($start = strpos($text, "[", $start)) !== false) {
         $start = strpos($text, "[");
         $end = strpos($text, "]", $start);
         // base expected length on the longest answer plus 10%
         $el = 0;
         for ($r = 0; array_key_exists("gap_{$g}_response_{$r}", $this->data); $r++) {
             $el = max($el, strlen($this->data["gap_{$g}_response_{$r}"]));
         }
         $el = ceil($el * 1.1);
         $text = substr($text, 0, $start) . '<textEntryInteraction responseIdentifier="RESPONSE_gap_' . $g++ . '" expectedLength="' . $el . '"/>' . substr($text, $end + 1);
     }
     // parse it as XML
     libxml_use_internal_errors(true);
     $textxml = simplexml_load_string($text);
     if ($textxml === false) {
         $this->errors[] = "Text body did not convert to valid XML";
         foreach (libxml_get_errors() as $error) {
             $this->errors[] = "Text body line " . $error->line . ", column " . $error->column . ": " . $error->message;
         }
         libxml_clear_errors();
     } else {
         simplexml_append($bt, $textxml);
     }
     libxml_use_internal_errors(false);
     // response processing
     $rp = $ai->addChild("responseProcessing");
     // set score = 0
     $sov = $rp->addChild("setOutcomeValue");
     $sov->addAttribute("identifier", "SCORE");
     $sov->addChild("baseValue", "0.0")->addAttribute("baseType", "float");
     for ($g = 0; array_key_exists("gap_{$g}_response_0", $this->data); $g++) {
         $rc = $rp->addChild("responseCondition");
         // if
         $ri = $rc->addChild("responseIf");
         // not null
         $ri->addChild("not")->addChild("isNull")->addChild("variable")->addAttribute("identifier", "RESPONSE_gap_{$g}");
         // increment score
         $sov = $ri->addChild("setOutcomeValue");
         $sov->addAttribute("identifier", "SCORE");
         $s = $sov->addChild("sum");
         $s->addChild("variable")->addAttribute("identifier", "SCORE");
         $s->addChild("mapResponse")->addAttribute("identifier", "RESPONSE_gap_{$g}");
     }
     if (!empty($this->errors)) {
         return false;
     }
     // validate the QTI
     validateQTI($ai, $this->errors, $this->warnings, $this->messages);
     if (!empty($this->errors)) {
         return false;
     }
     $this->qti = $ai;
     return $this->qti;
 }
Esempio n. 4
0
function qti_get_stimulus(SimpleXMLElement $ib)
{
    $stimulus = simplexml_load_string('<stimulus xmlns="http://www.imsglobal.org/xsd/imsqti_v2p1"/>', null);
    foreach ($ib->children() as $child) {
        if (containsQTIInteraction($child)) {
            continue;
        }
        // doesn't contain a QTI interaction so it counts as stimulus
        simplexml_append($stimulus, $child);
    }
    return xml_remove_wrapper_element($stimulus->asXML());
}
Esempio n. 5
0
 public function buildQTI($data = null)
 {
     if (!is_null($data)) {
         $this->data = $data;
     }
     if (empty($this->data)) {
         return false;
     }
     // container element and other metadata
     $ai = $this->initialXML();
     // response declarations
     for ($q = 0; array_key_exists("question_{$q}_prompt", $this->data); $q++) {
         $rd = $ai->addChild("responseDeclaration");
         $rd->addAttribute("identifier", "RESPONSE_question_{$q}");
         $rd->addAttribute("cardinality", "single");
         $rd->addAttribute("baseType", "identifier");
         if (isset($this->data["question_{$q}_answer"])) {
             $rd->addChild("correctResponse")->addChild("value", "question_{$q}_" . $this->data["question_{$q}_answer"]);
         }
     }
     // outcome declaration
     $od = $ai->addChild("outcomeDeclaration");
     $od->addAttribute("identifier", "SCORE");
     $od->addAttribute("cardinality", "single");
     $od->addAttribute("baseType", "integer");
     $od->addChild("defaultValue");
     $od->defaultValue->addChild("value", "0");
     // item body
     $ib = $ai->addChild("itemBody");
     // get stimulus and add to the XML tree
     if (isset($this->data["stimulus"]) && !empty($this->data["stimulus"])) {
         $this->data["stimulus"] = wrapindiv($this->data["stimulus"]);
         // parse it as XML
         $stimulus = stringtoxml($this->data["stimulus"], "stimulus");
         if (is_array($stimulus)) {
             // errors
             $this->errors[] = "Stimulus is not valid XML. It must not only be valid XML but valid QTI, which accepts a subset of XHTML. Details on specific issues follow:";
             $this->errors = array_merge($this->errors, $stimulus);
         } else {
             simplexml_append($ib, $stimulus);
         }
     }
     // div with class eqiat-qm
     $d = $ib->addChild("div");
     $d->addAttribute("class", "eqiat-qm");
     // questions
     for ($q = 0; array_key_exists("question_{$q}_prompt", $this->data); $q++) {
         $ci = $d->addChild("choiceInteraction");
         $ci->addAttribute("maxChoices", "1");
         $ci->addAttribute("shuffle", "false");
         $ci->addAttribute("responseIdentifier", "RESPONSE_question_{$q}");
         $ci->addChild("prompt", $this->data["question_{$q}_prompt"]);
         foreach (array("true", "false") as $o) {
             $sc = $ci->addChild("simpleChoice", $o);
             $sc->addAttribute("identifier", "question_{$q}_{$o}");
         }
     }
     // response processing
     $rp = $ai->addChild("responseProcessing");
     // set score = 0
     $sov = $rp->addChild("setOutcomeValue");
     $sov->addAttribute("identifier", "SCORE");
     $sov->addChild("baseValue", "0")->addAttribute("baseType", "integer");
     for ($q = 0; array_key_exists("question_{$q}_prompt", $this->data); $q++) {
         $rc = $rp->addChild("responseCondition");
         // if
         $ri = $rc->addChild("responseIf");
         // criteria for a correct answer
         $m = $ri->addChild("match");
         $m->addChild("variable")->addAttribute("identifier", "RESPONSE_question_{$q}");
         $m->addChild("correct")->addAttribute("identifier", "RESPONSE_question_{$q}");
         // increment score
         $sov = $ri->addChild("setOutcomeValue");
         $sov->addAttribute("identifier", "SCORE");
         $s = $sov->addChild("sum");
         $s->addChild("variable")->addAttribute("identifier", "SCORE");
         $s->addChild("baseValue", "1")->addAttribute("baseType", "integer");
     }
     if (!empty($this->errors)) {
         return false;
     }
     // validate the QTI
     validateQTI($ai, $this->errors, $this->warnings, $this->messages);
     if (!empty($this->errors)) {
         return false;
     }
     $this->qti = $ai;
     return $this->qti;
 }
 public function buildQTI($data = null)
 {
     if (!is_null($data)) {
         $this->data = $data;
     }
     if (empty($this->data)) {
         return false;
     }
     // container element and other metadata
     $ai = $this->initialXML();
     // response declarations
     for ($q = 0; array_key_exists("question_{$q}_prompt", $this->data); $q++) {
         $rd = $ai->addChild("responseDeclaration");
         $rd->addAttribute("identifier", "RESPONSE_question_{$q}");
         $rd->addAttribute("cardinality", "multiple");
         $rd->addAttribute("baseType", "identifier");
         // build array of correct responses
         $correct = array();
         for ($o = 0; array_key_exists("option_{$o}_optiontext", $this->data); $o++) {
             if (isset($this->data["question_{$q}_option_{$o}_correct"])) {
                 $correct[] = $o;
             }
         }
         // add correctResponse node only if any options are correct
         if (!empty($correct)) {
             $rd->addChild("correctResponse");
             foreach ($correct as $o) {
                 $rd->correctResponse->addChild("value", "question_{$q}_option_{$o}");
             }
         }
     }
     // outcome declaration
     $od = $ai->addChild("outcomeDeclaration");
     $od->addAttribute("identifier", "SCORE");
     $od->addAttribute("cardinality", "single");
     $od->addAttribute("baseType", "integer");
     $od->addChild("defaultValue");
     $od->defaultValue->addChild("value", "0");
     // item body
     $ib = $ai->addChild("itemBody");
     // get stimulus and add to the XML tree
     if (isset($this->data["stimulus"]) && !empty($this->data["stimulus"])) {
         $this->data["stimulus"] = wrapindiv($this->data["stimulus"]);
         // parse it as XML
         $stimulus = stringtoxml($this->data["stimulus"], "stimulus");
         if (is_array($stimulus)) {
             // errors
             $this->errors[] = "Stimulus is not valid XML. It must not only be valid XML but valid QTI, which accepts a subset of XHTML. Details on specific issues follow:";
             $this->errors = array_merge($this->errors, $stimulus);
         } else {
             simplexml_append($ib, $stimulus);
         }
     }
     // div with class eqiat-emi
     $d = $ib->addChild("div");
     $d->addAttribute("class", "eqiat-emi");
     // list the options
     $options = "";
     for ($o = 0; array_key_exists("option_{$o}_optiontext", $this->data); $o++) {
         $options .= "<li>" . xmlspecialchars($this->data["option_{$o}_optiontext"]) . "</li>";
     }
     simplexml_append($d, simplexml_load_string('<ol class="emioptions">' . $options . '</ol>'));
     // questions
     for ($q = 0; array_key_exists("question_{$q}_prompt", $this->data); $q++) {
         $ci = $d->addChild("choiceInteraction");
         $ci->addAttribute("maxChoices", "0");
         $ci->addAttribute("minChoices", "0");
         $ci->addAttribute("shuffle", "false");
         $ci->addAttribute("responseIdentifier", "RESPONSE_question_{$q}");
         $ci->addChild("prompt", $this->data["question_{$q}_prompt"]);
         for ($o = 0; array_key_exists("option_{$o}_optiontext", $this->data); $o++) {
             $sc = $ci->addChild("simpleChoice", chr(ord("A") + $o));
             $sc->addAttribute("identifier", "question_{$q}_option_{$o}");
         }
     }
     // response processing
     $rp = $ai->addChild("responseProcessing");
     // set score = 0
     $sov = $rp->addChild("setOutcomeValue");
     $sov->addAttribute("identifier", "SCORE");
     $sov->addChild("baseValue", "0")->addAttribute("baseType", "integer");
     for ($q = 0; array_key_exists("question_{$q}_prompt", $this->data); $q++) {
         $rc = $rp->addChild("responseCondition");
         // if
         $ri = $rc->addChild("responseIf");
         // build array of correct responses
         $correct = array();
         for ($o = 0; array_key_exists("option_{$o}_optiontext", $this->data); $o++) {
             if (isset($this->data["question_{$q}_option_{$o}_correct"])) {
                 $correct[] = $o;
             }
         }
         // criteria for a correct answer
         if (empty($correct)) {
             // multiple response in which the correct response is to tick no
             // boxes -- check number of responses is equal to zero
             $e = $ri->addChild("equal");
             $e->addAttribute("toleranceMode", "exact");
             $e->addChild("containerSize")->addChild("variable")->addAttribute("identifier", "RESPONSE_question_{$q}");
             $e->addChild("baseValue", "0")->addAttribute("baseType", "integer");
         } else {
             // otherwise, we match responses to the correctResponse above
             $m = $ri->addChild("match");
             $m->addChild("variable")->addAttribute("identifier", "RESPONSE_question_{$q}");
             $m->addChild("correct")->addAttribute("identifier", "RESPONSE_question_{$q}");
         }
         // increment score
         $sov = $ri->addChild("setOutcomeValue");
         $sov->addAttribute("identifier", "SCORE");
         $s = $sov->addChild("sum");
         $s->addChild("variable")->addAttribute("identifier", "SCORE");
         $s->addChild("baseValue", "1")->addAttribute("baseType", "integer");
     }
     if (!empty($this->errors)) {
         return false;
     }
     // validate the QTI
     validateQTI($ai, $this->errors, $this->warnings, $this->messages);
     if (!empty($this->errors)) {
         return false;
     }
     $this->qti = $ai;
     return $this->qti;
 }