#!/usr/bin/perl use strict; use warnings; ############################################################################## # perltoxmi - extract class definitions from oo perl code, and write out xmi # for import into CASE tools # Charles Colbourn June 2006 # ############################################################################## # HISTORY # # 0.01 CColbourn 20060616 Rough first version # ############################################################################## # TO DO # # Error checking # Write to file instead of STDOUT # Clean up, abstract and make readable # detect require as well as use # detect double quoted attrib names just in case # ############################################################################## # Usage # perltoxmi MyClass.pm MyOtherClass.pm > classes.xmi # OR # cat *.pm |perltoxmi >classes.xmi # ############################################################################## # Notes # # The XMI output is cut and pasted from ArgoUML output. Pretty it isn't, but # it seems to work # Export your classes into xmi, then import xmi into Argo (works with v0.20 # definitely). Can use 'add namespace # to class diagram', and 'layout' to do an # initial layout. # It exports any classes included with 'use' but outside the files it's been # passed as interfaces (which aren't automatically added into class diagrams, # handily - otherwise there would be hundreds of association lines to 'strict') # To extract attributes, they need to have been used in the form # $self->{attrib} (single quotes permitted, but # '$self' is essential). # # It's a dirty, dirty hack, but it does what I need :-) # ############################################################################## # 16 hex digits in counter my ($header, $classtemplate, $attribtemplate, $methodtemplate, $generalisetemplate, $footer, $interfacetemplate, $associationtemplate); { local $/ = '%%ENDOFTEMPLATE%%'; $header = ; $classtemplate = ; $interfacetemplate = ; $attribtemplate = ; $methodtemplate = ; $generalisetemplate = ; $associationtemplate = ; $footer = ; } my $counter = 2000; my $package; my %obj; while (<>) { if ($_ =~/package ([\w:]+);/) { $package = $1; $obj{$1} = {}; } if ($_=~/use\s+([\w\:]+);/) { $obj{$package}{uses}{$1}++; } if ($_ =~/use base\s+(?:qw\(|\'|\")([\w\:\s]+)/) { my (@parents) = $1=~/([\w\:]+)/g; for (@parents) { $obj{$package}{parents}{$_}++ } } if ($_ =~/sub\s+(\w+)/) { #print $package."::".$1."\n"; $obj{$package}{methods}{$1}++; } if ($_ =~/\$self->\{[\']*([\w\s]*)\}/) { #print $package."->$1\n" $obj{$package}{attribs}{$1}++; } } my %classnametocounter; my %generalisations; my %associations; my $interfacexml; my %interfaces; my $xml = ""; for $package (keys %obj) { my $classprintcounter = sprintf("%016X",++$counter); my $class = $classtemplate; $class=~s/%%CLASSNAME%%/$package/g; $class=~s/%%COUNTER%%/$classprintcounter/g; my $attribsxml = ""; for (keys %{$obj{$package}{attribs}}) { my $attrib = $attribtemplate; $attrib=~s/%%ATTRIBNAME%%/$_/; my $printcounter = sprintf("%016X",++$counter); $attrib =~s/%%COUNTER%%/$printcounter/; my $visibility = "public"; if ($_=~/^\_/){$visibility = "private"} $attrib =~s/%%VISIBILITY%%/$visibility/g; $attribsxml .= $attrib; } my $methodsxml = ""; for (keys %{$obj{$package}{methods}}) { my $method = $methodtemplate; $method=~s/%%METHODNAME%%/$_/; my $printcounter = sprintf("%016X",++$counter); $method =~s/%%COUNTER%%/$printcounter/; my $visibility = "public"; if ($_=~/^\_/){$visibility = "private"} $method =~s/%%VISIBILITY%%/$visibility/g; $methodsxml .= $method; } $classnametocounter{$package} = $classprintcounter; for (keys %{$obj{$package}{parents}}) { $generalisations{$package} = $_; } for (keys %{$obj{$package}{uses}}) { $interfaces{$_}++; $associations{$package}{$_}++; } $class =~s/%%ATTRIBXML%%/$attribsxml/; $class =~s/%%METHODXML%%/$methodsxml/; $xml .= $class; } for my $intname (keys %interfaces) { if ($obj{$intname}){next} # don't create an interface if the class is in read scope my $printcounter = sprintf("%016X",++$counter); my $interface = $interfacetemplate; $interface=~s/%%INTERFACENAME%%/$intname/g; $interface=~s/%%COUNTER%%/$printcounter/g; $classnametocounter{$intname} = $printcounter; $xml .= $interface; } for (keys %generalisations) { my $generalisation = $generalisetemplate; my $childcounter = $classnametocounter{$_}; my $parentcounter = $classnametocounter{$generalisations{$_}}; $generalisation =~s/%%CHILDCOUNTER%%/$childcounter/; $generalisation =~s/%%PARENTCOUNTER%%/$parentcounter/; my $printcounter = sprintf("%016X",++$counter); $generalisation =~s/%%COUNTER%%/$printcounter/; #take out the association - we don't want both a vanilla association and a generalisation if ($associations{$_}{$generalisations{$_}}){delete $associations{$_}{$generalisations{$_}}} $xml .= $generalisation; } for my $package(keys %associations) { for my $association (keys %{$associations{$package}}) { my $associationxml = $associationtemplate; my $usingclasscounter = $classnametocounter{$package}; my $usedclasscounter = $classnametocounter{$association}; $associationxml =~s/%%USINGCLASS%%/$usingclasscounter/; $associationxml =~s/%%USEDCLASS%%/$usedclasscounter/; my $printcounter = sprintf("%016X",++$counter); $associationxml =~s/%%COUNTER%%/$printcounter/; my $usedclassendcounter = sprintf("%016X",++$counter); $associationxml =~s/%%USEDENDCOUNTER%%/$usedclassendcounter/; my $usingclassendcounter = sprintf("%016X",++$counter); $associationxml =~s/%%USINGENDCOUNTER%%/$usingclassendcounter/; $xml .= $associationxml; } } # get rid of the template markers $xml=~s/%%ENDOFTEMPLATE%%//sg; $header =~s/%%ENDOFTEMPLATE%%//sg; $footer=~s/%%ENDOFTEMPLATE%%//sg; print $header."\n"; print $xml; print $footer."\n"; __DATA__ ArgoUML (using Netbeans XMI Writer version 1.0) 0.20.x %%ENDOFTEMPLATE%% %%ATTRIBXML%% %%METHODXML%% %%ENDOFTEMPLATE%% %%ENDOFTEMPLATE%% %%ENDOFTEMPLATE%% %%ENDOFTEMPLATE%% %%ENDOFTEMPLATE%% %%ENDOFTEMPLATE%% %%ENDOFTEMPLATE%%