#!/usr/bin/perl # # cnvprb -h [header] # -s [script] # -t [footer] # -i [import prefix] # -f [file1] [file2] [file3] >[outputfile].problem # -l [library references] # # Written by Robert McQueen and Mark Lucas, Ohio University #------------------------------------------------------------------------------ # create alias for pre-defined Perl variables used in subroutines *corrected_list = *inlist = *_; # parse command-line args @header = (); @script = (); @footer = (); @files = (); @lib_refs = (); @import_prefix = (); &parse_ARGV(); # insert problem header @output = ""; # run through each file foreach $file (@files) { open(INFILE,"$file") || die "$file does not exist!!\n"; @prfile = ; close(INFILE); # pre-filter problem @prfile = &pre_filter(@prfile); $temp_file = "/tmp/OUcnvprb.tmp"; open(TMPFILE, ">$temp_file"); print TMPFILE @prfile; close(TMPFILE); # convert to LON-CAPA format push (@output, `capaconverter $import_prefix -f $temp_file`); @output = &remove_final_part_tag(@output); } # delete temporary file from system unlink("$temp_file"); # insert problem footer @output = (@output, ""); # filter the output %string_var; # list of string variables encountered in file @output = &remove_problem_num(@output); @output = &fix_refs(@output); @output = &fix_lon_capa_tags(@output); @output = &declare_responses(@output); @output = &fix_response_params(@output); # @output = &fix_hints(@output); @output = &format_html_tags(@output); @output = &fix_script_functs(@output); # @output = &fix_outtext_functs(@output); # @output = &exempt_tex_formatting(@output); # @output = &supplement_tex_formatting(@output); @output = &remove_empty_script_blocks(@output); @output = &add_newlines(@output); # @output = ÷_parts(@output); @output = &remove_single_part_tags(@output); if (@header) { map s|()|\1\n\n@header|, @output; } if (@footer) { map s|()|@footer\n\1|, @output; } if (@script) { map eval "@script", @output; } # output conversion to STDOUT print @output; #------------------------------------------------------------------------------ # parse_ARGV: parses and interpolates command-line arguments # ---------- # - headers = text to be output immediately following the tag # - scripts = scripts to be run on the post-translated problem # - footers = text to be output immediately before the tag # - import prefix = domain prefix to be placed before resource references # - files = files to be converted and translated # - library references = supported libraries [see fix_refs subroutine] # # * Calls: "interpolate_string" subroutine # ----- #------------------------------------------------------------------------------ sub parse_ARGV { unless ($ARGV[0] =~ /^-/) { die "usage: OUcnvprb [OPTION]... SOURCE... >[DEST].problem \n". " OPTIONS include: \n". " -h [headers] \n". " -s [scripts] \n". " -t [footers] \n". " -i [import prefix] \n". " -f [files] \n". " -l [library references] \n". " SOURCE can be any type of Perl string ". "[file name, command, variable...] \n"; } foreach (@ARGV) { if (/^-.$/) { if (/h/) { *argv = *header; } elsif (/s/) { *argv = *script; } elsif (/t/) { *argv = *footer; } elsif (/f/) { *argv = *files; } elsif (/i/) { *argv = *import_prefix; # push(@argv, $_); } elsif (/l/) { *argv = *lib_refs; } else { die "$_ option does not exist\n"; } } else { push(@argv, $_); } } map s|$_|interpolate_string($_)|e, @header; map s|$_|interpolate_string($_)|e, @script; map s|$_|interpolate_string($_)|e, @footer; map s|$_|interpolate_string($_)|e, @lib_refs; $import_prefix = $import_prefix[0-1]; } #------------------------------------------------------------------------------ # interpolate_string: (helper function for "parse_ARGV" subroutine) # ------------------ # - determines whether a string is a file or a literal # - returns the true value of the string #------------------------------------------------------------------------------ sub interpolate_string { my $input = $_[0]; if (-r $input) { open(INPUTFILE, $input); $input = ""; while () {$input .= $_;} close(INPUTFILE); } return "$input"; } #------------------------------------------------------------------------------ # pre_filter: handles special pre-filtering tasks # ---------- # - removes /DIS("") which would otherwise output arbitrary '' # - removes /DIS(stdline) which will be reinserted in the # appropriate place later # - removes all references to the problem() function call # which is not supported in LON-CAPA along with the # associated formatting statements # - replaces backquotes with single quotes # - escapes all single quotes to avoid later confusion # - special substitution for tipler image inclusion #------------------------------------------------------------------------------ sub pre_filter { map { s|/?/DIS\(""\)||g; s|/DIS\(stdline\)||g; s|//DIS|\#DIS|g; s|(?:/DIS\(tex\("[^"]*?","[^"]*?"\)\))?/DIS\(problem\(\)\)[\.]?(?:/DIS\(tex\("[^"]*?","[^"]*?"\)\))?[\.]?||g; s|\#DIS|//DIS|g; tr|`|'|; s|/DIS\("http://"\+machine_name\+webfigs_dir\+"(.*?)"\)|"/tipler/Graphics$1"|g; s|"\+psfigs_dir\+"|/tipler/Graphics|g; # temporary fix for mult. choice prbs s|(tex\("[\\]{2}",")("\))|\1
\2|g; } @inlist; return @corrected_list; } #------------------------------------------------------------------------------ # remove_final_part_tag: removes the final placed by the converter # --------------------- # # - assumes the final is not matched by a closing #------------------------------------------------------------------------------ sub remove_final_part_tag { map { return @corrected_list if (s|||); } reverse @inlist; } #------------------------------------------------------------------------------ # remove_problem_num: removes the Problem# statement # ------------------ # - Problem# import utility not supported in LON-CAPA -> delete! #------------------------------------------------------------------------------ sub remove_problem_num { map { s|.*?Problem#.*?||gi; } @inlist; return @corrected_list; } #------------------------------------------------------------------------------ # fix_refs: corrects all references to particular problem libraries # -------- # - adds .library file extension to all MCTools file references # - removes hyphen from references to the new serway-lib # due to system complications caused by using the hyphen # - adds domain prefix before all resource references #------------------------------------------------------------------------------ sub fix_refs { my $lib_refs = $_ = join(" ", @lib_refs); $lib_refs = join("|/", split); map { s|(/MCTools.*?)()|\1.library\2|g; s|(serway)-(lib)|$1$2|g; if (@lib_refs) { s|(/$lib_refs)|$import_prefix$1|gi unless (m||); } } @inlist; return @corrected_list; } #------------------------------------------------------------------------------ # fix_lon_capa_tags: modifies various LON-CAPA tags # ----------------- # - adds newlines to script tags # - places a
after tags # - places a
before tags # # * Calls: "split_lines" subroutine # ----- #------------------------------------------------------------------------------ sub fix_lon_capa_tags { my $first = 0; map { $first = 0 if (m||); $first = s|()|\1
|g unless ($first); s|([\s]*?)()|\1
\n\1\2|g; s|()|\n\1\n|g; } @inlist; @corrected_list = &split_lines(@corrected_list); return @corrected_list; } #------------------------------------------------------------------------------ # declare_responses: parses LON-CAPA response types # ----------------- # - declares string responses #------------------------------------------------------------------------------ sub declare_responses { my $string_opt; my $stringresponse = 0; my $scriptmode = 0; *is_string = *string_type = *string_var; map { $scriptmode = 1 if (m||); if ($scriptmode) { if (m|(\$[\w]+?)=&choose\(\$[\w]+?[,'A-H]+?\)|gi) { $string_type{$1} = 'type="mc"'; } elsif (m|(\$[\w]+?)=.*?['"]|gi) { $string_type{$1} = 'type="ci"'; } elsif (m|(\$[\w]+?)=([^\-\*/]*?)\;|gi) { my $vars_to_check = $2; { $vars_to_check =~ s|\;|+|g; $vars_to_check =~ s|&choose.*?,||g; } my @the_line = grep /\$[\w]+/, split (/[^\$\w]/, $vars_to_check); my $valid_string_line = 1; foreach $var (@the_line) { # test if other vars ecountered within the line were strings if ($valid_string_line) { if ($is_string{$var}) { $string_type{$1} = 'type="ci"'; $valid_string_line = 1; } else { delete $string_var{$1}; $valid_string_line = 0; } } } } elsif (m|(\$[\w]+?)=|gi) { delete $string_var{$1}; } } if (m||) { $string_opt = $string_type{$1}; if ($string_opt) { $stringresponse = s|||g; } else { $stringresponse = s|]*?type=".*?")|)|\1string\2|g; } } @inlist; return @corrected_list; } #------------------------------------------------------------------------------ # olddeclare_responses: parses LON-CAPA response types # -------------------- # - corrects response tags # * uses last variable declaration before response tag to determine # response type # * assumes: 1) string variable is assigned to a bare string # [not in a function call] # 2) multiple choice response variables are declared as # $CAPA4ANS [may be OU-specific] # * only handles numerical and string response types #------------------------------------------------------------------------------ sub olddeclare_responses { my $string_ans = 0; my $string_opt = ''; my $stringresponse = 0; map { s|]*?type=".*?")||) { if ($string_ans) { s|||g; } $stringresponse = $string_ans; } elsif (m|]*?>|) { $stringresponse = 0; } if ($stringresponse) { s|()|\1string\2|g; } } @inlist; return @corrected_list; } #------------------------------------------------------------------------------ # sub fix_response_params: parses LON-CAPA response types # ----------------------- # - replaces old usage of +/- for "Significant Figures" default # responseparam arguments with new , format #------------------------------------------------------------------------------ sub fix_response_params { my $base; my @plus, @minus; my $lower, $upper; map { if (m| around images -> enhances display of images #------------------------------------------------------------------------------ sub format_html_tags { map { if (/<[^<>]*?=[^<>]*?>/) { s#(.*?={1}(?:[\s]?)*)([^\s<>'"]+?)([\s]|[/]?>)#\1"\2"\3#g; } s|
|
|gi; # s|()
|\1|gi; # s||

