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