From 28e9d5d7896a4e2b0005a4029ff08639ba5a3ffb Mon Sep 17 00:00:00 2001 From: thepurpleblob Date: Fri, 6 Aug 2004 15:06:17 +0000 Subject: [PATCH] Whoops got rid of xml stuff - sorry my mistake --- mod/quiz/format/gift/format.php | 640 +++++++++++++++++++++++--------- 1 file changed, 465 insertions(+), 175 deletions(-) diff --git a/mod/quiz/format/gift/format.php b/mod/quiz/format/gift/format.php index f9aad38fd4..a5e2accf5f 100755 --- a/mod/quiz/format/gift/format.php +++ b/mod/quiz/format/gift/format.php @@ -1,146 +1,436 @@ 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 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 "$raw\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.
"; + $lines[$key] = " "; + } + } + + $text = trim(implode(" ", $lines)); + + if ($text == "") { + // echo "

Empty line.

"; + 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 "

$text

Could not find a {"; + } + return false; + } + + $answerfinish = strpos($text, "}"); + if ($answerfinish === false) { + if ($this->displayerrors) { + echo "

$text

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 "

$text

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 "

$text

Found tilde for multiple choice, + but too few answers for Multiple Choice.
+ Found $countanswers 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 "

$text

Found markers for Matching format + (= and ->), but too few answers -- must be at least 3.
+ Found $countanswers answers in answertext."; + } + return false; + break; + } + + foreach ($answers as $key => $answer) { + $answer = trim($answer); + if (strpos($answer, "->") <= 0) { + if ($this->displayerrors) { + echo "

$text

Error processing Matching question.
+ 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 "

$text

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 "

$text

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 "

$text

For numerical questions, answer must be numbers. +

Answer: $answer

ErrorMargin: $errormargin ."; + } + return false; + break; + } + + } // end foreach + + $question->defaultgrade = 1; + $question->image = ""; // No images with this format + return $question; + break; + + default: + if ($this->displayerrors) { + echo "

$text

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\n"; - - // add opening tag - $question_type = $this->get_qtype( $question->qtype ); - $name_text = $this->writetext( $question->name ); - $question_text = $this->writetext( $question->questiontext ); - $expout .= "\n"; - $expout .= "".$this->writetext($name_text)."\n"; - $expout .= "".$this->writetext($question_text)."\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 .= "\n"; - $expout .= $this->writetext("true")."\n"; - $expout .= "".$this->writetext( $question->trueanswer->feedback )."\n"; - $expout .= "\n"; - - - // false answer - $expout .= "\n"; - $expout .= $this->writetext("false")."\n"; - $expout .= "".$this->writetext( $question->falseanswer->feedback )."\n"; - $expout .= "\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 .= "\n"; - $expout .= $this->writetext( $answer->answer ); - $expout .= "".$this->writetext( $answer->feedback )."\n"; - $expout .= "\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 .= "\n"; - $expout .= $this->writetext( $answer->answer ); - $expout .= "".$this->writetext( $answer->feedback )."\n"; - $expout .= "\n"; + $weight = 100 * $answer->fraction; + $expout .= "\t=%".$weight."%".$answer->answer."#".$answer->feedback."\n"; } + $expout .= "}\n"; break; case NUMERICAL: - $expout .= "$question->min\n"; - $expout .= "$question->max\n"; - $expout .= "".$this->writetext( $answer->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 .= "\n"; - $expout .= $this->writetext( $subquestion->questiontext ); - $expout .= "".$this->writetext( $subquestion->answertext )."\n"; - $expout .= "\n"; + $expout .= "\t=".$subquestion->questiontext." -> ".$subquestion->answertext."\n"; } + $expout .= "}\n"; break; case DESCRIPTION: - $expout .= "\n"; + $expout .= "// DESCRIPTION type is not supported\n"; break; case MULTIANSWER: - $expout .= "\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 .= "\n"; - // run through xml tidy function - return $this->indent_xhtml( $expout, ' ' ); + // add empty line to delimit questions + $expout .= "\n"; + return $expout; } } - ?> -- 2.39.5