# Handler for parsing text upload problem descriptions into .problems # $Id: testbankimport.pm,v 1.12 2007/05/02 01:34:23 albertel Exp $ # # Copyright Michigan State University Board of Trustees # # This file is part of the LearningOnline Network with CAPA (LON-CAPA). # # LON-CAPA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # LON-CAPA is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with LON-CAPA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # /home/httpd/html/adm/gpl.txt # # http://www.lon-capa.org/ # package Apache::testbankimport; use strict; use Apache::Constants qw(:common :http :methods); use Apache::loncacc; use Apache::loncommon(); use Apache::lonnet; use HTML::Entities(); use Apache::lonlocal; use Apache::lonupload; use File::Basename(); use LONCAPA(); # ---------------------------------------------------------------- Display Control sub display_control { # figure out what page we're on and where we're heading. my $page = $env{'form.page'}; my $command = $env{'form.go'}; my $current_page = &calculate_page($page,$command); return $current_page; } # CALCULATE THE CURRENT PAGE sub calculate_page($$) { my ($prev,$dir) = @_; return 0 if $prev eq ''; # start with first page return $prev + 1 if $dir eq 'NextPage'; return $prev - 1 if $dir eq 'PreviousPage'; return $prev if $dir eq 'ExitPage'; return 0 if $dir eq 'BackToStart'; } # ---------------------------------------------------------------- Jscript One sub jscript_one { my $jsref = shift; $$jsref = <<"END_SCRIPT"; function verify() { if ((document.forms.display.blocks.value == "") || (!document.forms.display.blocks.value) || (document.forms.display.blocks.value == "0")) { alert("You must enter the number of blocks of questions of a given question type. This number must be 1 or more.") return false } if (document.forms.display.qnumformat.options[document.forms.display.qnumformat.selectedIndex].value == "-1") { alert("You must select the format used for the question number, e.g., (1), 1., (1, or 1).") return false } return true } function nextPage() { if (verify()) { document.forms.display.go.value="NextPage" document.forms.display.submit() } } function backPage() { document.forms.display.go.value="PreviousPage" document.forms.display.submit() } function setElements() { var iter = 0 var selParam = 0 END_SCRIPT if (exists($env{'form.blocks'}) ) { $$jsref .= qq| document.forms.display.blocks.value = $env{'form.blocks'}\n|; } elsif (exists($env{'form.qnumformat'}) ) { $$jsref .= <<"TO_HERE"; for (iter=0; iter= curmin ) { alert("The question number range for block "+iter+" overlaps with the question number range for one of the previous blocks - this is not permitted.") return false } } else { if (parseInt(poolForm.elements[5*i+1].value) <= curmax) { for (var j=parseInt(poolForm.elements[5*i+1].value); j<=parseInt(poolForm.elements[5*i+2].value); j++) { for (var k=0; k= parseInt(poolForm.elements[5*k+1].value)) && (j <= parseInt(poolForm.elements[5*k+2].value))) { var overlap = k+1 alert("The question number range for block "+iter+" overlaps with the question number range for block "+overlap+" - this is not permitted.") return false } } } } } if (parseInt(poolForm.elements[5*i+1].value) < curmin) { curmin = parseInt(poolForm.elements[5*i+1].value) } if (parseInt(poolForm.elements[5*i+2].value) > curmax) { curmax = parseInt(poolForm.elements[5*i+2].value) } } } if (curmax >$qcount+curmin) { alert("The last # for one or more of the blocks is too large - the last number of the last block can not be greater than $qcount: the total number of questions in the uploaded file.") return false } var endpt = $qcount + curmin for (var n=curmin; n= parseInt(poolForm.elements[5*m+1].value)) && (n <= parseInt(poolForm.elements[5*m+2].value))) { warnFlag = false } } if (warnFlag) { alert("The question type for question "+n+" could not be identified because it does not fall within the number ranges you have provided for any of the $blocks block(s)") return false } } return true } function nextPage() { if (verify()) { document.forms.display.go.value="NextPage" document.forms.display.submit() } } function backPage() { document.forms.display.go.value="PreviousPage" document.forms.display.submit() } function colSet(caller) { var poolForm = document.forms.display var curVal = poolForm.elements[caller*5+3].options[poolForm.elements[caller*5+3].selectedIndex].value poolForm.elements[caller*5+4].length = 0 if (poolForm.elements[caller*5+3].options[poolForm.elements[caller*5+3].selectedIndex].value == "-1") { poolForm.elements[caller*5+4].options[0] = new Option("<--- Set type ","-1",true,true) } else { if ((poolForm.elements[caller*5+3].options[poolForm.elements[caller*5+3].selectedIndex].value == "MC") || (poolForm.elements[caller*5+3].options[poolForm.elements[caller*5+3].selectedIndex].value == "MA") || (poolForm.elements[caller*5+3].options[poolForm.elements[caller*5+3].selectedIndex].value == "Ord")) { poolForm.elements[caller*5+4].options[0] = new Option("Please Select","-1",true,true) poolForm.elements[caller*5+4].options[1] = new Option("a.","lcperiod",false,false) poolForm.elements[caller*5+4].options[2] = new Option("A.","ucperiod",false,false) poolForm.elements[caller*5+4].options[3] = new Option("(a)","lcparen",false,false) poolForm.elements[caller*5+4].options[4] = new Option("(A)","ucparen",false,false) poolForm.elements[caller*5+4].options[5] = new Option("a)","lconeparen",false,false) poolForm.elements[caller*5+4].options[6] = new Option("A)","uconeparen",false,false) poolForm.elements[caller*5+4].options[7] = new Option("a.)","lcdotparen",false,false) poolForm.elements[caller*5+4].options[8] = new Option("A.)","ucdotparen",false,false) poolForm.elements[caller*5+4].options[9] = new Option("(i)","romparen",false,false) poolForm.elements[caller*5+4].options[10] = new Option("i)","romoneparen",false,false) poolForm.elements[caller*5+4].options[11] = new Option("i.)","romdotparen",false,false) poolForm.elements[caller*5+4].options[12] = new Option("i.","romperiod",false,false) poolForm.elements[caller*5+4].selectedIndex = 0 } else { poolForm.elements[caller*5+4].options[0] = new Option("Not required","0",true,true) } } poolForm.elements[caller*5+5].length = 0 if (poolForm.elements[caller*5+3].options[poolForm.elements[caller*5+3].selectedIndex].value == "-1") { poolForm.elements[caller*5+5].options[0] = new Option("<--- Set type ","-1",true,true) } else { if ((poolForm.elements[caller*5+3].options[poolForm.elements[caller*5+3].selectedIndex].value == "MA") || (poolForm.elements[caller*5+3].options[poolForm.elements[caller*5+3].selectedIndex].value == "FIB")) { poolForm.elements[caller*5+5].options[0] = new Option("Please Select","-1",true,true) poolForm.elements[caller*5+5].options[1] = new Option("single answer","single",false,false) poolForm.elements[caller*5+5].options[2] = new Option("comma","comma",false,false) poolForm.elements[caller*5+5].options[3] = new Option("space","space",false,false) poolForm.elements[caller*5+5].options[4] = new Option("new line","line",false,false) poolForm.elements[caller*5+5].options[5] = new Option("tab","tab",false,false) } else { if (poolForm.elements[caller*5+3].options[poolForm.elements[caller*5+3].selectedIndex].value == "Ord") { poolForm.elements[caller*5+5].options[0] = new Option("Please Select","-1",true,true) poolForm.elements[caller*5+5].options[1] = new Option("comma","comma",false,false) poolForm.elements[caller*5+5].options[2] = new Option("space","space",false,false) poolForm.elements[caller*5+5].options[3] = new Option("new line","line",false,false) poolForm.elements[caller*5+5].options[4] = new Option("tab","tab",false,false) } else { if (poolForm.elements[caller*5+3].options[poolForm.elements[caller*5+3].selectedIndex].value == "TF") { poolForm.elements[caller*5+5].options[0] = new Option("Please Select","-1",true,true) poolForm.elements[caller*5+5].options[1] = new Option("True or False","word",false,false) poolForm.elements[caller*5+5].options[2] = new Option("true or false","word",false,false) poolForm.elements[caller*5+5].options[3] = new Option("TRUE or FALSE","word",false,false) poolForm.elements[caller*5+5].options[4] = new Option("T or F","lett",false,false) poolForm.elements[caller*5+5].options[5] = new Option("t or f","lett",false,false) } else { poolForm.elements[caller*5+5].options[0] = new Option("Not required","0",true,true) } } } } } function setElements() { var iter = 0 var selParam = 0 END_SCRIPT my @names = ("start_","end_","qtype_","foilformat_","ansr_"); for (my $x=0; $x<$blocks; $x++) { foreach my $name (@names) { my $parname = $name.$x; my $value = $env{"form.$parname"}; if ($value ne "") { if (($name eq "start_") || ($name eq "end_")) { $$jsref .= qq| document.forms.display.$parname.value = $value\n|; } elsif ($name eq "qtype_") { $$jsref .= qq| for (iter=0; iter "0", 'leftmargin' => "0", 'marginwidth' => "0", 'topmargin' => "0", 'marginheight' => "0"); my $start_page = &Apache::loncommon::start_page('Create Testbank directory',undef, {'only_body' => 1, 'add_entries' => \%body_layout, 'js_ready' => 1,}); my $end_page = &Apache::loncommon::end_page({'js_ready' => 1,}); $$jsref = <<"END_OF_ONE"; function verify() { if ((document.forms.dataForm.newdir.value == '') || (!document.forms.dataForm.newdir.value)) { alert("Step 4: You must choose a destination directory for the import") return false } return true } function nextPage() { if (verify()) { document.forms.dataForm.go.value="NextPage" document.forms.dataForm.submit() } } function backPage() { document.forms.dataForm.go.value="PreviousPage" document.forms.dataForm.submit() } function createWin() { document.dataForm.newdir.value = ""; newWindow = window.open("","CreateDir","HEIGHT=400,WIDTH=750,scrollbars=yes") newWindow.document.open() newWindow.document.write('$start_page') newWindow.document.write("[Author Header]\\n") newWindow.document.write("\\n") newWindow.document.write("\\n") newWindow.document.write("\\n") newWindow.document.write("\\n") newWindow.document.write("\\n") newWindow.document.write("
  

