home *** CD-ROM | disk | FTP | other *** search
- #!/usr/bin/perl -w
- #
- # SpSVG
- #
- # Perl module for sodipodi extensions
- #
- # This is a temporary hack that provides the following:
- # * Some standard getopts (help, i/o, ids)
- # * A way to exit that produces the error codes outlined in
- # the extension specs (SpSVG::error)
- # * A method that takes a function as its arguments and passes
- # each specified element ('--id=foo --id=bar', 'ids=fooz,baaz',
- # and so forth) as plain text to the function. The function is
- # expected to return the processed version of this text.
- #
- # TODO:
- #
- # * Write POD
- # * Exit with a friendly message if XML::XQL isn't installed
- # * Decide how to implement the module interface
- # * Move from XML::XQL to SVG/SVG::Parser (see below)
- # * Make the process method more efficient (again, see below)
- #
- # Authors: Daniel Goude (goude@dtek.chalmers.se)
- #
-
- package SpSVG; # Think of a better name
- use strict;
- #use Carp;
- use Exporter;
- use Getopt::Long;
- #use Data::Dumper; # For debugging
-
- # From the SVG.pm documentation (actually
- # http://roasp.com/tutorial/tutorial6.shtml):
- #
- # > Currently, version 2.0 of SVG.pm does not internally support DOM
- # > traversiong functionality such as getting the children,siblings,or
- # > parent of an element, so the interaction capability between SVG::Parser
- # > and SVG is limited to manipulations of a known image. The next version
- # > of SVG will support all these and more key functions which will make
- # > SVG::Parser extremely useful.
- #
- # I plan to replace the /XML::XQL(::DOM)?/ code as soon as this is
- # fixed.
-
- #use SVG;
- #use SVG::Parser;
-
- use XML::XQL;
- use XML::XQL::DOM;
-
- use vars qw(@ISA @EXPORT $VERSION);
-
- $VERSION = 1.02; # fixme: use SpSVG 1.01 doesn't raise exception.
- @ISA = qw(Exporter);
-
- # Symbols
- @EXPORT = qw(
-
- );
-
- sub new {
- my $self = {
- status => make_status(),
- name => '', # Name of script
- usage => '', # Usage string
- opt_help => [], # Used for --help
-
- ids => [], # Array of ids that will be iterated over
- # in process()
- svg => '', # SVG document object
-
- };
- bless $self;
- }
-
- sub parse {
- my $self = shift;
-
- my $infile = $self->{'opts'}->{'file'};
-
- my $xml;
- {
- local $/=undef;
- if ($infile) {
- open (IN, $infile) or
- $self->error('IO_ERR', "Can't open $infile: $!\n");
- $xml = <IN>;
- close IN or
- $self->error('IO_ERR', "Can't close $infile: $!\n");
- } else {
- $xml = <>;
- }
- }
-
-
- $self->{'parser'} = new XML::DOM::Parser;
- my $parser = $self->{'parser'};
- my $svg = $parser->parse($xml) ||
- $self->error('INPUT_ERR', "Couldn't parse input: $!.");
- $self->{'svg'} = $svg;
- }
-
- # Return SVG document as a string
- sub get {
- my $self = shift;
- my $string = $self->{'svg'}->toString;
-
- }
-
- # Print to $outfile|STDOUT
- sub dump {
- my $self = shift;
- my $outfile = $self->{'opts'}->{'output'};
- if ($outfile) {
- open(OUT, ">$outfile") or
- $self->error('IO_ERR', "Can't open $outfile for writing: $!\n");
- print OUT $self->get;
- close OUT or $self->error('IO_ERR', "Can't close $outfile: $!\n");
- } else {
- print $self->get;
- }
- }
-
- sub process_ids {
- my $self = shift;
- my $func = shift;
-
- my @ids = @{$self->{'ids'}};
-
- # Apply a user supplied function to each id
- foreach my $id (@ids) {
- my $svg = $self->{'svg'};
- #warn "ID: $id\n";
- my @nodes = $svg->xql("//*[\@id = '$id']") or
- $self->error('NOOP_ERR', "Couldn't find element $id.");
- my $node = shift @nodes; # Ids are unique
- # fixme: Add more checking.
-
- # Call the user function on the node identified by $id
- my $new_node = $func->($node->toString);
-
- # Replace the comment with user generated SVG
- my $parent = $node->getParentNode;
- my $comment = $svg->createComment('SpSVG');
- $parent->replaceChild($comment, $node);
- my $output = $self->{'svg'}->toString;
- $output =~ s/<!--SpSVG-->/$new_node/;
-
- # Here the whole (new) document is parsed. Probably VERY inefficient,
- # but at least you get syntax checking for free..
- $self->{'svg'} = $self->{'parser'}->parse($output);
- #print $self->{'svg'}->toString;
- }
-
-
- }
-
- # Exit status codes
- sub make_status {
- my $self = shift;
- my %status = (
- 0 => ["SUCCESS", "Extension exited gracefully"],
- 1 => ["GEN_FAIL", "General failure"],
- 2 => ["MEM_ERR", "Memory error"],
- 3 => ["IO_ERR", "File I/O error"],
- 4 => ["MATH_ERR", "Math error"],
- 5 => ["INPUT_ERR", "Input not understood (not valid SVG)"],
- 6 => ["NOOP_ERR", "Could not operate on any objects in this " .
- "data stream"],
- 7 => ["ARG_ERR", "Incorrect script arguments"]
- );
-
- # Generate error subs dynamically
- foreach my $exit_code (sort keys %status) {
- eval "sub $status{$exit_code}[0] { $exit_code; }";
- die $@ if $@;
- }
- return \%status;
-
- }
-
- # Create an option array suitable for Getopt::Long
- sub make_opt_vals {
- my $self = shift;
- my @opt_desc = @_;
- my @opt_vals;
- my @opt_help = @{$self->{'opt_help'}};
- foreach (@opt_desc) {
- my %h = %$_;
- foreach my $key (keys %h) {
- #print "Key : $h{$key}\n";
- if ($key eq 'opt') {
- push @opt_vals, $h{'opt'};
- } elsif ($key eq 'desc') {
- my $option = $h{'opt'};
- $option =~ s/([^=]+)=.+/$1/;
- $option =~ s/([^|]+)/(length "$1" > 1 ? '--' : '-') . "$1"/eg;
- push @opt_help, [$option, $h{'desc'}];
- }
- }
- }
- $self->{'opt_help'} = \@opt_help;
- return @opt_vals;
- }
-
- # Parse command line options
- sub get_opts {
- my $self = shift;
- my @user_opt_desc = @_;
-
- my @opt_desc = (
- {
- opt => 'help|h',
- desc => 'Display this help and exit.',
- },
-
- {
- opt => 'version|v',
- desc => 'Print version and exit.',
- },
-
- {
- opt => 'file|F=s',
- desc => 'Input file (default: STDIN).',
- },
-
- {
- opt => 'output|o=s',
- desc => 'Output file (default: STDOUT).',
- },
-
- {
- opt => 'id=s@',
- desc => 'svg id to operate on (can be multiple).',
- },
-
- {
- opt => 'ids=s',
- desc => 'Comma-separated list of svg ids to operate on.',
- },
- );
-
- # Create option arrays for Getopt::Long
- my @opt_vals = $self->make_opt_vals(@opt_desc);
- my @user_opt_vals = $self->make_opt_vals(@user_opt_desc);
-
- # Append user options
- foreach (@user_opt_vals) {
- push @opt_vals, $_;
- }
-
- # Where the parsed options are stored
- my %opts;
-
- #exit 0;
-
- # Parse all options
- GetOptions(\%opts, @opt_vals) or usage();
-
- # Handle comma-separated 'ids=foo,bar'
- my @ids = @{$opts{'id'}} if $opts{'id'};
- if (exists $opts{'ids'} && $opts{'ids'} =~ /[\w\d_]+(,[\w\d_]+)*/) {
- push (@ids, split(/,/, $opts{'ids'}));
- }
-
- # Display usage etc. (and exit)
- exists $opts{'version'} && $self->version();
- exists $opts{'help'} && $self->usage();
-
- # Save id values for later processing
- $self->{'ids'} = \@ids;
-
- # Save options
- $self->{'opts'} = \%opts;
-
- # Return the options to script
- return %opts;
- }
-
- # Exit with named exit status
- sub error {
- my $self = shift;
- my $error_name = shift;
- my $script_error_msg = shift || '';
-
- my %status = %{$self->{'status'}};
-
- foreach (keys %status) {
- if ($status{$_}[0] eq $error_name) {
- $! = $_; # Set exit status
-
- # Commented out; let sodipodi handle the error code instead
- #my $msg = ($status{$_}->[1] . ": $script_error_msg");
-
- my $msg = "$script_error_msg";
- die $msg;
- }
- }
-
- # Will not be reached unless an improper error_name is given
- $! = 255; # Exit status
- warn "Illegal error code '$error_name' called from script\n";
- }
-
- # Some accessor methods
- sub set_usage {
- my $self = shift;
- my $usage = shift || die "No usage string supplied!\n";
- $self->{'usage'} = $usage;
- }
-
- sub set_name {
- my $self = shift;
- my $name = shift || die "No script name supplied!\n";
- $self->{'name'} = $name;
- }
-
- # Print usage and exit
- sub usage {
- my $self = shift;
- print "Usage: $self->{'name'} OPTIONS FILE\n";
- print $self->{'usage'};
-
- my @opt_help = @{$self->{'opt_help'}};
- foreach (@opt_help) {
- print pad($_->[0]) . $_->[1] . "\n";
- }
-
- exit ARG_ERR();
- }
-
- sub pad {
- my $string = shift;
- my $width = '20';
- return $string . ' ' x ($width - length($string));
- }
-
- # Print version
- sub version {
- print "Uses SpSVG version $VERSION\n";
- exit ARG_ERR();
- }
-
- # End of module; return something true
- 1;
-
- __END__
-
- DOCUMENTATION HERE
-