diff --git a/bin/convert-to-pgml.pl b/bin/convert-to-pgml.pl index 31d230411..63059cc62 100755 --- a/bin/convert-to-pgml.pl +++ b/bin/convert-to-pgml.pl @@ -70,14 +70,18 @@ ($filename) my $path = Mojo::File->new($filename); die "The file: $filename does not exist or is not readable" unless -r $path; - my $pg_source = $path->slurp; - my $converted_source = convertToPGML($pg_source); + my $pg_source = $path->slurp; + my $result = convertToPGML($pg_source); + if (ref($result) eq 'HASH' && $result->{errors}) { + warn "Error parsing $filename. " . $result->{errors}; + return; + } # copy the original file to a backup and then write the file my $new_path = $backup ? $path : Mojo::File->new($filename =~ s/\.pg/.$suffix/r); my $backup_file = $filename =~ s/\.pg$/.pg.bak/r; $path->copy_to($backup_file) if $backup; - $new_path->spurt($converted_source); + $new_path->spurt($result->{pgmlCode}); print "Writing converted file to $new_path\n" if $verbose; print "Backing up original file to $backup_file\n" if $verbose && $backup; } diff --git a/lib/WeBWorK/PG/ConvertToPGML.pm b/lib/WeBWorK/PG/ConvertToPGML.pm index 04e4eeddb..d3abb491d 100644 --- a/lib/WeBWorK/PG/ConvertToPGML.pm +++ b/lib/WeBWorK/PG/ConvertToPGML.pm @@ -76,7 +76,17 @@ my @ans_list; sub convertToPGML { my ($pg_source) = @_; - # First get a list of all of the ANS, LABELED_ANS, etc. in the problem. + # Check that the file is not already in PGML format by looking for PGML.pl in the loadMacros statement, + # and there are no BEGIN_TEXT, BEGIN_SOLUTION, etc. blocks. + + return { pgmlCode => $pg_source } + if ($pg_source =~ /loadMacros\((.*)PGML\.pl(.*)\)/m && $pg_source !~ /BEGIN_(TEXT|HINT|SOLUTION)/); + + # Return an error if the loadMacros isn't in the form loadMacros( ... ); + return { errors => "The loadMacros command cannot be parsed.", pgmlCode => $pg_source } + unless $pg_source =~ /loadMacros\((.*?)\)\s*;/m; + + # Get a list of all of the ANS, LABELED_ANS, etc. in the problem. @ans_list = getANS($pg_source); my @pgml_block; @@ -101,29 +111,71 @@ sub convertToPGML { } elsif ($in_pgml_block) { push(@pgml_block, $row); } elsif ($row =~ /loadMacros\(/) { - # Parse the macros, which may be on multiple rows. - # Remove comments within loadMacros block (should we keep them?) - my $macros = $row; - while ($row && $row !~ /\);\s*$/) { + # Parse the macros, which may be on multiple rows and may be in a qw block. + my $macros = ''; + my $num_macro_lines = 1; # store the number of lines in the loadMacro so the output is similar to the input. + while ($row !~ /\)\s*;/) { + # Remove comments within loadMacros block (should we keep them?) + $row =~ s/#.*$//; + $macros .= $row; + ++$num_macro_lines; $row = shift @rows; - my @mrow = split(/#/, $row); - # This only adds the row if there is something relevent to the left of a # - $macros .= $mrow[0] if $mrow[0] !~ /^\s*$/; } - # Split by commas and pull out the quotes. - my @macros = - grep { $_ !~ /^#/ } - grep { - $_ !~ - /(PGstandard|PGML|PGauxiliaryFunctions|PGbasicmacros|PGanswermacros|MathObjects|PGcourse|AnswerFormatHelp).pl/ + $macros .= $row; + + my @macros; + my ($qw_start, $qw_end); # the characters if the loadMacros has a qw block. + + # The following can parse loadMacros in the form loadMacros('macro1.pl', 'macro2.pl'); or + # loadMacros(qw{macro1.pl macro2.pl}); + if ($macros =~ /loadMacros\((.*?)\);/ms) { + my @macro_str = split(/\s*,\s*/, $1); + + for my $str (@macro_str) { + if ($str =~ /^qw(.)/) { + my $qw_matches = { '{' => '}', '(' => ')', '[' => ']', '/' => '/', '|' => '|' }; + $qw_start = $1; + $qw_end = $qw_matches->{$qw_start}; + + if ($str =~ /^qw\Q${qw_start}\E(.*?)\Q${qw_end}\E/) { + push(@macros, split(/\s+/, $1)); + } + } else { + push(@macros, $str); + } } - map {s/['"\s]//gr} - split(/\s*,\s*/, $macros =~ s/loadMacros\((.*)\)\;$/$1/r); - push(@all_lines, - 'loadMacros(' - . join(', ', map {"'$_'"} ('PGstandard.pl', 'PGML.pl', @macros, 'PGcourse.pl')) - . ');'); + @macros = + grep { + $_ + && $_ !~ + /(PGstandard|PGML|PGauxiliaryFunctions|PGbasicmacros|PGanswermacros|MathObjects|PGcourse|AnswerFormatHelp).pl/x + } + map {s/['"]//gr} @macros; + + # Remove any duplicates: + my %seen; + @macros = grep { !$seen{$_}++ } @macros; + } else { + return { + errors => 'The loadMacros command cannot be processed.', + pgmlCode => $pg_source + }; + } + + @macros = ('PGstandard.pl', 'PGML.pl', @macros, 'PGcourse.pl'); + + if ($qw_start) { + if ($num_macro_lines > 1) { # put each macro on a separate line + push(@all_lines, "loadMacros(qw$qw_start"); + push(@all_lines, "\t$_") for (@macros); + push(@all_lines, "$qw_end);"); + } else { + push(@all_lines, "loadMacros(qw$qw_start" . join(' ', @macros) . "$qw_end);", ''); + } + } else { + push(@all_lines, 'loadMacros(' . join(', ', map {"'$_'"} @macros) . ');', ''); + } } else { push(@all_lines, cleanUpCode($row)); } @@ -137,7 +189,7 @@ sub convertToPGML { splice(@all_lines, $empty_lines[$n], 1); } } - return join "\n", @all_lines; + return { pgmlCode => join "\n", @all_lines }; } # This subroutine converts a block (passed in as an array ref of strings) to