#!/usr/bin/env perl use XML::Parser; $serializer_method = "Serialize"; $deserializer_method = "Deserialize"; $stream_class = "JILStream"; $output_file_basename = ""; foreach (@ARGV){ if(/^-h/){ &Usage(); }elsif(/^h=/){ push(@headerfiles, $'); }elsif(/^o=/){ $output_file_basename = $'; }else{ $xmlfilename = $_; } } if(length($xmlfilename) == 0){&Usage();} &SerializeXMLFile($xmlfilename); #==================== End Global Scope Program ================ #---------------------------------------------------------- # SerializeXMLFile #---------------------------------------------------------- sub SerializeXMLFile() { my $xmlfilename = $_[0]; # Read entire XML file into a string that can be written # as a static C-style string later in the serializers file open(FILE, $xmlfilename); foreach(){ chomp; s/\"/\\\"/g; push(@dictionary, "\"$_\\n\"\n"); } close(FILE); # Parse entire XML file and get reference to the contents # in the form of a tree my $parser = new XML::Parser(Style => 'Tree'); my $tree = $parser->parsefile($xmlfilename); # Loop over outer tags and process the content of any # JILDictionary sections for($ii=0; $ii<@$tree; $ii+=2){ my $tag = $$tree[$ii]; my $content = $$tree[$ii+1]; if($tag eq "JILDictionary"){ProcessTree($content);} } # Base output header and source file names on input XML file name $xmlfilename =~ /\.xml/; my $cppfilename = $`."_serializers.cc"; my $headerfilename = $`."_serializers.h"; if($output_file_basename ne ""){ $cppfilename = $output_file_basename.".cc"; $headerfilename = $output_file_basename.".h"; } # Write out header file open(FILE,">$headerfilename"); print "Writing $headerfilename ... "; # Print the file header &PrintFileHeader($xmlfilename); &PrintHeaderFile(); close(FILE); open(FILE,"$headerfilename"); @lines = ; close(FILE); print @lines." lines\n"; # Write the serializer methods to C++ file open(FILE,">$cppfilename"); print "Writing $cppfilename ... "; &PrintFileHeader($xmlfilename); &PrintSerializerMethods($headerfilename); close(FILE); open(FILE,"$cppfilename"); @lines = ; close(FILE); print @lines." lines\n"; } #---------------------------------------------------------- # PrintHeaderFile #---------------------------------------------------------- sub PrintHeaderFile() { # make a list of class names and sort them in alphabetical order my @classes; my $attr; my $tag; while( ($tag, $attr) = each %attributes){push(@classes, $$attr{"name"});} @classes = sort(@classes); # typeid() to name. Note the name() method of the type_info structure # is a bit different than the actual class name, so we make the connection # here. This MUST appear before the template of the same name is declared # in JILStream.h print FILE "\n// -------------------- typeid2name ----------------\n"; print FILE "#include \n"; print FILE "const char* JILtypeid2name(const std::type_info *t);\n\n"; print FILE "\n// -------------------- JILMakeObject ----------------\n"; print FILE "#include \n"; print FILE "class JILObjectRecord;\n"; print FILE "class $stream_class;\n"; print FILE "JILObjectRecord* JILMakeObject(const char* type_name, $stream_class *s, std::string tag);\n\n"; print FILE "#ifndef _JIL_SERIALIZERS_H_\n"; print FILE "#define _JIL_SERIALIZERS_H_\n\n"; # Print include statements for headers print FILE "#include <$stream_class.h>\n"; foreach(@headerfiles){print FILE "#include \"$_\"\n";} print FILE "\n\n"; # Print out the function declarations print FILE "// ---------------- Function Prototypes ------------------\n"; my $classname; foreach $classname (@classes){ my $tmpname = $classname; $tmpname =~ s/\{/\/g; print FILE "$stream_class& operator<<($stream_class &s, const $tmpname &c);\n"; print FILE "$stream_class& operator>>($stream_class &s, $tmpname* &c);\n"; } print FILE "\n// -------------------- JILMyDictionary ----------------\n"; print FILE "const char* JILMyDictionary(void);\n\n"; print FILE "#endif // _JIL_SERIALIZERS_H_\n\n"; } #---------------------------------------------------------- # PrintSerializerMethods #---------------------------------------------------------- sub PrintSerializerMethods() { my $headerfilename = $_[0]; print FILE "#include \"$headerfilename\"\n\n"; # make a list of class names and sort them in alphabetical order my @classes; my $attr; my $tag; while( ($tag, $attr) = each %attributes){push(@classes, $$attr{"name"});} @classes = sort(@classes); # typeid() to name. Note the name() method of the type_info structure # is a bit different than the actual class name, so we make the connection # here. print FILE "\n// -------------------- typeid2name ----------------\n"; print FILE "const char* JILtypeid2name(const type_info *t)\n{\n"; foreach $classname (@classes){ my $tmpname = $classname; $tmpname =~ s/\{/\/g; print FILE "\tif(t == &typeid($tmpname))return \"$tmpname\";\n"; } print FILE "\n"; print FILE "\tif(t == &typeid(short))return \"short\";\n"; print FILE "\tif(t == &typeid(int))return \"int\";\n"; print FILE "\tif(t == &typeid(long))return \"long\";\n"; print FILE "\tif(t == &typeid(unsigned short))return \"ushort\";\n"; print FILE "\tif(t == &typeid(unsigned int))return \"uint\";\n"; print FILE "\tif(t == &typeid(unsigned long))return \"ulong\";\n"; print FILE "\tif(t == &typeid(float))return \"float\";\n"; print FILE "\tif(t == &typeid(double))return \"double\";\n"; print FILE "\tif(t == &typeid(string))return \"string\";\n"; print FILE "\n\treturn \"unknown\";\n}\n\n"; # JILMakeObject(). This routine creates a JILObjectRecordT<> object # of the specified type which, in turn, creates the actual object # of type T and fills its values from the stream. print FILE "\n// -------------------- JILMakeObject ----------------\n"; print FILE "JILObjectRecord* JILMakeObject(const char* type_name, JILStream *s, std::string tag)\n{\n"; foreach $classname (@classes){ my $tmpname = $classname; $tmpname =~ s/\{/\/g; print FILE "\tif(!strcmp(type_name,JILtypeid2name(&typeid($tmpname))))return new JILObjectRecordT<$tmpname >(s, tag);\n"; } print FILE "\n\treturn NULL;\n}\n\n"; # Loop over all classes, writing out serializer and deserializer methods foreach $classname (@classes){ my $attr = $attributes{"$classname"}; my $contentref = $classdefs{"$classname"}; $classname =~ s/\{/\/g; my $baseclass = $$attr{"baseclass"}; $baseclass =~ s/\{/\/g; # Write out stream operator for public part print FILE "\n//----------------- $classname -----------------\n"; # --------------- Serializer routine(s) --------------- print FILE "$stream_class& operator<<($stream_class &s, const $classname &c){\n"; $tabs .= "\t"; # If this class has a serializer method declared then we need to # have the operator<< routine call it and define the Serialize() # method. Otherwise, just fill operator<< with the public data members if($$attr{"hasSerializer"} eq "true"){ print FILE $tabs."c.$serializer_method(s);\n"; print FILE $tabs."return s;\n"; print FILE "}\n"; print FILE "\nvoid $classname"."::$serializer_method($stream_class &s) const\n{\n"; &PrintMembersToStream($classname, $contentref,$baseclass); }else{ &PrintMembersToStream($classname, $contentref,$baseclass,"c"); print FILE $tabs."return s;\n"; } chop($tabs); print FILE "}\n\n"; # --------------- Deserializer routine(s) --------------- print FILE "$stream_class& operator>>($stream_class &s, $classname* &c){\n"; $tabs .= "\t"; print FILE $tabs."// First, check that the object is here and hasn't already been read in\n"; print FILE $tabs."// This will also create an object and set c to point to it if c is NULL\n"; print FILE $tabs."if(!s.ReadObject(c))return s;\n\n"; #print FILE $tabs."// Make sure object exists for us to stream to.\n"; #print FILE $tabs."if(c == NULL)c = new $classname();\n\n"; # If this class has a serializer method declared then we need to # have the operator<< routine call it and define the Serialize() # method. Otherwise, just fill operator<< with the public data members if($$attr{"hasDeserializer"} eq "true"){ print FILE $tabs."c->$deserializer_method(s);\n"; print FILE $tabs."return s;\n"; print FILE "}\n"; print FILE "\nvoid $classname"."::$deserializer_method($stream_class &s)\n{\n"; &PrintMembersFromStream($classname, $contentref,$baseclass); }else{ &PrintMembersFromStream($classname, $contentref,$baseclass,"c"); print FILE $tabs."return s;\n"; } chop($tabs); print FILE "}\n"; } print FILE "\n// -------------------- JILMyDictionary ----------------\n"; print FILE "const char* JILMyDictionary(void)\n{\n"; print FILE "\treturn "; print FILE @dictionary; print FILE ";\n}\n\n"; } #---------------------------------------------------------- # PrintFileHeader # # Put useful info at top of generated file #---------------------------------------------------------- sub PrintFileHeader() { my $xmlfilename = $_[0]; print FILE "//\n"; print FILE "// Auto-generated serializer methods:\n"; print FILE "// This file was generated from the file $xmlfilename\n"; print FILE "// on ".`date`; print FILE "//\n"; print FILE "// Command line options: "; foreach(@ARGV){print FILE $_." ";} print FILE "\n//\n\n"; } #---------------------------------------------------------- # PrintMembersToStream # # Print the C++ code to send each element of this object # into a stream. Elements that themselves have a serializer # method will have that called. Otherwise, the steam operator # (<<) will be used. #---------------------------------------------------------- sub PrintMembersToStream() { my $classname = $_[0]; my $contentref = $_[1]; my $baseclass = $_[2]; my $prefix = $_[3]; # Reference the object different depending upon whether # we're in a method or global-scope routine my $object_ptr = "this"; if(length($prefix)>0){ $object_ptr = "&c"; } # Write out the object type print FILE "\n"; print FILE $tabs."// Send object type and address to stream.\n"; print FILE $tabs."if(!s.WriteObject($object_ptr))"; if(length($prefix)>0){ print FILE "return s<0){ print FILE "\n"; print FILE $tabs."// Write out base class first\n"; print FILE $tabs."s<<*(($baseclass*)$object_ptr);\n\n"; #if(length($prefix)>0){ # Inside operator<<, use "c" # print FILE $tabs."s<<*(($baseclass*)&c);\n\n"; #}else{ # Inside Serialize, use "this" # print FILE $tabs."s<<*(($baseclass*)this);\n\n"; #} } # Loop over data members of the class. for(my $i=0; $i<@$contentref; $i+=2){ if($$contentref[$i] ne "typedef"){next;} my $memref = $$contentref[$i+1]; my $memattr = $$memref[0]; my $varname = $$memattr{"name"}; my $section = $$memattr{"section"}; my $type = $$memattr{"type"}; my $size_list = $$memattr{"size"}; # If $prefix is non-empty then it represents the object # that should be explicitly dereferenced. Simply add # the prefix to $varname. Note that this also indicates # that we can only access this member if it is "public". # If it is not, then write the code, but comment it out. print FILE $tabs; my $public_only = false; if(length($prefix) > 0){ $varname = "$prefix.$varname"; $public_only = true; if($section ne "public"){print FILE "// ";} } if($size_list>1){ # For multi-dimenisional arrays, we must write out the # for-loops here for all but the last dimension which # is then handled by WriteArray @sizes = split(/\,/,$size_list); my $mytabs = $tabs; if($public_only == true && $section ne "public"){$mytabs .= "//";} for(my $i=0; $i<@sizes-1; $i++){ print FILE "for(unsigned int i$i=0; i$i<".$sizes[$i]."; i$i++)\n"; $mytabs .= "\t"; print FILE $mytabs; $varname .= "[i$i]"; } $size = pop(@sizes); print FILE "s.WriteArray($varname, $size);"; $size_spec = "[".$size_list."]"; $size_spec =~ s/\,/\]\[/g; $type .= $size_spec; }else{ if($enums{$type} eq "true"){$varname = "(int)$varname";} print FILE "s<<$varname;"; } print FILE " // $type ($section)\n"; } # Write out end of object flag print FILE "\n"; print FILE $tabs."// Send end of object flag to stream\n"; print FILE $tabs."s<>) will be used. #---------------------------------------------------------- sub PrintMembersFromStream() { my $classname = $_[0]; my $contentref = $_[1]; my $baseclass = $_[2]; my $prefix = $_[3]; # The object type will already have been read in and a new object # created so we don't need to read anything in here. # If this class has a base class, read that part in first if(length($baseclass)>0){ print FILE $tabs."// Read in base class first\n"; if(length($prefix)>0){ # Inside operator>>, use "c" print FILE $tabs."s>>($baseclass*&)c;\n\n"; }else{ # Inside Deserialize, use "this" print FILE $tabs."$classname *c = this;\n"; print FILE $tabs."s>>($baseclass*&)c;\n\n"; } } # Loop over data members of the class. for(my $i=0; $i<@$contentref; $i+=2){ if($$contentref[$i] ne "typedef"){next;} my $memref = $$contentref[$i+1]; my $memattr = $$memref[0]; my $varname = $$memattr{"name"}; my $section = $$memattr{"section"}; my $type = $$memattr{"type"}; my $size_list = $$memattr{"size"}; # If $prefix is non-empty then it represents the object # that should be explicitly dereferenced. Simply add # the prefix to $varname. Note that this also indicates # that we can only access this member if it is "public". # If it is not, then write the code, but comment it out. print FILE $tabs; my $public_only = false; if(length($prefix) > 0){ $varname = "$prefix->$varname"; $public_only = true; if($section ne "public"){print FILE "// ";} } #if($$memattr{"pointer"} eq "true"){ # print FILE "if(s.StartPointerReadT($varname))"; #} if($size_list>1){ # For multi-dimenisional arrays, we must write out the # for-loops here for all but the last dimension which # is then handled by ReadArray @sizes = split(/\,/,$size_list); my $mytabs = $tabs; if($public_only == true){$mytabs .= "//";} for(my $i=0; $i<@sizes-1; $i++){ print FILE "for(unsigned int i$i=0; i$i<".$sizes[$i]."; i$i++)\n"; $mytabs .= "\t"; print FILE $mytabs; $varname .= "[i$i]"; } $size = pop(@sizes); print FILE "s.ReadArray($varname, $size);"; $size_spec = "[".$size_list."]"; $size_spec =~ s/\,/\]\[/g; $type .= $size_spec; }else{ if($enums{$type} eq "true"){$varname = "*(int*)&$varname";} print FILE "s>>$varname;"; } print FILE " // $type ($section)\n"; } # Ignore any end-of-object flags in the stream. Let the higher level # parser handle those. print FILE "\n"; } #---------------------------------------------------------- # ProcessTree # # Take the parsed XML tree and extract the info into the # global hashes "attributes" and "classdefs" #---------------------------------------------------------- sub ProcessTree(){ my $treetop = $_[0]; %attributes = (); %classdefs = (); # Loop over classes copying the content reference, attributes, # and name into hash arrays. my $i; for($i=1; $i<@$treetop; $i+=2){ my $tag = $$treetop[$i]; if($tag eq "enum"){ my $tmpref = $$treetop[$i+1]; my $contentref; @$contentref = @$tmpref; my $attributesref = shift @$contentref; my $enumname = $$attributesref{"name"}; $enums{$enumname} = "true"; } if($tag ne "class"){next;} my $tmpref = $$treetop[$i+1]; my $contentref; @$contentref = @$tmpref; my $attributesref = shift @$contentref; # We need to store 1.)the attributes for the class # and 2.)the content. We do this in two hashes, both # indexed by the classname. $classname = $$attributesref{"name"}; $attributes{"$classname"} = $attributesref; $classdefs{"$classname"} = $contentref; } } #---------------------------------------------------------- # Usage #---------------------------------------------------------- sub Usage() { print "\nUsage:\n"; print " jil_xml2cpp [options] file.xml\n"; print "\n"; print "Convert a JIL file (XML format) into a C++ file\n"; print "that defines the serializer/deserializer methods\n"; print "needed to store he objects defined therin.\n"; print "\n"; exit(0); }