|gi; # s|()

()|\1\2|gi; s|||gi; s|||gi; s|

|

|gi; s|

|

|gi; } @inlist; return @corrected_list; } #------------------------------------------------------------------------------ # replace_old_functs: reformats seemingly obsolete uses of functions # ------------------ # - &tex(1,2) calls -> 1 makes more xml-ish # - &var_in_tex(1) calls -> 1 makes more xml-ish # - if no formatting is involved, removes &to_string() call # * neither LON-CAPA nor Perl discerns between specific scalar types # - &html(*) -> * makes more xml-ish # - combines consecutive statements into one * # - flags images within &web() calls for later handling # - all other &web(1,2,3) calls -> 2 #------------------------------------------------------------------------------ sub replace_old_functs { # map { s|&tex\('(.*?)','(?:.*?)'\)|\1|g; s|&var_in_tex\((.*?)\)|\1|g; s|&to_string\(([^,]*?)\)|\1|g; s|&html\('?(.*?)'?\)|\1|g; s|([^<]*?)[\s]*?(.*?)|\1\2|g; s|&web(\('(?:.*)','(?:.*)','.*?\1|g; # } @inlist; # return @corrected_list; } #------------------------------------------------------------------------------ # fix_script_functs: formats function calls that appear within script blocks # ----------------- # - removes blank comment lines # - combines consecutive statements into one * # * also combines consecutive math modes # - places xml tags within &xmlparse() calls # * also if needed, places a ; after call # - maintains images within &web(1,2,3) calls # - handles string concatenation # - if &xmlparse() calls are unassigned # -> &xmlparse(*) -> * # -> move this to next outtext area # -> if no more exist, then simply move outside of script block #------------------------------------------------------------------------------ sub fix_script_functs { my @outlist = (); my $scriptmode = 0; map { $scriptmode = 1 if (m||); if ($scriptmode) { # $_ =~ &replace_old_functs; s|^#[\s]*$||g; s|(.*?)[\s\+]*?(.*?)|\1\2|g; s|\$\$||g; s#(<(?:m|web|tex)>.*?)#&xmlparse('\1')#g; s|(&xmlparse\('.*?'\))([^;,)\.\+\-\*\/])|\1;\2|g; s|&WEBFIG|&web|g; # handle string concatenation &concatenate_strings($_); } } @inlist; return @corrected_list; } #------------------------------------------------------------------------------ # concatenate_strings: (helper function for "fix_script_functs" subroutine) # ------------------- handles string concatenation # # * replaces a + with a . when it appears: # - between an unescaped quote and a quoted string # as well as between a function call that has a quoted argument # and a quoted string # - before a function call that has a quoted argument # - between two quoted strings and/or a quoted string and a scalar # string variable #------------------------------------------------------------------------------ sub concatenate_strings { s|[\+]([\s]*?)(\$[\w]+)|if ($is_string{$2}){".$1$2"} else {"+$1$2"}|ge; s|(\$[\w]+)([\s]*?)[\+]|if ($is_string{$1}){"$1$2."} else {"$1$2+"}|ge; s|([^\\]['][)]?[\s]*?)\+([\s]*?['][^,);.])|\1.\2|g; s|\+([\s]*?&[\w]+?\(')|.\1|g; } #------------------------------------------------------------------------------ # fix_outtext_functs: formats function calls that appear within outtext # ------------------ blocks # # - converts images within &web(1,2,3) calls into # 12 # - combines consecutive statements into one * # * also combines consecutive math modes # - places tags around &choose() calls # - removes \ from single quotes escaped during pre-processing #------------------------------------------------------------------------------ sub fix_outtext_functs { my $textmode = 0; map { $textmode = 1 if (m||); $textmode = 0 if (m||); if ($textmode) { $_ =~ &replace_old_functs(); s|(.*?)[\s]*?(.*?)|\1\2|g; s|(.*?)\$[\s]*?\$(.*?)|\1\2|g; s|&WEBFIG\('(?:.*)','(.*)','(.*?\1\2|gi; s|(&choose\([^&]*?\))|\1|g; s|[\\](['])|\1|g; } } @inlist; return @corrected_list; } #------------------------------------------------------------------------------ # exempt_tex_formatting: parses statements for tex-only output # -------------------- # - places tex figures and formatting commands within tags # - accounts for unmatched closing braces caused by the above action # which would otherwise cause display problems for LON-CAPA # # * Special Note: This function was created in response to # ------------ difficulties experienced with using #------------------------------------------------------------------------------ sub exempt_tex_formatting { map { s|([^<]*?epsf[^<]*?)|\1|gi; s|([^<]*?\.[e]?ps[^<]*?)|\1|gi; s#([^<]*?(?:skip|indent|space)[^<]*?)#\1#gi; s#([^<]*?(?:box|quote|put)[^<]*?)#\1#gi; s#([\s]*?[}][\s]*?)#\1#gi; } @inlist; return @corrected_list; } #------------------------------------------------------------------------------ # supplement_tex_formatting: supplements basic tex formatting with # ------------------------- corresponding web formatting # # - tex \\ -> \\
# - tex *box -> *box*

