5
# Perl module for sodipodi extensions
7
# This is a temporary hack that provides the following:
8
# * Some standard getopts (help, i/o, ids)
9
# * A way to exit that produces the error codes outlined in
10
# the extension specs (SpSVG::error)
11
# * A method that takes a function as its arguments and passes
12
# each specified element ('--id=foo --id=bar', 'ids=fooz,baaz',
13
# and so forth) as plain text to the function. The function is
14
# expected to return the processed version of this text.
19
# * Exit with a friendly message if XML::XQL isn't installed
20
# * Decide how to implement the module interface
21
# * Move from XML::XQL to SVG/SVG::Parser (see below)
22
# * Make the process method more efficient (again, see below)
24
# Authors: Daniel Goude (goude@dtek.chalmers.se)
27
package SpSVG; # Think of a better name
32
#use Data::Dumper; # For debugging
34
# From the SVG.pm documentation (actually
35
# http://roasp.com/tutorial/tutorial6.shtml):
37
# > Currently, version 2.0 of SVG.pm does not internally support DOM
38
# > traversiong functionality such as getting the children,siblings,or
39
# > parent of an element, so the interaction capability between SVG::Parser
40
# > and SVG is limited to manipulations of a known image. The next version
41
# > of SVG will support all these and more key functions which will make
42
# > SVG::Parser extremely useful.
44
# I plan to replace the /XML::XQL(::DOM)?/ code as soon as this is
53
use vars qw(@ISA @EXPORT $VERSION);
55
$VERSION = 1.02; # fixme: use SpSVG 1.01 doesn't raise exception.
65
status => make_status(),
66
name => '', # Name of script
67
usage => '', # Usage string
68
opt_help => [], # Used for --help
70
ids => [], # Array of ids that will be iterated over
72
svg => '', # SVG document object
81
my $infile = $self->{'opts'}->{'file'};
88
$self->error('IO_ERR', "Can't open $infile: $!\n");
91
$self->error('IO_ERR', "Can't close $infile: $!\n");
98
$self->{'parser'} = new XML::DOM::Parser;
99
my $parser = $self->{'parser'};
100
my $svg = $parser->parse($xml) ||
101
$self->error('INPUT_ERR', "Couldn't parse input: $!.");
102
$self->{'svg'} = $svg;
105
# Return SVG document as a string
108
my $string = $self->{'svg'}->toString;
112
# Print to $outfile|STDOUT
115
my $outfile = $self->{'opts'}->{'output'};
117
open(OUT, ">$outfile") or
118
$self->error('IO_ERR', "Can't open $outfile for writing: $!\n");
119
print OUT $self->get;
120
close OUT or $self->error('IO_ERR', "Can't close $outfile: $!\n");
130
my @ids = @{$self->{'ids'}};
132
# Apply a user supplied function to each id
133
foreach my $id (@ids) {
134
my $svg = $self->{'svg'};
136
my @nodes = $svg->xql("//*[\@id = '$id']") or
137
$self->error('NOOP_ERR', "Couldn't find element $id.");
138
my $node = shift @nodes; # Ids are unique
139
# fixme: Add more checking.
141
# Call the user function on the node identified by $id
142
my $new_node = $func->($node->toString);
144
# Replace the comment with user generated SVG
145
my $parent = $node->getParentNode;
146
my $comment = $svg->createComment('SpSVG');
147
$parent->replaceChild($comment, $node);
148
my $output = $self->{'svg'}->toString;
149
$output =~ s/<!--SpSVG-->/$new_node/;
151
# Here the whole (new) document is parsed. Probably VERY inefficient,
152
# but at least you get syntax checking for free..
153
$self->{'svg'} = $self->{'parser'}->parse($output);
154
#print $self->{'svg'}->toString;
164
0 => ["SUCCESS", "Extension exited gracefully"],
165
1 => ["GEN_FAIL", "General failure"],
166
2 => ["MEM_ERR", "Memory error"],
167
3 => ["IO_ERR", "File I/O error"],
168
4 => ["MATH_ERR", "Math error"],
169
5 => ["INPUT_ERR", "Input not understood (not valid SVG)"],
170
6 => ["NOOP_ERR", "Could not operate on any objects in this " .
172
7 => ["ARG_ERR", "Incorrect script arguments"]
175
# Generate error subs dynamically
176
foreach my $exit_code (sort keys %status) {
177
eval "sub $status{$exit_code}[0] { $exit_code; }";
184
# Create an option array suitable for Getopt::Long
189
my @opt_help = @{$self->{'opt_help'}};
190
foreach (@opt_desc) {
192
foreach my $key (keys %h) {
193
#print "Key : $h{$key}\n";
195
push @opt_vals, $h{'opt'};
196
} elsif ($key eq 'desc') {
197
my $option = $h{'opt'};
198
$option =~ s/([^=]+)=.+/$1/;
199
$option =~ s/([^|]+)/(length "$1" > 1 ? '--' : '-') . "$1"/eg;
200
push @opt_help, [$option, $h{'desc'}];
204
$self->{'opt_help'} = \@opt_help;
208
# Parse command line options
211
my @user_opt_desc = @_;
216
desc => 'Display this help and exit.',
221
desc => 'Print version and exit.',
226
desc => 'Input file (default: STDIN).',
231
desc => 'Output file (default: STDOUT).',
236
desc => 'svg id to operate on (can be multiple).',
241
desc => 'Comma-separated list of svg ids to operate on.',
245
# Create option arrays for Getopt::Long
246
my @opt_vals = $self->make_opt_vals(@opt_desc);
247
my @user_opt_vals = $self->make_opt_vals(@user_opt_desc);
249
# Append user options
250
foreach (@user_opt_vals) {
254
# Where the parsed options are stored
260
GetOptions(\%opts, @opt_vals) or usage();
262
# Handle comma-separated 'ids=foo,bar'
263
my @ids = @{$opts{'id'}} if $opts{'id'};
264
if (exists $opts{'ids'} && $opts{'ids'} =~ /[\w\d_]+(,[\w\d_]+)*/) {
265
push (@ids, split(/,/, $opts{'ids'}));
268
# Display usage etc. (and exit)
269
exists $opts{'version'} && $self->version();
270
exists $opts{'help'} && $self->usage();
272
# Save id values for later processing
273
$self->{'ids'} = \@ids;
276
$self->{'opts'} = \%opts;
278
# Return the options to script
282
# Exit with named exit status
285
my $error_name = shift;
286
my $script_error_msg = shift || '';
288
my %status = %{$self->{'status'}};
290
foreach (keys %status) {
291
if ($status{$_}[0] eq $error_name) {
292
$! = $_; # Set exit status
294
# Commented out; let sodipodi handle the error code instead
295
#my $msg = ($status{$_}->[1] . ": $script_error_msg");
297
my $msg = "$script_error_msg";
302
# Will not be reached unless an improper error_name is given
303
$! = 255; # Exit status
304
warn "Illegal error code '$error_name' called from script\n";
307
# Some accessor methods
310
my $usage = shift || die "No usage string supplied!\n";
311
$self->{'usage'} = $usage;
316
my $name = shift || die "No script name supplied!\n";
317
$self->{'name'} = $name;
320
# Print usage and exit
323
print "Usage: $self->{'name'} OPTIONS FILE\n";
324
print $self->{'usage'};
326
my @opt_help = @{$self->{'opt_help'}};
327
foreach (@opt_help) {
328
print pad($_->[0]) . $_->[1] . "\n";
337
return $string . ' ' x ($width - length($string));
342
print "Uses SpSVG version $VERSION\n";
346
# End of module; return something true