=head1 NAME UML::Sequence::Svg - converts xml sequence files to svg =head1 SYNOPSIS use UML::Sequence::Svg; seq2svg @ARGV; =head1 DESCRIPTION This module supports the seq2svg.pl script like Pod::Html supports pod2html. The array passed to seq2svg.pl should have the following form: ([-o output_file_name], [input_file_name]) By default input is from standard in and output is to standard out. =cut package UML::Sequence::Svg; use Exporter; @ISA = qw(Exporter); @EXPORT = qw(seq2svg); use strict; use XML::DOM; use Getopt::Std; # Constant declarations. my $CLASS_TEXT_Y = 40; my $CLASS_BOX_Y = 25; my $CLASS_BOX_HEIGHT = 20; my $CLASS_BOX_WIDTH = 125; my $CLASS_SPACING = 3; my $LEFT_EDGE = 30; my $ACTIVATION_WIDTH = 15; my $ACTIVATION_OFFSET = 10; my $FIRST_ARROW = 55; my $ARROW_SPACING = 40; # Global variable: my $output_file = "-"; sub seq2svg { local (@ARGV) = @_; parse_command_line(); my $input_file = shift @ARGV; if (defined $input_file) { open INPUT, "$input_file" or die "Couldn't open $input_file for input: $!\n"; } else { *INPUT = *STDIN; } my $parser = XML::DOM::Parser->new(); my $doc = $parser->parse(*INPUT); my $sequence = $doc->getDocumentElement(); my $title = $sequence->getAttribute("title"); my $classes = $doc->getElementsByTagName("class"); my $class_output = draw_classes($classes); my $class_hash = build_class_name_hash($classes); my $arrow_output = draw_arrows($doc, $class_hash); my $class_count = scalar (keys %$class_hash); my $arrow_count = count_arrows($doc); my $width = $class_count * ($CLASS_BOX_WIDTH + $CLASS_SPACING) + 40; my $height = 2.5 * $CLASS_TEXT_Y + $arrow_count * ($ARROW_SPACING); open SVGOUT, ">$output_file"; print SVGOUT < EOJ if ($title) { print SVGOUT < $title EOJ } print SVGOUT < EOJ } sub draw_classes { my $classes = shift; my $retval; my $x = $LEFT_EDGE; my $box_left = $LEFT_EDGE - 8; my $y = $CLASS_TEXT_Y; my $max_extent; for (my $i = 0; $i < $classes->getLength(); $i++) { my $class = $classes->item($i); my $life_x = int($x + $CLASS_BOX_WIDTH / 2); my $class_name = $class ->getAttribute("name"); my $born = $class ->getAttribute("born") * $ARROW_SPACING + $FIRST_ARROW; my $extends_to = ($class ->getAttribute("extends-to") + 1) * $ARROW_SPACING + $FIRST_ARROW; if (not defined $max_extent) { $max_extent = $extends_to; } $retval .= "$class_name\n"; $retval .= " \n"; $retval .= " \n"; my $activation_x = int($box_left + $CLASS_BOX_WIDTH / 2); my @activations = $class->getElementsByTagName("activation"); foreach my $activation (@activations) { my $born = $activation->getAttribute("born"); my $extends_to = $activation->getAttribute("extends-to"); my $offset = $activation->getAttribute("offset"); my $top = $FIRST_ARROW + $born * $ARROW_SPACING; my $height = ($extends_to - $born + .5) * $ARROW_SPACING; my $left = $activation_x + $offset * $ACTIVATION_OFFSET; $retval .= " \n"; } $x += $CLASS_BOX_WIDTH + $CLASS_SPACING; $box_left += $CLASS_BOX_WIDTH + $CLASS_SPACING; $retval .= "\n"; } return $retval; } sub count_arrows { my $doc = shift; my $arrows = $doc->getElementsByTagName("arrow"); return $arrows->getLength(); } sub draw_arrows { my $doc = shift; my $class_hash = shift; my $retval; my $arrows = $doc->getElementsByTagName("arrow"); for (my $i = 0; $i < $arrows->getLength(); $i++) { my $arrow = $arrows->item($i); my $from = $arrow->getAttribute("from" ); my $to = $arrow->getAttribute("to" ); my $type = $arrow->getAttribute("type" ); my $label = $arrow->getAttribute("label" ); my $from_offset = $arrow->getAttribute("from-offset"); my $to_offset = $arrow->getAttribute("to-offset" ); my $y = $FIRST_ARROW + ($i + 1) * $ARROW_SPACING; my $from_number = $class_hash->{$from}; my $to_number = $class_hash->{$to}; $label =~ s//>/g; if ($from_number < $to_number) { # arrow from left to right my $x1 = $from_number * ($CLASS_BOX_WIDTH + $CLASS_SPACING) + $LEFT_EDGE + ($CLASS_BOX_WIDTH + $ACTIVATION_WIDTH)/2 + $from_offset * $ACTIVATION_OFFSET; my $x2 = $to_number * ($CLASS_BOX_WIDTH + $CLASS_SPACING) + $LEFT_EDGE + ($CLASS_BOX_WIDTH - $ACTIVATION_WIDTH)/2; my $xlab = $x1 + $CLASS_SPACING; my $ylab = $y - 6; $retval .= "\n"; $retval .= "$label\n" if defined $label; } elsif ($from_number > $to_number) { # arrow from right to left my $x1 = $from_number * ($CLASS_BOX_WIDTH + $CLASS_SPACING) + $LEFT_EDGE + ($CLASS_BOX_WIDTH - $ACTIVATION_WIDTH)/2; my $x2 = $to_number * ($CLASS_BOX_WIDTH + $CLASS_SPACING) + $LEFT_EDGE + ($CLASS_BOX_WIDTH + $ACTIVATION_WIDTH)/2 + $to_offset * $ACTIVATION_OFFSET; $retval .= "\n"; my $xlab = $x2 + $CLASS_SPACING; my $ylab = $y - 6; $retval .= "$label\n" if defined $label; } else { # arrow from and to same class my $x1 = $from_number * ($CLASS_BOX_WIDTH + $CLASS_SPACING) + $LEFT_EDGE + ($CLASS_BOX_WIDTH + $ACTIVATION_WIDTH)/2 + $from_offset * $ACTIVATION_OFFSET; my $x2 = $to_number * ($CLASS_BOX_WIDTH + $CLASS_SPACING) + $LEFT_EDGE + ($CLASS_BOX_WIDTH + $ACTIVATION_WIDTH)/2 + $to_offset * $ACTIVATION_OFFSET; $y -= 10; my $y2 = $y + 20; my $x1padded = $x1 + $ACTIVATION_OFFSET + 15; $retval .= "\n" . "\n" . "\n"; my $xlab = $x1padded + $CLASS_SPACING; my $ylab = ($y + $y2) / 2; $retval .= "$label\n" if defined $label; } } return $retval; } sub build_class_name_hash { my $class_nodes = shift; my %classes; # keyed by class name store left to right position for (my $i = 0; $i < $class_nodes->getLength(); $i++) { my $class = $class_nodes->item($i); my $class_name = $class->getAttribute("name"); $classes{$class_name} = $i; } return \%classes; } sub parse_command_line { getopt('o'); use vars qw($opt_o); $output_file = $opt_o if defined $opt_o; } 1;