From 0890110fbc69f34f987dccd7c162bb2f478f803b Mon Sep 17 00:00:00 2001 From: thepurpleblob Date: Fri, 6 Aug 2004 13:03:33 +0000 Subject: [PATCH] vaguely working version of xml export - much more work needed though --- mod/quiz/format/gift/format.php | 595 +++++++++----------------------- 1 file changed, 155 insertions(+), 440 deletions(-) diff --git a/mod/quiz/format/gift/format.php b/mod/quiz/format/gift/format.php index 6a1fb02e74..4463851016 100755 --- a/mod/quiz/format/gift/format.php +++ b/mod/quiz/format/gift/format.php @@ -1,436 +1,146 @@ 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 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; - } - - function escapedchar_pre($string) { - //Replaces escaped control characters with a placeholder BEFORE processing - - $escapedcharacters = array("\\#", "\\=", "\\{", "\\}", "\\~" ); - $placeholders = array("&&035;", "&&061;", "&&123;", "&&125;", "&&126;"); - - $string = str_replace("\\\\", "&&092;", $string); - $string = str_replace($escapedcharacters, $placeholders, $string); - $string = str_replace("&&092;", "\\", $string); - return $string; - } - - 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; - } - - - 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 +function indent_xhtml($source, $indenter = ' ') { + // xml tidier-upper + // (c) Ari Koivula http://ventionline.com - // 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); - } + // 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 - $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; - } + return ".xml"; +} - } // end foreach +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" ); + } + return $name; +} - $question->defaultgrade = 1; - $question->image = ""; // No images with this format - return $question; - break; +function writetext( $raw ) { + // generates tags, processing raw text therein - default: - if ($this->displayerrors) { - echo "

$text

No valid question type. Error in switch(question->qtype)"; - } - return false; - break; - - } // end switch ($question->qtype) + // for now, don't allow any additional tags in text + // otherwise xml rules would probably get broken + $raw = strip_tags( $raw ); - } // end function readquestion($lines) + return "$raw\n"; +} function writequestion( $question ) { // turns question into string @@ -440,29 +150,33 @@ function writequestion( $question ) { $expout = ""; // add comment - $expout .= "// question: $question->id name: $question->name \n"; + $expout .= "\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 .= "$name_text\n"; + $expout .= "$question_text\n"; // output depends on question type switch($question->qtype) { case TRUEFALSE: - 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"; + $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"; break; case MULTICHOICE: $expout .= "::".$question->name."::".$question->questiontext."{\n"; @@ -510,9 +224,10 @@ function writequestion( $question ) { default: error( "No handler for qtype $question->qtype for GIFT export" ); } - // add empty line to delimit questions - $expout .= "\n"; - return $expout; + // close the question tag + $expout .= "\n"; + // run through xml tidy function + return $this->indent_xhtml( $expout, ' ' ); } } -- 2.39.5