]> git.mjollnir.org Git - moodle.git/commitdiff
Whoops got rid of xml stuff - sorry my mistake
authorthepurpleblob <thepurpleblob>
Fri, 6 Aug 2004 15:06:17 +0000 (15:06 +0000)
committerthepurpleblob <thepurpleblob>
Fri, 6 Aug 2004 15:06:17 +0000 (15:06 +0000)
mod/quiz/format/gift/format.php

index f9aad38fd4d92b238c1602035f4945aba4b3f95f..a5e2accf5f134827a070e2f8c4568f6eb2c405fb 100755 (executable)
 <?php // $Id$
 //
 ///////////////////////////////////////////////////////////////
-// XML import/export
+// The GIFT import filter was designed as an easy to use method 
+// for teachers writing questions as a text file. It supports most
+// question types and the missing word format.
 //
+// Multiple Choice / Missing Word
+//     Who's buried in Grant's tomb?{~Grant ~Jefferson =no one}
+//     Grant is {~buried =entombed ~living} in Grant's tomb.
+// True-False:
+//     Grant is buried in Grant's tomb.{FALSE}
+// Short-Answer.
+//     Who's buried in Grant's tomb?{=no one =nobody}
+// Numerical
+//     When was Ulysses S. Grant born?{#1822:5}
+// Matching
+//     Match the following countries with their corresponding
+//     capitals.{=Canada->Ottawa =Italy->Rome =Japan->Tokyo}
+//
+// Comment lines start with a double backslash (//). 
+// Optional question names are enclosed in double colon(::). 
+// Answer feedback is indicated with hash mark (#).
+// Percentage answer weights immediately follow the tilde (for
+// multiple choice) or equal sign (for short answer and numerical),
+// and are enclosed in percent signs (% %). See docs and examples.txt for more.
+// 
+// This filter was written through the collaboration of numerous 
+// members of the Moodle community. It was originally based on 
+// the missingword format, which included code from Thomas Robb
+// and others. Paul Tsuchido Shew wrote this filter in December 2003.
 //////////////////////////////////////////////////////////////////////////
 // Based on default.php, included by ../import.php
 
 class quiz_file_format extends quiz_default_format {
 
-function indent_xhtml($source, $indenter = ' ') { 
-    // xml tidier-upper
-    // (c) Ari Koivula http://ventionline.com
-    
-    // Remove all pre-existing formatting. 
-    // Remove all newlines. 
-    $source = str_replace("\n", '', $source); 
-    $source = str_replace("\r", '', $source); 
-    // Remove all tabs. 
-    $source = str_replace("\t", '', $source); 
-    // Remove all space after ">" and before "<". 
-    $source = ereg_replace(">( )*", ">", $source); 
-    $source = ereg_replace("( )*<", "<", $source); 
-
-    // Iterate through the source. 
-    $level = 0; 
-    $source_len = strlen($source); 
-    $pt = 0; 
-    while ($pt < $source_len) { 
-        if ($source{$pt} === '<') { 
-            // We have entered a tag. 
-            // Remember the point where the tag starts. 
-            $started_at = $pt; 
-            $tag_level = 1; 
-            // If the second letter of the tag is "/", assume its an ending tag. 
-            if ($source{$pt+1} === '/') { 
-                $tag_level = -1; 
-            } 
-            // If the second letter of the tag is "!", assume its an "invisible" tag. 
-            if ($source{$pt+1} === '!') { 
-                $tag_level = 0; 
-            } 
-            // Iterate throught the source until the end of tag. 
-            while ($source{$pt} !== '>') { 
-                $pt++; 
-            } 
-            // If the second last letter is "/", assume its a self ending tag. 
-            if ($source{$pt-1} === '/') { 
-                $tag_level = 0; 
-            } 
-            $tag_lenght = $pt+1-$started_at; 
-             
-            // Decide the level of indention for this tag. 
-            // If this was an ending tag, decrease indent level for this tag.. 
-            if ($tag_level === -1) { 
-                $level--; 
-            } 
-            // Place the tag in an array with proper indention. 
-            $array[] = str_repeat($indenter, $level).substr($source, $started_at, $tag_lenght); 
-            // If this was a starting tag, increase the indent level after this tag. 
-            if ($tag_level === 1) { 
-                $level++; 
-            } 
-            // if it was a self closing tag, dont do shit. 
-        } 
-        // Were out of the tag. 
-        // If next letter exists... 
-        if (($pt+1) < $source_len) { 
-            // ... and its not an "<". 
-            if ($source{$pt+1} !== '<') { 
-                $started_at = $pt+1; 
-                // Iterate through the source until the start of new tag or until we reach the end of file. 
-                while ($source{$pt} !== '<' && $pt < $source_len) { 
-                    $pt++; 
-                } 
-                // If we found a "<" (we didnt find the end of file) 
-                if ($source{$pt} === '<') { 
-                    $tag_lenght = $pt-$started_at; 
-                    // Place the stuff in an array with proper indention. 
-                    $array[] = str_repeat($indenter, $level).substr($source, $started_at, $tag_lenght); 
-                } 
-            // If the next tag is "<", just advance pointer and let the tag indenter take care of it. 
-            } else { 
-                $pt++; 
-            } 
-        // If the next letter doesnt exist... Were done... well, almost.. 
-        } else { 
-            break; 
-        } 
-    } 
-    // Replace old source with the new one we just collected into our array. 
-    $source = implode($array, "\n"); 
-    return $source; 
-} 
-
-
-function export_file_extension() {
-    // override default type so extension is .xml
+    function answerweightparser(&$answer) {
+        $answer = substr($answer, 1);                        // removes initial %
+        $end_position  = strpos($answer, "%");
+        $answer_weight = substr($answer, 0, $end_position);  // gets weight as integer
+        $answer_weight = $answer_weight/100;                 // converts to percent
+        $answer = substr($answer, $end_position+1);          // removes comment from answer
+        return $answer_weight;
+    }
+
+
+    function commentparser(&$answer) {
+        if (strpos($answer,"#") > 0){
+            $hashpos = strpos($answer,"#");
+            $comment = substr($answer, $hashpos+1);
+            $comment = addslashes(trim($this->escapedchar_post($comment)));
+            $answer  = substr($answer, 0, $hashpos);
+        } else {
+            $comment = " ";
+        }
+        return $comment;
+    }
     
-    return ".xml";
-}
+    function escapedchar_pre($string) {
+        //Replaces escaped control characters with a placeholder BEFORE processing
+        
+        $escapedcharacters = array("\\#",    "\\=",    "\\{",    "\\}",    "\\~"   );
+        $placeholders      = array("&&035;", "&&061;", "&&123;", "&&125;", "&&126;");
 
-function get_qtype( $type_id ) {
-    // translates question type code number into actual name
-   
-    switch( $type_id ) {
-    case TRUEFALSE:
-        $name = 'truefalse';
-        break;
-    case MULTICHOICE:
-        $name = 'multichoice';
-        break;
-    case SHORTANSWER:
-        $name = 'shortanswer';
-        break;
-    case NUMERICAL:
-        $name = 'numerical';
-        break;
-    case MATCH:
-        $name = 'matching';
-        break;
-    case DESCRIPTION:
-        $name = 'description';
-        break;
-    case MULTIANSWER:
-        $name = 'cloze';
-        break;
-    default:
-        $name = '';
-        error( "question type $type_id is not defined in get_qtype" );
+        $string = str_replace("\\\\", "&&092;", $string);
+        $string = str_replace($escapedcharacters, $placeholders, $string);
+        $string = str_replace("&&092;", "\\", $string);
+        return $string;
     }
-    return $name;
-}
 
-function writetext( $raw ) {
-    // generates <text></text> tags, processing raw text therein 
+    function escapedchar_post($string) {
+        //Replaces placeholders with corresponding character AFTER processing is done
+        $placeholders = array("&&035;", "&&061;", "&&123;", "&&125;", "&&126;");
+        $characters   = array("#",      "=",      "{",      "}",      "~"     );
+        $string = str_replace($placeholders, $characters, $string);
+        return $string;
+    }
 
-    // for now, don't allow any additional tags in text 
-    // otherwise xml rules would probably get broken
-    $raw = strip_tags( $raw );
 
-    return "<text>$raw</text>\n";
-}
+    function readquestion($lines) {
+    // Given an array of lines known to define a question in this format, this function
+    // converts it into a question object suitable for processing and insertion into Moodle.
+
+        $question = NULL;
+        $comment = NULL;
+        // define replaced by simple assignment, stop redefine notices
+        $gift_answerweight_regex = "^%\-*([0-9]{1,2})\.?([0-9]*)%";        
+
+        // REMOVED COMMENTED LINES and IMPLODE
+        foreach ($lines as $key => $line) {
+           $line = trim($line);
+           if (substr($line, 0, 2) == "//") {
+                // echo "Commented line removed.<br />";
+                $lines[$key] = " ";
+                }
+        }
+
+        $text = trim(implode(" ", $lines));
+
+        if ($text == "") {
+            // echo "<p>Empty line.</p>";
+            return false;
+        }
+
+        // Substitute escaped control characters with placeholders
+        $text = $this->escapedchar_pre($text);
+
+        // QUESTION NAME parser
+        if (substr($text, 0, 2) == "::") {
+            $text = substr($text, 2);
+
+            $namefinish = strpos($text, "::");
+            if ($namefinish === false) {
+                $question->name = false;
+                // name will be assigned after processing question text below
+             } else {
+                $questionname = substr($text, 0, $namefinish);
+                $question->name = addslashes(trim($this->escapedchar_post($questionname)));
+                $text = trim(substr($text, $namefinish+2)); // Remove name from text
+            }
+        } else {
+            $question->name = false;
+        }
+
+        // FIND ANSWER section
+        $answerstart = strpos($text, "{");
+        if ($answerstart === false) {
+            if ($this->displayerrors) {
+                echo "<P>$text<P>Could not find a {";
+            }
+            return false;
+        }
+
+        $answerfinish = strpos($text, "}");
+        if ($answerfinish === false) {
+            if ($this->displayerrors) {
+                echo "<P>$text<P>Could not find a }";
+            }
+            return false;
+        }
+
+        $answerlength = $answerfinish - $answerstart;
+        $answertext = trim(substr($text, $answerstart + 1, $answerlength - 1));
+
+        // Format QUESTION TEXT without answer, inserting "_____" as necessary
+        if (substr($text, -1) == "}") {
+            // no blank line if answers follow question, outside of closing punctuation
+            $questiontext = substr_replace($text, "", $answerstart, $answerlength+1);
+        } else {
+            // inserts blank line for missing word format
+            $questiontext = substr_replace($text, "_____", $answerstart, $answerlength+1);
+        }
+        $question->questiontext = addslashes(trim($this->escapedchar_post($questiontext)));
+
+        // set question name if not already set
+        if ($question->name === false) {
+            $question->name = $question->questiontext;
+            }
+
+
+         // determine QUESTION TYPE
+        $question->qtype = NULL;
+
+        if ($answertext{0} == "#"){
+            $question->qtype = NUMERICAL;
+
+        } elseif (strpos($answertext, "~") !== false)  {
+            // only Multiplechoice questions contain tilde ~
+            $question->qtype = MULTICHOICE;
+    
+        } elseif (strpos($answertext, "=")  !== false 
+              AND strpos($answertext, "->") !== false) {
+              // only Matching contains both = and ->
+            $question->qtype = MATCH;
+
+        } else { // either TRUEFALSE or SHORTANSWER
+    
+            // TRUEFALSE question check
+            $truefalse_check = $answertext;
+            if (strpos($answertext,"#") > 0){ 
+                // strip comments to check for TrueFalse question
+                $truefalse_check = trim(substr($answertext, 0, strpos($answertext,"#")));
+            }
+
+            $valid_tf_answers = array("T", "TRUE", "F", "FALSE");
+            if (in_array($truefalse_check, $valid_tf_answers)) {
+                $question->qtype = TRUEFALSE;
+
+            } else { // Must be SHORTANSWER
+                    $question->qtype = SHORTANSWER;
+            }
+        }
+
+        if (!isset($question->qtype)) {
+            if ($this->displayerrors) {
+                echo "<P>$text<P>Question type not set.";
+                }
+            return false;
+        }
+
+        switch ($question->qtype) {
+            case MULTICHOICE:
+                if (strpos($answertext,"=") === false) {
+                    $question->single = 0;   // multiple answers are enabled if no single answer is 100% correct                        
+                } else {
+                    $question->single = 1;   // only one answer allowed (the default)
+                }
+
+                $answertext = str_replace("=", "~=", $answertext);
+                $answers = explode("~", $answertext);
+                if (isset($answers[0])) {
+                    $answers[0] = trim($answers[0]);
+                }
+                if (empty($answers[0])) {
+                    array_shift($answers);
+                }
+    
+                $countanswers = count($answers);
+                if ($countanswers < 2) {
+                    if ($this->displayerrors) {
+                        echo "<P>$text<P>Found tilde for multiple choice, 
+                            but too few answers for Multiple Choice.<br />
+                            Found <u>$countanswers</u> answers in answertext.";
+                    }
+                    return false;
+                    break;
+                }
+    
+                foreach ($answers as $key => $answer) {
+                    $answer = trim($answer);
+
+                    // determine answer weight
+                    if ($answer[0] == "=") {
+                        $answer_weight = 1;
+                        $answer = substr($answer, 1);
+    
+                    } elseif (ereg($gift_answerweight_regex, $answer)) {    // check for properly formatted answer weight
+                        $answer_weight = $this->answerweightparser($answer);
+                    
+                    } else {     //default, i.e., wrong anwer
+                        $answer_weight = 0;
+                    }
+                    $question->fraction[$key] = $answer_weight;
+                    $question->feedback[$key] = $this->commentparser($answer); // commentparser also removes comment from $answer
+                    $question->answer[$key]   = addslashes($this->escapedchar_post($answer));    
+                }  // end foreach answer
+    
+                $question->defaultgrade = 1;
+                $question->image = "";   // No images with this format
+                return $question;
+                break;
+
+            case MATCH:
+                $answers = explode("=", $answertext);
+                if (isset($answers[0])) {
+                    $answers[0] = trim($answers[0]);
+                }
+                if (empty($answers[0])) {
+                    array_shift($answers);
+                }
+    
+                $countanswers = count($answers);
+                if ($countanswers < 3) {
+                    if ($this->displayerrors) {
+                        echo "<P>$text<P>Found markers for Matching format 
+                            (= and ->), but too few answers -- must be at least 3.<br />
+                            Found <u>$countanswers</u> answers in answertext.";
+                    }
+                    return false;
+                    break;
+                }
+    
+                foreach ($answers as $key => $answer) {
+                    $answer = trim($answer);
+                    if (strpos($answer, "->") <= 0) {
+                        if ($this->displayerrors) {
+                        echo "<P>$text<P>Error processing Matching question.<br />
+                            Improperly formatted answer: $answer";
+                        }
+                        return false;
+                        break 2;
+                    }
+
+                    $marker = strpos($answer,"->");
+                    $question->subquestions[$key] = addslashes(trim($this->escapedchar_post(substr($answer, 0, $marker))));
+                    $question->subanswers[$key]   = addslashes(trim($this->escapedchar_post(substr($answer, $marker+2))));
+
+                }  // end foreach answer
+    
+                $question->defaultgrade = 1;
+                $question->image = "";   // No images with this format
+                return $question;
+                break;
+            
+            case TRUEFALSE:
+                $answer = $answertext;
+                $comment = $this->commentparser($answer); // commentparser also removes comment from $answer
+
+                if ($answer == "T" OR $answer == "TRUE") {
+                    $question->answer = 1;
+                    $question->feedbackfalse = $comment; //feedback if answer is wrong
+                    $question->feedbacktrue = ""; // make sure this exists to stop notifications
+                } else {
+                    $question->answer = 0;
+                    $question->feedbacktrue = $comment; //feedback if answer is wrong
+                    $question->feedbackfalse = ""; // make sure this exists to stop notifications
+                }
+                $question->defaultgrade = 1;
+                $question->image = "";   // No images with this format
+                return $question;
+                break;
+                
+            case SHORTANSWER:
+                // SHORTANSWER Question
+                $answers = explode("=", $answertext);
+                if (isset($answers[0])) {
+                    $answers[0] = trim($answers[0]);
+                }
+                if (empty($answers[0])) {
+                    array_shift($answers);
+                }
+    
+                if (count($answers) == 0) {
+                    // invalid question
+                    if ($this->displayerrors) {
+                        echo "<P>$text<P>Found equals=, but no answers in answertext";
+                    }
+                    return false;
+                    break;
+                }
+
+                foreach ($answers as $key => $answer) {
+                    $answer = trim($answer);
+
+                    // Answer Weight
+                    if (ereg(GIFT_ANSWERWEIGHT_REGEX, $answer)) {    // check for properly formatted answer weight
+                        $answer_weight = $this->answerweightparser($answer);
+                    } else {     //default, i.e., full-credit anwer
+                        $answer_weight = 1;
+                    }
+                    $question->fraction[$key] = $answer_weight;
+                    $question->feedback[$key] = $this->commentparser($answer); //commentparser also removes comment from $answer
+                    $question->answer[$key]   = addslashes($this->escapedchar_post($answer));
+                }     // end foreach
+
+                $question->usecase = 0;  // Ignore case
+                $question->defaultgrade = 1;
+                $question->image = "";   // No images with this format
+                return $question;
+                break;
+
+            case NUMERICAL:
+                // Note similarities to ShortAnswer
+                $answertext = substr($answertext, 1); // remove leading "#"
+
+                $answers = explode("=", $answertext);
+                if (isset($answers[0])) {
+                    $answers[0] = trim($answers[0]);
+                }
+                if (empty($answers[0])) {
+                    array_shift($answers);
+                }
+    
+                if (count($answers) == 0) {
+                    // invalid question
+                    if ($this->displayerrors) {
+                        echo "<P>$text<P>No answers found in answertext (Numerical answer)";
+                    }
+                    return false;
+                    break;
+                }
+
+                foreach ($answers as $key => $answer) {
+                    $answer = trim($answer);
+
+                    // Answer weight
+                    if (ereg(GIFT_ANSWERWEIGHT_REGEX, $answer)) {    // check for properly formatted answer weight
+                        $answer_weight = $this->answerweightparser($answer);
+                    } else {     //default, i.e., full-credit anwer
+                        $answer_weight = 1;
+                    }
+                    $question->fraction[$key] = $answer_weight;
+                    $question->feedback[$key] = $this->commentparser($answer); //commentparser also removes comment from $answer
+
+                    //Calculate Answer and Min/Max values
+                    if (strpos($answer,"..") > 0) { // optional [min]..[max] format
+                        $marker                 = strpos($answer,"..");
+                        $question->max[$key]    = trim(substr($answer, $marker+2));
+                        $question->min[$key]    = trim(substr($answer, 0, $marker));
+                        $question->answer[$key] = ($question->max[$key] + $question->min[$key])/2;
+
+                    } elseif (strpos($answer,":") > 0){ // standard [answer]:[errormargin] format
+                        $marker                 = strpos($answer,":");
+                        $errormargin            = trim(substr($answer, $marker+1));
+                        $question->answer[$key] = trim(substr($answer, 0, $marker));
+                        $question->max[$key]    = $question->answer[$key] + $errormargin;
+                        $question->min[$key]    = $question->answer[$key] - $errormargin;
+
+                    } else { // only one valid answer (zero errormargin)
+                        $errormargin = 0;
+                        $question->answer[$key] = trim($answer);
+                        $question->max[$key]    = $question->answer[$key] + $errormargin;
+                        $question->min[$key]    = $question->answer[$key] - $errormargin;
+                    }
+    
+                    if (!is_numeric($question->answer[$key]) 
+                     OR !is_numeric($question->max[$key])
+                     OR !is_numeric($question->max[$key])) {
+                        if ($this->displayerrors) {
+                            echo "<P>$text<P>For numerical questions, answer must be numbers.
+                                <P>Answer: <u>$answer</u><P>ErrorMargin: <u>$errormargin</u> .";
+                        }
+                        return false;
+                        break;
+                    }
+
+                }     // end foreach
+
+                $question->defaultgrade = 1;
+                $question->image = "";   // No images with this format
+                return $question;
+                break;
+
+                default:
+                if ($this->displayerrors) {
+                    echo "<P>$text<P> No valid question type. Error in switch(question->qtype)";
+                }
+                return false;
+                break;                
+        
+        } // end switch ($question->qtype)
+
+    }    // end function readquestion($lines)
 
 function writequestion( $question ) {
     // turns question into string
@@ -150,79 +440,79 @@ function writequestion( $question ) {
     $expout = "";
 
     // add comment
-    $expout .= "\n\n<!-- question: $question->id  name: $question->name -->\n";
-
-    // add opening tag
-    $question_type = $this->get_qtype( $question->qtype );
-    $name_text = $this->writetext( $question->name );
-    $question_text = $this->writetext( $question->questiontext );
-    $expout .= "<question type=\"$question_type\">\n";   
-    $expout .= "<name>".$this->writetext($name_text)."</name>\n";
-    $expout .= "<questiontext>".$this->writetext($question_text)."</questiontext>\n";   
+    $expout .= "// question: $question->id  name: $question->name \n";
 
     // output depends on question type
     switch($question->qtype) {
     case TRUEFALSE:
-        $true_percent = round( $question->trueanswer->fraction * 100 );
-        $false_percent = round( $question->falseanswer->fraction * 100 );
-        // true answer
-        $expout .= "<answer fraction=\"$true_percent\">\n";
-        $expout .= $this->writetext("true")."\n";
-        $expout .= "<feedback>".$this->writetext( $question->trueanswer->feedback )."</feedback>\n";
-        $expout .= "</answer>\n";
-
-
-        // false answer
-        $expout .= "<answer fraction=\"$false_percent\">\n";
-        $expout .= $this->writetext("false")."\n";
-        $expout .= "<feedback>".$this->writetext( $question->falseanswer->feedback )."</feedback>\n";
-        $expout .= "</answer>\n";
+        if ($question->trueanswer->fraction==1) {
+            $answertext = 'TRUE';
+            $wrong_feedback = $question->falseanswer->feedback;
+            $right_feedback = $question->trueanswer->feedback;
+        }
+        else {
+            $answertext = 'FALSE';
+            $wrong_feedback = $question->trueanswer->feedback;
+            $right_feedback = $question->falseanswer->feedback;
+        }
+        $expout .= "::".$question->name."::".$question->questiontext."{".$answertext;
+        if ($wrong_feedback!="") {
+            $expout .= "#".$wrong_feedback;
+        }
+        if ($right_feedback!="") {
+            $expout .= "#".$right_feedback;
+        }
+        $expout .= "}\n";
         break;
     case MULTICHOICE:
+        $expout .= "::".$question->name."::".$question->questiontext."{\n";
         foreach($question->answers as $answer) {
-            $percent = round( $answer->fraction * 100 );
-            $expout .= "<answer fraction=\"$percent\">\n";
-            $expout .= $this->writetext( $answer->answer );
-            $expout .= "<feedback>".$this->writetext( $answer->feedback )."</feedback>\n";
-            $expout .= "</answer>\n";
+            if ($answer->fraction==1) {
+                $answertext = '=';
+            }
+            else {
+                $answertext = '~';
             }
+            $expout .= "\t".$answertext.$answer->answer;
+            if ($answer->feedback!="") {
+                $expout .= "#".$answer->feedback;
+            }
+            $expout .= "\n";
+        }
+        $expout .= "}\n";
         break;
     case SHORTANSWER:
+        $expout .= "::".$question->name."::".$question->questiontext."{\n";
         foreach($question->answers as $answer) {
-            $percent = 100 * $answer->fraction;
-            $expout .= "<answer fraction=\"$percent\">\n";
-            $expout .= $this->writetext( $answer->answer );
-            $expout .= "<feedback>".$this->writetext( $answer->feedback )."</feedback>\n";
-            $expout .= "</answer>\n";
+            $weight = 100 * $answer->fraction;
+            $expout .= "\t=%".$weight."%".$answer->answer."#".$answer->feedback."\n";
         }
+        $expout .= "}\n";
         break;
     case NUMERICAL:
-        $expout .= "<min>$question->min</min>\n";
-        $expout .= "<max>$question->max</max>\n";
-        $expout .= "<feedback>".$this->writetext( $answer->feedback )."</feedback>\n";
+        $expout .= "::".$question->name."::".$question->questiontext."{\n";
+        $expout .= "\t#".$question->min."..".$question->max."#".$question->answer->feedback."\n";
+        $expout .= "}\n";
         break;
     case MATCH:
+        $expout .= "::".$question->name."::".$question->questiontext."{\n";
         foreach($question->subquestions as $subquestion) {
-            $expout .= "<subquestion>\n";
-            $expout .= $this->writetext( $subquestion->questiontext );
-            $expout .= "<answer>".$this->writetext( $subquestion->answertext )."</answer>\n";
-            $expout .= "</subquestion>\n";
+            $expout .= "\t=".$subquestion->questiontext." -> ".$subquestion->answertext."\n";
         }
+        $expout .= "}\n";
         break;
     case DESCRIPTION:
-        $expout .= "<!-- DESCRIPTION type is not supported -->\n";
+        $expout .= "// DESCRIPTION type is not supported\n";
         break;
     case MULTIANSWER:
-        $expout .= "<!-- CLOZE type is not supported -->\n";
+        $expout .= "// CLOZE type is not supported\n";
         break;
     default:
         error( "No handler for qtype $question->qtype for GIFT export" );
     }
-    // close the question tag
-    $expout .= "</question>\n";
-    // run through xml tidy function
-    return $this->indent_xhtml( $expout, '  ' ); 
+    // add empty line to delimit questions
+    $expout .= "\n";
+    return $expout;
 }
 }
-
 ?>