--- rat/lonuserstate.pm 2007/06/28 23:26:38 1.118.2.2 +++ rat/lonuserstate.pm 2011/10/06 23:30:06 1.137.2.1 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Construct and maintain state and binary representation of course for user # -# $Id: lonuserstate.pm,v 1.118.2.2 2007/06/28 23:26:38 albertel Exp $ +# $Id: lonuserstate.pm,v 1.137.2.1 2011/10/06 23:30:06 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -62,6 +62,12 @@ my %hiddenurl; # this URL (or complete f # ----------------------------------- Remove version from URL and store in hash +sub versionerror { + my ($uri,$usedversion,$unusedversion)=@_; + return '
'.&mt('Version discrepancy: resource [_1] included in both version [_2] and version [_3]. Using version [_2].', + $uri,$usedversion,$unusedversion).'
'; +} + sub versiontrack { my $uri=shift; if ($uri=~/\.(\d+)\.\w+$/) { @@ -69,7 +75,9 @@ sub versiontrack { $uri=~s/\.\d+\.(\w+)$/\.$1/; unless ($hash{'version_'.$uri}) { $hash{'version_'.$uri}=$version; - } + } elsif ($version!=$hash{'version_'.$uri}) { + $errtext.=&versionerror($uri,$hash{'version_'.$uri},$version); + } } return $uri; } @@ -109,13 +117,24 @@ sub processversionfile { sub loadmap { my ($uri,$parent_rid)=@_; if ($hash{'map_pc_'.$uri}) { - $errtext.=&mt('
Multiple use of sequence/page [_1]! The course will not function properly.',$uri); + $errtext.='

'. + &mt('Multiple use of sequence/page [_1]! The course will not function properly.',''.$uri.''). + '

