--- loncom/xml/lonxml.pm 2007/05/31 04:15:59 1.447 +++ loncom/xml/lonxml.pm 2008/05/28 01:32:59 1.478 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # XML Parser Module # -# $Id: lonxml.pm,v 1.447 2007/05/31 04:15:59 albertel Exp $ +# $Id: lonxml.pm,v 1.478 2008/05/28 01:32:59 www Exp $ # # Copyright Michigan State University Board of Trustees # @@ -92,8 +92,10 @@ use Apache::loncacc(); use Apache::lonmaxima(); use Apache::lonlocal; -#================================================== Main subroutine: xmlparse +#==================================== Main subroutine: xmlparse + #debugging control, to turn on debugging modify the correct handler + $Apache::lonxml::debug=0; # keeps count of the number of warnings and errors generated in a parse @@ -125,6 +127,9 @@ $evaluate = 1; # stores the list of active tag namespaces @namespace=(); +# stores all Scrit Vars displays for later showing +my @script_var_displays=(); + # a pointer the the Apache request object $Apache::lonxml::request=''; @@ -132,6 +137,16 @@ $Apache::lonxml::request=''; $Apache::lonxml::counter=1; $Apache::lonxml::counter_changed=0; +# Part counter hash. In analysis mode, the +# problems can use this to record which parts increment the counter +# by how much. The counter subs will maintain this hash via +# their optional part parameters. Note that the assumption is that +# analysis is done in one request and therefore it is not necessary to +# save this information request-to-request. + + +%Apache::lonxml::counters_per_part = (); + #internal check on whether to look at style defs $Apache::lonxml::usestyle=1; @@ -316,13 +331,14 @@ sub xmlparse { } } } - } elsif ($env{'construct.style'} && ($env{'request.state'} eq 'construct')) { + } elsif ($env{'construct.style'} + && ($env{'request.state'} eq 'construct')) { my $location=&Apache::lonnet::filelocation('',$env{'construct.style'}); my $styletext=&Apache::lonnet::getfile($location); - if ($styletext ne '-1') { - %style_for_target = (%style_for_target, - &Apache::style::styleparser($target,$styletext)); - } + if ($styletext ne '-1') { + %style_for_target = (%style_for_target, + &Apache::style::styleparser($target,$styletext)); + } } #&printalltags(); my @pars = (); @@ -357,7 +373,16 @@ sub xmlparse { &clean_safespace($safeeval); + if (@script_var_displays) { + $finaloutput .= join('',@script_var_displays); + undef(@script_var_displays); + } + &init_state(); if ($env{'form.return_only_error_and_warning_counts'}) { + if ($env{'request.filename'}=~/\.(html|htm|xml)$/i) { + my $error=&verify_html($content_file_string); + if ($error) { $errorcount++; } + } return "$errorcount:$warningcount"; } return $finaloutput; @@ -610,6 +635,24 @@ sub callsub { return $currentstring; } +{ + my %state; + + sub init_state { + undef(%state); + } + + sub set_state { + my ($key,$value) = @_; + $state{$key} = $value; + return $value; + } + sub get_state { + my ($key) = @_; + return $state{$key}; + } +} + sub setup_globals { my ($request,$target)=@_; $Apache::lonxml::request=$request; @@ -618,8 +661,12 @@ sub setup_globals { $Apache::lonxml::default_homework_loaded=0; $Apache::lonxml::usestyle=1; &init_counter(); + &clear_bubble_lines_for_part(); + &init_state(); + &set_state('target',$target); @Apache::lonxml::pwd=(); @Apache::lonxml::extlinks=(); + @script_var_displays=(); @Apache::lonxml::ssi_info=(); $Apache::lonxml::post_evaluate=1; $Apache::lonxml::warnings_error_header=''; @@ -680,6 +727,7 @@ sub init_safespace { $safehole->wrap(\&Apache::chemresponse::chem_standard_order,$safeeval, '&chem_standard_order'); $safehole->wrap(\&Apache::response::check_status,$safeeval,'&check_status'); + $safehole->wrap(\&Apache::response::implicit_multiplication,$safeeval,'&implicit_multiplication'); $safehole->wrap(\&Apache::lonmaxima::maxima_eval,$safeeval,'&maxima_eval'); $safehole->wrap(\&Apache::lonmaxima::maxima_check,$safeeval,'&maxima_check'); @@ -790,6 +838,7 @@ sub init_safespace { $safehole->wrap(\&Math::Random::random_set_seed_from_phrase,$safeeval,'&random_set_seed_from_phrase'); $safehole->wrap(\&Math::Random::random_get_seed,$safeeval,'&random_get_seed'); $safehole->wrap(\&Math::Random::random_set_seed,$safeeval,'&random_set_seed'); + $safehole->wrap(\&Apache::loncommon::languages,$safeeval,'&languages'); $safehole->wrap(\&Apache::lonxml::error,$safeeval,'&LONCAPA_INTERNAL_ERROR'); $safehole->wrap(\&Apache::lonxml::debug,$safeeval,'&LONCAPA_INTERNAL_DEBUG'); $safehole->wrap(\&Apache::lonnet::logthis,$safeeval,'&LONCAPA_INTERNAL_LOGTHIS'); @@ -898,6 +947,9 @@ sub endredirection { } pop @Apache::lonxml::outputstack; } +sub in_redirection { + return ($Apache::lonxml::redirection > 0) +} sub end_tag { my ($tagstack,$parstack,$token)=@_; @@ -958,8 +1010,8 @@ sub decreasedepth { sub get_id { my ($parstack,$safeeval)=@_; my $id= &Apache::lonxml::get_param('id',$parstack,$safeeval); - if ($env{'request.state'} eq 'construct' && $id =~ /(\.|_)/) { - &error(&mt("IDs are not allowed to contain "_" or "."")); + if ($env{'request.state'} eq 'construct' && $id =~ /([._]|[^\w\d\s[:punct:]])/) { + &error(&mt("ID "[_1]" contains invalid characters, IDs are only allowed to contain letters, numbers, spaces and -",''.$id.'')); } if ($id =~ /^\s*$/) { $id = $Apache::lonxml::curdepth; } return $id; @@ -995,8 +1047,15 @@ sub get_all_text_unbalanced { } } return $result + } +######################################################################### +# # +# bubble line counter management # +# # +######################################################################### + =pod For bubble grading mode and exam bubble printing mode, the tracking of @@ -1012,16 +1071,32 @@ Increments the internal counter environm Optional Arguments: $increment - amount to increment by (defaults to 1) + Also 1 if the value is negative or zero. + $part_response - A concatenation of the part and response id + identifying exactly what is being 'answered'. + =cut sub increment_counter { - my ($increment) = @_; - if (defined($increment) && $increment gt 0) { - $Apache::lonxml::counter+=$increment; - } else { - $Apache::lonxml::counter++; + my ($increment, $part_response) = @_; + if ($env{'form.grade_target'} eq 'analyze') { return; } + if (!defined($increment) || $increment le 0) { + $increment = 1; + } + $Apache::lonxml::counter += $increment; + + # If the caller supplied the response_id parameter, + # Maintain its counter.. creating if necessary. + + if (defined($part_response)) { + if (!defined($Apache::lonxml::counters_per_part{$part_response})) { + $Apache::lonxml::counters_per_part{$part_response} = 0; + } + $Apache::lonxml::counters_per_part{$part_response} += $increment; + my $new_value = $Apache::lonxml::counters_per_part{$part_response}; } + $Apache::lonxml::counter_changed=1; } @@ -1047,7 +1122,7 @@ sub init_counter { } sub store_counter { - &Apache::lonnet::appenv(('form.counter' => $Apache::lonxml::counter)); + &Apache::lonnet::appenv({'form.counter' => $Apache::lonxml::counter}); $Apache::lonxml::counter_changed=0; return ''; } @@ -1068,7 +1143,7 @@ sub store_counter { sub restore_problem_counter { if (defined($state)) { - &Apache::lonnet::appenv(('form.counter' => $state)); + &Apache::lonnet::appenv({'form.counter' => $state}); } } sub get_problem_counter { @@ -1078,6 +1153,73 @@ sub store_counter { } } +=pod + +=item bubble_lines_for_part(part_response) + +Returns the number of lines required to get a response for +$part_response (this is just $Apache::lonxml::counters_per_part{$part_response} + +=cut + +sub bubble_lines_for_part { + my ($part_response) = @_; + + if (!defined($Apache::lonxml::counters_per_part{$part_response})) { + return 0; + } else { + return $Apache::lonxml::counters_per_part{$part_response}; + } +} + +=pod + +=item clear_bubble_lines_for_part + +Clears the hash of bubble lines per part. If a caller +needs to analyze several resources this should be called between +resources to reset the hash for each problem being analyzed. + +=cut + +sub clear_bubble_lines_for_part { + undef(%Apache::lonxml::counters_per_part); +} + +=pod + +=item set_bubble_lines(part_response, value) + +If there is a problem part, that for whatever reason +requires bubble lines that are not +the same as the counter increment, it can call this sub during +analysis to set its hash value explicitly. + +=cut + +sub set_bubble_lines { + my ($part_response, $value) = @_; + + $Apache::lonxml::counters_per_part{$part_response} = $value; +} + +=pod + +=item get_bubble_line_hash + +Returns the current bubble line hash. This is assumed to +be small so we return a copy + + +=cut + +sub get_bubble_line_hash { + return %Apache::lonxml::counters_per_part; +} + + +#-------------------------------------------------- + sub get_all_text { my($tag,$pars,$style)= @_; my $gotfullstack=1; @@ -1238,7 +1380,7 @@ sub writeallows { &Apache::lonnet::hreflocation($thisdir,&unescape($_))}=$thisurl; } @extlinks=(); - &Apache::lonnet::appenv(%httpref); + &Apache::lonnet::appenv(\%httpref); } sub register_ssi { @@ -1254,6 +1396,12 @@ sub do_registered_ssi { &Apache::lonnet::ssi($url,%form); } } + +sub add_script_result { + my ($display) = @_; + push(@script_var_displays, $display); +} + # # Afterburner handles anchors, highlights and links # @@ -1332,44 +1480,98 @@ SIMPLECONTENT return $filecontents; } +sub verify_html { + my ($filecontents)=@_; + if ($filecontents!~/(?:\<|\<\;)(?:html|xml)[^\<]*(?:\>|\>\;)/is) { + return &mt('File does not have [_1] or [_2] starting tag','<html>','<xml>'); + } + if ($filecontents!~/(?:\<|\<\;)\/(?:html|xml)(?:\>|\>\;)/is) { + return &mt('File does not have [_1] or [_2] ending tag','<html>','<xml>'); + } + if ($filecontents!~/(?:\<|\<\;)(?:body|frameset)[^\<]*(?:\>|\>\;)/is) { + return &mt('File does not have [_1] or [_2] starting tag','<body>','<frameset>'); + } + if ($filecontents!~/(?:\<|\<\;)\/(?:body|frameset)[^\<]*(?:\>|\>\;)/is) { + return &mt('File does not have [_1] or [_2] ending tag','<body>','<frameset>'); + } + return ''; +} + +sub renderingoptions { + my %langchoices=('' => ''); + foreach (&Apache::loncommon::languageids()) { + if (&Apache::loncommon::supportedlanguagecode($_)) { + $langchoices{&Apache::loncommon::supportedlanguagecode($_)} + = &Apache::loncommon::plainlanguagedescription($_); + } + } + return + ''. + &mt('Language: '). + &Apache::loncommon::select_form($env{'form.languages'},'languages', + %langchoices).' + + '. + &mt('Math Rendering: '). + &Apache::loncommon::select_form($env{'form.texengine'},'texengine', + ('' => '', + 'tth' => 'tth', + 'jsMath' => 'jsMath', + 'mimetex' => 'mimetex')).' + '; +} sub inserteditinfo { - my ($result,$filecontents,$filetype)=@_; + my ($filecontents, $filetype, $filename)=@_; $filecontents = &HTML::Entities::encode($filecontents,'<>&"'); -# my $editheader='Edit below