# # * Special Note: This function was created in response to # ------------ difficulties experienced with using #------------------------------------------------------------------------------ sub supplement_tex_formatting { map { s|(\\\\)|\1
|g; s|([^<]*?box[^<]*?)|\1

|g; } @inlist; return @corrected_list; } #------------------------------------------------------------------------------ # fix_hints: handles placement and formatting of hintgroups # --------- # - removes


after hint tag # - places a tab before each hintgroup line # - places hintgroup into an array # - immediately outputs hintgroup within next <*response> tag # * outputs immediately after #------------------------------------------------------------------------------ sub fix_hints { my @outlist = (); my $hintmode = 0; my $pasthintmode = 0; my $responsemode = 0; my $pastresponsemode = 0; my $inlist_index = 0; my @hint_group = (); map { if (m||) { $hintmode = 1; } elsif (m||) { $hintmode = 0; } elsif (m||) { $responsemode = 1; } elsif (m||) { $responsemode = 0; } if ($hintmode || $pasthintmode) { s|()
|\1|g; push(@hint_group,"\t$_"); $_ = ""; } elsif (!$pasthintmode && @hint_group) { my $num_repsonse_blocks = 0; my @inlistcpy = @inlist; for ($cpyindex = 0; $cpyindex < $inlist_index; $cpyindex++) { shift(@inlistcpy); } foreach (@inlistcpy) { if (m||) { $num_repsonse_blocks++; } } if (!$responsemode && $pastresponsemode || !$num_repsonse_blocks) { push(@outlist,@hint_group); @hint_group = (); } } push(@outlist,$_); $pasthintmode = $hintmode; $pastresponsemode = $responsemode; $inlist_index++; } @inlist; return @outlist; #return corrected list } #------------------------------------------------------------------------------ # divide_parts: separates a problem into parts based on number of # ------------ response blocks # # - counts number of <*reponse> blocks # - if there is more than one response block, divides the problem # into its respective parts # - adds trailing $stdline # # * Calls: "insert_part_tags" subroutine # ----- "insert_stdline" subroutine #------------------------------------------------------------------------------ sub divide_parts { my $parts = 0; $parts = map m||, @inlist; if ($parts > 1) { @corrected_list = &insert_part_tags(@inlist, $parts); } # @corrected_list = &insert_stdline(@corrected_list, $parts); return @corrected_list; } #------------------------------------------------------------------------------ # insert_part_tags: (helper function for "divide_parts" subroutine) # ---------------- inserts respective tags into problem file # # - places the first after # - places intermittent and after each response # - corrects above procedure for the final response #------------------------------------------------------------------------------ sub insert_part_tags { my $num_parts = pop(@_); my $part = 1; map { if ($part <= $num_parts) { s|()|\1\n\n|g; if (m||) { s|()|\1\n\n\n|g; if ($part++ == $num_parts) { s|()\n\n\n|\1|g; } } } # only used for efficiency purposes s|()|\n\1|g; } @inlist; return @corrected_list; } #------------------------------------------------------------------------------ # remove_single_part_tags: corrects one-part problem syntax # ----------------------- # # - removes the part tags the converter places around one-part problems #------------------------------------------------------------------------------ sub remove_single_part_tags { my @num_parts = grep m||, @inlist; map s|||g, @inlist unless ($#num_parts); return @corrected_list; } #------------------------------------------------------------------------------ # insert_stdline: (helper function for "divide_parts" subroutine) # -------------- inserts trailing $stdline # # - for multipart problems, inserts the $stdline before # - otherwise, inserts it after # [placement of $stdline is purely aesthetic] #------------------------------------------------------------------------------ sub insert_stdline { my $num_parts = pop(@_); my $stdline = "\n\n\$stdline\n
\n"; if ($num_parts > 1) { $stdline = "\n\n" . $stdline; } else { $stdline .= "\n\n"; } map s||$stdline|g, @inlist; return @corrected_list; } #------------------------------------------------------------------------------ # remove_empty_script_blocks: removes ||) { s|