#!/usr/bin/perl ############################################################# # The Shodor Education Foundation # # Brian Block - 6/21/2006 # # v2j_make_form.cgi # # create form options for the vensim2java model # # Based on code written by Dave Joiner for Stella2Java # # # # Purpose: v2j_make_form.cgi is based on a completely # # revised parsing method with a more object oriented # # design. Ideally, this keeps things more organized and # # any future large-scale modifications will be simpler. # ############################################################# use CGI qw/:standard *table start_ul/; #load standard CGI routines use CGI::Pretty qw( :html3 ); #format html source so it isnt in 1 line #contains the vensim documentation, as input by the user $user_input=param('user_input'); #issues with ^M in input. ^M is a carrage return, referred to by octal key \015 $user_input =~ tr/[\015]//; #the idea behind the OOD of this script is based on the fact #that in order to create the outfile that is sent to the applet-making #backend, the script has to have an understanding of the model and #how each element connects. s2j uses a series of arrays, but this #seemed to make more sense to me. # #there is the main container 'MODEL' which contains hashes of #the objects that make up the vensim model (stocks, flows, constants, #and aux variables) #######################################START_DEFINE_CLASSES #///////////////////////////MODEL CONTAINER package MODEL; sub new{ #constructor equivalent my($type) = shift; my($self) = { "stock" => {}, #initially empty "flow" => {}, # " " "constant" => {}, # " " "variable" => {} # " " }; our $stock_count; our $flow_count; our $var_count; our $constant_count; bless($self, $type); return($self); } sub print_model_contents{ #used to check that the vensim data is parsed properly and that #the MODEL is built correctly. strictly for debugging purposes. my($self) = shift; my($debug_file) = shift; #parsed_input.txt - used for debugging my ($key); my ($index) = 0; open (PARSEFILE, "> $debug_file"); #DO THIS TYPE OF THING FOR EVERY PROPERTY OF A NODE, AND #FOR EVERY OBJECT TYPE IN A SEPERATE FXN (hash traversals #are random, therefore the $objtype throws some things off). while (($objtype, $obj) = each %$self){ if ($objtype eq 'stock'){ print PARSEFILE "STOCKS:\n"; foreach $key (keys %$obj){ print PARSEFILE "$key = " . $self->{$objtype}->{$key}->{'value'} . " \(" . $self->{$objtype}->{$key}->{'units'} . "\) \n"; } print PARSEFILE "\n\n"; } elsif ($objtype eq 'constant'){ print PARSEFILE "CONSTANTS:\n"; foreach $key (keys %$obj){ print PARSEFILE "$key = " . $self->{$objtype}->{$key}->{'value'} . " \(" . $self->{$objtype}->{$key}->{'units'} . "\) \n"; } print PARSEFILE "\n\n"; } elsif ($objtype eq 'variable'){ print PARSEFILE "AUX VARIABLES:\n"; foreach $key (keys %$obj){ print PARSEFILE "$key = " . $self->{$objtype}->{$key}->{'value'} . " \(" . $self->{$objtype}->{$key}->{'units'} . "\) \n" . "is_lookup= " . $self->{$objtype}->{$key}->{'is_lookup'} . "\n"; } print PARSEFILE "\n\n"; } elsif ($objtype eq 'flow'){ print PARSEFILE "FLOWS:\n"; foreach $key (keys %$obj){ print PARSEFILE "$key = " . $self->{$objtype}->{$key}->{'value'} . " \(" . $self->{$objtype}->{$key}->{'units'} . "\) \n" . "flow_to: " . $self->{$objtype}->{$key}->{'flow_to'} . "\n" . "flow_from: " . $self->{$objtype}->{$key}->{'flow_from'} . "\n" . "is_lookup= " . $self->{$objtype}->{$key}->{'is_lookup'} . "\n"; } print PARSEFILE "\n\n"; } } close(PARSEFILE); } sub add_stock{ #the 'self' here is the MODEL object (passed automatically), whereas #the indexing key (stock name) is passed manually my($self) = shift; my($key) = shift; $self->{'stock'}->{$key} = STOCK->new(); $stock_count += 1; } sub add_flow{ my($self) = shift; my($key) = shift; $self->{'flow'}->{$key} = FLOW->new(); $flow_count += 1; } sub add_constant{ my($self) = shift; my($key) = shift; $self->{'constant'}->{$key} = VENSIM_VARIABLE->new(); $constant_count += 1; } sub add_vensim_variable{ #everything left over in this category is deemed an 'aux variable' my($self) = shift; my($key) = shift; $self->{'variable'}->{$key} = VENSIM_VARIABLE->new(); $var_count += 1; } #////////////////////////////////////////// #//////////////////////////PARENT CLASS #this is the main 'variable' class inherited by #each of the other classes. package VENSIM_VARIABLE; sub new{ #constructor my($type) = shift; #pops class name off @_ my($self) = { #stores class data in hash "units" => undef, "value" => undef, "is_lookup" => 0, #bool to see if var is a graph "is_ite" => 0, "ites" => [] }; #$self is a reference to a hash bless($self, $type); #the 'bless' command changes an object's type # in this case from HASH to VENSIM_VARIABLE return($self); } #///////////////////////////////////////// #///////////////////////////FLOW CLASS package FLOW; @ISA = (VENSIM_VARIABLE); #inherit methods from parent sub new{ my($type) = shift; my($self) = VENSIM_VARIABLE->new(); #inherit properties from parent #the flow_to/from keys are specific to flows #and point to where they flow to and what they flow #from (default = "NULL") $self->{"flow_to"} = "NULL"; $self->{"flow_from"} = "NULL"; bless($self, $type); return($self); } #///////////////////////////////////////// #///////////////////////////STOCK CLASS package STOCK; @ISA = (VENSIM_VARIABLE); sub new{ my($type) = shift; my($initial_value) = shift; #unique property to STOCKs my($self) = VENSIM_VARIABLE->new(); $self->{"initial_value"} = undef; bless($self, $type); return($self); } #////////////////////////////////////// package main; #END_DEFINE_CLASSES########################################## ###############################################START_DATA_PARSE $model = new MODEL; #container for all vensim objects #################temp file creation $tmp = "/tmp"; $tmp_num = "$$"; #gets a unique number $tmp_dir = "v2j$tmp_num"; #makes a tmp folder $template_path = "/home/httpd/cgi-bin/misctools/vensim2java/template"; $input_file = "$tmp/$tmp_dir/input.txt"; #contains the user $short_input_file = "$tmp/$tmp_dir/short_input.txt"; #used for the expand/collapse #option on the form page $parsed_input = "$tmp/$tmp_dir/parsed_input.txt"; #used for print_model_contents (debugging) system "mkdir $tmp/$tmp_dir"; system "chmod -R 755 $tmp/$tmp_dir"; system "rm $tmp/$tmp_dir/*"; system "cp $template_path/* $tmp/$tmp_dir"; open(TEMPFILE, "> $input_file"); print TEMPFILE $user_input; close(TEMPFILE); #end temp file creation############# open(INFILE, "$input_file") || die "$input_file not found"; @vensim_data = ""; $i=0; while($line = ){ #read input line by line chomp $line; $vensim_data[$i] = $line; $i+=1; } close(INFILE); #@vensim_data stores each line with original spacing/formatting (excluding # the chomped part) #sort through array and format multi-line strings into one line. then, using #different methods, sort the data into the appropriate object. $total_number_of_lines = @vensim_data; for ($i=0; $i<$total_number_of_lines; $i+=1){ $current_line = $vensim_data[$i]; if ($current_line =~ /^\(\d+\)/){ #line starts with: "()" #$current_line format status: original spacing - the only lines that #dont have leading whitespace are ones that start with an object id #with the format "(n)..." where n is the id number $current_line =~ s/^\(\d+\)//; #remove id #so now we know the current line starts with an object name (now that #the id has been removed. the string format currently has a leading space, #an unformated object name (vensim allows all kinds of things). after #another potential space, there's the "=" assignment operator. #the only object type (at least the only one i've seen so far) that #doesnt have an = sign is a named lookup. so check if the line doesnt #have an = sign, and if it doesnt, add one after valid name characters. if ($current_line !~ /\=/){ if ($current_line =~ /^([\w\s]*)/){ $testing = $1; $current_line =~ s/$testing/$testing\=/; } } #split line at the "=" sign to check validity of object name (left hand side) @split_on_equals = split("\=",$current_line); #now split_on_equals contains 2 entries - the first is the unformatted #object name, and the second is the rest of the line $potential_var = shift @split_on_equals; #pops off first element #now test and format the object name: if (!(&is_valid_object_name($potential_var))){ &html_error("$potential_var"); } $potential_var = &format_object_name($potential_var); #now we have a nicely formatted and readable object name. the next #step is to perform similar operations on the equation associated #with this object $current_object_value = shift @split_on_equals; ####EQUATION_BUILDING_START############################# #keep concating following lines, until you reach the line #that starts with 'Units' $current_object_value = &strip_whitespace($current_object_value); $next_line=$vensim_data[$i+=1]; #look at next line #while equation is still being read... while (!($next_line =~ /^\s*Units/)){ $current_object_value = $current_object_value . &strip_whitespace($next_line); $next_line=($vensim_data[$i+=1]); } #remove spaces around operators, but replace them with _ around #object names $current_object_value = &format_value_equation($current_object_value); #################################EQUATION_BUILDING_END### if( &IS_STOCK($current_object_value)){ my $stock_equation = &get_lone_value($current_object_value); my $stock_initial_value = &get_initial_value($current_object_value); my $stock_units = &get_units($next_line); $model->add_stock($potential_var); $model->{'stock'}->{$potential_var}->{'value'} = $stock_equation; $model->{'stock'}->{$potential_var}->{'initial_value'} = $stock_initial_value; $model->{'stock'}->{$potential_var}->{'units'} = $stock_units; } elsif( &IS_CONSTANT($current_object_value)){ my $constant_value = $current_object_value; my $constant_units = &get_units($next_line); $model->add_constant($potential_var); $model->{'constant'}->{$potential_var}->{'value'} = $constant_value; $model->{'constant'}->{$potential_var}->{'units'} = $constant_units; } else{ my $variable_value = $current_object_value; my $variable_units = &get_units($next_line); my $lookup_boolean = 0; #used to see if aux var is a vensim lookup table $model->add_vensim_variable($potential_var); $model->{'variable'}->{$potential_var}->{'value'} = $variable_value; $model->{'variable'}->{$potential_var}->{'units'} = $variable_units; $model->{'variable'}->{$potential_var}->{'is_lookup'} = &IS_LOOKUP($variable_value); $model->{'variable'}->{$potential_var}->{'is_ite'} = &IS_ITE($variable_value); #if a named lookup was found, push its name onto a stack. each aux #var and flow will be checked for instances of this name, which will #mean that those vars/flows are equivalent to a regular lookup. if ($model->{'variable'}->{$potential_var}->{'is_lookup'} == 2){ push @named_lookups, $potential_var; } } } } my %object_counter = &get_number_of_objects(); if ($object_counter{totalcount} == 0){ &html_empty_error(); } #after named lookups are found, change the equations to the equivalent #of regular lookups (WITH_LOOKUP) &replace_named_lookups($model, @named_lookups); #search each stock to see what variables it contains and #set those as flows &get_flows($model); &remove_old_temp_files(); #intended to remove any tempfiles created more than 30 min ago $model->print_model_contents($parsed_input); &html_start($user_input); #END_DATA_PARSE################################################## #####################################START_HELPER_FUNCTIONS sub remove_old_temp_files{ my $TIME_ON_SERVER = 3600; #in seconds...3600 sec = 1 hour my $line, $filename, $time_last_modified, $file_loc; my $tmp = "/tmp"; my $dir_contents = "/$tmp/dir_contents.txt"; system "ls -l /tmp/ > $dir_contents"; open(INFILE, "$dir_contents"); my $current_time = time(); while($line = ){ chomp($line); $line =~ m/\d\d:\d\d (.*)/; $filename = $1; if ($filename =~ /^v2j.*/){ $file_loc = "/$tmp/$filename"; $time_last_modified = (stat($file_loc))[9]; #the stat fxn returns an array of variables containing #information on the given file...the 9th element is the #last time the variable was modified if (($current_time - $time_last_modified) > $TIME_ON_SERVER){ &rm($filename); } } } } sub rm{ #system remove command my $name = shift; system "rm -rf /tmp/$name"; } sub is_valid_object_name{ #returns false if object name has any chars besides alphanumeric, underscore, #or whitespace my ($tempname) = shift; if ($tempname =~ /[^\w\s]/){ return 0; } return 1; } sub format_object_name{ #input: unformatted string that passes the validity test #actions: strip leading and trailing spaces, then replace #any spaces remaining with underscores my ($tempname) = shift; #delete lead/trailing whitespace $tempname = &strip_whitespace($tempname); #swap remaining spaces with _ $tempname =~ s/\s+/_/g; return $tempname; } sub IS_ITE{ #boolean function that returns true if a variable starts with vensim's #if-then-else syntax my ($line) = shift; if ($line =~ m/^IF_THEN_ELSE/i){ return 1; } else {return 0}; } sub parse_ite{ #this function is the precursor to parsing nested if-then-elses. it splits #up a variable's value, then passes the tokens to recursive_ite, which actually #handles the nested if-then-elses. $nested_ite_variable_name = shift; my $variable_value = shift; my @tokens; $nested_ite_counter = 0; @parsed_equations = ""; while ($variable_value =~ /\)\)/){ #split stacked parens (e.g. ite((mod(1,2))) $variable_value =~ s/\)\)/\),\)/g; # would become ite(,(mod(1,2),),) - this } # allows the offset to be checked with each paren while ($variable_value =~ /\(\(/){ $variable_value =~ s/\(\(/\(,\(/g; } @tokens = split(/\,/, $variable_value); &recursive_ite(1, @tokens); if ($parsed_equations[0] == ""){ shift @parsed_equations; } return @parsed_equations; } sub recursive_ite{ #this function steps through a value (split up into tokens) and matches #segments based on parentheses. if an if-then-else is found nested inside another, #it makes an entirely new variable and places a reference to that variable #inside the outer if-then-else. this allows for future implementation of #individual manipulation of each if-then-else (e.g. with sliders). note that #this feature is not yet implemented, but it will be easier to do later on. my $offset = shift; my @toks = @_; my $ite = shift @toks; while (&get_offset($ite) != 0){ #while the parentheses dont even out... if ($toks[0] =~ /^IF_THEN_ELSE\(/){ #start a new instance if nested @toks = &recursive_ite(1,@toks); # ite is found. } elsif ($toks[0] eq ""){ #if splitting results in a comma next to another comma, shift @toks; #an empty token is created. this removes it. } else{ $ite = $ite . "," . (shift @toks); #keep concating the string if everything } # checks out. } $ite =~ s/,+/,/g; #get rid of the commas that were previously added $ite =~ s/\(,/\(/g; # for easier parsability. $ite =~ s/,\)/\)/g; push @parsed_equations, $ite; #this "static" array holds each if-then-else found #after an if-then-else is found, push a reference to it as the next token unshift @toks, ($nested_ite_variable_name . "__" . $nested_ite_counter); $nested_ite_counter++; return @toks; } sub is_ifthenelse{ #this function checks if the given string contains vensim syntax #for if-then-else and parses it to the appropriate inf file #syntax. i wanted to have this done when the other string #formatting features were applied, but the spaces in the name #caused errors later on. therefore, it is called on each #objects 'value' when the outfile is built my ($line) = shift; my ($parsed_token_counter) = 0; #used to put parsed tokens in the right spot my (@tokens,@parsed_tokens); my ($i); if ($line =~ /^IF_THEN_ELSE\(/i){ $line =~ s/^IF_THEN_ELSE\(//i; #remove leading marker $line =~ s/\)$//; #remove trailing parenthesies @tokens = split(/\,/, $line); #split on each comma $i = 0; #rebuild based on balancing parentheses while ($i < @tokens){ $line = $tokens[$i]; $i++; while (&get_offset($line) != 0){ $line = $line . "," . $tokens[$i]; $i++; } $parsed_tokens[$parsed_token_counter] = $line; $parsed_token_counter++; } $line = "IF $parsed_tokens[0] then $parsed_tokens[1] ELSE $parsed_tokens[2]"; } return $line; } sub get_offset{ #keeps track of balanced parentheses. #i.e.: () = 0, (() = 1, ())) = -2. my ($string) = shift; my ($offset); while ( $string =~ /(\(|\))/g ) { if ($1 =~ /\(/) {$offset++;} elsif ($1 =~ /\)/) {$offset--;} } return $offset; } sub format_value_equation{ #this function gets the value of a stock/flow/auxvar in equation format #and filters out the object names, removing unnecesary whitespace and #replacing spaces in variable names with underscores my($line) = shift; @object_names = split(/(\+|\-|\/|\*|\,|\(|\)|\>|\<|\=|\<\=|\>\=)/, $line); my ($fixedline) = ""; foreach (@object_names){ $tempname = &replace_spaces(&strip_whitespace($_)); $fixedline = "$fixedline" . "$tempname$1"; } return $fixedline; } sub IS_CONSTANT{ #this function checks the syntax of an objects value. #if it contains anything besides numbers, decimals, "e", #+, -, or ^, then return false. otherwise return true my ($line) = &purge_whitespace(shift); if ($line =~ /[^\d\.e\+\-\^]/){ return 0; } else{ return 1; } } sub get_units { #if the line starts with "Units:" (as per the vensim #syntax), then it contains the units associated with #the vensim object. my ($line) = &purge_whitespace(shift); $line =~ s/^Units\://; return $line; } sub get_lone_value { #input: value of a stock...returns just the equation (initial value excluded) my ($line) = shift; $line =~ s/^INTEG\(//i; $line =~ s/\,.*//; return $line; } sub get_initial_value { #input: value of a stock where the initial value is separated from #the equation by a comma...return numerical value after comma my ($line) = shift; $line =~ s/^[^,]*,//; #remove everything up to first comma $line =~ s/\)\Z//; #remove trailing ')' return $line; } sub strip_whitespace { #removes leading and trailing whitespace my ($line) = @_; chomp $line; $line =~ s/\A\s+//g; $line =~ s/\s+\Z//g; return $line; } #removes all whitespace sub purge_whitespace { my ($line) = @_; chomp $line; $line =~ s/\s+//g; return $line; } sub IS_STOCK { #input: string (line with digit tag at start). #it seems that the function INTEGR() is specific to #stocks - that is, if "INTEG(" immediately follows #the first "=" sign, the object in question is a stock my $sample_line = shift; #pop string off @_ if ( $sample_line =~ /^INTEG ?\(/ ){ return 1; } else { return 0; } } sub parse_lookup{ #input: string containing the value of an aux var #the format of a "lookup" parameter in vensim is # WITH LOOKUP(variable,([(xmin,ymin)-(xmax,ymax)],(x1,y1),(x2,y2)...)) # this fxn returns a string of the format # variable:(x1,y1),(x2,y2)... my $line = shift; my $varname, $datapoints; #remove lookup function call $line =~ s/^WITH_LOOKUP\(//; $line =~s/\)$//; #matches the variable for lookup and assigns to varname #then matches the datapoints, which are located after the #range syntax [(xmin,ymin)-(xmax,ymax)] # -var- --------------------------range syntax------------------ -points- $line =~ m/([^,]*),.*\[\(\d+[\d\.]*\,\d+[\d\.]*\)\-\(\d+[\d\.]*\,\d+[\d\.]*\)\],(.*)\)$/; $varname = "\(" . $1 . "\)"; #add parentheses in case of equation $datapoints = $2; return $varname . ":" . $datapoints; } sub IS_LOOKUP{ #input: string containing the value of an aux var. #checks to see if it starts with "WITH_LOOKUP(" and #returns an appropriate boolean value. # #named lookups are like lookup templates - they contain a table with no #values to apply the dataset to. the syntax starts with the data range #[(xmin,ymin)-(xmax,ymax)]. if this is found, return 2. my $line = shift; if ($line =~ /^WITH_LOOKUP\(/){ return 1; } elsif ($line =~ /^\(\[\(\d+[\d\.]*\,\d+[\d\.]*\)\-\(\d+[\d\.]*\,\d+[\d\.]*\)\]/ ){ return 2; } else {return 0;} } sub replace_named_lookups{ #the array @named_lookups contains a list of the named lookup id's. this #function scans every variable and looks for each of the names. if one is found, #change the value to use a WITH_LOOKUP using the values from the named lookup my $self = shift; my @named_lookups = @_; my (%variables) = %{%$self->{'variable'}}; my $tempvalue; foreach $named_lookup (@named_lookups){ foreach $variable_name (keys %variables){ $tempvalue = $named_lookup; #tempvalue = the named lookup id if ($variables{$variable_name}->{'value'} =~ /^$tempvalue\((.*)\)/){ $tempvalue = $1; #tempvalue = the equation the lookup #table applies to. $model->{'variable'}->{$variable_name}->{'value'} = "WITH_LOOKUP(($tempvalue),$model->{'variable'}->{$named_lookup}->{'value'})"; $model->{'variable'}->{$variable_name}->{'is_lookup'} = 1; } } delete $model->{'variable'}->{$named_lookup}; #remove the named lookup } } sub FLOW_EXISTS { #checks if the flow is already contained in the #'flow' hash in MODEL my ($key) = shift; my (%flows) = %{%$model->{'flow'}}; foreach $flowname (keys %flows){ if ($flowname eq $key){ return 1; } } return 0; } sub get_flows{ #the purpose of this function is to see which 'variables' are in which 'stocks' #but not part of the 'initial value'. the theory is that, in order to determine #which of the remaining variables are flows and which are aux vars, any involved #in the equation of a STOCK are flows, and any involved in the initialization #are aux vars...seems to hold true so far. my ($self) = shift; my ($flows_in_to, $flows_out_of); my (%stocks) = %{%$self->{'stock'}}; my (%variables) = %{%$self->{'variable'}}; while (($stockname, $stock) = each %stocks){ foreach $variablename (keys %variables){ $purged_name = quotemeta &purge_whitespace($variablename); $stock_equation = $stock->{'value'}; $stock_equation =~ s/\,.*//; #remove stuff after comma (initialization) #if flow isnt already in FLOW hash if ($stock_equation =~ /\b$purged_name\b/){ if (!FLOW_EXISTS($variablename)){ $model->add_flow($variablename); $model->{'flow'}->{$variablename}->{'value'} = $variables{$variablename}->{'value'}; $model->{'flow'}->{$variablename}->{'units'} = $variables{$variablename}->{'units'}; #set flag if the flow contains a lookup table $model->{'flow'}->{$variablename}->{'is_lookup'} = &IS_LOOKUP($variables{$variablename}->{'value'}); delete $model->{'variable'}->{$variablename}; } } #set flow to/from properties based on role in stock equation (+=to -=from) if ($stock_equation =~ /\-\b$purged_name\b/){ $model->{'flow'}->{$variablename}->{'flow_from'} = $stockname } elsif ($stock_equation =~ /\b$purged_name\b/){ $model->{'flow'}->{$variablename}->{'flow_to'} = $stockname } } } } sub replace_spaces{ #this functions replaces any spaces #with underscores my ($replace_my_spaces) = shift; $replace_my_spaces =~ s/\s+/\_/g; return $replace_my_spaces; } sub is_digit{ #boolean function takes in a string and verifies if it contains only digits #and decimals my ($check_me) = shift; #check if variable is made of only digits if ($check_me =~ /^\d*\.?\d+$/) {return 1;} else {return 0;} } sub is_even{ #this function is used when creating the form. every other element #should have a different color, and this fxn helps track when to switch. my $n = shift; if ($n % 2 == 0) {return 1;} else {return 0;} } sub get_number_of_objects{ #returns the total number of vensim objects used in equations my (%flows) = %{%$model->{'flow'}}; my (%stocks) = %{%$model->{'stock'}}; my (%variables) = %{%$model->{'variable'}}; my (%constants) = %{%$model->{'constant'}}; my $flowcount = keys(%flows); my $stockcount = keys(%stocks); my $auxcount = keys(%variables); my $constantcount = keys(%constants); my $n = $flowcount + $stockcount + $auxcount + $constantcount; #$n = keys(%flows) + keys(%stocks) + keys(%variables) + keys(%constants); my %object_counter = ( flowcount => "$flowcount", stockcount => "$stockcount", auxcount => "$auxcount", constantcount => "$constantcount", totalcount => "$n" ); open(COUNTME, ">/tmp/countme.txt"); print COUNTME $object_counter{totalcount} . "\n" . $n . "\n"; close COUNTME; return %object_counter; } ##########END_HELPER_FUNCTIONS############################################### sub toggle_options_javascript{ #contains the javascript code to hide the slider options qq | function toggle_slider_options(id) { var e = document.getElementById(id); if(e.style.display == 'none') e.style.display = 'block'; else e.style.display = 'none'; } function swap_table(shortcode, longcode){ if (shortcode.style.display == 'block'){ shortcode.style.display = 'none'; longcode.style.display = 'block'; document.getElementById("toggletable").innerHTML=('' + "[collapse]" + ''); } else{ shortcode.style.display = 'block'; longcode.style.display = 'none'; document.getElementById("toggletable").innerHTML=('' + "[expand]" + ''); } } |; } sub recheck_javascript_onload{ my $onload_script; my %object_counter = &get_number_of_objects(); my $flowcount = $object_counter{flowcount}; #not needed (no hidden divs) my $stockcount = $object_counter{stockcount}; my $auxcount = $object_counter{auxcount}-1; #-1 due to SAVEPER my $constantcount = $object_counter{constantcount}-3; #-3 due to FINAL_TIME, INITIAL_TIME, and TIME_STEP $onload_script = "function onload_div_check(){\n"; for (my $i=0; $i<$stockcount; $i++){ $onload_script = $onload_script . qq| var check = document.getElementById("variv$i"); var div = document.getElementById("varID$i"); if (check.checked) div.style.display = 'block'; else div.style.display = 'none'; |; } for (my $i=0; $i<$auxcount; $i++){ $onload_script = $onload_script . qq| var check = document.getElementById("auxiv$i"); var div = document.getElementById("auxID$i"); if (check.checked) div.style.display = 'block'; else div.style.display = 'none'; |; } for (my $i=0; $i<$constantcount; $i++){ $onload_script = $onload_script . qq| var check = document.getElementById("paramiv$i"); var div = document.getElementById("paramID$i"); if (check.checked) div.style.display = 'block'; else div.style.display = 'none'; |; } $onload_script = $onload_script . "}"; return $onload_script; } =pod sub condense_equations_javascript{ qq| |; } =cut sub css_table_style{ #contains the css code for the table styles qq| |; } sub get_short_input{ #input: the user-entered vensim equations #this fxn takes the first and last vensim objects from the equations #and makes a text file appropriate for condensing on the form (to lessen clutter) my $i, $j, $line, $string; my %object_counter = &get_number_of_objects(); my $object_total = $object_counter{totalcount}; my @data, @first, @last; open(INFILE2, "$input_file"); $i=0; while($line = ){ #read input line by line chomp $line; $data[$i] = $line; $i+=1; } close(INFILE2); open(SHORT, "> $short_input_file"); $i=0; $j=0; while ($i < @data){ if ($data[$i] =~ /^\([0]*1\)/){ while (!($data[$i] =~ /^\s*$/)){ $first[$j] = $data[$i]; $j++; $i++; } } elsif ($data[$i] =~ /^\([0]*$object_total\)/){ $j=0; while (!($data[$i] =~ /^\s*$/)){ $last[$j] = $data[$i]; $j++; $i++; } } else {$i++;} } foreach $line (@first){ $string = $string . $line; } $string = $string . "\t\t.\n\t\t.\n\t\t.\n"; #print vertical elipse (tabbed over to look nice) foreach $line (@last){ $string = $string . $line; } print SHORT $string; close(SHORT); return $string; } sub html_start{ my ($input) = shift; my (%flows) = %{%$model->{'flow'}}; my (%stocks) = %{%$model->{'stock'}}; my (%variables) = %{%$model->{'variable'}}; my (%constants) = %{%$model->{'constant'}}; my ($stock_name); my $slider_init; print header(-type => 'text/html'); print start_html( -title=>"Vensim2Java - Applet Settings", -script=>toggle_options_javascript() . recheck_javascript_onload(), -onload=>'onload_div_check()', -bgcolor=>"#ffffff") . css_table_style(); print h2("
Vensim 2 Java Applet Options Form
"); print table({width=>'80%', style=>'border-style:none', align=>'center'}, Tr(td( "
The Vensim equations are printed below and are condensed for your convenience. Click 'expand' to view the entire equation text or 'collapse' to shorten it. If you made a mistake while submitting, please hit the back button on your browser and try again. Otherwise, scroll down, answer a few questions about your model, and submit the form to create your applet.