Location: $fullpath

New Directory

  
\\n") newWindow.document.write("Enter the name of the new directory where you will save the converted testbank questions

") newWindow.document.write("") newWindow.document.write("") newWindow.document.write("") newWindow.document.write("$fullpath") newWindow.document.write("") newWindow.document.write("
") newWindow.document.write('$end_page') newWindow.document.close() newWindow.focus() } END_OF_ONE if ($source eq "PreviousPage") { $$jsref .= qq| function setElements() { var iter = 0 var selParam = 0 |; foreach my $item (keys %env) { if ($item =~ m/^form\.(\w+)$/) { my $name = $1; my $value = $env{"form.$name"}; unless ($value eq "") { if ($name eq "newdir") { $$jsref .= qq( document.forms.dataForm.$name.value = "$value"\n); } } } } $$jsref .= "}"; } } # ---------------------------------------------------------------- Jscript Four sub jscript_four { my ($jsref,$fullpath) = @_; $$jsref = qq| function backtoStart() { document.location.href="$fullpath" } function backpage() { document.forms.verify.go.value="PreviousPage" document.forms.verify.submit() } |; } # ---------------------------------------------------------------- Display Zero sub display_zero { my ($r,$uname,$fn,$page,$fullpath) = @_; $r->print(qq|
  The Testbank Upload utility can be used by LON-CAPA authors to convert multiple choice, multiple answer correct, fill-in-the-blank, ordering/ranking, true/false and essay questions from a plain text testbank file to LON-CAPA problem files. Five requirements must be met to ensure that you will succeed in converting your plain text file of testbank questions to functioning LON-CAPA problems.
  1. The questions and answers you upload must be in plain text format. Any header lines should occur before the text containing the questions and answers.
  2. All questions (including question text and all foils) must occur before any of the answers. Each question should begin on a new line, and should start with the question number. Questions should be numbered sequentially using a number followed immediately by a space, a period, or enclosed in parentheses, i.e., 1 , 1., (1), 1), or (1 .
  3. Multiple choice and multiple answer correct questions should consist of: (i) the question number followed by (ii) the question text beginning on the same line and (iii) two or more foils, with each foil beginning on a new line and prefixed by a unique letter, or Roman numeral, listed in alphabetic or numeric order, beginning at a (alphabetic) or i (Roman numeral), followed by a period, or enclosed in parentheses, i.e., a., (a), i., or (i).
  4. One or more correct answers should be provided for all questions (although blank answers may be provided for essay questions). Answers should be numbered sequentially, using the same scheme as used for the questions, and must occur after all the questions.
  5. If fill-in-the-blank or multiple answer questions have more than one correct answer, each answer should appear in a comma-, tab-, space-, or new line-delimited list. For a ranking/ordering question, the "answer" should contain the foil identifiers correctly ordered in a similarly delimited list.
Five steps are involved in the import process.
  1. Upload your text file to the server.|); if ($fn eq '') { $r->print("Incomplete. Please return to the construction space menu to upload a file"); } else { $r->print(" Completed - successful upload of $fn"); } $r->print(qq|
  2. Provide information about the question format - i.e., question numbering style, and the number of blocks of questions of each question type.
  3. Provide information about the questions in each block, including question type, start and end question numbers for each block, and foil labelling style and answer format where required.
  4. Create a new directory where you will save the converted testbank questions.
  5. Complete the import of questions to the selected pool.


|); } # ---------------------------------------------------------------- Display One sub display_one { my ($r,$uname,$fn,$page,$textref) = @_; $r->print(qq|
 

