Added ability to specify destination category within GIFT file.
[moodle.git] / question / format / xml / format.php
CommitLineData
84769fd8 1<?php // $Id$
2//
3///////////////////////////////////////////////////////////////
4// XML import/export
5//
6//////////////////////////////////////////////////////////////////////////
7// Based on default.php, included by ../import.php
8
84769fd8 9require_once( "$CFG->libdir/xmlize.php" );
10
f5565b69 11class qformat_xml extends qformat_default {
84769fd8 12
13 function provide_import() {
14 return true;
15 }
16
17 function provide_export() {
18 return true;
19 }
20
21 // IMPORT FUNCTIONS START HERE
22
6e557c08 23 /**
c81415c7 24 * Translate human readable format name
25 * into internal Moodle code number
6e557c08 26 * @param string name format name from xml file
27 * @return int Moodle format code
c81415c7 28 */
84769fd8 29 function trans_format( $name ) {
84769fd8 30 $name = trim($name);
31
32 if ($name=='moodle_auto_format') {
33 $id = 0;
34 }
35 elseif ($name=='html') {
36 $id = 1;
37 }
38 elseif ($name=='plain_text') {
39 $id = 2;
40 }
41 elseif ($name=='wiki_like') {
42 $id = 3;
43 }
44 elseif ($name=='markdown') {
45 $id = 4;
46 }
47 else {
48 $id = 0; // or maybe warning required
49 }
50 return $id;
51 }
52
6e557c08 53 /**
c81415c7 54 * Translate human readable single answer option
55 * to internal code number
6e557c08 56 * @param string name true/false
57 * @return int internal code number
c81415c7 58 */
84769fd8 59 function trans_single( $name ) {
2da44816 60 $name = trim($name);
61 if ($name == "false" || !$name) {
62 return 0;
63 } else {
64 return 1;
65 }
84769fd8 66 }
67
6e557c08 68 /**
c81415c7 69 * process text string from xml file
6e557c08 70 * @param array $text bit of xml tree after ['text']
71 * @return string processed text
c81415c7 72 */
84769fd8 73 function import_text( $text ) {
84769fd8 74 $data = $text[0]['#'];
75 $data = html_entity_decode( $data );
76 return addslashes(trim( $data ));
77 }
78
307f045f 79 /**
80 * Process text from an element in the XML that may or not be there.
81 * @param string $subelement the name of the element which is either present or missing.
82 * @param array $question a bit of xml tree, this method looks for $question['#'][$subelement][0]['#']['text'].
83 * @return string If $subelement is present, return the content of the text tag inside it.
84 * Otherwise returns an empty string.
85 */
86 function import_optional_text($subelement, $question) {
87 if (array_key_exists($subelement, $question['#'])) {
88 return $this->import_text($question['#'][$subelement][0]['#']['text']);
89 } else {
90 return '';
91 }
92 }
93
6e557c08 94 /**
c81415c7 95 * import parts of question common to all types
6e557c08 96 * @param array question question array from xml tree
97 * @return object question object
c81415c7 98 */
84769fd8 99 function import_headers( $question ) {
84769fd8 100 // this routine initialises the question object
101 $name = $this->import_text( $question['#']['name'][0]['#']['text'] );
102 $qtext = $this->import_text( $question['#']['questiontext'][0]['#']['text'] );
103 $qformat = $question['#']['questiontext'][0]['@']['format'];
104 $image = $question['#']['image'][0]['#'];
d08e16b2 105 if (!empty($question['#']['image_base64'][0]['#'])) {
106 $image_base64 = stripslashes( trim( $question['#']['image_base64'][0]['#'] ) );
107 $image = $this->importimagefile( $image, $image_base64 );
108 }
a4514d91 109 if (array_key_exists('generalfeedback', $question['#'])) {
110 $generalfeedback = $this->import_text( $question['#']['generalfeedback'][0]['#']['text'] );
1b8a7434 111 } else {
a4514d91 112 $generalfeedback = '';
1b8a7434 113 }
c81415c7 114 if (!empty($question['#']['defaultgrade'][0]['#'])) {
115 $qo->defaultgrade = $question['#']['defaultgrade'][0]['#'];
116 }
84769fd8 117 $penalty = $question['#']['penalty'][0]['#'];
118
51bcdf28 119 $qo = $this->defaultquestion();
84769fd8 120 $qo->name = $name;
121 $qo->questiontext = $qtext;
122 $qo->questiontextformat = $this->trans_format( $qformat );
123 $qo->image = ((!empty($image)) ? $image : '');
a4514d91 124 $qo->generalfeedback = $generalfeedback;
84769fd8 125 $qo->penalty = $penalty;
126
127 return $qo;
128 }
129
6e557c08 130 /**
c81415c7 131 * import the common parts of a single answer
6e557c08 132 * @param array answer xml tree for single answer
133 * @return object answer object
c81415c7 134 */
84769fd8 135 function import_answer( $answer ) {
84769fd8 136 $fraction = $answer['@']['fraction'];
137 $text = $this->import_text( $answer['#']['text']);
138 $feedback = $this->import_text( $answer['#']['feedback'][0]['#']['text'] );
139
140 $ans = null;
141 $ans->answer = $text;
142 $ans->fraction = $fraction / 100;
143 $ans->feedback = $feedback;
144
145 return $ans;
146 }
147
6e557c08 148 /**
c81415c7 149 * import multiple choice question
6e557c08 150 * @param array question question array from xml tree
151 * @return object question object
c81415c7 152 */
84769fd8 153 function import_multichoice( $question ) {
84769fd8 154 // get common parts
155 $qo = $this->import_headers( $question );
156
157 // 'header' parts particular to multichoice
158 $qo->qtype = MULTICHOICE;
159 $single = $question['#']['single'][0]['#'];
160 $qo->single = $this->trans_single( $single );
307f045f 161 if (array_key_exists('shuffleanswers', $question['#'])) {
162 $shuffleanswers = $question['#']['shuffleanswers'][0]['#'];
163 } else {
164 $shuffleanswers = 'false';
165 }
2da44816 166 $qo->shuffleanswers = $this->trans_single($shuffleanswers);
307f045f 167 $qo->correctfeedback = $this->import_optional_text('correctfeedback', $question);
168 $qo->partiallycorrectfeedback = $this->import_optional_text('partiallycorrectfeedback', $question);
169 $qo->incorrectfeedback = $this->import_optional_text('incorrectfeedback', $question);
170
84769fd8 171 // run through the answers
172 $answers = $question['#']['answer'];
173 $a_count = 0;
174 foreach ($answers as $answer) {
175 $ans = $this->import_answer( $answer );
176 $qo->answer[$a_count] = $ans->answer;
177 $qo->fraction[$a_count] = $ans->fraction;
178 $qo->feedback[$a_count] = $ans->feedback;
179 ++$a_count;
180 }
181
182 return $qo;
183 }
184
6e557c08 185 /**
c81415c7 186 * import cloze type question
6e557c08 187 * @param array question question array from xml tree
188 * @return object question object
c81415c7 189 */
7b8bc256 190 function import_multianswer( $questions ) {
7b8bc256 191 $qo = qtype_multianswer_extract_question($this->import_text(
192 $questions['#']['questiontext'][0]['#']['text'] ));
193
194 // 'header' parts particular to multianswer
195 $qo->qtype = MULTIANSWER;
196 $qo->course = $this->course;
197
71ffbac2 198 if (!empty($questions)) {
7b8bc256 199 $qo->name = $this->import_text( $questions['#']['name'][0]['#']['text'] );
200 }
201
202 return $qo;
203 }
204
6e557c08 205 /**
c81415c7 206 * import true/false type question
6e557c08 207 * @param array question question array from xml tree
208 * @return object question object
c81415c7 209 */
84769fd8 210 function import_truefalse( $question ) {
84769fd8 211 // get common parts
212 $qo = $this->import_headers( $question );
213
214 // 'header' parts particular to true/false
215 $qo->qtype = TRUEFALSE;
216
217 // get answer info
218 $answers = $question['#']['answer'];
219 $fraction0 = $answers[0]['@']['fraction'];
220 $feedback0 = $this->import_text($answers[0]['#']['feedback'][0]['#']['text']);
221 $fraction1 = $answers[1]['@']['fraction'];
222 $feedback1 = $this->import_text($answers[1]['#']['feedback'][0]['#']['text']);
223
224 // sort out which is true and build object accordingly
225 if ($fraction0==100) { // then 0 index is true
226 $qo->answer = 1;
227 $qo->feedbacktrue=$feedback0;
228 $qo->feedbackfalse=$feedback1;
229 }
230 else {
231 $qo->answer = 0;
232 $qo->feedbacktrue = $feedback1;
233 $qo->feedbackfalse = $feedback0;
234 }
235
236 return $qo;
237 }
238
6e557c08 239 /**
c81415c7 240 * import short answer type question
6e557c08 241 * @param array question question array from xml tree
242 * @return object question object
c81415c7 243 */
84769fd8 244 function import_shortanswer( $question ) {
84769fd8 245 // get common parts
246 $qo = $this->import_headers( $question );
247
248 // header parts particular to shortanswer
249 $qo->qtype = SHORTANSWER;
250
251 // get usecase
252 $qo->usecase = $question['#']['usecase'][0]['#'];
253
254 // run through the answers
255 $answers = $question['#']['answer'];
256 $a_count = 0;
257 foreach ($answers as $answer) {
258 $ans = $this->import_answer( $answer );
259 $qo->answer[$a_count] = $ans->answer;
260 $qo->fraction[$a_count] = $ans->fraction;
261 $qo->feedback[$a_count] = $ans->feedback;
262 ++$a_count;
263 }
264
265 return $qo;
266 }
7b8bc256 267
6e557c08 268 /**
c81415c7 269 * import regexp type question
6e557c08 270 * @param array question question array from xml tree
271 * @return object question object
c81415c7 272 */
7b8bc256 273 function import_regexp( $question ) {
7b8bc256 274 // get common parts
275 $qo = $this->import_headers( $question );
276
277 // header parts particular to shortanswer
278 $qo->qtype = regexp;
279
280 // get usecase
281 $qo->usecase = $question['#']['usecase'][0]['#'];
282
283 // run through the answers
284 $answers = $question['#']['answer'];
285 $a_count = 0;
286 foreach ($answers as $answer) {
287 $ans = $this->import_answer( $answer );
288 $qo->answer[$a_count] = $ans->answer;
289 $qo->fraction[$a_count] = $ans->fraction;
290 $qo->feedback[$a_count] = $ans->feedback;
291 ++$a_count;
292 }
84769fd8 293
7b8bc256 294 return $qo;
295 }
296
6e557c08 297 /**
c81415c7 298 * import description type question
6e557c08 299 * @param array question question array from xml tree
300 * @return object question object
c81415c7 301 */
7b8bc256 302 function import_description( $question ) {
303 // get common parts
304 $qo = $this->import_headers( $question );
305 // header parts particular to shortanswer
306 $qo->qtype = DESCRIPTION;
307 return $qo;
308 }
84769fd8 309
6e557c08 310 /**
c81415c7 311 * import numerical type question
6e557c08 312 * @param array question question array from xml tree
313 * @return object question object
c81415c7 314 */
315 function import_numerical( $question ) {
84769fd8 316 // get common parts
317 $qo = $this->import_headers( $question );
318
319 // header parts particular to numerical
320 $qo->qtype = NUMERICAL;
321
322 // get answers array
323 $answers = $question['#']['answer'];
324 $qo->answer = array();
325 $qo->feedback = array();
326 $qo->fraction = array();
327 $qo->tolerance = array();
328 foreach ($answers as $answer) {
329 $qo->answer[] = $answer['#'][0];
a0d187bf 330 $qo->feedback[] = $this->import_text( $answer['#']['feedback'][0]['#']['text'] );
84769fd8 331 $qo->fraction[] = $answer['#']['fraction'][0]['#'];
332 $qo->tolerance[] = $answer['#']['tolerance'][0]['#'];
333 }
334
335 // get units array
84769fd8 336 $qo->unit = array();
a0d187bf 337 if (isset($question['#']['units'][0]['#']['unit'])) {
338 $units = $question['#']['units'][0]['#']['unit'];
339 $qo->multiplier = array();
340 foreach ($units as $unit) {
341 $qo->multiplier[] = $unit['#']['multiplier'][0]['#'];
342 $qo->unit[] = $unit['#']['unit_name'][0]['#'];
343 }
84769fd8 344 }
84769fd8 345 return $qo;
346 }
347
6e557c08 348 /**
c81415c7 349 * import matching type question
6e557c08 350 * @param array question question array from xml tree
351 * @return object question object
c81415c7 352 */
51bcdf28 353 function import_matching( $question ) {
51bcdf28 354 // get common parts
355 $qo = $this->import_headers( $question );
356
357 // header parts particular to matching
358 $qo->qtype = MATCH;
ee800653 359 if (!empty($question['#']['shuffleanswers'])) {
360 $qo->shuffleanswers = $question['#']['shuffleanswers'][0]['#'];
361 } else {
362 $qo->shuffleanswers = false;
363 }
51bcdf28 364
365 // get subquestions
366 $subquestions = $question['#']['subquestion'];
367 $qo->subquestions = array();
368 $qo->subanswers = array();
369
370 // run through subquestions
371 foreach ($subquestions as $subquestion) {
a0d187bf 372 $qtext = $this->import_text( $subquestion['#']['text'] );
373 $atext = $this->import_text( $subquestion['#']['answer'][0]['#']['text'] );
51bcdf28 374 $qo->subquestions[] = $qtext;
375 $qo->subanswers[] = $atext;
376 }
51bcdf28 377 return $qo;
378 }
379
6e557c08 380 /**
c81415c7 381 * import essay type question
6e557c08 382 * @param array question question array from xml tree
383 * @return object question object
c81415c7 384 */
385 function import_essay( $question ) {
386 // get common parts
387 $qo = $this->import_headers( $question );
388
389 // header parts particular to essay
390 $qo->qtype = ESSAY;
391
392 // get feedback
393 $qo->feedback = $this->import_text( $question['#']['answer'][0]['#']['feedback'][0]['#']['text'] );
394
395 // get fraction
396 $qo->fraction = $question['#']['answer'][0]['#']['fraction'][0]['#'];
397
398 return $qo;
399 }
84769fd8 400
c81415c7 401 /**
402 * parse the array of lines into an array of questions
403 * this *could* burn memory - but it won't happen that much
404 * so fingers crossed!
6e557c08 405 * @param array lines array of lines from the input file
406 * @return array (of objects) question objects
c81415c7 407 */
408 function readquestions($lines) {
84769fd8 409 // we just need it as one big string
410 $text = implode($lines, " ");
411 unset( $lines );
412
413 // this converts xml to big nasty data structure
414 // the 0 means keep white space as it is (important for markdown format)
415 // print_r it if you want to see what it looks like!
416 $xml = xmlize( $text, 0 );
417
418 // set up array to hold all our questions
419 $questions = array();
420
421 // iterate through questions
422 foreach ($xml['quiz']['#']['question'] as $question) {
423 $question_type = $question['@']['type'];
424 $questiontype = get_string( 'questiontype','quiz',$question_type );
84769fd8 425
426 if ($question_type=='multichoice') {
427 $qo = $this->import_multichoice( $question );
428 }
429 elseif ($question_type=='truefalse') {
430 $qo = $this->import_truefalse( $question );
431 }
432 elseif ($question_type=='shortanswer') {
433 $qo = $this->import_shortanswer( $question );
434 }
7b8bc256 435 //elseif ($question_type=='regexp') {
436 // $qo = $this->import_regexp( $question );
437 //}
84769fd8 438 elseif ($question_type=='numerical') {
439 $qo = $this->import_numerical( $question );
440 }
7b8bc256 441 elseif ($question_type=='description') {
442 $qo = $this->import_description( $question );
443 }
51bcdf28 444 elseif ($question_type=='matching') {
445 $qo = $this->import_matching( $question );
446 }
7b8bc256 447 elseif ($question_type=='cloze') {
c81415c7 448 $qo = $this->import_multianswer( $question );
449 }
450 elseif ($question_type=='essay') {
451 $qo = $this->import_essay( $question );
7b8bc256 452 }
84769fd8 453 else {
ee800653 454 $notsupported = get_string( 'xmltypeunsupported','quiz',$question_type );
84769fd8 455 echo "<p>$notsupported</p>";
456 $qo = null;
457 }
458
459 // stick the result in the $questions array
460 if ($qo) {
461 $questions[] = $qo;
462 }
463 }
464
465 return $questions;
466 }
467
468 // EXPORT FUNCTIONS START HERE
469
84769fd8 470 function export_file_extension() {
471 // override default type so extension is .xml
472
473 return ".xml";
474 }
475
c81415c7 476
477 /**
478 * Turn the internal question code into a human readable form
479 * (The code used to be numeric, but this remains as some of
480 * the names don't match the new internal format)
6e557c08 481 * @param mixed type_id Internal code
482 * @return string question type string
c81415c7 483 */
84769fd8 484 function get_qtype( $type_id ) {
84769fd8 485 switch( $type_id ) {
486 case TRUEFALSE:
487 $name = 'truefalse';
488 break;
489 case MULTICHOICE:
490 $name = 'multichoice';
491 break;
492 case SHORTANSWER:
493 $name = 'shortanswer';
494 break;
7b8bc256 495 //case regexp:
496 // $name = 'regexp';
497 // break;
84769fd8 498 case NUMERICAL:
499 $name = 'numerical';
500 break;
501 case MATCH:
502 $name = 'matching';
503 break;
504 case DESCRIPTION:
505 $name = 'description';
506 break;
507 case MULTIANSWER:
508 $name = 'cloze';
509 break;
c81415c7 510 case ESSAY:
511 $name = 'essay';
512 break;
84769fd8 513 default:
514 $name = 'unknown';
515 }
516 return $name;
517 }
518
6e557c08 519 /**
c81415c7 520 * Convert internal Moodle text format code into
521 * human readable form
6e557c08 522 * @param int id internal code
523 * @return string format text
c81415c7 524 */
84769fd8 525 function get_format( $id ) {
84769fd8 526 switch( $id ) {
527 case 0:
528 $name = "moodle_auto_format";
529 break;
530 case 1:
531 $name = "html";
532 break;
533 case 2:
534 $name = "plain_text";
535 break;
536 case 3:
537 $name = "wiki_like";
538 break;
539 case 4:
540 $name = "markdown";
541 break;
542 default:
543 $name = "unknown";
544 }
545 return $name;
546 }
547
6e557c08 548 /**
c81415c7 549 * Convert internal single question code into
550 * human readable form
6e557c08 551 * @param int id single question code
552 * @return string single question string
c81415c7 553 */
84769fd8 554 function get_single( $id ) {
84769fd8 555 switch( $id ) {
556 case 0:
557 $name = "false";
558 break;
559 case 1:
560 $name = "true";
561 break;
562 default:
563 $name = "unknown";
564 }
565 return $name;
566 }
567
6e557c08 568 /**
c81415c7 569 * generates <text></text> tags, processing raw text therein
6e557c08 570 * @param int ilev the current indent level
571 * @param boolean short stick it on one line
572 * @return string formatted text
c81415c7 573 */
84769fd8 574 function writetext( $raw, $ilev=0, $short=true) {
84769fd8 575 $indent = str_repeat( " ",$ilev );
576
577 // encode the text to 'disguise' HTML content
578 $raw = htmlspecialchars( $raw );
579
580 if ($short) {
581 $xml = "$indent<text>$raw</text>\n";
582 }
583 else {
584 $xml = "$indent<text>\n$raw\n$indent</text>\n";
585 }
586
587 return $xml;
588 }
589
590 function presave_process( $content ) {
591 // override method to allow us to add xml headers and footers
592
593 $content = "<?xml version=\"1.0\"?>\n" .
594 "<quiz>\n" .
595 $content . "\n" .
596 "</quiz>";
597
598 return $content;
599 }
600
6e557c08 601 /**
c81415c7 602 * Include an image encoded in base 64
6e557c08 603 * @param string imagepath The location of the image file
604 * @return string xml code segment
c81415c7 605 */
d08e16b2 606 function writeimage( $imagepath ) {
d08e16b2 607 global $CFG;
608
609 if (empty($imagepath)) {
610 return '';
611 }
612
613 $courseid = $this->course->id;
614 if (!$binary = file_get_contents( "{$CFG->dataroot}/$courseid/$imagepath" )) {
615 return '';
616 }
617
618 $content = " <image_base64>\n".addslashes(base64_encode( $binary ))."\n".
619 "\n </image_base64>\n";
620 return $content;
621 }
622
6e557c08 623 /**
c81415c7 624 * Turns question into an xml segment
6e557c08 625 * @param array question question array
626 * @return string xml segment
c81415c7 627 */
84769fd8 628 function writequestion( $question ) {
84769fd8 629 // initial string;
630 $expout = "";
631
632 // add comment
633 $expout .= "\n\n<!-- question: $question->id -->\n";
634
635 // add opening tag
7b8bc256 636 // generates specific header for Cloze type question
637 if ($question->qtype != MULTIANSWER) {
638 // for all question types except Close
639 $question_type = $this->get_qtype( $question->qtype );
640 $name_text = $this->writetext( $question->name );
641 $qtformat = $this->get_format($question->questiontextformat);
642 $question_text = $this->writetext( $question->questiontext );
a4514d91 643 $generalfeedback = $this->writetext( $question->generalfeedback );
7b8bc256 644 $expout .= " <question type=\"$question_type\">\n";
645 $expout .= " <name>$name_text</name>\n";
646 $expout .= " <questiontext format=\"$qtformat\">\n";
647 $expout .= $question_text;
648 $expout .= " </questiontext>\n";
649 $expout .= " <image>{$question->image}</image>\n";
650 $expout .= $this->writeimage($question->image);
a4514d91 651 $expout .= " <generalfeedback>\n";
652 $expout .= $generalfeedback;
653 $expout .= " </generalfeedback>\n";
c81415c7 654 $expout .= " <defaultgrade>{$question->defaultgrade}</defaultgrade>\n";
7b8bc256 655 $expout .= " <penalty>{$question->penalty}</penalty>\n";
656 $expout .= " <hidden>{$question->hidden}</hidden>\n";
657 }
658 else {
659 // for Cloze type only
660 $question_type = $this->get_qtype( $question->qtype );
661 $name_text = $this->writetext( $question->name );
662 $question_text = $this->writetext( $question->questiontext );
663 $expout .= " <question type=\"$question_type\">\n";
664 $expout .= " <name>$name_text</name>\n";
665 $expout .= " <questiontext>\n";
666 $expout .= $question_text;
667 $expout .= " </questiontext>\n";
668 }
669
d08e16b2 670 if (!empty($question->options->shuffleanswers)) {
671 $expout .= " <shuffleanswers>{$question->options->shuffleanswers}</shuffleanswers>\n";
672 }
673 else {
674 $expout .= " <shuffleanswers>0</shuffleanswers>\n";
675 }
84769fd8 676
677 // output depends on question type
678 switch($question->qtype) {
679 case TRUEFALSE:
36e2232e 680 foreach ($question->options->answers as $answer) {
681 $fraction_pc = round( $answer->fraction * 100 );
682 $expout .= " <answer fraction=\"$fraction_pc\">\n";
683 $expout .= $this->writetext(strtolower($answer->answer),3)."\n";
684 $expout .= " <feedback>\n";
685 $expout .= $this->writetext( $answer->feedback,4,false );
686 $expout .= " </feedback>\n";
687 $expout .= " </answer>\n";
688 }
84769fd8 689 break;
690 case MULTICHOICE:
691 $expout .= " <single>".$this->get_single($question->options->single)."</single>\n";
307f045f 692 $expout .= " <shuffleanswers>".$this->get_single($question->options->shuffleanswers)."</shuffleanswers>\n";
693 $expout .= " <correctfeedback>".$this->writetext($question->options->correctfeedback, 3)."</correctfeedback>\n";
694 $expout .= " <partiallycorrectfeedback>".$this->writetext($question->options->partiallycorrectfeedback, 3)."</partiallycorrectfeedback>\n";
695 $expout .= " <incorrectfeedback>".$this->writetext($question->options->incorrectfeedback, 3)."</incorrectfeedback>\n";
84769fd8 696 foreach($question->options->answers as $answer) {
697 $percent = $answer->fraction * 100;
698 $expout .= " <answer fraction=\"$percent\">\n";
699 $expout .= $this->writetext( $answer->answer,4,false );
700 $expout .= " <feedback>\n";
6e557c08 701 $expout .= $this->writetext( $answer->feedback,5,false );
84769fd8 702 $expout .= " </feedback>\n";
703 $expout .= " </answer>\n";
704 }
705 break;
706 case SHORTANSWER:
707 $expout .= " <usecase>{$question->options->usecase}</usecase>\n ";
708 foreach($question->options->answers as $answer) {
709 $percent = 100 * $answer->fraction;
710 $expout .= " <answer fraction=\"$percent\">\n";
711 $expout .= $this->writetext( $answer->answer,3,false );
712 $expout .= " <feedback>\n";
713 $expout .= $this->writetext( $answer->feedback,4,false );
714 $expout .= " </feedback>\n";
715 $expout .= " </answer>\n";
716 }
717 break;
7b8bc256 718 //case regexp:
719 //$expout .= " <usecase>{$question->options->usecase}</usecase>\n ";
720 // foreach($question->options->answers as $answer) {
721 // $percent = 100 * $answer->fraction;
722 // $expout .= " <answer fraction=\"$percent\">\n";
723 // $expout .= $this->writetext( $answer->answer,3,false );
724 // $expout .= " <feedback>\n";
725 // $expout .= $this->writetext( $answer->feedback,4,false );
726 // $expout .= " </feedback>\n";
727 // $expout .= " </answer>\n";
728 // }
729 // break;
84769fd8 730 case NUMERICAL:
731 foreach ($question->options->answers as $answer) {
732 $tolerance = $answer->tolerance;
733 $expout .= "<answer>\n";
734 $expout .= " {$answer->answer}\n";
735 $expout .= " <tolerance>$tolerance</tolerance>\n";
736 $expout .= " <feedback>".$this->writetext( $answer->feedback )."</feedback>\n";
737 $expout .= " <fraction>{$answer->fraction}</fraction>\n";
738 $expout .= "</answer>\n";
739 }
740
741 $units = $question->options->units;
742 if (count($units)) {
743 $expout .= "<units>\n";
744 foreach ($units as $unit) {
745 $expout .= " <unit>\n";
746 $expout .= " <multiplier>{$unit->multiplier}</multiplier>\n";
747 $expout .= " <unit_name>{$unit->unit}</unit_name>\n";
748 $expout .= " </unit>\n";
749 }
750 $expout .= "</units>\n";
751 }
752 break;
753 case MATCH:
754 foreach($question->options->subquestions as $subquestion) {
755 $expout .= "<subquestion>\n";
756 $expout .= $this->writetext( $subquestion->questiontext );
757 $expout .= "<answer>".$this->writetext( $subquestion->answertext )."</answer>\n";
758 $expout .= "</subquestion>\n";
759 }
760 break;
761 case DESCRIPTION:
c81415c7 762 // nothing more to do for this type
84769fd8 763 break;
764 case MULTIANSWER:
7b8bc256 765 $a_count=1;
766 foreach($question->options->questions as $question) {
767 $thispattern = addslashes("{#".$a_count."}");
768 $thisreplace = $question->questiontext;
769 $expout=ereg_replace($thispattern, $thisreplace, $expout );
770 $a_count++;
771 }
772 break;
c81415c7 773 case ESSAY:
774 foreach ($question->options->answers as $answer) {
775 $expout .= "<answer>\n";
776 $expout .= " <feedback>".$this->writetext( $answer->feedback )."</feedback>\n";
777 $expout .= " <fraction>{$answer->fraction}</fraction>\n";
778 $expout .= "</answer>\n";
779 }
780
781 break;
84769fd8 782 default:
783 $expout .= "<!-- Question type is unknown or not supported (Type=$question->qtype) -->\n";
784 }
785
786 // close the question tag
787 $expout .= "</question>\n";
788
84769fd8 789 return $expout;
790 }
791}
792
793?>