" ))); my $shortinput = &get_short_input(); print "
"; print "\n"; print qq|
|; print "[expand]"; print "
"; print "
"; print qq|"; print qq|
\n|; print table({width=>'600', align=>'center', style=>'border-width:1px'}, Tr( td({style=>'background-color:#efefef'}, pre($shortinput)))), br, br; print "
"; #start making the form print start_form(-method=>'post', -action=>'v2j_make_applet.cgi'); print "\n"; #pass unique name to next script print "\n"; #stocks... if ( scalar(keys(%stocks)) != 0 ){ print "\n"; print "\n"; $i = 0; foreach $stock_name (keys %stocks){ print "\n"; if (is_even($i)) { print qq|\n"; } print "
Stocks
\n|; } else { print qq|\n|; } print p, "For stock $stock_name, please answer the following:
", checkbox(-name=>"vargraph$i", -label=>"Graph this variable"), p, checkbox(-name=>"variv$i", -label=>"Use a slider to set the initial value", -onclick=>"toggle_slider_options('varID$i')"), "
"; print "
\n"; $i++; print "
"; } #flows... if ( scalar(keys(%flows)) != 0 ){ print "
\n"; print "\n"; $i=0; foreach $flow_name (keys %flows){ my $lookup_var, $lookup_datapoints; my @lookup; print "\n"; if (is_even($i)) { print qq|... (rows), # td() = (columns). ############################################################ #remove leading trailing parenths for cosmetic look on form while($lookup_var =~ s/^\(//){ $lookup_var =~ s/\)$//; } print "For flow $flow_name (containing a lookup table), please answer the following:
", p, table({border=>'undef', align=>'center', cellpadding=>'3', style=>'border-width:1px'}, Tr(th( {style=>'border-width:1px;color:#ffffff;'}, [" $lookup_var ", " $flow_name "])), #table header $rows #apply previously created html syntax for table ), p, #get graph type "Graph type:", br, radio_group(-name=>"flowgraphtype$i", -values=>['0', '1'], -default=>'0', -labels=>{'0'=>'Line Graph ', '1'=>'Box Graph '}), p; } else{ print p, "For flow $flow_name, please answer the following:
"; } print checkbox(-name=>"flowgraph$i", -label=>"Allow flow to be graphed"), p, "
"; $i++; print "
\n"; } print "
Flows
\n|; } else { print qq|\n|; } if ($flows{$flow_name}->{'is_lookup'} == 1){ #find the values of the lookup table (the independent variable it # uses, and the datapoints for the table). the string # returned by parse_lookup is "variable:datapoints", so # split it accordingly. $lookup_var = &parse_lookup($flows{$flow_name}->{'value'}); @lookup = split(":",$lookup_var); $lookup_var = $lookup[0]; $lookup_datapoints = $lookup[1]; ########################################################### #build html table to display the values of the lookup table my @xy_values; #each array element contains an x,y value #put x,y values into array: $lookup_datapoints =~ s/^\(//; #remove leading ( $lookup_datapoints =~ s/\)$//; #remove trailing ) @xy_values = split(/\),\(/, $lookup_datapoints); #split between each x,y pair my $rows = ''; foreach my $pair (@xy_values){ $pair =~ m/(.*),(.*)/; #$1=x and $2=y for each x,y pair $rows .= Tr({-align=>'center'},td([$1,$2])); } # $rows is a string containing the valid html syntax for a table # thanks to the CGI.pm module (Tr() =
...
"; } #aux variables... if ( scalar(keys(%variables)) > 1 ){ #condition is >1 instead of >0 because #SAVE_PER is counted as a var print "
\n"; print "\n"; $i=0; foreach $variable_name (keys %variables){ my $lookup_var, $lookup_datapoints, $var_type; my @lookup; if ($variable_name ne "SAVEPER"){ print "\n"; if (is_even($i)) { print qq|... (rows), # td() = (columns). ############################################################ #remove leading trailing parenths for cosmetic look on form while($lookup_var =~ s/^\(//){ $lookup_var =~ s/\)$//; } print p, "For lookup table $variable_name, please answer the following:
", p, table({border=>'undef', align=>'center', cellpadding=>'3', style=>'border-width:1px'}, Tr(th( {style=>'border-width:1px;color:#ffffff;'}, [" $lookup_var ", " $variable_name "])), #table header $rows #apply previously created html syntax for table ), p, #get graph type "Graph type:", br, radio_group(-name=>"auxgraphtype$i", -values=>['0', '1'], -default=>'0', -labels=>{'0'=>'Line Graph ', '1'=>'Box Graph '}), p; } elsif ($variables{$variable_name}->{'is_ite'} == 1) { $var_type = "conditional variable"; print p, "For the conditional variable $variable_name, please answer the following:
"; #the commented-out code is for possible future implementation #allowing the user to change the conditional parameters. =pod my @nested_ites = &parse_ite($variable_name, $variables{$variable_name}->{'value'}); my $tempvalue; $tempvalue = $variables{$variable_name}->{'value'}; $tempvalue =~ s/,/, /g; print checkbox(-name=>"auxcond$i", -label=>"Change conditional parameters", -onclick=>"toggle_slider_options(auxcondID$i')"), "
"; print "
\n"; =cut } #foreach $myite (@{%{$variables{$variable_name}->{'ites'}}}) { # print "$myite", p; #} else{ $var_type = "variable parameter"; print p, "For variable parameter $variable_name, please answer the following:
"; } print checkbox(-name=>"auxgraph$i", -label=>"Allow $var_type to be graphed"), p checkbox(-name=>"auxiv$i", -label=>"Replace $var_type with a slider", -onclick=>"toggle_slider_options('auxID$i')"), "
"; print "
"; $i++; print "
\n"; } } print "
Auxillary Variables
\n|; } else { print qq|\n|; } #for lookup tables... if ($variables{$variable_name}->{'is_lookup'} == 1){ $var_type = "lookup"; #find the values of the lookup table (the independent variable it # uses, and the datapoints for the table). the string # returned by parse_lookup is "variable:datapoints", so # split it accordingly. $lookup_var = &parse_lookup($variables{$variable_name}->{'value'}); @lookup = split(":",$lookup_var); $lookup_var = $lookup[0]; $lookup_datapoints = $lookup[1]; ########################################################### #build html table to display the values of the lookup table my @xy_values; #each array element contains an x,y value #put x,y values into array: $lookup_datapoints =~ s/^\(//; #remove leading ( $lookup_datapoints =~ s/\)$//; #remove trailing ) @xy_values = split(/\),\(/, $lookup_datapoints); #split between each x,y pair my $rows = ''; foreach my $pair (@xy_values){ $pair =~ m/(.*),(.*)/; #$1=x and $2=y for each x,y pair $rows .= Tr({-align=>'center'},td([$1,$2])); } # $rows is a string containing the valid html syntax for a table # thanks to the CGI.pm module (Tr() =
...
"; } #constants... NOTE: stell2java counts down for this (counts up here) if ( scalar(keys(%constants)) > 3 ){ #condition is >3 instead of >0 because #FINAL_TIME, TIME_STEP, and INITIAL_TIME #are counted as constants print "
\n"; print "\n"; $i = 0; foreach $constant_name (keys %constants){ if ($constant_name ne "FINAL_TIME" && $constant_name ne "TIME_STEP" && $constant_name ne "INITIAL_TIME"){ print "\n"; if (is_even($i)) { print qq|\n"; } } print "
Constants
\n|; } else { print qq|\n|; } print p, "For constant $constant_name, please answer the following:
", checkbox(-name=>"paramgraph$i", -label=>"Allow constant to be graphed"), p, checkbox(-name=>"paramiv$i", -label=>"Use a slider to set the initial value", -onclick=>"toggle_slider_options('paramID$i')"), "
"; print "
"; $i++; print "
"; } #graph options print "
\n"; print "
Graph Options
\n"; print p, br, "Please enter the following responses regarding your default independent variable display preferences (taken from Vensim):", p, "Initial time: ", textfield(-name=>'inittime', -default=>"$constants{'INITIAL_TIME'}->{'value'}"), p, "Final time: ", textfield(-name=>'finaltime', -default=>"$constants{'FINAL_TIME'}->{'value'}"), p, "Time step: ", textfield(-name=>'timestep', -default=>"$constants{'TIME_STEP'}->{'value'}"); print p, br, "Please enter the following responses regarding your default dependent variable display preferences (defaults used by vensim2java):",p, "Y-axis minimum: ", textfield(-name=>'ymin', -default=>"0"), p, "Y-axis maximum: ", textfield(-name=>'ymax', -default=>"50"); print p, br, "Please enter a graph title and labels, or leave blank if you do not want one:", p, "Graph title: ", textfield(-name=>'title'), p, "Y-axis label: ", textfield(-name=>'depLabel'); print "
"; print p, "
", reset(-value=>'Reset Form'), "    ", submit(-value=>'Submit Form'), "
"; print endform; } sub html_empty_error{ print header(-type => 'text/html'); print start_html( -title=>"Vensim2Java - Error", -bgcolor=>"#ffffff"); print h2("
Vensim2Java Error Report
"); print table({width=>'60%', align=>'center', cellpadding=>'15', style=>'border:solid;border-collapse:collapse;border-width:2px'}, Tr( td({style=>'background-color:#e5d8b4;'}, "Vensim2Java encountered an error while processing the Vensim equations. The model you have submitted appears to be empty. Note that the equations should be copied directly from Vensim.", br, br, "If you cannot find the cause of this error, please visit the bug page for more help, or to submit a bug.") )); exit; } sub html_error{ #if it is detected that a variable contains invalid characters, an #error is thrown. my ($invalid_name) = shift; print header(-type => 'text/html'); print start_html( -title=>"Vensim2Java - Error", -bgcolor=>"#ffffff"); print h2("
Vensim2Java Error Report
"); print table({width=>'60%', align=>'center', cellpadding=>'15', style=>'border:solid;border-collapse:collapse;border-width:2px'}, Tr( td({style=>'background-color:#e5d8b4;'}, "Vensim2Java encountered an error while processing the Vensim equations. The variable:
", br, $invalid_name, br, br, "
contains one or more invalid characters. Although it may be an acceptable variable name in Vensim, Vensim2Java only allows alphanumeric characters (including underscores and whitespace).", br, br, "If you cannot find the cause of this error, please visit the bug page for more help, or to submit a bug.") )); exit; }