#!/usr/bin/perl use CGI qw/:standard/; use CGI::Carp qw{ fatalsToBrowser }; CGI::ReadParse(); use Data::Dumper; use lib qq{/home/steven/www/cgi-bin/local_libraries/lib/perl5/site_perl/5.8.0/}; use XML::Parser; use XML::Simple; #Global Variables my $DEBUG = 0; my $TITLE = qq{Steven's FlashCard Program}; # A dispatch table my %action = ( default => \&default_visual_layout, manage => \&manage, save => \&save_controller, use => \&test_controller, ); # The default root decision structure if ( $in{func_choice} =~ m|\w+| ) { print header,start_html($TITLE); lc($in{func_choice} =~ s/\s.*//); $action{ $in{func_choice} }->(); } else { $action{'default'}->(); } exit; ############################################################################### # SUBROUTINES ############################################################################### ############################################################################### # CONTROLLER ROUTINES ############################################################################### sub test_controller{ $file = $in{filename}; if ($in{writefile}){ if ($in{writefile} eq 'rfile'){ @correct_ids = split (//,$in{saveids}); @correct_ids = grep {/\d/} @correct_ids; my $rayRef = transform_xml_file_data_structure_to_rayRef( parse_in_xml_file($file) ); my %better_struct; for (@$rayRef) { $better_struct{$_->{id}} = $_; } $rayRef = \%better_struct; my @tempRay; for (@correct_ids){ $_++; push (@tempRay, $rayRef->{$_}); } my $big_ol_string = turn_qml_entry_array_to_string(\@tempRay); save_string_to_file(preformat_string($big_ol_string), $in{writefile_name}); print start_form; print submit('','Back to Beginning'); print end_form; }else{ @correct_ids = split (//,$in{saveids}); @correct_ids = grep {/\d/} @correct_ids; my $rayRef = transform_xml_file_data_structure_to_rayRef( parse_in_xml_file($file) ); my %better_struct; for (@$rayRef) { $better_struct{$_->{id}} = $_; } $rayRef = \%better_struct; my @tempRay; for (@correct_ids){ $_++; push (@tempRay, $rayRef->{$_}); } my $big_ol_string = turn_qml_entry_array_to_string(\@tempRay); save_string_to_file(preformat_string($big_ol_string), $in{writefile_name}); print start_form; print submit('','Back to Beginning'); print end_form; } return; } if ($in{submit} =~ /Evaluate/){ my @list = grep {/qa_set_entry.*/} (keys(%in)); my $as = make_answer_data_structure (\@list); my $results = grade_answer_struct($as); my $xml_struct = parse_in_xml_file($file); my $struct_size = keys( %$xml_struct ); my $good_answer_count = keys( % {+ $results->{good_answer} } ); my $wrong_answer_count = keys( % {+ $results->{wrong_answer} } ); evaluate_good_answers(answers => $results->{good_answer}, answers_size => $good_answer_count, total_size => $struct_size); evaluate_wrong_answers(answers => $results->{wrong_answer}, answers_size => $wrong_answer_count, total_size => $struct_size); post_test_visual_layout($results->{good_answer}, $results->{wrong_answer} ); return; } $file = $in{filename}; my $rayRef = transform_xml_file_data_structure_to_rayRef( parse_in_xml_file($file) ); my %better_struct; for (@$rayRef) { $better_struct{$_->{id}} = $_; } my $bs = \%better_struct; my $rayRef = randomize_entry_order_and_answer_order($rayRef); my $count= scalar(@$rayRef); my @index_ray = (1..$count); my @rand_index_ray = shuffle(@index_ray); my @big_shuffled_array; foreach my $dex (@rand_index_ray){ push (@big_shuffled_array, $bs->{$dex}); } my $rayRef = \@big_shuffled_array; print start_form; foreach my $entry (@$rayRef){ print "\n"; print hr; question_form_from_entry($entry) ; } print hidden('func_choice','evaluate'); print hidden('filename','$file'); print submit('submit',qq{Evaluate Responses}); print end_form; } ########################################################################## # Below this point are all the controller functions related to # editing the QML file ########################################################################## sub modify_existing_file{ return_to_main_button(); print start_form; print h2(qq{Add entry}); my $hidden_code = sub { print hidden(-name=>'filename', -default=>$file); }; entry_form('standalone' => 1, 'hidden_params' => $hidden_code); print end_form; print hr,h2(qq{Modify / Delete an Entry}); my $rayRef = transform_xml_file_data_structure_to_rayRef( parse_in_xml_file($file) ); xml_file_visual_layout($rayRef); } sub manage{ my $file = $in{filename} if (error_check_filename($in{filename})); # This conditional tests for the actual writing of deleted # or modified entries if ($in{mod_del_flag}=~/delete/i){ my $rayRef = transform_xml_file_data_structure_to_rayRef( parse_in_xml_file($file) ); $rayRef= remove_entry_with_id(ray=>$rayRef, delEntry=>$in{mwId}); my $big_ol_string = turn_qml_entry_array_to_string($rayRef); save_string_to_file(preformat_string($big_ol_string), $file); backButton( sub { print hidden(func_choice,'manage'), hidden(filename, $in{filename}) } ); return; }elsif ($in{mod_del_flag} =~ /edit/i){ my $rayRef = transform_xml_file_data_structure_to_rayRef( parse_in_xml_file($file) ); my $entry = return_entry_with_id(ray=> $rayRef, selEntry=>$in{mwId}); modify_entry_form($entry); return; } if (-e $file){ ######################################################################### # ADD Write Routine ######################################################################### if ($in{submit} =~ /Add/){ my @tempRay; # Get the file contents and put them in a format such that # turn_qml_entry_array_to_string can create the XML string my $rayRef = transform_xml_file_data_structure_to_rayRef( parse_in_xml_file($file) ); # Get the CGI buffer new file and put it at the end push (@tempRay,@$rayRef,create_qml_entry_from_CGI()); my $big_ol_string = turn_qml_entry_array_to_string(\@tempRay); save_string_to_file(preformat_string($big_ol_string), $file); backButton( sub { print hidden(func_choice,'manage'), hidden(filename, $in{filename}) } ); return; }elsif ($in{submit} =~ /Save/){ # This does the actual write in the case that we have # had an entry modified my @tempRay; # Get the entry that is to replace the old id entry $replacement_entry = create_qml_entry_from_CGI(); # Get the XML file as an array my $rayRef = transform_xml_file_data_structure_to_rayRef( parse_in_xml_file($file) ); my $replaced_ray_ref = replace_entry_with_entry(ray=>$rayRef, selEntry=>$replacement_entry->{id}, repEntry=>$replacement_entry, @_); my $big_ol_string = turn_qml_entry_array_to_string($replaced_ray_ref); save_string_to_file(preformat_string($big_ol_string), $file); backButton( sub { print hidden(func_choice,'manage'), hidden(filename, $in{filename}) } ); return; } #end if =~ ADD modify_existing_file($file); }else{ if ($in{writeNewFile}){ # A binding to actually saving the data that comes # from the create_new_qml_file subroutine. # # This produces only 'backtrack' visual data, and only # is meant for saving the new file. ######################################################################## # NEW FILE w/ENTRY CREATE ROUTINE ######################################################################## my @tempRay; push (@tempRay,create_qml_entry_from_CGI()); my $big_ol_string = turn_qml_entry_array_to_string(\@tempRay); save_string_to_file(preformat_string($big_ol_string), $file); backButton( sub { print hidden(func_choice,'create'), hidden(filename, $in{filename}) } ); }else{ create_new_qml_file($file) } } } sub save_controller{ } sub create_new_qml_file{ my $file = shift; my $hidden_code = sub { print hidden(-name=>'filename', -default=>$file); print hidden(-name=>'writeNewFile', -default=>'1'); }; print p(qq{Creating: $file}); entry_form('standalone' => 1, 'hidden_params' => $hidden_code); } ############################################################################### # VISUAL LAYOUTS ############################################################################### sub post_test_visual_layout{ print hr; my $right_ref= shift; my $wrong_ref= shift; my @rray = keys(%$right_ref); my @wray = keys(%$wrong_ref); print start_form; print hidden('func_choice', 'write_file'); print hidden(-name=>'saveids', -default=>\@rray); print hidden('filename', $file); print "Save ", b("right"), " answers to file: ", textfield(-name=>'writefile_name', -default=>'', -size=>50, -maxlength=>50), submit('writefile','rfile'),p; print end_form; print start_form; print hidden('func_choice', 'write_file'); print hidden(-name=>'saveids', -default=>\@wray); print hidden('filename', $file); print "Save ", b("wrong"), " answers to file: ", textfield(-name=>'writefile_name', -default=>'', -size=>50, -maxlength=>50),submit('writefile','wfile'),p; print end_form; print start_form; $in{func_choice} = undef; print hidden('func_choice', 'default'); print hidden('filename', $file); print submit('writefile','Back to Beginning'); print end_form; } sub question_form_from_entry{ my $entry = shift(); print "\n", hidden("qa_set_entry_id_" . $entry->{id}, $entry->{id}), "\n"; print hidden("qa_set_entry_id_" . $entry->{id} . "_sol_id" , $entry->{solutionId}); print $entry->{question}, p(); %labels = % {+ $entry->{answers} }; @dex_ray = qw/1 2 3 4/; @rand_dex_ray = shuffle(@dex_ray); print radio_group(-name=>"qa_set_entry_id_" . $entry->{id} . "_choice_id", -values=>\@rand_dex_ray, -default=>'', -linebreak=>'true', -labels=>\%labels, -attributes=>\%attributes); } sub return_to_main_button{ print start_form; print hidden('filename', $file); print submit(mod_del_flag,qq{Return To Main Menu}); print end_form; } sub xml_file_visual_layout{ my @entries = @{+ shift }; foreach (@entries){ print start_form; modify_entry_visual_layout($_); print hidden('filename', $file), hidden('func_choice', 'manage'); print end_form; } delete_all; } sub modify_entry_visual_layout{ # This is the subroutine that creates a display of the question # the answers and the solution afterwards. my $entry = shift; print p("[$entry->{id}].", $entry->{question}); # A long-winded way to say 1..4 ;) for (1..scalar(keys (%{+ $entry->{answers}}))){ print p({-style=>'margin-left: 10px;'},"[A$_:]", $entry->{answers}->{$_}); } print p({-style=>'margin-left: 10px; color: red;'}, "[Solution: $entry->{solutionId}]"); print "\n", hidden('mwId',"$entry->{id}"), "\n"; print submit("mod_del_flag",Edit); print submit('mod_del_flag','Delete'); print hr; } sub backButton{ $cRef = shift; print start_form; $cRef->(); print submit('submit', 'Back'); } # This is the modify an entry layout sub modify_entry_form{ my $entry = shift(); my $q = new CGI; $q->delete_all; $in{question_field}=$entry->{question}; $in{answer_field1}=$entry->{answers}->{1}; $in{answer_field2}=$entry->{answers}->{2}; $in{answer_field3}=$entry->{answers}->{3}; $in{answer_field4}=$entry->{answers}->{4}; $in{solution}=$entry->{solutionId}; print start_form; $in{question_field} = $entry->{question}; print "Enter your question:",br, textfield(-name=>'question_field', -default=>'', -size=>50, -maxlength=>150),p; print hidden(filename, $in{filename}); print "Enter possible answers",p; for (1..4){ my $backTag = answer_field . $_; $in{backTag} = $entry->{answers}->{$_}; print "[$_] ", textfield(-name=>"answer_field$_", -default=>$entry->{answers}->{$_}, -size=>50, -maxlength=>150), p; } $in{solution}=$entry->{solutionId}; print p("Enter number of correct answer"), textfield("solution",$entry->{solutionId}, 2,10),p; print hidden(-name=>'func_choice', -default=>'save'), hidden('id', $entry->{id}); $params{hidden_params}->() if $params{hidden_params}; undef %in; print submit('submit','Save Updated Entry'); } # This creates the standard set of text fields that are to be used # to enter a QML entry. sub entry_form{ my %params = (standalone => 0, @_); print start_form if ($params{standalone}); print "Enter your question:",br, textfield(-name=>'question_field', -default=>'', -size=>50, -maxlength=>150),p; print "Enter possible answers",p; for (1..4){ print "[$_] ", textfield(-name=>"answer_field$_", -default=>"", -size=>50, -maxlength=>150), p; } print p("Enter number of correct answer"),textfield("solution","", 2,10),p; print hidden(-name=>'func_choice', -default=>'save'); $params{hidden_params}->() if $params{hidden_params}; print submit('submit','Add Entry'); } sub default_visual_layout{ print header,start_html($TITLE); print h1({-style=>'text-align: center;'},$TITLE); print p(qq{Please choose between the following options:}); my %labels = ( use => 'Review an extant file', manage => "Create / manage a file", ); print start_form, radio_group('func_choice', [ 'use', 'manage', ], 'create','true',\%labels,\%attributes); print "Filename: ", textfield('filename',''),p; print submit('submit','Get Started'); } ############################################################################### # ATOMIC ACTIONS ############################################################################### sub evaluate_wrong_answers{ %params = (answers => '', answers_size => '', total_size => '', @_); return unless ($params{answers}); $p = \%params; $success_percentage = ( $p->{answers_size} / $p->{total_size} *100); print h2("You got the following question(s) wrong:"); my $rayRef = transform_xml_file_data_structure_to_rayRef( parse_in_xml_file($file) ); my %better_struct; for (@$rayRef){ $better_struct{$_->{id}} = $_; } $rayRef = \%better_struct; my @liList; for (keys(% {+ $p->{answers} } ) ){ $_++; push (@liList, $rayRef->{$_}->{question}); } print ul( li({-type=>'disc'},\@liList) ); } sub evaluate_good_answers{ %params = (answers => '', answers_size => '', total_size => '', @_); $p = \%params; $success_percentage = ( $p->{answers_size} / $p->{total_size} *100); if ($p->{total_size} == $p->{answers_size}){ print h2("100% - You're awesome!") }elsif(0 == $p->{answers_size}){ print h2("0% - You need some more practice!") }else{ print h2("Your success percentage is: $success_percentage"); } print h2("You got the following question(s) right:"); my $rayRef = transform_xml_file_data_structure_to_rayRef( parse_in_xml_file($file) ); my %better_struct; for (@$rayRef){ $better_struct{$_->{id}} = $_; } $rayRef = \%better_struct; my @liList; for (keys(% {+ $p->{answers} } ) ){ $_++; push (@liList, $rayRef->{$_}->{question}); } print ul( li({-type=>'disc'},\@liList) ); } sub grade_answer_struct{ my $as = shift; my %good_answer, %wrong_answer; my @tempRay= keys(%$as); $limit = $#tempRay; for (0..$limit){ my $counter = $_; if ($as->{$counter}->{choice} == $as->{$counter}->{solution}){ $good_answer{$counter} = $as->{$counter}->{solution} }else{ $wrong_answer{$counter} = $as->{$counter}->{solution} } } my %return_hash=( 'good_answer' => \%good_answer, 'wrong_answer' => \%wrong_answer); return \%return_hash; } sub make_answer_data_structure{ my $rayRef = shift(@_); @array = @$rayRef; my %answer_struct; my $count = $#array /3; for (0..$count){ shift(@array); $answer_struct{$_}->{solution}=$in{shift(@array)}; $answer_struct{$_}->{choice}=$in{shift(@array)}; } return \%answer_struct; } sub randomize_entry_order_and_answer_order{ return $_[0]; my $rayRef=shift(); my @ray=@$rayRef; my $count = $#ray; ++$count; my %better_struct; foreach (@$rayRef){ $better_struct{$_->{id}} = $_; } my %rand_struct; @keysray = keys(%better_struct); preprint (@keysray); use List::Util 'shuffle'; @shuffled_keys_ray = shuffle(@keysray); my @randRay; for (@shuffled_keys_ray){ my @answers = qw/1 2 3 4/; my @rand_answers = shuffle(@answers); my %ref = ($_ => \@rand_answers); push (@randRay, \%ref); } foreach $rand_element (@randRay){ preprint($rand_element); } exit; } sub turn_qml_entry_array_to_string{ my @array = @{+ shift()}; my $master_string; # I had originally been using $ds->{id}, but had cases where # this would produce id conflict. I'd rather not have to # pass around a "count" parameter so that I don't get conflict. # # Seems easier to re-index. my $id_counter = 1; foreach $ds (@array){ ($var = < $ds->{question} $ds->{answers}->{1} $ds->{answers}->{2} $ds->{answers}->{3} $ds->{answers}->{4} $ds->{solutionId} FINIS ; $master_string .= $var; $id_counter++; } return $master_string; } sub error_check_filename{ $file = shift; print p("file to be checked was: $file"),p if ($DEBUG); unless ($file){ print h1("SYSTEM ERROR:"), p("The filename that was to be modified was empty: $!"); exit 1; } return 1; } sub create_qml_entry_from_CGI{ my %answerStruct; for (1..4){ my $x = 'answer_field' . $_; $answerStruct{$_} = $in{$x} } # It is worth noting that this is the canonical # structure of a QML entry. my %entry = ( id => 1, question => $in{question_field}, answers => \%answerStruct, solutionId => $in{solution} ); return \%entry; } sub preformat_string{ $x = shift; $y = join ('', "\n", $x, qq{}); return $y; } sub save_string_to_file{ my $bigString = shift; my $f = shift; open (X, ">$f") or die ("Could not open $f: $!"); print X $bigString; close X; print p("File has been saved.") if (-e $file); } sub transform_xml_file_data_structure_to_rayRef{ my $d = shift; my @tempRay; # Remember, we started counting these at 1, as reflected in # the tag! my $counter = 1; while ($d->{$counter}){ my %answerStruct; for (1..4){ $answerStruct{$_} = ($d->{$counter}->{answer}->{$_}->{content}) } # Convert to match the canonical entry def: my %entry = ( id => $counter, question => $d->{$counter}->{question}, answers => \%answerStruct, solutionId => $d->{$counter}->{solution}, ); push (@tempRay, \%entry); $counter++ } return \@tempRay } sub parse_in_xml_file{ my $file = shift; # create object $xml = new XML::Simple(ForceArray => [entry]); # read XML file $data = $xml->XMLin($file); return $data->{entry} } sub remove_entry_with_id{ my %param = (ray=>'', delEntry=>'', @_); my @returnRay; die ("Missing crit entry!: $!") unless ( ($param{ray}) and ($param{delEntry}) ); for (@ {+ $param{ray} } ){ if ($_->{id} == $param{delEntry}){ print p("I need to remove this entry"); }else{ push (@returnRay, $_); } } return \@returnRay; } sub return_entry_with_id{ my %param = (ray=>'', selEntry=>'', @_); my @returnRay; die ("Missing crit entry!: $!") unless ( ($param{ray}) and ($param{selEntry}) ); for (@ {+ $param{ray} } ){ if ($_->{id} == $param{selEntry}){ return $_ } } return \@returnRay; } sub replace_entry_with_entry{ my %param = (ray=>'', selEntry=>'', repEntry=>'', @_); my @returnRay; die ("Missing crit entry!: $!") unless ( ($param{ray}) and ($param{selEntry}) and ($param{repEntry}) ); for (@ {+ $param{ray} } ){ if ($_->{id} == $param{selEntry}){ push (@returnRay, $param{repEntry}) }else{ push (@returnRay, $_); } } return \@returnRay; } ############################################################################### # DEBUG methods ############################################################################### sub preprint{ print "
";
  print Dumper $_[0];
  print "
"; }