Step 2: Identification of blocks of questions 

 
  Testbank data uploaded to the server:
 
 
 
 
     Select the format of the question number [e.g., 1, 1., 1), (1 or (1)]. 
 
  A number in the specified format should appear at the start of each question. For multiple choice questions, the question number must begin the line that contains the question text; foils (starting (a), (i) etc.) should occur on subsequent lines. Correct answers should be numbered in the same way as the questions and should appear after all the questions (including question text and possible foils for all questions). Each numbered question must have a corresponding numbered answer, although the answer itself may be blank for essay questions.
 
  For example, you would select 1. if your text file contained the following questions:

1. The capital of the USA is ..
  (a) Washington D.C.
  (b) New York
  (c) Los Angeles

2. The capital of Canada is ..
  (a) Toronto
  (b) Vancouver
  (c) Ottawa

3. Describe an experiment you could conduct to measure c, the speed of light in a vacuum.

1. (a)
2. (c)
3.
 
     Please indicate the number of blocks of different question types in the text file.  
 
  For example, you would enter 6 if your text file contained the following sequence of questions:

10 multiple choice questions
5 essay questions
5 fill-in-the-blank questions
5 multiple answer questions
4 multiple choice questions
3 essay questions
 
  You will indicate the question type and the question number range for each of the blocks on the next page.
 
|); } # ---------------------------------------------------------------- Display Two sub display_two { my ($r,$uname,$fn,$page,$textref,$qcount) = @_; my $blocks = $env{'form.blocks'}; my $qnumformat = $env{'form.qnumformat'}; my @types = ("MC","MA","TF","Ess","FIB","Ord"); my %typenames = ( MC => "Multiple Choice", TF => "True/False", MA => "Multiple Answer", Ess => "Essay", FIB => "Fill-in-the-blank", Ord => "Ranking/ordering", ); my %qnumtypes = ( number => "1", period => "1.", paren => "(1)", leadparen => "(1", trailparen => "1)", ); my @bgcolors = ('#ffffff','#eeeeee'); my $bl1st = ''; my $bl1end = ''; if ($blocks == 1) { $bl1st = '1'; $bl1end = $qcount; } $r->print(<<"END_OF_FUNC");