'; return; } $pc++; my $lpc=$pc; $hash{'map_pc_'.$uri}=$lpc; $hash{'map_id_'.$lpc}=$uri; + if ($parent_rid =~ /^(\d+)\.\d+$/) { + my $parent_pc = $1; + if (defined($hash{'map_hierarchy_'.$parent_pc})) { + $hash{'map_hierarchy_'.$lpc}=$hash{'map_hierarchy_'.$parent_pc}.','. + $parent_pc; + } else { + $hash{'map_hierarchy_'.$lpc}=$parent_pc; + } + } # Determine and check filename my $fn=&Apache::lonnet::filelocation('',&putinversion($uri)); @@ -174,13 +193,23 @@ sub loadmap { } if ($randomize) { - my $seed = (split(/\./,$parent_rid))[1]; - if ($randompickseed{$parent_rid}) { - $seed = $randompickseed{$parent_rid}; + if (!$env{'request.role.adv'}) { + my $seed; + if (defined($randompickseed{$parent_rid})) { + $seed = $randompickseed{$parent_rid}; + } else { + my ($mapid,$resid)=split(/\./,$parent_rid); + my $symb= + &Apache::lonnet::encode_symb($hash{'map_id_'.$mapid}, + $resid,$hash{'src_'.$parent_rid}); + + $seed = $symb; + } + + my $rndseed=&Apache::lonnet::rndseed($seed); + &Apache::lonnet::setup_random_from_rndseed($rndseed); + @map_ids=&Math::Random::random_permutation(@map_ids); } - my $rndseed=&Apache::lonnet::rndseed($seed); - &Apache::lonnet::setup_random_from_rndseed($rndseed); - my @map_ids=&Math::Random::random_permutation(@map_ids); my $from = shift(@map_ids); my $from_rid = $lpc.'.'.$from; $hash{'map_start_'.$uri} = $from_rid; @@ -197,6 +226,17 @@ sub loadmap { $hash{'map_finish_'.$uri}= $from_rid; $hash{'type_'.$from_rid}='finish'; } + + $parser = HTML::TokeParser->new(\$instr); + $parser->attr_encoded(1); + # last parse out the mapalias params so as to ignore anything + # refering to non-existant resources + while (my $token = $parser->get_token) { + next if ($token->[0] ne 'S'); + if ($token->[1] eq 'param') { + &parse_mapalias_param($token,$lpc); + } + } } @@ -224,7 +264,7 @@ sub parse_resource { $turi=~/\.(\w+)$/; my $embstyle=&Apache::loncommon::fileembstyle($1); if ($token->[2]->{'external'} eq 'true') { # external - $turi=~s/^http\:\/\//\/adm\/wrapper\/ext\//; + $turi=~s/^https?\:\/\//\/adm\/wrapper\/ext\//; } elsif ($turi=~/^\/*uploaded\//) { # uploaded if (($embstyle eq 'img') || ($embstyle eq 'emb') @@ -255,7 +295,7 @@ sub parse_resource { $hash{'ids_'.$idsuri}=''.$rid; } - if ($turi=~/\/(syllabus|aboutme|navmaps|smppg|bulletinboard)$/) { + if ($turi=~/\/(syllabus|aboutme|navmaps|smppg|bulletinboard|viewclasslist)$/) { $turi.='?register=1'; } @@ -320,7 +360,10 @@ sub parse_condition { my $rid=$lpc.'.'.$token->[2]->{'id'}; $hash{'kind_'.$rid}='cond'; - $cond[$#cond+1]=$token->[2]->{'value'}; + + my $condition = $token->[2]->{'value'}; + $condition =~ s/[\n\r]+/ /gs; + push(@cond, $condition); $hash{'condid_'.$rid}=$#cond; if ($token->[2]->{'type'}) { $cond[$#cond].=':'.$token->[2]->{'type'}; @@ -351,9 +394,6 @@ sub parse_param { } else { $hash{'param_'.$referid}=''.$newparam; } - if ($token->[2]->{'name'}=~/^parameter_(0_)*mapalias$/) { - $hash{'mapalias_'.$token->[2]->{'value'}}=$referid; - } if ($token->[2]->{'name'}=~/^parameter_(0_)*randompick$/) { $randompick{$referid}=$token->[2]->{'value'}; } @@ -362,7 +402,6 @@ sub parse_param { } if ($token->[2]->{'name'}=~/^parameter_(0_)*randomorder$/) { $randomorder{$referid}=$token->[2]->{'value'}; - &Apache::lonnet::logthis("roing $referid ".$randomorder{$referid}); } if ($token->[2]->{'name'}=~/^parameter_(0_)*encrypturl$/) { if ($token->[2]->{'value'}=~/^yes$/i) { @@ -376,6 +415,17 @@ sub parse_param { } } +sub parse_mapalias_param { + my ($token,$lpc) = @_; + my $referid=$lpc.'.'.$token->[2]->{'to'}; + return if (!exists($hash{'src_'.$referid})); + + if ($token->[2]->{'name'}=~/^parameter_(0_)*mapalias$/) { + &count_mapalias($token->[2]->{'value'},$referid); + $hash{'mapalias_'.$token->[2]->{'value'}}=$referid; + } +} + # --------------------------------------------------------- Simplify expression sub simplify { @@ -387,7 +437,7 @@ sub simplify { # 8&8=8 $expression=~s/([^_\.\d])([_\.\d]+)\&\2([^_\.\d])/$1$2$3/g; # 8|8=8 - $expression=~s/([^_\.\d])([_\.\d]+)\|\2([^_\.\d])/$1$2$3/g; + $expression=~s/([^_\.\d])([_\.\d]+)(?:\|\2)+([^_\.\d])/$1$2$3/g; # (5&3)&4=5&3&4 $expression=~s/\(([_\.\d]+)((?:\&[_\.\d]+)+)\)\&([_\.\d]+[^_\.\d])/$1$2\&$3/g; # (((5&3)|(4&6)))=((5&3)|(4&6)) @@ -404,7 +454,7 @@ sub simplify { sub traceroute { my ($sofar,$rid,$beenhere,$encflag,$hdnflag)=@_; my $newsofar=$sofar=simplify($sofar); - unless ($beenhere=~/\&$rid\&/) { + unless ($beenhere=~/\&\Q$rid\E\&/) { $beenhere.=$rid.'&'; my ($mapid,$resid)=split(/\./,$rid); my $symb=&Apache::lonnet::encode_symb($hash{'map_id_'.$mapid},$resid,$hash{'src_'.$rid}); @@ -438,7 +488,8 @@ sub traceroute { if (defined($hash{'map_start_'.$hash{'src_'.$rid}})) { $sofar=$newsofar= &traceroute($sofar, - $hash{'map_start_'.$hash{'src_'.$rid}},'&', + $hash{'map_start_'.$hash{'src_'.$rid}}, + $beenhere, $encflag || $encurl{$rid}, $hdnflag || $hiddenurl{$rid}); } @@ -539,8 +590,9 @@ sub accinit { $acchash{'acc.res.'.$short.'.'}='&:0&'; my $courseuri=$uri; $courseuri=~s/^\/res\///; - &Apache::lonnet::delenv('(acc\.|httpref\.)'); - &Apache::lonnet::appenv(%acchash); + my $regexp = 1; + &Apache::lonnet::delenv('(acc\.|httpref\.)',$regexp); + &Apache::lonnet::appenv(\%acchash); } # ---------------- Selectively delete from randompick maps and hidden url parms @@ -596,7 +648,7 @@ sub hiddenurls { } # --------------------------------------- append randomout entry to environment if ($randomoutentry) { - &Apache::lonnet::appenv('acc.randomout' => $randomoutentry); + &Apache::lonnet::appenv({'acc.randomout' => $randomoutentry}); } } @@ -610,90 +662,52 @@ sub readmap { my $uri; $short=~s/\//\_/g; unless ($uri=$cenv{'url'}) { - &Apache::lonnet::logthis("WARNING: ". + &Apache::lonnet::logthis('WARNING: '. "Could not load course $short."); return ('',&mt('No course data available.'));; } @cond=('true:normal'); - open(LOCKFILE,">$fn.db.lock"); + unless (open(LOCKFILE,">$fn.db.lock")) { + $errtext.='
'.&mt('Map not loaded - Lock file could not be opened when reading map:').' '.$fn.'.'; + $retfurl = ''; + return ($retfurl,$errtext); + } my $lock=0; + my $gotstate=0; if (flock(LOCKFILE,LOCK_EX|LOCK_NB)) { $lock=1; - unlink($fn.'.db'); - unlink($fn.'_symb.db'); - unlink($fn.'.state'); - unlink($fn.'parms.db'); + &unlink_tmpfiles($fn); } undef %randompick; undef %hiddenurl; undef %encurl; $retfrid=''; - if ($lock && (tie(%hash,'GDBM_File',"$fn.db",&GDBM_WRCREAT(),0640)) && - (tie(%parmhash,'GDBM_File',$fn.'_parms.db',&GDBM_WRCREAT(),0640))) { - %hash=(); - %parmhash=(); - $errtext=''; - $pc=0; - &processversionfile(%cenv); - my $furi=&Apache::lonnet::clutter($uri); - $hash{'src_0.0'}=&versiontrack($furi); - $hash{'title_0.0'}=&Apache::lonnet::metadata($uri,'title'); - $hash{'ids_'.$furi}='0.0'; - $hash{'is_map_0.0'}=1; - loadmap($uri,'0.0'); - if (defined($hash{'map_start_'.$uri})) { - &Apache::lonnet::appenv("request.course.id" => $short, - "request.course.fn" => $fn, - "request.course.uri" => $uri); - $env{'request.course.id'}=$short; - &traceroute('0',$hash{'map_start_'.$uri},'&'); - &accinit($uri,$short,$fn); - &hiddenurls(); - } -# ------------------------------------------------------- Put versions into src - foreach my $key (keys(%hash)) { - if ($key=~/^src_/) { - $hash{$key}=&putinversion($hash{$key}); - } elsif ($key =~ /^(map_(?:start|finish|pc)_)(.*)/) { - my ($type, $url) = ($1,$2); - my $value = $hash{$key}; - $hash{$type.&putinversion($url)}=$value; - } - } -# ---------------------------------------------------------------- Encrypt URLs - foreach my $id (keys(%encurl)) { -# $hash{'src_'.$id}=&Apache::lonenc::encrypted($hash{'src_'.$id}); - $hash{'encrypted_'.$id}=1; - } -# ----------------------------------------------- Close hashes to finally store -# --------------------------------- Routine must pass this point, no early outs - $hash{'first_rid'}=$retfrid; - my ($mapid,$resid)=split(/\./,$retfrid); - $hash{'first_mapurl'}=$hash{'map_id_'.$mapid}; - my $symb=&Apache::lonnet::encode_symb($hash{'map_id_'.$mapid},$resid,$hash{'src_'.$retfrid}); - $retfurl=&add_get_param($hash{'src_'.$retfrid},{ 'symb' => $symb }); - if ($hash{'encrypted_'.$retfrid}) { - $retfurl=&Apache::lonenc::encrypted($retfurl,(&Apache::lonnet::allowed('adv') ne 'F')); - } - $hash{'first_url'}=$retfurl; - unless ((untie(%hash)) && (untie(%parmhash))) { - &Apache::lonnet::logthis("WARNING: ". - "Could not untie coursemap $fn for $uri."); - } -# ---------------------------------------------------- Store away initial state - { - my $cfh; - if (open($cfh,">$fn.state")) { - print $cfh join("\n",@cond); - } else { - &Apache::lonnet::logthis("WARNING: ". - "Could not write statemap $fn for $uri."); - } - } + my ($untiedhash,$untiedparmhash,$tiedhash,$tiedparmhash); + if ($lock) { + if (tie(%hash,'GDBM_File',"$fn.db",&GDBM_WRCREAT(),0640)) { + $tiedhash = 1; + if (tie(%parmhash,'GDBM_File',$fn.'_parms.db',&GDBM_WRCREAT(),0640)) { + $tiedparmhash = 1; + $gotstate = &build_tmp_hashes($uri,$fn,$short,\%cenv); + unless ($gotstate) { + &Apache::lonnet::logthis('Failed to write statemap at first attempt '.$fn.' for '.$uri.'.
'); + } + $untiedparmhash = untie(%parmhash); + unless ($untiedparmhash) { + &Apache::lonnet::logthis('WARNING: '. + 'Could not untie coursemap parmhash '.$fn.' for '.$uri.'.'); + } + } + $untiedhash = untie(%hash); + unless ($untiedhash) { + &Apache::lonnet::logthis('WARNING: '. + 'Could not untie coursemap hash '.$fn.' for '.$uri.'.'); + } + } flock(LOCKFILE,LOCK_UN); - close(LOCKFILE); - } else { + } + unless ($lock && $tiedhash && $tiedparmhash) { # if we are here it is likely because we are already trying to # initialize the course in another child, busy wait trying to # tie the hashes for the next 90 seconds, if we succeed forward @@ -702,34 +716,113 @@ sub readmap { if ($lock) { # Got the lock but not the DB files flock(LOCKFILE,LOCK_UN); + $lock = 0; } - untie(%hash); - untie(%parmhash); - &Apache::lonnet::logthis("WARNING: ". - "Could not tie coursemap $fn for $uri."); + if ($tiedhash) { + unless($untiedhash) { + untie(%hash); + } + } + if ($tiedparmhash) { + unless($untiedparmhash) { + untie(%parmhash); + } + } + &Apache::lonnet::logthis('WARNING: '. + "Could not tie coursemap $fn for $uri."); + $tiedhash = ''; + $tiedparmhash = ''; my $i=0; while($i<90) { $i++; sleep(1); - if (flock(LOCKFILE,LOCK_EX|LOCK_NB) && - (tie(%hash,'GDBM_File',"$fn.db",&GDBM_READER(),0640))) { - if (tie(%parmhash,'GDBM_File',$fn.'_parms.db',&GDBM_READER(),0640)) { - $retfurl='/adm/navmaps'; - &Apache::lonnet::appenv("request.course.id" => $short, - "request.course.fn" => $fn, - "request.course.uri" => $uri); - untie(%hash); - untie(%parmhash); - last; - } - } - untie(%hash); - untie(%parmhash); + if (flock(LOCKFILE,LOCK_EX|LOCK_NB)) { + $lock = 1; + if (tie(%hash,'GDBM_File',"$fn.db",&GDBM_READER(),0640)) { + $tiedhash = 1; + if (tie(%parmhash,'GDBM_File',$fn.'_parms.db',&GDBM_READER(),0640)) { + $tiedparmhash = 1; + if (-e "$fn.state") { + $retfurl='/adm/navmaps'; + &Apache::lonnet::appenv({"request.course.id" => $short, + "request.course.fn" => $fn, + "request.course.uri" => $uri}); + $untiedhash = untie(%hash); + $untiedparmhash = untie(%parmhash); + $gotstate = 1; + last; + } + $untiedparmhash = untie(%parmhash); + } + $untiedhash = untie(%hash); + } + } } - flock(LOCKFILE,LOCK_UN); - close(LOCKFILE); + if ($lock) { + flock(LOCKFILE,LOCK_UN); + $lock = 0; + if ($tiedparmhash) { + unless ($untiedparmhash) { + &Apache::lonnet::logthis('WARNING: '. + 'Could not untie coursemap parmhash '.$fn.' for '.$uri.'.'); + } + } + if ($tiedparmhash) { + unless ($untiedhash) { + &Apache::lonnet::logthis('WARNING: '. + 'Could not untie coursemap hash '.$fn.' for '.$uri.'.'); + } + } + } + } + unless ($gotstate) { + $lock = 0; + &Apache::lonnet::logthis('WARNING: '. + 'Could not read statemap '.$fn.' for '.$uri.'.'); + &unlink_tmpfiles($fn); + if (flock(LOCKFILE,LOCK_EX|LOCK_NB)) { + $lock=1; + } + undef %randompick; + undef %hiddenurl; + undef %encurl; + $retfrid=''; + if ($lock) { + if (tie(%hash,'GDBM_File',"$fn.db",&GDBM_WRCREAT(),0640)) { + if (tie(%parmhash,'GDBM_File',$fn.'_parms.db',&GDBM_WRCREAT(),0640)) { + $gotstate = &build_tmp_hashes($uri,$fn,$short,\%cenv); + unless ($gotstate) { + &Apache::lonnet::logthis('WARNING: '. + 'Failed to write statemap at second attempt '.$fn.' for '.$uri.'.'); + } + unless (untie(%parmhash)) { + &Apache::lonnet::logthis('WARNING: '. + 'Could not untie coursemap parmhash '.$fn.'.db for '.$uri.'.'); + } + } else { + &Apache::lonnet::logthis('WARNING: '. + 'Could not tie coursemap '.$fn.'__parms.db for '.$uri.'.'); + } + unless (untie(%hash)) { + &Apache::lonnet::logthis('WARNING: '. + 'Could not untie coursemap hash '.$fn.'.db for '.$uri.'.'); + } + } else { + &Apache::lonnet::logthis('WARNING: '. + 'Could not tie coursemap '.$fn.'.db for '.$uri.'.'); + } + flock(LOCKFILE,LOCK_UN); + $lock = 0; + } else { + &Apache::lonnet::logthis('WARNING: '. + 'Could not obtain lock to tie coursemap hash '.$fn.'.db for '.$uri.'.'); + } + } + close(LOCKFILE); + unless (($errtext eq '') || ($env{'request.course.uri'} =~ m{^/uploaded/})) { + &Apache::lonmsg::author_res_msg($env{'request.course.uri'}, + $errtext); } - &Apache::lonmsg::author_res_msg($env{'request.course.uri'},$errtext); # ------------------------------------------------- Check for critical messages my @what=&Apache::lonnet::dump('critical',$env{'user.domain'}, @@ -742,6 +835,91 @@ sub readmap { return ($retfurl,$errtext); } +sub build_tmp_hashes { + my ($uri,$fn,$short,$cenvref) = @_; + unless(ref($cenvref) eq 'HASH') { + return; + } + my %cenv = %{$cenvref}; + my $gotstate = 0; + %hash=(); + %parmhash=(); + $errtext=''; + $pc=0; + &clear_mapalias_count(); + &processversionfile(%cenv); + my $furi=&Apache::lonnet::clutter($uri); + $hash{'src_0.0'}=&versiontrack($furi); + $hash{'title_0.0'}=&Apache::lonnet::metadata($uri,'title'); + $hash{'ids_'.$furi}='0.0'; + $hash{'is_map_0.0'}=1; + &loadmap($uri,'0.0'); + if (defined($hash{'map_start_'.$uri})) { + &Apache::lonnet::appenv({"request.course.id" => $short, + "request.course.fn" => $fn, + "request.course.uri" => $uri}); + $env{'request.course.id'}=$short; + &traceroute('0',$hash{'map_start_'.$uri},'&'); + &accinit($uri,$short,$fn); + &hiddenurls(); + } + $errtext .= &get_mapalias_errors(); +# ------------------------------------------------------- Put versions into src + foreach my $key (keys(%hash)) { + if ($key=~/^src_/) { + $hash{$key}=&putinversion($hash{$key}); + } elsif ($key =~ /^(map_(?:start|finish|pc)_)(.*)/) { + my ($type, $url) = ($1,$2); + my $value = $hash{$key}; + $hash{$type.&putinversion($url)}=$value; + } + } +# ---------------------------------------------------------------- Encrypt URLs + foreach my $id (keys(%encurl)) { +# $hash{'src_'.$id}=&Apache::lonenc::encrypted($hash{'src_'.$id}); + $hash{'encrypted_'.$id}=1; + } +# ----------------------------------------------- Close hashes to finally store +# --------------------------------- Routine must pass this point, no early outs + $hash{'first_rid'}=$retfrid; + my ($mapid,$resid)=split(/\./,$retfrid); + $hash{'first_mapurl'}=$hash{'map_id_'.$mapid}; + my $symb=&Apache::lonnet::encode_symb($hash{'map_id_'.$mapid},$resid,$hash{'src_'.$retfrid}); + $retfurl=&add_get_param($hash{'src_'.$retfrid},{ 'symb' => $symb }); + if ($hash{'encrypted_'.$retfrid}) { + $retfurl=&Apache::lonenc::encrypted($retfurl,(&Apache::lonnet::allowed('adv') ne 'F')); + } + $hash{'first_url'}=$retfurl; +# ---------------------------------------------------- Store away initial state + { + my $cfh; + if (open($cfh,">$fn.state")) { + print $cfh join("\n",@cond); + $gotstate = 1; + } else { + &Apache::lonnet::logthis("WARNING: ". + "Could not write statemap $fn for $uri."); + } + } + return $gotstate; +} + +sub unlink_tmpfiles { + my ($fn) = @_; + if ($fn =~ m{^\Q$Apache::lonnet::perlvar{'lonDaemons'}\E/tmp/}) { + my @files = qw (.db _symb.db .state _parms.db); + foreach my $file (@files) { + if (-e $fn.$file) { + unless (unlink($fn.$file)) { + &Apache::lonnet::logthis("WARNING: ". + "Could not unlink ".$fn.$file.""); + } + } + } + } + return; +} + # ------------------------------------------------------- Evaluate state string sub evalstate { @@ -778,10 +956,47 @@ sub evalstate { } } } - &Apache::lonnet::appenv('user.state.'.$env{'request.course.id'} => $state); + &Apache::lonnet::appenv({'user.state.'.$env{'request.course.id'} => $state}); return $state; } +{ + my %mapalias_cache; + sub count_mapalias { + my ($value,$resid) = @_; + push(@{ $mapalias_cache{$value} }, $resid); + } + + sub get_mapalias_errors { + my $error_text; + foreach my $mapalias (sort(keys(%mapalias_cache))) { + next if (scalar(@{ $mapalias_cache{$mapalias} } ) == 1); + my $count; + my $which = + join('
  • ', + map { + my $id = $_; + if (exists($hash{'src_'.$id})) { + $count++; + } + my ($mapid) = split(/\./,$id); + &mt('Resource "[_1]"
    in Map "[_2]"', + $hash{'title_'.$id}, + $hash{'title_'.$hash{'ids_'.$hash{'map_id_'.$mapid}}}); + } (@{ $mapalias_cache{$mapalias} })); + next if ($count < 2); + $error_text .= '
    '. + &mt('Error: Found the mapalias "[_1]" defined multiple times.', + $mapalias). + '
    '; + } + &clear_mapalias_count(); + return $error_text; + } + sub clear_mapalias_count { + undef(%mapalias_cache); + } +} 1; __END__ @@ -804,37 +1019,33 @@ of course for user. This is part of the LearningOnline Network with CAPA project described at http://www.lon-capa.org. -=head1 HANDLER SUBROUTINE - -There is no handler subroutine. - -=head1 OTHER SUBROUTINES +=head1 SUBROUTINES -=over 4 +=over -=item * +=item loadmap() -loadmap() : Loads map from disk +Loads map from disk -=item * +=item simplify() -simplify() : Simplify expression +Simplify expression -=item * +=item traceroute() -traceroute() : Build condition hash +Build condition hash -=item * +=item accinit() -accinit() : Cascading conditions, quick access, parameters +Cascading conditions, quick access, parameters -=item * +=item readmap() -readmap() : Read map and all submaps +Read map and all submaps -=item * +=item evalstate() -evalstate() : Evaluate state string +Evaluate state string =back