This weekend I set out for doing a Rational Rose file import plugin for Modlr. I wrote the code that successfully traversed the rather strange file format that they use. Rational Rose is a pre-xml product:
- (object Petal
- version 47
- _written "Rose 8.0.0303.1400"
- charSet 0)
- (object Design "Logical View"
- is_unit TRUE
- is_loaded TRUE
- quid "4B7FCC8D02EF"
- defaults (object defaults
- rightMargin 0.250000
- leftMargin 0.250000
- topMargin 0.250000
- bottomMargin 0.500000
- pageOverlap 0.250000
- clipIconLabels TRUE
- autoResize TRUE
- snapToGrid TRUE
- gridX 0
- gridY 0
- defaultFont (object Font
- size 12
- face "Arial"
- bold FALSE
- italics FALSE
- underline FALSE
- strike FALSE
- color 0
- default_color TRUE)
- showMessageNum 3
- showClassOfObject TRUE
- notation "Unified")
- root_usecase_package (object Class_Category "Use Case View"
- quid "4B7FCC8D02F1"
So when I had all that done I thought that it would an easy and quick task to wrap that information into a model designed in eco so that I could populate it with objects from the file. Once that was done I planned to write the code that transformed from that rose model to the eco modellayer model…
But I immediately got super tired when I realized that I had to create some 30-40 classes with a lot of attributes and relations all by hand in order to hold the rational rose data from the file…
It seemed silly that I would need to do stuff by hand at all. After all we are the tool making species! So I started to think that this is a common scenario – I have a description, or data adhering to a description, in one format, and I want to have that as a model so I can code towards it with strongly typed classes ( the older (and wiser?) I get, I get increasingly more allergic to scripting ).
So if this is a common problem that at least I come across from time to time maybe I should solve that first?
I asked myself what format the data I need to handle usually come in : XML.
So to avoid losing focus of my initial problem all together I used my Rational Rose File decoder logic and made it spit out the same information as XML:
- <?xml version="1.0" encoding="utf-8"?>
- <root>
- <Petal>
- <name></name>
- <key></key>
- <version>47</version>
- <_written>"Rose 8.0.0303.1400"</_written>
- </Petal>
- <Design>
- <name>Logical View</name>
- <key></key>
- <is_unit>TRUE</is_unit>
- <is_loaded>TRUE</is_loaded>
- <quid>"4B7FCC8D02EF"</quid>
- <defaults>
- <defaults>
- <name></name>
- <key></key>
- <rightMargin>0.250000</rightMargin>
- <leftMargin>0.250000</leftMargin>
- <topMargin>0.250000</topMargin>
- <bottomMargin>0.500000</bottomMargin>
- <pageOverlap>0.250000</pageOverlap>
- <clipIconLabels>TRUE</clipIconLabels>
- <autoResize>TRUE</autoResize>
- <snapToGrid>TRUE</snapToGrid>
- <gridX>0</gridX>
- <gridY>0</gridY>
- <defaultFont>
- <Font>
- <name></name>
The general idea was to write a plugin that given this XML, or a similar XML, or preferably any XML, could derive a model for me, saving me from doing stuff manually.
I know that some of you are thinking “oh boy this guy is writing a plugin in order to write a plugin in order to convert a rose file?”, and yes you are correct. But one plugin will be very generic – taking almost any XML and turning it into a modlr model.
So nothing fancy:
- XDocument xd = XDocument.Load(file);
- foreach (XElement xe in xd.Root.Elements())
- {
- Analyze(xe);
- }
And the magic of the plugin:
- private Eco.ModelLayer.Class Analyze(XElement xe)
- {
- Eco.ModelLayer.Class c = EnsureClass(GetNameFromXE(xe));
- foreach (XAttribute xa in xe.Attributes())
- {
- EnsureAttribute(c, xa.Name.ToString());
- }
- foreach (XElement subxe in xe.Elements())
- {
- if (subxe.FirstNode is XText || subxe.FirstNode==null)
- {
- // treat as model attribute
- EnsureAttribute(c, GetNameFromXE(subxe));
- }
- else
- {
- // treat as model association
- /*
- We treat to types of linking:
- single link may look like this:
- obj1
- attr1
- link
- attr1
- * multilink often looks like this:
- obj1
- attr1
- link
- obj2
- attr1
- */
- if (subxe.Elements().Count() > 0 && (subxe.Elements().First().FirstNode is XText || subxe.Elements().First().FirstNode == null))
- {
- Eco.ModelLayer.Class targetclass = Analyze(subxe);
- AssociationEnd ae = EnsureAssociation(c, targetclass, GetNameFromXE(subxe));
- if (ae!=null)
- ae.Multiplicity = "0..1";
- }
- else
- {
- List<Eco.ModelLayer.Class> classesfoundinrelation = new List<Eco.ModelLayer.Class>();
- foreach (XElement xeObjectInAssoc in subxe.Elements())
- {
- Eco.ModelLayer.Class targetclass = Analyze(xeObjectInAssoc);
- if (classesfoundinrelation.IndexOf(targetclass) == -1)
- classesfoundinrelation.Add(targetclass);
- }
- if (classesfoundinrelation.Count > 0)
- {
- // if classesfoundinrelation.Count>1 the association is most likely pointing to a super class of the classes found, but we dont have enough info – pick first
- AssociationEnd ae = EnsureAssociation(c, classesfoundinrelation[0], GetNameFromXE(subxe));
- if (ae!=null)
- ae.Multiplicity = "0..*";
- }
- }
- }
- }
- return c;
- }
The logic to actually create a Class, an Attribute or a Relation looks like this:
- private AssociationEnd EnsureAssociation(Eco.ModelLayer.Class cfrom, Eco.ModelLayer.Class cto, string xName)
- {
- if (_esp != null)
- {
- var res = (from x in cfrom.AssociationEnd where x.OtherEnd != null && x.OtherEnd.Name == xName select x);
- if (res.Count() == 0)
- {
- AssociationEnd ae1=new AssociationEnd(_esp);
- AssociationEnd ae2 = new AssociationEnd(_esp);
- Association a = new Association(_esp);
- a.AssociationEnd.Add(ae1);
- a.AssociationEnd.Add(ae2);
- a.Package_ = _package;
- ae2.Name = xName;
- ae2.IsNavigable = true;
- cfrom.AssociationEnd.Add(ae1);
- cto.AssociationEnd.Add(ae2);
- return ae2;
- }
- return res.First().OtherEnd;
- }
- return null;
- }
- private void EnsureAttribute(Eco.ModelLayer.Class c, string xName)
- {
- if (_esp != null)
- {
- foreach (ModelElement me in c.AllFeatures)
- {
- if (me is Eco.ModelLayer.Attribute)
- {
- if (xName == (me as Eco.ModelLayer.Attribute).Name)
- {
- return;
- }
- }
- }
- // not found
- Eco.ModelLayer.Attribute a=new Eco.ModelLayer.Attribute(_esp);
- a.Name = xName;
- if (_stringDataType==null)
- _stringDataType=(from x in _esp.GetEcoService<IExtentService>().AllInstances<Datatype>() where x.Name.ToLower()=="string" select x).First<Datatype>();
- a.Type = _stringDataType;
- c.Feature.Add(a);
- }
- }
- private Eco.ModelLayer.Class EnsureClass(string xName)
- {
- if (_esp != null)
- {
- string completename = _prefix + xName;
- var result = (from x in _esp.GetEcoService<IExtentService>().AllInstances<Eco.ModelLayer.Class>()
- where (x.Name == completename && x.OwningPackage == _package) select x).ToArray<Eco.ModelLayer.Class>();
- if (result.Count() == 0)
- {
- Eco.ModelLayer.Class c = new Eco.ModelLayer.Class(_esp);
- c.Name = completename;
- c.Package_=_package;
- return c;
- }
- else
- return result[0];
- }
- return null;
- }
- }
I tried the logic on several different xml files that I found in my temp folder and found some special cases. This lead me to the conclusion that I needed at least two different strategies: one where the xml element has a generic name and the real name is given in some attribute. One example of this is the EcoMdl file format:
- <?xml version="1.0" encoding="utf-8"?>
- <contents>
- <object type="Diagram" href="ClassDiagram_Diagram1">
- <doc><![CDATA[]]></doc>
- <modlrdiagram>diagramimages/efcd3388-264f-469f-b6e2-5e0a1dc68839.jpg</modlrdiagram>
- <attribute name="ColorOnNew">0</attribute>
- <attribute name="Id">efcd3388-264f-469f-b6e2-5e0a1dc68839</attribute>
- <attribute name="SnapGridSize">15</attribute>
- <attribute name="PresentationName">Diagram1</attribute>
- <attribute name="ShowMethodSignatures">False</attribute>
- <attribute name="ShowAssociationNames">DimDefaults</attribute>
- <attribute name="ShowVisibility">False</attribute>
- <attribute name="SquareNewAssociations">False</attribute>
- <attribute name="SquareNewGeneralizations">False</attribute>
- <attribute name="ShowMethodReturnTypes">False</attribute>
- <attribute name="Name">Diagram1</attribute>
- <link name="PlacedClass">
- <object type="PlacedClass" href="Class1">
- <attribute name="RenderedWidth">200,8</attribute>
- <attribute name="id">0aac4fb8-5dd6-4082-b16b-73c07565fc57</attribute>
- <attribute name="Color">1692800256</attribute>
- <attribute name="Size">1</attribute>
- <attribute name="LastKnownName">Class1</attribute>
- <attribute name="RenderedHeight">49,25</attribute>
But the more common scenario is the format where the xml elements have specific names. Like the pad format:
- <?xml version="1.0" encoding="UTF-8"?>
- <XML_DIZ_INFO>
- <MASTER_PAD_VERSION_INFO>
- <MASTER_PAD_VERSION>3.01</MASTER_PAD_VERSION>
- <MASTER_PAD_EDITOR>PADManager 2.0.46</MASTER_PAD_EDITOR>
- <MASTER_PAD_INFO>Portable Application Description, or PAD for short, is a data set that is used by shareware authors to disseminate information to anyone interested in their software products. To find out more go to http://www.asp-shareware.org/pad</MASTER_PAD_INFO>
- </MASTER_PAD_VERSION_INFO>
- <RoboSoft>
- <Company_UIN>CMP-009701CA593</Company_UIN>
- <Company_Description></Company_Description>
- <Product_UIN>APP-009701FB886</Product_UIN>
- <Publish_on_CD>FALSE</Publish_on_CD>
- <Search_String></Search_String>
- <Press_Release_Search_String></Press_Release_Search_String>
- <NewsFeed_Search_String></NewsFeed_Search_String>
- <Search_Engine_Search_String></Search_Engine_Search_String>
- <Web_Directories_Search_String></Web_Directories_Search_String>
- <Search_String_Unique></Search_String_Unique>
- <Comments_For_Reviewer></Comments_For_Reviewer>
- <Additional_Categories></Additional_Categories>
- <Backlink></Backlink>
- </RoboSoft>
- <Company_Info>
- <Company_Name>CapableObjects.com</Company_Name>
- <Address_1>Box 1200</Address_1>
- <Address_2>Huddinge</Address_2>
- <City_Town>Stockholm</City_Town>
I added a window that pops up when executing the plugin, it lets you pick the xml file, choose strategy, choose target package and GO:
And it gave me this (I dragged them onto the diagram myself)
Puh, now when I have a good starting point for the rational rose object model I can back to my original task… But that will be some other day…
(the plugin is in the demos folder for the next available eco build and it is called PluginXmlToModel)