f1acd3424c856d2502a257bb82a0413bd6165f45
[moodle.git] / question / format / examview / format.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Examview question importer.
19  *
20  * @package    qformat
21  * @subpackage examview
22  * @copyright  2005 Howard Miller
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
27 defined('MOODLE_INTERNAL') || die();
29 require_once($CFG->libdir . '/xmlize.php');
32 /**
33  * Examview question importer.
34  *
35  * @copyright  2005 Howard Miller
36  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37  */
38 class qformat_examview extends qformat_default {
40     public $qtypes = array(
41         'tf' => TRUEFALSE,
42         'mc' => MULTICHOICE,
43         'yn' => TRUEFALSE,
44         'co' => SHORTANSWER,
45         'ma' => MATCH,
46         'mtf' => 99,
47         'nr' => NUMERICAL,
48         'pr' => 99,
49         'es' => ESSAY,
50         'ca' => 99,
51         'ot' => 99,
52         'sa' => SHORTANSWER
53         );
55     public $matching_questions = array();
57     public function provide_import() {
58         return true;
59     }
61     public function mime_type() {
62         return 'application/xml';
63     }
65     /**
66      * unxmlise reconstructs part of the xml data structure in order
67      * to identify the actual data therein
68      * @param array $xml section of the xml data structure
69      * @return string data with evrything else removed
70      */
71     protected function unxmlise( $xml ) {
72         // If it's not an array then it's probably just data.
73         if (!is_array($xml)) {
74             $text = s($xml);
75         } else {
76             // Otherwise parse the array.
77             $text = '';
78             foreach ($xml as $tag => $data) {
79                 // If tag is '@' then it's attributes and we don't care.
80                 if ($tag!=='@') {
81                     $text = $text . $this->unxmlise( $data );
82                 }
83             }
84         }
86         // Currently we throw the tags we found.
87         $text = strip_tags($text);
88         return $text;
89     }
90     protected function text_field($text) {
91         return array(
92             'text' => htmlspecialchars(trim($text), ENT_NOQUOTES),
93             'format' => FORMAT_HTML,
94             'files' => array(),
95         );
96     }
98     protected function add_blank_combined_feedback($question) {
99         $question->correctfeedback['text'] = '';
100         $question->correctfeedback['format'] = $question->questiontextformat;
101         $question->correctfeedback['files'] = array();
102         $question->partiallycorrectfeedback['text'] = '';
103         $question->partiallycorrectfeedback['format'] = $question->questiontextformat;
104         $question->partiallycorrectfeedback['files'] = array();
105         $question->incorrectfeedback['text'] = '';
106         $question->incorrectfeedback['format'] = $question->questiontextformat;
107         $question->incorrectfeedback['files'] = array();
108         return $question;
109     }
111     protected function parse_matching_groups($matching_groups) {
112         if (empty($matching_groups)) {
113             return;
114         }
115         foreach ($matching_groups as $match_group) {
116             $newgroup = new stdClass();
117             $groupname = trim($match_group['@']['name']);
118             $questiontext = $this->unxmlise($match_group['#']['text'][0]['#']);
119             $newgroup->questiontext = trim($questiontext);
120             $newgroup->subchoices = array();
121             $newgroup->subquestions = array();
122             $newgroup->subanswers = array();
123             $choices = $match_group['#']['choices']['0']['#'];
124             foreach ($choices as $key => $value) {
125                 if (strpos(trim($key), 'choice-') !== false) {
126                     $key = strtoupper(trim(str_replace('choice-', '', $key)));
127                     $newgroup->subchoices[$key] = trim($value['0']['#']);
128                 }
129             }
130             $this->matching_questions[$groupname] = $newgroup;
131         }
132     }
134     protected function parse_ma($qrec, $groupname) {
135         $match_group = $this->matching_questions[$groupname];
136         $phrase = trim($this->unxmlise($qrec['text']['0']['#']));
137         $answer = trim($this->unxmlise($qrec['answer']['0']['#']));
138         $answer = strip_tags( $answer );
139         $match_group->subquestions[] = $phrase;
140         $match_group->subanswers[] = $match_group->subchoices[$answer];
141         $this->matching_questions[$groupname] = $match_group;
142         return null;
143     }
145     protected function process_matches(&$questions) {
146         if (empty($this->matching_questions)) {
147             return;
148         }
149         foreach ($this->matching_questions as $match_group) {
150             $question = $this->defaultquestion();
151             $htmltext = s($match_group->questiontext);
152             $question->questiontext = $htmltext;
153             $question->questiontextformat = FORMAT_HTML;
154             $question->questiontextfiles = array();
155             $question->name = shorten_text( $question->questiontext, 250 );
156             $question->qtype = MATCH;
157             $question = $this->add_blank_combined_feedback($question);
158             $question->subquestions = array();
159             $question->subanswers = array();
160             foreach ($match_group->subquestions as $key => $value) {
161                 $htmltext = s($value);
162                 $question->subquestions[] = $this->text_field($htmltext);
164                 $htmltext = s($match_group->subanswers[$key]);
165                 $question->subanswers[] = $htmltext;
166             }
167             $questions[] = $question;
168         }
169     }
171     protected function cleanunicode($text) {
172         return str_replace('&#x2019;', "'", $text);
173     }
175     protected function readquestions($lines) {
176         // Parses an array of lines into an array of questions,
177         // where each item is a question object as defined by
178         // readquestion().
180         $questions = array();
181         $currentquestion = array();
183         $text = implode($lines, ' ');
184         $text = $this->cleanunicode($text);
186         $xml = xmlize($text, 0);
187         if (!empty($xml['examview']['#']['matching-group'])) {
188             $this->parse_matching_groups($xml['examview']['#']['matching-group']);
189         }
191         $questionnode = $xml['examview']['#']['question'];
192         foreach ($questionnode as $currentquestion) {
193             if ($question = $this->readquestion($currentquestion)) {
194                 $questions[] = $question;
195             }
196         }
198         $this->process_matches($questions);
199         return $questions;
200     }
202     public function readquestion($qrec) {
204         $type = trim($qrec['@']['type']);
205         $question = $this->defaultquestion();
206         if (array_key_exists($type, $this->qtypes)) {
207             $question->qtype = $this->qtypes[$type];
208         } else {
209             $question->qtype = null;
210         }
211         $question->single = 1;
212         // Only one answer is allowed.
213         $htmltext = $this->unxmlise($qrec['#']['text'][0]['#']);
214         $question->questiontext = $htmltext;
215         $question->questiontextformat = FORMAT_HTML;
216         $question->questiontextfiles = array();
217         $question->name = shorten_text( $question->questiontext, 250 );
219         switch ($question->qtype) {
220             case MULTICHOICE:
221                 $question = $this->parse_mc($qrec['#'], $question);
222                 break;
223             case MATCH:
224                 $groupname = trim($qrec['@']['group']);
225                 $question = $this->parse_ma($qrec['#'], $groupname);
226                 break;
227             case TRUEFALSE:
228                 $question = $this->parse_tf_yn($qrec['#'], $question);
229                 break;
230             case SHORTANSWER:
231                 $question = $this->parse_co($qrec['#'], $question);
232                 break;
233             case ESSAY:
234                 $question = $this->parse_es($qrec['#'], $question);
235                 break;
236             case NUMERICAL:
237                 $question = $this->parse_nr($qrec['#'], $question);
238                 break;
239                 break;
240             default:
241                 print("<p>Question type ".$type." import not supported for ".$question->questiontext."<p>");
242                 $question = null;
243         }
245         return $question;
246     }
248     protected function parse_tf_yn($qrec, $question) {
249         $choices = array('T' => 1, 'Y' => 1, 'F' => 0, 'N' => 0 );
250         $answer = trim($qrec['answer'][0]['#']);
251         $question->answer = $choices[$answer];
252         $question->correctanswer = $question->answer;
253         if ($question->answer == 1) {
254             $question->feedbacktrue = $this->text_field('Correct');
255             $question->feedbackfalse = $this->text_field('Incorrect');
256         } else {
257             $question->feedbacktrue = $this->text_field('Incorrect');
258             $question->feedbackfalse = $this->text_field('Correct');
259         }
260         return $question;
261     }
263     protected function parse_mc($qrec, $question) {
264         $question = $this->add_blank_combined_feedback($question);
265         $answer = 'choice-'.strtolower(trim($qrec['answer'][0]['#']));
267         $choices = $qrec['choices'][0]['#'];
268         foreach ($choices as $key => $value) {
269             if (strpos(trim($key), 'choice-') !== false) {
271                 $question->answer[$key] = $this->text_field(s($this->unxmlise($value[0]['#'])));
272                 if (strcmp($key, $answer) == 0) {
273                     $question->fraction[$key] = 1;
274                     $question->feedback[$key] = $this->text_field('Correct');
275                 } else {
276                     $question->fraction[$key] = 0;
277                     $question->feedback[$key] = $this->text_field('Incorrect');
278                 }
279             }
280         }
281         return $question;
282     }
284     protected function parse_co($qrec, $question) {
285         $question->usecase = 0;
286         $answer = trim($this->unxmlise($qrec['answer'][0]['#']));
287         $answer = strip_tags( $answer );
288         $answers = explode("\n", $answer);
290         foreach ($answers as $key => $value) {
291             $value = trim($value);
292             if (strlen($value) > 0) {
293                 $question->answer[$key] = $value;
294                 $question->fraction[$key] = 1;
295                 $question->feedback[$key] = $this->text_field("Correct");
296             }
297         }
298         return $question;
299     }
301     protected function parse_es($qrec, $question) {
302         $feedback = trim($this->unxmlise($qrec['answer'][0]['#']));
303         $question->graderinfo =  $this->text_field($feedback);
304         $question->feedback = $feedback;
305         $question->responseformat = 'editor';
306         $question->responsefieldlines = 15;
307         $question->attachments = 0;
308         $question->fraction = 0;
309         return $question;
310     }
312     protected function parse_nr($qrec, $question) {
313         $answer = trim($this->unxmlise($qrec['answer'][0]['#']));
314         $answer = strip_tags( $answer );
315         $answers = explode("\n", $answer);
317         foreach ($answers as $key => $value) {
318             $value = trim($value);
319             if (is_numeric($value)) {
320                 $errormargin = 0;
321                 $question->answer[$key] = $value;
322                 $question->fraction[$key] = 1;
323                 $question->feedback[$key] = $this->text_field("Correct");
324                 $question->tolerance[$key] = $errormargin;
325             }
326         }
327         return $question;
328     }
331 // End class.