From 4bcd539dcdf9ba911d4f5ab44bf68c581e05d10b Mon Sep 17 00:00:00 2001 From: Peter Staab Date: Thu, 28 May 2026 15:20:51 -0400 Subject: [PATCH 01/13] Add the ability to parse loadMacros if the arguments are in a qw block. Also, removes empty macros and duplicate macros. In addition, if it appears that the problem is already in PGML mode, then return the file without changes. --- lib/WeBWorK/PG/ConvertToPGML.pm | 64 +++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/lib/WeBWorK/PG/ConvertToPGML.pm b/lib/WeBWorK/PG/ConvertToPGML.pm index d22d84420..dee4aadd1 100644 --- a/lib/WeBWorK/PG/ConvertToPGML.pm +++ b/lib/WeBWorK/PG/ConvertToPGML.pm @@ -70,12 +70,21 @@ This returns a string that is the converted input string. =cut +use Mojo::Util qw(dumper); + # This stores the answers inside of ANS and related functions. my @ans_list; sub convertToPGML { my ($pg_source) = @_; + print dumper ($pg_source); + + # 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 $pg_source if ($pg_source =~ /loadMacros\((.*)PGML\.pl(.*)\)/ && $pg_source !~ /BEGIN_(TEXT|HINT|SOLUTION)/); + # First get a list of all of the ANS, LABELED_ANS, etc. in the problem. @ans_list = getANS($pg_source); @@ -101,29 +110,48 @@ 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 = ''; + while (1) { + # Remove comments within loadMacros block (should we keep them?) + $row =~ s/#.*$//; + $macros .= $row; + last if ($row =~ /(.*)\);/); $row = shift @rows; my @mrow = split(/#/, $row); # This only adds the row if there is something relevant 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/ - } - 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')) - . ');'); + + 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\((qw(.))?(.*?)(.)?\)/ms) { + ($qw_start, $qw_end) = ($2, $4); + @macros = + grep { + $_ + && $_ !~ + /(PGstandard|PGML|PGauxiliaryFunctions|PGbasicmacros|PGanswermacros|MathObjects|PGcourse|AnswerFormatHelp).pl/ + } + map {s/['"]//gr} split(/\s+|\s*,\s*/, $3); + + # Remove any duplicates: + my %seen; + @macros = grep { !$seen{$_}++ } @macros; + } else { + warn 'The loadMacros statement in this file could not be processed.'; + } + + @macros = ('PGstandard.pl', 'PGML.pl', @macros, 'PGcourse.pl'); + + if ($qw_start) { + push(@all_lines, "loadMacros(qw$qw_start\n\t" . join("\n\t", @macros) . "\n$qw_end);"); + } else { + push(@all_lines, 'loadMacros(' . join(', ', map {"'$_'"} @macros) . ');'); + } } else { push(@all_lines, cleanUpCode($row)); } From b814fa06d795a2c2005d28561dfa548a21af394d Mon Sep 17 00:00:00 2001 From: Peter Staab Date: Mon, 1 Jun 2026 09:13:55 -0400 Subject: [PATCH 02/13] remove debug statements --- lib/WeBWorK/PG/ConvertToPGML.pm | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/WeBWorK/PG/ConvertToPGML.pm b/lib/WeBWorK/PG/ConvertToPGML.pm index dee4aadd1..f311234d8 100644 --- a/lib/WeBWorK/PG/ConvertToPGML.pm +++ b/lib/WeBWorK/PG/ConvertToPGML.pm @@ -70,16 +70,12 @@ This returns a string that is the converted input string. =cut -use Mojo::Util qw(dumper); - # This stores the answers inside of ANS and related functions. my @ans_list; sub convertToPGML { my ($pg_source) = @_; - print dumper ($pg_source); - # 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. From 4edb2b0b70e6a2f5657ea094ef83853be3647f7b Mon Sep 17 00:00:00 2001 From: Peter Staab Date: Tue, 9 Jun 2026 11:49:20 -0400 Subject: [PATCH 03/13] Handle cases where qw blocks are mixed with strings in ' or " in loadMacros --- lib/WeBWorK/PG/ConvertToPGML.pm | 34 +++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/lib/WeBWorK/PG/ConvertToPGML.pm b/lib/WeBWorK/PG/ConvertToPGML.pm index f311234d8..ca6864fcc 100644 --- a/lib/WeBWorK/PG/ConvertToPGML.pm +++ b/lib/WeBWorK/PG/ConvertToPGML.pm @@ -107,32 +107,48 @@ sub convertToPGML { push(@pgml_block, $row); } elsif ($row =~ /loadMacros\(/) { # Parse the macros, which may be on multiple rows and may be in a qw block. - my $macros = ''; + my $macros = ''; + my $num_macro_lines = 0; # store the number of lines in the loadMacro so the output is similar to the input. while (1) { # Remove comments within loadMacros block (should we keep them?) $row =~ s/#.*$//; $macros .= $row; last if ($row =~ /(.*)\);/); + ++$num_macro_lines; $row = shift @rows; my @mrow = split(/#/, $row); # This only adds the row if there is something relevant to the left of a # $macros .= $mrow[0] if $mrow[0] !~ /^\s*$/; } - 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\((qw(.))?(.*?)(.)?\)/ms) { - ($qw_start, $qw_end) = ($2, $4); + 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); + } + } + @macros = grep { $_ && $_ !~ /(PGstandard|PGML|PGauxiliaryFunctions|PGbasicmacros|PGanswermacros|MathObjects|PGcourse|AnswerFormatHelp).pl/ } - map {s/['"]//gr} split(/\s+|\s*,\s*/, $3); + map {s/['"]//gr} @macros; # Remove any duplicates: my %seen; @@ -144,7 +160,13 @@ sub convertToPGML { @macros = ('PGstandard.pl', 'PGML.pl', @macros, 'PGcourse.pl'); if ($qw_start) { - push(@all_lines, "loadMacros(qw$qw_start\n\t" . join("\n\t", @macros) . "\n$qw_end);"); + 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) . ');'); } From c8bd029146714e6ebc96114bbce2955c8765aab2 Mon Sep 17 00:00:00 2001 From: Peter Staab Date: Thu, 25 Jun 2026 07:03:02 -0400 Subject: [PATCH 04/13] Add some error checking for loadMacros on the source Also, return a hash of the converted code and any errors. --- bin/convert-to-pgml.pl | 10 +++++++--- lib/WeBWorK/PG/ConvertToPGML.pm | 33 +++++++++++++++++++++------------ 2 files changed, 28 insertions(+), 15 deletions(-) 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 ca6864fcc..3cb59ec05 100644 --- a/lib/WeBWorK/PG/ConvertToPGML.pm +++ b/lib/WeBWorK/PG/ConvertToPGML.pm @@ -76,12 +76,17 @@ my @ans_list; sub convertToPGML { my ($pg_source) = @_; - # Check that the file is not already in PGML format by looking for PGML.pl in the loadMacros statement. + # 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 $pg_source if ($pg_source =~ /loadMacros\((.*)PGML\.pl(.*)\)/ && $pg_source !~ /BEGIN_(TEXT|HINT|SOLUTION)/); + return { pgmlCode => $pg_source } + if ($pg_source =~ /loadMacros\((.*)PGML\.pl(.*)\)/m && $pg_source !~ /BEGIN_(TEXT|HINT|SOLUTION)/); - # First get a list of all of the ANS, LABELED_ANS, etc. in the problem. + # 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; @@ -108,19 +113,20 @@ sub convertToPGML { } elsif ($row =~ /loadMacros\(/) { # Parse the macros, which may be on multiple rows and may be in a qw block. my $macros = ''; - my $num_macro_lines = 0; # store the number of lines in the loadMacro so the output is similar to the input. - while (1) { + 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; - last if ($row =~ /(.*)\);/); ++$num_macro_lines; $row = shift @rows; my @mrow = split(/#/, $row); # This only adds the row if there is something relevant to the left of a # $macros .= $mrow[0] if $mrow[0] !~ /^\s*$/; } - my @macros = (); + $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 @@ -146,7 +152,7 @@ sub convertToPGML { grep { $_ && $_ !~ - /(PGstandard|PGML|PGauxiliaryFunctions|PGbasicmacros|PGanswermacros|MathObjects|PGcourse|AnswerFormatHelp).pl/ + /(PGstandard|PGML|PGauxiliaryFunctions|PGbasicmacros|PGanswermacros|MathObjects|PGcourse|AnswerFormatHelp).pl/x } map {s/['"]//gr} @macros; @@ -154,7 +160,10 @@ sub convertToPGML { my %seen; @macros = grep { !$seen{$_}++ } @macros; } else { - warn 'The loadMacros statement in this file could not be processed.'; + return { + errors => 'The loadMacros command cannot be processed.', + pgmlCode => $pg_source + }; } @macros = ('PGstandard.pl', 'PGML.pl', @macros, 'PGcourse.pl'); @@ -165,10 +174,10 @@ sub convertToPGML { push(@all_lines, "\t$_") for (@macros); push(@all_lines, "$qw_end);"); } else { - push(@all_lines, "loadMacros(qw$qw_start" . join(' ', @macros) . "$qw_end);"); + push(@all_lines, "loadMacros(qw$qw_start" . join(' ', @macros) . "$qw_end);", ''); } } else { - push(@all_lines, 'loadMacros(' . join(', ', map {"'$_'"} @macros) . ');'); + push(@all_lines, 'loadMacros(' . join(', ', map {"'$_'"} @macros) . ');', ''); } } else { push(@all_lines, cleanUpCode($row)); @@ -183,7 +192,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 From c9a4c2ba1b7d38bbca9c6231bc999729816ecf40 Mon Sep 17 00:00:00 2001 From: Peter Staab Date: Mon, 13 Jul 2026 13:59:13 -0400 Subject: [PATCH 05/13] Changed the parsing of the load macros Add better documentation of the convert-to-pgml script. Separate the loadMacros to a separate subroutine and perform error handling in the subroutine to better capture possible syntax errors. Also, add a better way to parse the answer blanks in PGML form. --- bin/convert-to-pgml.pl | 35 ++++---- lib/WeBWorK/PG/ConvertToPGML.pm | 136 ++++++++++++++++++-------------- 2 files changed, 95 insertions(+), 76 deletions(-) diff --git a/bin/convert-to-pgml.pl b/bin/convert-to-pgml.pl index 63059cc62..e2939dbf5 100755 --- a/bin/convert-to-pgml.pl +++ b/bin/convert-to-pgml.pl @@ -8,6 +8,14 @@ =head1 SYNOPSIS convert-to-pgml -b -s pgml file1.pg file2.pg ... +Options: + + -b|--backup Create a backup of the original file before converting. + -s|--suffix=s Suffix for the converted files. Default is 'pgml'. + -v|--verbose Print verbose output. + -h|--help Show the help message. + + =head1 DESCRIPTION This converts each pg file to PGML formatting. In particular, text blocks are @@ -20,14 +28,14 @@ =head1 DESCRIPTION to the form C<[_]{}> Many code features that are no longer needed are removed including -C, C<texStrings;>> and C<normalStrings;>>. +C, C<< Context()->texStrings; >> and C<< Context()->normalStrings; >>. Any C commands are commented out. The C command is parsed, the C is included and C is removed (because it is loaded by C) and C is added to the end of the list. -Note: many of the features are converted correctly, but often there will be errors +Note: many of the features are converted correctly, but there may be errors after the conversion. Generally after using this script, the PGML style answers will need to have their corresponding variable added. @@ -42,28 +50,25 @@ =head2 OPTIONS =cut -use strict; -use warnings; -use experimental 'signatures'; +use Mojo::Base -signatures; use Mojo::File qw(curfile); use Getopt::Long; +use Pod::Usage; use lib curfile->dirname->dirname . '/lib'; use WeBWorK::PG::ConvertToPGML qw(convertToPGML); -my $backup = 0; -my $verbose = 0; -my $suffix = 'pgml'; - GetOptions( - "b|backup" => \$backup, - "s|suffix=s" => \$suffix, - "v|verbose" => \$verbose, + 'b|backup' => \my $backup, + 's|suffix=s' => \my $suffix, + 'v|verbose' => \my $verbose, + 'h|help' => \my $show_help ); +pod2usage(2) if $show_help || @ARGV == 0; -die 'arguments must have a list of pg files' unless @ARGV > 0; +$suffix //= 'pgml'; convertFile($_) for (grep { $_ =~ /\.pg$/ } @ARGV); sub convertFile ($filename) { @@ -73,11 +78,11 @@ ($filename) my $pg_source = $path->slurp; my $result = convertToPGML($pg_source); if (ref($result) eq 'HASH' && $result->{errors}) { - warn "Error parsing $filename. " . $result->{errors}; + warn "Error parsing $filename. " . $result->{errors} . "\n"; return; } - # copy the original file to a backup and then write the file + # 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; diff --git a/lib/WeBWorK/PG/ConvertToPGML.pm b/lib/WeBWorK/PG/ConvertToPGML.pm index 3cb59ec05..d7f95f605 100644 --- a/lib/WeBWorK/PG/ConvertToPGML.pm +++ b/lib/WeBWorK/PG/ConvertToPGML.pm @@ -57,7 +57,7 @@ use parent qw(Exporter); use strict; use warnings; -our @EXPORT = qw(convertToPGML); +our @EXPORT_OK = qw(convertToPGML); =head2 convertToPGML @@ -76,16 +76,6 @@ my @ans_list; sub convertToPGML { my ($pg_source) = @_; - # 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); @@ -126,58 +116,32 @@ sub convertToPGML { } $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); - } - } + my $load_macros_block = parseLoadMacros($macros); - @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 - }; - } + # If PGML.pl is a macro and there are no BEGIN_TEXT/HINT/SOLUTION blocks + # return the original source. + return { pgmlCode => $pg_source } + if (!defined($load_macros_block->{errors}) + && grep { $_ eq 'PGML.pl' } @{ $load_macros_block->{macros} } + && $pg_source !~ /^\s*BEGIN_(TEXT|HINT|SOLUTION)/m); - @macros = ('PGstandard.pl', 'PGML.pl', @macros, 'PGcourse.pl'); + return { errors => $load_macros_block->{errors}, pgmlCode => $pg_source } if ($load_macros_block->{errors}); - if ($qw_start) { + if ($load_macros_block->{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);"); + push(@all_lines, 'loadMacros(qw' . $load_macros_block->{qw_start}); + push(@all_lines, "\t$_") for (@{ $load_macros_block->{macros} }); + push(@all_lines, $load_macros_block->{qw_end} . ');'); } else { - push(@all_lines, "loadMacros(qw$qw_start" . join(' ', @macros) . "$qw_end);", ''); + push(@all_lines, + 'loadMacros(qw' + . $load_macros_block->{qw_start} + . join(' ', @{ $load_macros_block->{macros} }) + . $load_macros_block->{qw_end} . ');', + ''); } } else { - push(@all_lines, 'loadMacros(' . join(', ', map {"'$_'"} @macros) . ');', ''); + push(@all_lines, 'loadMacros(' . join(', ', map {"'$_'"} @{ $load_macros_block->{macros} }) . ');', ''); } } else { push(@all_lines, cleanUpCode($row)); @@ -195,6 +159,56 @@ sub convertToPGML { return { pgmlCode => join "\n", @all_lines }; } +sub parseLoadMacros { + my ($macros) = @_; + + my $error_string = 'The loadMacros statement could not be parsed. Check for syntax errors.'; + + return { errors => $error_string } + if $macros =~ /loadMacros\(.*?\)(.*?);/s && $1 !~ /^\s*$/m; + + my @macros; + my ($qw_start, $qw_end); # the characters if the loadMacros has a qw block. + my $qw_matches = { '{' => '}', '(' => ')', '[' => ']', '/' => '/', '|' => '|' }; + + # 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(.)/) { + $qw_start = $1; + $qw_end = $qw_matches->{$qw_start}; + push(@macros, split(/\s+/, $1)) if $str =~ /^qw\Q${qw_start}\E(.*?)\Q${qw_end}\E/; + } else { + push(@macros, $str); + } + } + + @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 => $error_string }; + } + + @macros = ('PGstandard.pl', 'PGML.pl', @macros, 'PGcourse.pl'); + return { + qw_start => $qw_start, + qw_end => $qw_end, + macros => \@macros + }; +} + # This subroutine converts a block (passed in as an array ref of strings) to # PGML format. This includes: # * converting BEGIN_TEXT/END_TEXT to BEGIN_PGML/END_PGML @@ -269,12 +283,12 @@ sub convertPGMLBlock { } # After many other variables have been replaced, replace the variables in the PGML block. - # However if not in a {}, assumed to be in an answer blank. + # If a variable is inside [_]{}, like '[_]{$a}', then leave it alone. if (my @matches = $row =~ /\$[\w\_]+/g) { - for my $m (@matches) { - $m =~ s/\$/\\\$/; - # Wrap variables in []. Handle arrays, hashes, array refs and hashrefs. - $row =~ s/(?)?\{.*?\})?)/[$1]/; + my %seen; + for my $m (grep { !$seen{$_}++ } @matches) { + # $row =~ s/(? Date: Tue, 14 Jul 2026 14:33:00 -0400 Subject: [PATCH 06/13] Update to handle another loadMacros case. --- lib/WeBWorK/PG/ConvertToPGML.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WeBWorK/PG/ConvertToPGML.pm b/lib/WeBWorK/PG/ConvertToPGML.pm index d7f95f605..37e765ad5 100644 --- a/lib/WeBWorK/PG/ConvertToPGML.pm +++ b/lib/WeBWorK/PG/ConvertToPGML.pm @@ -173,7 +173,7 @@ sub parseLoadMacros { # The following can parse loadMacros in the form loadMacros('macro1.pl', 'macro2.pl'); or # loadMacros(qw{macro1.pl macro2.pl}); - if ($macros =~ /loadMacros\((.*?)\);/ms) { + if ($macros =~ /loadMacros\(\s*(.*?)\s*\);/ms) { my @macro_str = split(/\s*,\s*/, $1); for my $str (@macro_str) { From f37862623f759446e81c68cbd6d5a3e8b888f814 Mon Sep 17 00:00:00 2001 From: Peter Staab Date: Thu, 28 May 2026 15:20:51 -0400 Subject: [PATCH 07/13] Add the ability to parse loadMacros if the arguments are in a qw block. Also, removes empty macros and duplicate macros. In addition, if it appears that the problem is already in PGML mode, then return the file without changes. --- lib/WeBWorK/PG/ConvertToPGML.pm | 73 ++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/lib/WeBWorK/PG/ConvertToPGML.pm b/lib/WeBWorK/PG/ConvertToPGML.pm index 37e765ad5..b689700cf 100644 --- a/lib/WeBWorK/PG/ConvertToPGML.pm +++ b/lib/WeBWorK/PG/ConvertToPGML.pm @@ -70,13 +70,22 @@ This returns a string that is the converted input string. =cut +use Mojo::Util qw(dumper); + # This stores the answers inside of ANS and related functions. my @ans_list; sub convertToPGML { my ($pg_source) = @_; - # Get a list of all of the ANS, LABELED_ANS, etc. in the problem. + print dumper ($pg_source); + + # 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 $pg_source if ($pg_source =~ /loadMacros\((.*)PGML\.pl(.*)\)/ && $pg_source !~ /BEGIN_(TEXT|HINT|SOLUTION)/); + + # First get a list of all of the ANS, LABELED_ANS, etc. in the problem. @ans_list = getANS($pg_source); my @pgml_block; @@ -102,46 +111,46 @@ sub convertToPGML { push(@pgml_block, $row); } elsif ($row =~ /loadMacros\(/) { # 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*;/) { + my $macros = ''; + while (1) { # Remove comments within loadMacros block (should we keep them?) $row =~ s/#.*$//; $macros .= $row; - ++$num_macro_lines; + last if ($row =~ /(.*)\);/); $row = shift @rows; my @mrow = split(/#/, $row); # This only adds the row if there is something relevant to the left of a # $macros .= $mrow[0] if $mrow[0] !~ /^\s*$/; } - $macros .= $row; - - my $load_macros_block = parseLoadMacros($macros); - - # If PGML.pl is a macro and there are no BEGIN_TEXT/HINT/SOLUTION blocks - # return the original source. - return { pgmlCode => $pg_source } - if (!defined($load_macros_block->{errors}) - && grep { $_ eq 'PGML.pl' } @{ $load_macros_block->{macros} } - && $pg_source !~ /^\s*BEGIN_(TEXT|HINT|SOLUTION)/m); - - return { errors => $load_macros_block->{errors}, pgmlCode => $pg_source } if ($load_macros_block->{errors}); - - if ($load_macros_block->{qw_start}) { - if ($num_macro_lines > 1) { # put each macro on a separate line - push(@all_lines, 'loadMacros(qw' . $load_macros_block->{qw_start}); - push(@all_lines, "\t$_") for (@{ $load_macros_block->{macros} }); - push(@all_lines, $load_macros_block->{qw_end} . ');'); - } else { - push(@all_lines, - 'loadMacros(qw' - . $load_macros_block->{qw_start} - . join(' ', @{ $load_macros_block->{macros} }) - . $load_macros_block->{qw_end} . ');', - ''); - } + + 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\((qw(.))?(.*?)(.)?\)/ms) { + ($qw_start, $qw_end) = ($2, $4); + @macros = + grep { + $_ + && $_ !~ + /(PGstandard|PGML|PGauxiliaryFunctions|PGbasicmacros|PGanswermacros|MathObjects|PGcourse|AnswerFormatHelp).pl/ + } + map {s/['"]//gr} split(/\s+|\s*,\s*/, $3); + + # Remove any duplicates: + my %seen; + @macros = grep { !$seen{$_}++ } @macros; + } else { + warn 'The loadMacros statement in this file could not be processed.'; + } + + @macros = ('PGstandard.pl', 'PGML.pl', @macros, 'PGcourse.pl'); + + if ($qw_start) { + push(@all_lines, "loadMacros(qw$qw_start\n\t" . join("\n\t", @macros) . "\n$qw_end);"); } else { - push(@all_lines, 'loadMacros(' . join(', ', map {"'$_'"} @{ $load_macros_block->{macros} }) . ');', ''); + push(@all_lines, 'loadMacros(' . join(', ', map {"'$_'"} @macros) . ');'); } } else { push(@all_lines, cleanUpCode($row)); From 9ccd028a9a3b750c44d3340f6ee311a20a221de8 Mon Sep 17 00:00:00 2001 From: Peter Staab Date: Mon, 1 Jun 2026 09:13:55 -0400 Subject: [PATCH 08/13] remove debug statements --- lib/WeBWorK/PG/ConvertToPGML.pm | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/WeBWorK/PG/ConvertToPGML.pm b/lib/WeBWorK/PG/ConvertToPGML.pm index b689700cf..a769d396b 100644 --- a/lib/WeBWorK/PG/ConvertToPGML.pm +++ b/lib/WeBWorK/PG/ConvertToPGML.pm @@ -70,16 +70,12 @@ This returns a string that is the converted input string. =cut -use Mojo::Util qw(dumper); - # This stores the answers inside of ANS and related functions. my @ans_list; sub convertToPGML { my ($pg_source) = @_; - print dumper ($pg_source); - # 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. From 9f50297b484c43c80fdd083a9278745e76095f17 Mon Sep 17 00:00:00 2001 From: Peter Staab Date: Tue, 9 Jun 2026 11:49:20 -0400 Subject: [PATCH 09/13] Handle cases where qw blocks are mixed with strings in ' or " in loadMacros --- lib/WeBWorK/PG/ConvertToPGML.pm | 34 +++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/lib/WeBWorK/PG/ConvertToPGML.pm b/lib/WeBWorK/PG/ConvertToPGML.pm index a769d396b..100fb66ea 100644 --- a/lib/WeBWorK/PG/ConvertToPGML.pm +++ b/lib/WeBWorK/PG/ConvertToPGML.pm @@ -107,32 +107,48 @@ sub convertToPGML { push(@pgml_block, $row); } elsif ($row =~ /loadMacros\(/) { # Parse the macros, which may be on multiple rows and may be in a qw block. - my $macros = ''; + my $macros = ''; + my $num_macro_lines = 0; # store the number of lines in the loadMacro so the output is similar to the input. while (1) { # Remove comments within loadMacros block (should we keep them?) $row =~ s/#.*$//; $macros .= $row; last if ($row =~ /(.*)\);/); + ++$num_macro_lines; $row = shift @rows; my @mrow = split(/#/, $row); # This only adds the row if there is something relevant to the left of a # $macros .= $mrow[0] if $mrow[0] !~ /^\s*$/; } - 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\((qw(.))?(.*?)(.)?\)/ms) { - ($qw_start, $qw_end) = ($2, $4); + 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); + } + } + @macros = grep { $_ && $_ !~ /(PGstandard|PGML|PGauxiliaryFunctions|PGbasicmacros|PGanswermacros|MathObjects|PGcourse|AnswerFormatHelp).pl/ } - map {s/['"]//gr} split(/\s+|\s*,\s*/, $3); + map {s/['"]//gr} @macros; # Remove any duplicates: my %seen; @@ -144,7 +160,13 @@ sub convertToPGML { @macros = ('PGstandard.pl', 'PGML.pl', @macros, 'PGcourse.pl'); if ($qw_start) { - push(@all_lines, "loadMacros(qw$qw_start\n\t" . join("\n\t", @macros) . "\n$qw_end);"); + 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) . ');'); } From f357023c83b15ce8ccdda6b7bcdfe57dca8887ff Mon Sep 17 00:00:00 2001 From: Peter Staab Date: Thu, 25 Jun 2026 07:03:02 -0400 Subject: [PATCH 10/13] Add some error checking for loadMacros on the source Also, return a hash of the converted code and any errors. --- bin/convert-to-pgml.pl | 2 +- lib/WeBWorK/PG/ConvertToPGML.pm | 31 ++++++++++++++++++++----------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/bin/convert-to-pgml.pl b/bin/convert-to-pgml.pl index e2939dbf5..9dd7c29cc 100755 --- a/bin/convert-to-pgml.pl +++ b/bin/convert-to-pgml.pl @@ -78,7 +78,7 @@ ($filename) my $pg_source = $path->slurp; my $result = convertToPGML($pg_source); if (ref($result) eq 'HASH' && $result->{errors}) { - warn "Error parsing $filename. " . $result->{errors} . "\n"; + warn "Error parsing $filename. " . $result->{errors}; return; } diff --git a/lib/WeBWorK/PG/ConvertToPGML.pm b/lib/WeBWorK/PG/ConvertToPGML.pm index 100fb66ea..04e9b8037 100644 --- a/lib/WeBWorK/PG/ConvertToPGML.pm +++ b/lib/WeBWorK/PG/ConvertToPGML.pm @@ -76,12 +76,17 @@ my @ans_list; sub convertToPGML { my ($pg_source) = @_; - # Check that the file is not already in PGML format by looking for PGML.pl in the loadMacros statement. + # 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 $pg_source if ($pg_source =~ /loadMacros\((.*)PGML\.pl(.*)\)/ && $pg_source !~ /BEGIN_(TEXT|HINT|SOLUTION)/); + return { pgmlCode => $pg_source } + if ($pg_source =~ /loadMacros\((.*)PGML\.pl(.*)\)/m && $pg_source !~ /BEGIN_(TEXT|HINT|SOLUTION)/); - # First get a list of all of the ANS, LABELED_ANS, etc. in the problem. + # 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; @@ -108,19 +113,20 @@ sub convertToPGML { } elsif ($row =~ /loadMacros\(/) { # Parse the macros, which may be on multiple rows and may be in a qw block. my $macros = ''; - my $num_macro_lines = 0; # store the number of lines in the loadMacro so the output is similar to the input. - while (1) { + 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; - last if ($row =~ /(.*)\);/); ++$num_macro_lines; $row = shift @rows; my @mrow = split(/#/, $row); # This only adds the row if there is something relevant to the left of a # $macros .= $mrow[0] if $mrow[0] !~ /^\s*$/; } - my @macros = (); + $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 @@ -146,7 +152,7 @@ sub convertToPGML { grep { $_ && $_ !~ - /(PGstandard|PGML|PGauxiliaryFunctions|PGbasicmacros|PGanswermacros|MathObjects|PGcourse|AnswerFormatHelp).pl/ + /(PGstandard|PGML|PGauxiliaryFunctions|PGbasicmacros|PGanswermacros|MathObjects|PGcourse|AnswerFormatHelp).pl/x } map {s/['"]//gr} @macros; @@ -154,7 +160,10 @@ sub convertToPGML { my %seen; @macros = grep { !$seen{$_}++ } @macros; } else { - warn 'The loadMacros statement in this file could not be processed.'; + return { + errors => 'The loadMacros command cannot be processed.', + pgmlCode => $pg_source + }; } @macros = ('PGstandard.pl', 'PGML.pl', @macros, 'PGcourse.pl'); @@ -165,10 +174,10 @@ sub convertToPGML { push(@all_lines, "\t$_") for (@macros); push(@all_lines, "$qw_end);"); } else { - push(@all_lines, "loadMacros(qw$qw_start" . join(' ', @macros) . "$qw_end);"); + push(@all_lines, "loadMacros(qw$qw_start" . join(' ', @macros) . "$qw_end);", ''); } } else { - push(@all_lines, 'loadMacros(' . join(', ', map {"'$_'"} @macros) . ');'); + push(@all_lines, 'loadMacros(' . join(', ', map {"'$_'"} @macros) . ');', ''); } } else { push(@all_lines, cleanUpCode($row)); From bd9b9d8daf72abd28e64165bfd1b013b82ba3cac Mon Sep 17 00:00:00 2001 From: Peter Staab Date: Mon, 13 Jul 2026 13:59:13 -0400 Subject: [PATCH 11/13] Changed the parsing of the load macros Add better documentation of the convert-to-pgml script. Separate the loadMacros to a separate subroutine and perform error handling in the subroutine to better capture possible syntax errors. Also, add a better way to parse the answer blanks in PGML form. --- bin/convert-to-pgml.pl | 2 +- lib/WeBWorK/PG/ConvertToPGML.pm | 76 +++++++++------------------------ 2 files changed, 21 insertions(+), 57 deletions(-) diff --git a/bin/convert-to-pgml.pl b/bin/convert-to-pgml.pl index 9dd7c29cc..e2939dbf5 100755 --- a/bin/convert-to-pgml.pl +++ b/bin/convert-to-pgml.pl @@ -78,7 +78,7 @@ ($filename) my $pg_source = $path->slurp; my $result = convertToPGML($pg_source); if (ref($result) eq 'HASH' && $result->{errors}) { - warn "Error parsing $filename. " . $result->{errors}; + warn "Error parsing $filename. " . $result->{errors} . "\n"; return; } diff --git a/lib/WeBWorK/PG/ConvertToPGML.pm b/lib/WeBWorK/PG/ConvertToPGML.pm index 04e9b8037..d7f95f605 100644 --- a/lib/WeBWorK/PG/ConvertToPGML.pm +++ b/lib/WeBWorK/PG/ConvertToPGML.pm @@ -76,16 +76,6 @@ my @ans_list; sub convertToPGML { my ($pg_source) = @_; - # 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); @@ -126,58 +116,32 @@ sub convertToPGML { } $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); - } - } + my $load_macros_block = parseLoadMacros($macros); - @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 - }; - } + # If PGML.pl is a macro and there are no BEGIN_TEXT/HINT/SOLUTION blocks + # return the original source. + return { pgmlCode => $pg_source } + if (!defined($load_macros_block->{errors}) + && grep { $_ eq 'PGML.pl' } @{ $load_macros_block->{macros} } + && $pg_source !~ /^\s*BEGIN_(TEXT|HINT|SOLUTION)/m); - @macros = ('PGstandard.pl', 'PGML.pl', @macros, 'PGcourse.pl'); + return { errors => $load_macros_block->{errors}, pgmlCode => $pg_source } if ($load_macros_block->{errors}); - if ($qw_start) { + if ($load_macros_block->{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);"); + push(@all_lines, 'loadMacros(qw' . $load_macros_block->{qw_start}); + push(@all_lines, "\t$_") for (@{ $load_macros_block->{macros} }); + push(@all_lines, $load_macros_block->{qw_end} . ');'); } else { - push(@all_lines, "loadMacros(qw$qw_start" . join(' ', @macros) . "$qw_end);", ''); + push(@all_lines, + 'loadMacros(qw' + . $load_macros_block->{qw_start} + . join(' ', @{ $load_macros_block->{macros} }) + . $load_macros_block->{qw_end} . ');', + ''); } } else { - push(@all_lines, 'loadMacros(' . join(', ', map {"'$_'"} @macros) . ');', ''); + push(@all_lines, 'loadMacros(' . join(', ', map {"'$_'"} @{ $load_macros_block->{macros} }) . ');', ''); } } else { push(@all_lines, cleanUpCode($row)); @@ -209,7 +173,7 @@ sub parseLoadMacros { # The following can parse loadMacros in the form loadMacros('macro1.pl', 'macro2.pl'); or # loadMacros(qw{macro1.pl macro2.pl}); - if ($macros =~ /loadMacros\(\s*(.*?)\s*\);/ms) { + if ($macros =~ /loadMacros\((.*?)\);/ms) { my @macro_str = split(/\s*,\s*/, $1); for my $str (@macro_str) { From 663322f99922ad9c465f1a879f36e6b30fd2eae7 Mon Sep 17 00:00:00 2001 From: Peter Staab Date: Tue, 14 Jul 2026 14:33:00 -0400 Subject: [PATCH 12/13] Update to handle another loadMacros case. --- lib/WeBWorK/PG/ConvertToPGML.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WeBWorK/PG/ConvertToPGML.pm b/lib/WeBWorK/PG/ConvertToPGML.pm index d7f95f605..37e765ad5 100644 --- a/lib/WeBWorK/PG/ConvertToPGML.pm +++ b/lib/WeBWorK/PG/ConvertToPGML.pm @@ -173,7 +173,7 @@ sub parseLoadMacros { # The following can parse loadMacros in the form loadMacros('macro1.pl', 'macro2.pl'); or # loadMacros(qw{macro1.pl macro2.pl}); - if ($macros =~ /loadMacros\((.*?)\);/ms) { + if ($macros =~ /loadMacros\(\s*(.*?)\s*\);/ms) { my @macro_str = split(/\s*,\s*/, $1); for my $str (@macro_str) { From 9f50d879f8c4797e1c2ed93f2aec73aff8eed15d Mon Sep 17 00:00:00 2001 From: Peter Staab Date: Thu, 23 Jul 2026 09:10:00 -0400 Subject: [PATCH 13/13] Another fix for parsing loadMacros. --- lib/WeBWorK/PG/ConvertToPGML.pm | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/WeBWorK/PG/ConvertToPGML.pm b/lib/WeBWorK/PG/ConvertToPGML.pm index 37e765ad5..90886b5c7 100644 --- a/lib/WeBWorK/PG/ConvertToPGML.pm +++ b/lib/WeBWorK/PG/ConvertToPGML.pm @@ -106,13 +106,9 @@ sub convertToPGML { 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; + $macros .= $row =~ s/#.*$//r; ++$num_macro_lines; $row = shift @rows; - my @mrow = split(/#/, $row); - # This only adds the row if there is something relevant to the left of a # - $macros .= $mrow[0] if $mrow[0] !~ /^\s*$/; } $macros .= $row;