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