Step 3: Classification of blocks 

 
  You indicated that all questions (and the corresponding answer(s) for each question) begin with a number in the following format: $qnumtypes{$qnumformat}.

A total of $qcount questions and $qcount corresponding answers were found in the file you uploaded. If this questions total does not match the number you expect, please examine your original text file to verify that each question and each answer begins with a number in the specified format. If necessary use a text editor to edit your text file of questions, and click "Return to step 2" on this page and the "Return to Step 1" on the preceding page, so you can upload your text file again.

You also indicated that the $qcount questions can be divided into $blocks blocks of questions of a particular question type.
 
  Please provide additional information below ,about the types of questions you have uploaded, and, if applicable, the format of answers and "foils" for specific types of questions.
 
  The following data were uploaded to the server
 
   Information about question types and formats in each block.
 
  For each of the $blocks question blocks, please specify the question numbers of the first and last questions in the block (e.g., 1 and 10), and the question type of the questions in the block. Please provide additional information about foil formats and answer formats if required for the question type you selected.
 
 
|); for (my $i=0; $i<$blocks; $i++) { my $iter = $i+1; my $rowcol = $i%2; $r->print(qq| |); } $r->print(qq|
 Block     First number     Last number     Question type     Foil format     Answer format 
 $iter.              
 
  For multiple choice, multiple correct answer and ranking type questions, you must use the Foil format column to choose the format of the identifier used for each of the possible answers (e.g., (a), a, a., i, (i) etc.) provided for a given question stem. For multiple correct answer and fill-in-the-blank questions with more than one correct answer you must use the Answer format column to choose the separator used between the answers, e.g., if the correct answers for question 28. were listed as: 28. (a),(d),(e) you would choose "comma", or if they were listed as:
28. (a)
 (d)
 (e)
you would choose "new line". For true/false questions you must use the Answer format column to choose how the correct answer - True or False, is displayed in the text file (e.g., T or F, true or false etc.). For ranking questions you must use the Answer format column to choose the separator used between the (ranked) answers.

 
|); } # ---------------------------------------------------------------- Display Three sub display_three { my ($r,$uname,$fn,$page,$textref,$qcount) = @_; my $qnumformat = $env{'form.qnumformat'}; my $filename = $env{'form.filename'}; my $source = $env{'form.go'}; my $blocks = $env{'form.blocks'}; my @items = (); my @bgcolors = ('#ffffff','#eeeeee'); my @types = ("MC","MA","TF","Ess","FIB","Ord"); my @alphabet = ("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"); my @romans = ("i","ii","iii","iv","v","vi","vii","viii","ix","x","xi","xii","xiii","xiv","xv","xvi","xvii","xviii","xix","xx","xxi","xxii","xxiii","xxiv","xxv","xxvi"); my @start = (); my @end = (); my @nums = (); my @qtype = (); my @foilformats = (); my @ansrtypes = (); my %multparts = (); my $numitems = 0; for (my $i=0; $i<$blocks; $i++) { if (($env{"form.start_$i"} ne '') && ($env{"form.end_$i"} ne '')) { $start[$i] = $env{"form.start_$i"}; $end[$i] = $env{"form.end_$i"}; $nums[$i] = $end[$i]-$start[$i] +1; $qtype[$i] = $env{"form.qtype_$i"}; if (($qtype[$i] eq "MC") || ($qtype[$i] eq "MA") || ($qtype[$i] eq "Ord")) { $foilformats[$i] = $env{"form.foilformat_$i"}; } else { $foilformats[$i] = ''; } if (($qtype[$i] eq "MA") || ($qtype[$i] eq "FIB") || ($qtype[$i] eq "TF") || ($qtype[$i] eq "Ord")) { $ansrtypes[$i] = $env{"form.ansr_$i"}; } else { $ansrtypes[$i] = ''; } } else { $nums[$i] = 0; } $numitems += $nums[$i]; } my $import = join//,@{$textref}; @items = &file_split(\@start,\@end,\@nums,$qnumformat,\@foilformats,$textref,\%multparts,$numitems,\@qtype,$blocks); $r->print(<<"END_OF_ONE");

Step 4: Review and selection of destination directory 

 
  Based on your previous responses your data have been split into a total of $numitems questions.
 
 
END_OF_ONE for (my $j=0; $j<$numitems; $j++) { my $qnum = $j+1; my $rowcol = $j%2; $rowcol = @bgcolors[$rowcol]; for (my $i=0; $i<$blocks; $i++) { if ($nums[$i] > 0) { if (($j+1 >= $start[$i]) && ($j+1 <= $end[$i])) { if (($qtype[$i] eq "MC") || ($qtype[$i] eq "MA")) { for (my $k=0; $k<@{$multparts{$j}}; $k++) { if ($k == 0) { $r->print(qq||); } else { $r->print(qq||); } last; } } } } $r->print(qq|
#TypeQuestionAnswer
$qnum.$qtype[$i]$multparts{$j}[$k]

\n|); } else { my $foiltag = ''; if ($foilformats[$i] eq "lcperiod") { $foiltag = $alphabet[$k-1].'.'; } elsif ($foilformats[$i] eq "lcparen") { $foiltag = '('.$alphabet[$k-1].')'; } elsif ($foilformats[$i] eq "lconeparen") { $foiltag = $alphabet[$k-1].')'; } elsif ($foilformats[$i] eq "lcdotparen") { $foiltag = $alphabet[$k-1].'.)'; } elsif ($foilformats[$i] eq "ucperiod") { $foiltag = $alphabet[$k-1].'.'; $foiltag =~ tr/a-z/A-Z/; } elsif ($foilformats[$i] eq "ucparen") { $foiltag = '('.$alphabet[$k-1].')'; $foiltag =~ tr/a-z/A-Z/; } elsif ($foilformats[$i] eq "uconeparen") { $foiltag = $alphabet[$k-1].')'; $foiltag =~ tr/a-z/A-Z/; } elsif ($foilformats[$i] eq "ucdotparen") { $foiltag = $alphabet[$k-1].'.)'; $foiltag =~ tr/a-z/A-Z/; } elsif ($foilformats[$i] eq "romperiod") { $foiltag = $romans[$k-1].'.'; } elsif ($foilformats[$i] eq "romparen") { $foiltag = '('.$romans[$k-1].')'; } elsif ($foilformats[$i] eq "romoneparen") { $foiltag = $romans[$k-1].')'; } elsif ($foilformats[$i] eq "romdotparen") { $foiltag = $romans[$k-1].'.)'; } $r->print(qq|$foiltag $multparts{$j}[$k]
\n|); } } $r->print(qq|
$items[$j+$numitems]
$qnum.$qtype[$i]$items[$j]$items[$j+$numitems]
 
   Create a directory to save your testbank questions.
 
  Please choose a destination LON-CAPA directory in which to save your uploaded questions.  
 
  If you are satisfied with the questions and answers extracted from your uploaded text file, as shown above, and you have created a destination directory click the "Continue to step 5" button to convert the questions in your testbank to LON-CAPA problem files.
|); for (my $i=0; $i<$blocks; $i++) { $r->print(qq| |); if (($qtype[$i] eq "MC") || ($qtype[$i] eq "MA") || ($qtype[$i] eq "Ord")) { $r->print(qq| |); } if (($qtype[$i] eq "MA") || ($qtype[$i] eq "FIB") || ($qtype[$i] eq "TF") || ($qtype[$i] eq "Ord")) { $r->print(qq| |); } } $r->print(qq|
 

|); } # ---------------------------------------------------------------- Final Display sub final_display { my ($r,$uname,$fn,$page,$textref) = @_; my $qnumformat = $env{'form.qnumformat'}; my $blocks = $env{'form.blocks'}; my $newdir = $env{'form.newdir'}; my $linkdir = $newdir; if ($linkdir =~ m#^/home/$uname/public_html/(.+)$#) { $linkdir = '/priv/'.$uname.'/'.$1; } my $question_id = ''; my @question_title = (); my @question_status = (); my @qtype = (); my @start = (); my @nums = (); my @end = (); my @foilformats = (); my @ansrtypes = (); my %multparts = (); my $numitems = 0; for (my $i=0; $i<$blocks; $i++) { $start[$i] = $env{"form.start_$i"}; $end[$i] = $env{"form.end_$i"}; if (($end[$i] - $start[$i]) >= 0) { $nums[$i] = $end[$i] - $start[$i]+1; } else { $nums[$i] = 0; } $qtype[$i] = $env{"form.qtype_$i"}; if (($qtype[$i] eq "MC") || ($qtype[$i] eq "MA") || ($qtype[$i] eq "Ord")) { $foilformats[$i] = $env{"form.foilformat_$i"}; } else { $foilformats[$i] = ''; } if (($qtype[$i] eq "MA") || ($qtype[$i] eq "FIB") || ($qtype[$i] eq "TF") || ($qtype[$i] eq "Ord")) { $ansrtypes[$i] = $env{"form.ansr_$i"}; } $numitems += $nums[$i]; } my @bgcolors = ('#ffffff','#eeeeee'); my $import = join/'\s'/,@{$textref}; my %answers = (); my @items = &file_split(\@start,\@end,\@nums,$qnumformat,\@foilformats,$textref,\%multparts,$numitems,\@qtype,$blocks); # Converting MC and MA answer to number, and splitting answers for FIB, and ordering for Ord. my @alphabet = ("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"); my @romans = ("i","ii","iii","iv","v","vi","vii","viii","ix","x","xi","xii","xiii","xiv","xv","xvi","xvii","xviii","xix","xx","xxi","xxii","xxiii","xxiv","xxv","xxvi"); my %patterns = ( comma => ',', space => '\s+', line => '[\r\n\f]+', tab => '\t+', ); for (my $i=0; $i<$blocks; $i++) { if ($nums[$i] > 0) { if (($qtype[$i] eq "MC") || ($qtype[$i] eq "MA") || ($qtype[$i] eq "FIB") || ($qtype[$i] eq "Ord")) { for (my $k=$numitems+$start[$i]-1; $k<$numitems+$end[$i]; $k++) { @{$answers{$k}} = (); if ($qtype[$i] eq "MC") { $items[$k] =~ tr/A-Z/a-z/; $items[$k] =~ s/\W//g; if ($foilformats[$i] eq "lcperiod" || $foilformats[$i] eq "lcparen" || $foilformats[$i] eq "lconeparen" || $foilformats[$i] eq "lcdotparen" || $foilformats[$i] eq "ucparen" || $foilformats[$i] eq "ucperiod" || $foilformats[$i] eq "uconeparen" || $foilformats[$i] eq "ucdotparen") { for (my $j=0; $j<@alphabet; $j++) { if ($alphabet[$j] eq $items[$k]) { push @{$answers{$k}}, $j; last; } } } elsif (($foilformats[$i] eq "romparen") || ($foilformats[$i] eq "romperiod") || ($foilformats[$i] eq "romoneparen") || ($foilformats[$i] eq "romdotparen")) { for (my $j=0; $j<@romans; $j++) { if ($romans[$j] eq $items[$k]) { push @{$answers{$k}}, $j; last; } } } } elsif (($qtype[$i] eq "MA") || ($qtype[$i] eq "Ord")) { $items[$k] =~ tr/A-Z/a-z/; my @corrects = split/$patterns{$ansrtypes[$i]}/,$items[$k]; foreach my $correct (@corrects) { $correct =~s/\W//g; if ($foilformats[$i] eq "lcperiod" || $foilformats[$i] eq "lcparen" || $foilformats[$i] eq "ucparen" || $foilformats[$i] eq "ucperiod") { for (my $j=0; $j<@alphabet; $j++) { if ($alphabet[$j] eq $correct) { push @{$answers{$k}}, $j; last; } } } elsif (($foilformats[$i] eq "romparen") || ($foilformats[$i] eq "romperiod") || ($foilformats[$i] eq "romoneparen") || ($foilformats[$i] eq "romdotparen")) { for (my $j=0; $j<@romans; $j++) { if ($romans[$j] eq $correct) { push @{$answers{$k}}, $j; last; } } } } } elsif ($qtype[$i] eq "FIB") { @{$answers{$k}} = split/$patterns{$ansrtypes[$i]}/,$items[$k]; for (my $j=0; $j<@{$answers{$k}}; $j++) { $answers{$k}[$j] =~ s/^\s+//; $answers{$k}[$j] =~ s/\s+$//; } } } } } } my $pooltarget = ''; my $pooldesc = ''; my @newquestions = (); my $numquestions = 0; my %qtype = (); my %qtext = (); my %qflag = (); $r->print(<<"END_OF_BLOCK");
END_OF_BLOCK if ($newdir ne "") { my @qn_file = (); my $qcount = 0; for (my $i=0; $i<$blocks; $i++) { if ($nums[$i] > 0) { if (($qtype[$i] eq "MC") || ($qtype[$i] eq "MA") || ($qtype[$i] eq "FIB") || ($qtype[$i] eq "Ord")) { for (my $j=$start[$i]-1; $j<$end[$i]; $j++) { my $answer = $j + $numitems; my $numans = scalar(@{$answers{$answer}}); my $foilcount = 0; if (($qtype[$i] eq "MC") || ($qtype[$i] eq "MA") || ($qtype[$i] eq "Ord")) { $foilcount = @{$multparts{$j}}; $foilcount --; } $qn_file[$qcount] = &create_mcq($newdir,\@{$multparts{$j}},\@{$answers{$answer}},$qtype[$i],$j); $qcount ++; push @newquestions, $question_id; } } elsif ($qtype[$i] eq "TF") { for (my $j=$start[$i]-1; $j<$end[$i]; $j++) { my $answer = $j + $numitems; $items[$answer] =~ s/^\s+//; $items[$answer] =~ s/\s+$//; $items[$answer] =~ s/\W//g; $items[$answer] =~ tr/A-Z/a-z/; my $answer_id = ''; if ($ansrtypes[$i] eq 'word' ) { if ($items[$answer] =~ m/true/) { $answer_id = 0; } else { $answer_id = 1; } } elsif ($ansrtypes[$i] eq 'lett') { if ($items[$answer] =~ m/^t/) { $answer_id = 0; } else { $answer_id = 1; } } $qn_file[$qcount] = create_ess($newdir,$answer_id,$items[$j],$items[$answer],$qtype[$i],$j); push @newquestions, $question_id; $qcount ++; } } elsif ($qtype[$i] eq "Ess") { for (my $j=$start[$i]-1; $j<$end[$i]; $j++) { my $answer = $j + $numitems; my $answer_id = ''; $qn_file[$qcount] = create_ess($newdir,$answer_id,$items[$j],$items[$answer],$qtype[$i],$j); push @newquestions, $question_id; $qcount ++; } } } } $r->print(qq| |); } else { $r->print(qq|
 
      Result of conversion of tesbank questions to LON-CAPA problems.
 
 Individual problem files have been created from the problems included in the textbank file:
    |); for (my $i=0; $i<@qn_file; $i++) { my $display = $i+1; $r->print(qq|
  • Problem $display file
  • |); } $r->print(qq|
  The problems must be published before they can be used in a course.
  No destination file was selected or created, so import of your questions could not proceed. Please return to the previous page and select a valid file into which to import the questions. |); for (my $i=0; $i<$blocks; $i++) { $r->print(qq| |); } $r->print(<<"END_OF_FAIL");
END_OF_FAIL return; } $r->print(<<"END_OF_BODY");  
END_OF_BODY } sub question_count { my ($qnumformat,$textref) = @_; my $text_in = join "\n", @{$textref}; $text_in = "\n ".$text_in; my $qpattern =''; if ($qnumformat eq "period") { $qpattern = '\d{1,}\.'; } elsif ($qnumformat eq "paren") { $qpattern = '\(\d{1,}\)'; } elsif ($qnumformat eq "number") { $qpattern = '\d{1,}'; } elsif ($qnumformat eq "leadparen") { $qpattern = '\(\d{1,}'; } elsif ($qnumformat eq "trailparen") { $qpattern = '\d{1,}\)'; } my @questions = split/[\r\n\f]+\s?$qpattern\s?/,$text_in; my $qcount = scalar(@questions); $qcount = $qcount/2; $qcount = int($qcount); return $qcount; } sub file_split { my ($startsref,$endsref,$numsref,$qnumformat,$foilsref,$textref,$multpartsref,$numitems,$qtyperef,$blocks) = @_; my $text_in = join "\n", @{$textref}; $text_in = "\n ".$text_in; my $dignum = length($numitems); my $numpat; if ($dignum > 1) { $numpat = ','.$dignum.'}'; } else { $numpat = '}'; } my $qpattern =''; if ($qnumformat eq "period") { $qpattern = '\d{1'.$numpat.'\.'; } elsif ($qnumformat eq "paren") { $qpattern = '\(\d{1'.$numpat.'\)'; } elsif ($qnumformat eq "number") { $qpattern = '\d{1'.$numpat; } elsif ($qnumformat eq "leadparen") { $qpattern = '\(\d{1'.$numpat; } elsif ($qnumformat eq "trailparen") { $qpattern = '\d{1'.$numpat.'\)'; } my @questions = split/[\r\n\f]+\s*$qpattern\s*/,$text_in; # my @questions = split/\n\s\d{1,3}\.\s/,$text_in; shift @questions; my %multparts = (); for (my $i=0; $i<$blocks; $i++) { if (${$numsref}[$i] > 0) { if ((${$qtyperef}[$i] eq "MC") || (${$qtyperef}[$i] eq "MA")) { my $splitstr = ''; if (${$foilsref}[$i] eq "lcperiod") { $splitstr = '[a-z]\.'; } elsif (${$foilsref}[$i] eq "lcparen") { $splitstr = '\([a-z]\)'; } elsif (${$foilsref}[$i] eq "lconeparen") { $splitstr = '[a-z]\)'; } elsif (${$foilsref}[$i] eq "lcdotparen") { $splitstr = '[a-z]\.\)'; } elsif (${$foilsref}[$i] eq "ucperiod") { $splitstr = '[A-Z]\.'; } elsif (${$foilsref}[$i] eq "ucparen") { $splitstr = '\([A-Z]\)'; } elsif (${$foilsref}[$i] eq "uconeparen") { $splitstr = '[A-Z]\)'; } elsif (${$foilsref}[$i] eq "ucdotparen") { $splitstr = '[A-Z]\.\)'; } elsif (${$foilsref}[$i] eq "romperiod") { $splitstr = '[ivx]+\.'; } elsif (${$foilsref}[$i] eq "romparen") { $splitstr = '\([ivx]+\)'; } elsif (${$foilsref}[$i] eq "romoneparen") { $splitstr = '[ivx]+\)'; } elsif (${$foilsref}[$i] eq "romdotparen") { $splitstr = '[ivx]+\.\)'; } for (my $j=${$startsref}[$i]-1; $j<${$endsref}[$i]; $j++) { @{$multparts{$j}} = split/[\r\n\f]+\s*$splitstr\s*/,$questions[$j]; chomp(@{$multparts{$j}}); } } elsif (${$qtyperef}[$i] eq "FIB") { for (my $j=${$startsref}[$i]-1; $j<${$endsref}[$i]; $j++) { @{$multparts{$j}} = ("$questions[$j]"); } } } } %{$multpartsref} = %multparts; return @questions; } # create_mcq builds an MC, MA, Ord or FIB question sub create_mcq { my ($newdir,$qstnref,$answerref,$qtype,$qnum) = @_; $qnum ++; if (length($qnum) == 1) { $qnum = "00".$qnum; } elsif (length($qnum) == 2) { $qnum = "0".$qnum; } my $qstn = ${$qstnref}[0]; my $numfoils = scalar(@{$qstnref}) - 1; my $datestamp = localtime; my $timestamp = time; my $libfile = 'question_'.$qnum; $libfile .= '.problem'; my $numansrs = scalar(@{$answerref}); my $output = qq| $qstn |; if ($qtype eq "MA") { $output .= qq| |; for (my $k=0; $k<@{$qstnref}-1; $k++) { $output .= " \n"; } chomp($output); $output .= qq| |; } if ($qtype eq "MC") { $output .= qq| |; for (my $k=0; $k<@{$qstnref}-1; $k++) { $output .= " \n"; } chomp($output); $output .= qq| |; } if ($qtype eq "Ord") { $output .= qq| |; for (my $k=0; $k<@{$qstnref}-1; $k++) { $output .= " ".${$qstnref}[$k+1]."\n"; } chomp($output); $output .= qq| |; } if ($qtype eq "FIB") { my $numerical = 1; for (my $i=0; $i<@{$answerref}; $i++) { if (${$answerref}[$i] =~ m/([^\d\.]|\.\.)/) { $numerical = 0; } } if ($numerical) { my $numans; my $tol; if (@{$answerref} == 1) { $tol = 5; $numans = $$answerref[0]; } else { my $min = $$answerref[0]; my $max = $$answerref[0]; for (my $i=1; $i<@{$answerref}; $i++) { if ($$answerref[$i]<=$min) { $min = $$answerref[$i]; } elsif ($$answerref[$i] >= $max) { $max = $$answerref[$i]; } } $numans = ($max + $min)/2; $tol = 100*($max - $min)/($numans*2); } $output .= qq| |; } else { if (@{$answerref} == 1) { $output .= qq| |; } else { for (my $i=0; $i<@{$answerref}; $i++) { ${$answerref}[$i] =~ s/\|/\|/g; } my $regexpans = join('|',@{$answerref}); $regexpans = '/('.$regexpans.')/'; $output .= qq| |; } } } open(PROB,">$newdir/$libfile"); print PROB $output; close PROB; return $libfile; } # create_ess builds an essay or True/False question sub create_ess { my ($newdir,$answer_id,$qstn,$answertxt,$qtype,$qnum) = @_; $qnum ++; if (length($qnum) == 1) { $qnum = "00".$qnum; } elsif (length($qnum) == 2) { $qnum = "0".$qnum; } my $libfile = 'question_'.$qnum; $libfile .= '.problem'; my $output = qq| $qstn|; my $answer = ''; my $answerlog = ''; if ($qtype eq "Ess") { $output .= qq| $answertxt |; } elsif ($qtype eq "TF") { $answer = $answer_id; $output .= qq| |; $output .= " "; if ($answer_id) { $output .= "False"; } else { $output .= "True"; } $output .= "\n"; $output .= " "; if ($answer_id) { $output .= "True"; } else { $output .= "False"; } $output .= qq| |; } open(PROB,">$newdir/$libfile"); print PROB $output; close PROB; return $libfile; } sub file_error { my ($r,$uname,$fn,$current_page); $r->print("No data here"); } # ---------------------------------------------------------------- Main Handler sub handler { my $r=shift; my $uname; my $udom; my $javascript = ''; my $page_name = ''; my $current_page = ''; my $qcount = ''; # # phase two: re-attach user # if ($env{'form.uploaduname'}) { $env{'form.filename'}='/priv/'.$env{'form.uploaduname'}.'/'. $env{'form.filename'}; } ($uname,$udom)= &Apache::loncacc::constructaccess($env{'form.filename'}, $r->dir_config('lonDefDomain')); unless (($uname) && ($udom)) { $r->log_reason($uname.' at '.$udom. ' trying to publish file '.$env{'form.filename'}. ' - not authorized', $r->filename); return HTTP_NOT_ACCEPTABLE; } my $fn; my $badfile = 0; if ($env{'form.filename'}) { $fn=$env{'form.filename'}; $fn=~s/^http\:\/\/[^\/]+\///; $fn=~s/^\///; $fn=~s{(~|priv/)($LONCAPA::username_re)}{}; $fn=~s/\/+/\//g; } else { $r->log_reason($env{'user.name'}.' at '.$env{'user.domain'}. ' unspecified filename for upload', $r->filename); return HTTP_NOT_FOUND; } my $pathname = &File::Basename::dirname($fn); my $fullpath = '/priv/'.$uname.$pathname; unless ($pathname eq '/') { $fullpath .= '/'; } my $dirpath = '/home/'.$uname.'/public_html'; my @text = (); if ($env{'form.phase'} eq 'three') { if (-e "$dirpath$fn") { open(TESTBANK,"<$dirpath$fn"); @text = ; close(TESTBANK); } else { $badfile = 1; } } # ----------------------------------------------------------- Start page output &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; my %loadentries; if ($env{'form.phase'} eq 'three') { $current_page = &display_control(); my @PAGES = ('Welcome','Blocks','Format','Target','Confirmation'); $page_name = $PAGES[$current_page]; if ($page_name eq 'Blocks') { $loadentries{'onload'} = "setElements()"; &jscript_one(\$javascript); } elsif ($page_name eq 'Format') { $qcount = question_count($env{'form.qnumformat'},\@text); &jscript_two(\$javascript,$qcount); } elsif ($page_name eq 'Target') { if ($env{'form.go'} eq "PreviousPage") { $loadentries{'onload'} = "setElements()"; } &jscript_three($fullpath,\$javascript); } elsif ($page_name eq 'Confirmation') { &jscript_four(\$javascript,$fullpath); } } $javascript = "\n"; $r->print(&Apache::loncommon::start_page('Upload testbank questions to Construction Space', $javascript, {'add_entries' => \%loadentries})); if (($uname ne $env{'user.name'}) || ($udom ne $env{'user.domain'})) { $r->print('

'.&mt('Co-Author').': '.$uname. &mt(' at ').$udom.'

'); } if ($env{'form.phase'} eq 'three') { if ($badfile) { &file_error($r,$uname,$fn,$current_page); } else { &display_zero ($r,$uname,$fn,$current_page,$fullpath) if $page_name eq 'Welcome'; &display_one ($r,$uname,$fn,$current_page,\@text) if $page_name eq 'Blocks'; &display_two ($r,$uname,$fn,$current_page,\@text,$qcount) if $page_name eq 'Format'; &display_three ($r,$uname,$fn,$current_page,\@text,$qcount) if $page_name eq 'Target'; &final_display ($r,$uname,$fn,$current_page,\@text) if $page_name eq 'Confirmation'; } } elsif ($env{'form.phase'} eq 'two') { my $flag = &Apache::lonupload::phasetwo($r,$fn,$uname,$udom,'testbank'); if ($flag eq 'ok') { my $current_page = 0; &display_zero($r,$uname,$fn,$current_page,$fullpath); } } else { &Apache::lonupload::phaseone($r,$fn,$uname,$udom,'testbank'); } $r->print(&Apache::loncommon::end_page()); return OK; } 1; __END__