/// <summary> /// Starts the serialization process. Takes the abstract of the graph and /// constructs a NMLType proxy-like object which will be serialized via the /// standard .Net XmlSerializer process. /// </summary> /// <param name="writer">An XmlWriter</param> /// <param name="g">The GraphAbstract object to be serialized</param> public void Serialize(XmlWriter writer, GraphAbstract g) { try { //the root of the NML NMLType nml = new NMLType(); //add the version node nml.Version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); //the graph node GraphType graph = new GraphType(); nml.Graph = graph; //add the graph information graph.GraphInformation = new GraphInformationType(g.GraphInformation); //serialize the shapes foreach (Shape s in g.Shapes) { graph.Items.Add(SerializeNode(s)); } //serialize the connections foreach (Connection c in g.Connections) { graph.Items.Add(SerializeEdge(c)); } // foreach(DictionaryEntry de in keyList) // { // nml.Key.Add(BuildKeyType((String)de.Key)); // } // serialize XmlSerializer ser = new XmlSerializer(typeof(NMLType)); ser.Serialize(writer, nml); } catch (Exception exc) { site.OutputInfo(exc.Message, "NMLSerializer.Serialize", OutputInfoLevels.Exception); } catch { site.OutputInfo("Non-CLS exception caught.", "BinarySerializer.Serialize", OutputInfoLevels.Exception); } finally { } }
/// <summary> /// Deserializes the graph's xml /// </summary> /// <returns></returns> public GraphAbstract Deserialize(XmlReader reader) { //very important; always use the same instantiation parameter of the XmlSerializer as //you used for serialization! XmlSerializer ser = new XmlSerializer(typeof(NMLType)); if (ser.CanDeserialize(reader)) { NMLType gtype = ser.Deserialize(reader) as NMLType; return(Deserialize(gtype)); } else { throw new Exception("The supplied file cannot be deserialized to a graph (check the XML)"); } }
/// <summary> /// Deserializes the graphtype, here's where all the smart stuff happens /// </summary> /// <param name="gml">the graphtype which acts as an intermediate storage between XML and the GraphAbstract /// </param> /// <returns></returns> private GraphAbstract Deserialize(NMLType gml) { GraphAbstract abs = new GraphAbstract(); #region Load the graph information GraphType g = gml.Graph; abs.GraphInformation = g.GraphInformation.ToGraphInformation(); #endregion Shape shape = null; ShapeType node; DataType dt; ConnectorType ct; Connection con = null; ConnectionType et; string linePath = string.Empty; //see the split deserialization of the connection FromToCollection ftc = new FromToCollection(); //temporary store for from-to relations of connections Hashtable connectors = new Hashtable(); //temporary collection of connector #region Loop over all items for(int k =0; k<g.Items.Count;k++) //loop over all serialized items { try { if(g.Items[k] is ShapeType) { Trace.WriteLine("Node: " + (g.Items[k] as ShapeType).UID,"NMLSerializer.Deserialize"); node = g.Items[k] as ShapeType; #region find out which type of shape needs to be instantiated if(node!=null && node.InstanceKey!=string.Empty) shape = GetShape(node.InstanceKey); if(shape==null) { Trace.WriteLine("...but failed to instantiate the appropriate shape (missing or not loaded library?", "NMLSerializer.Deserialize"); continue; } #endregion #region use the attribs again to reconstruct the props for(int m=0; m<node.Data.Count;m++) //loop over the serialized data { if(node.Data[m] is DataType) { #region Handle data node dt = node.Data[m] as DataType; if(dt==null) continue; foreach (PropertyInfo pi in shape.GetType().GetProperties()) { if (Attribute.IsDefined(pi, typeof(GraphMLDataAttribute))) { try { if(pi.Name==dt.Name) { if(pi.GetIndexParameters().Length==0) { if(pi.PropertyType.Equals(typeof(int))) pi.SetValue(shape,Convert.ToInt32(dt.Value[0]),null); else if(pi.PropertyType.Equals(typeof(Color))) //Color is stored as an integer pi.SetValue(shape,Color.FromArgb(int.Parse(dt.Value[0].ToString())),null); else if(pi.PropertyType.Equals(typeof(string))) pi.SetValue(shape,(string)(dt.Value[0]),null); else if(pi.PropertyType.Equals(typeof(bool))) pi.SetValue(shape,Convert.ToBoolean(dt.Value[0]),null); else if(pi.PropertyType.Equals(typeof(Guid))) pi.SetValue(shape,new Guid((string) dt.Value[0]),null); else if(pi.PropertyType.Equals(typeof(float))) pi.SetValue(shape, Convert.ToSingle(dt.Value[0]),null); else if(pi.PropertyType.BaseType.Equals(typeof(Enum)))//yesyes, you can imp/exp enum types too pi.SetValue(shape, Enum.Parse(pi.PropertyType,dt.Value[0].ToString()),null); else if(dt.IsCollection) { #region Some explanations /* OK, here's the deal. * This part is related to the possibility to imp/exp property-collections from/to NML. * However, since (see more specifically the ClassShape) the collections will usually be defined * where the shapes is defined, i.e. in an external assembly, the collection-type is unknown. * Importing/exporting collections to NML is itsel a problem and in this part of the code the data is reflected again. * To bypass the problem that the collection-type is unknown I hereby assume that the collection can be built up again via * a public constructor of the collection with argument 'ArrayList'. In the ArrayList the collection-elements are of type string[] * whereby the order of the strings reflect the order of the GraphMLData-tagged properties of the collection elements. * For example, the ClassPropertiesCollection has the required constructor and the ClassProperty object is instantiated via the string[] elements in the * ArrayList. * Of course, this brings some restriction but it's for the moment the most flexible way I have found. * If the ClassProperty would itself have a property inheriting from CollectionBase this will not work...one has to draw a line somewhere. * It's the price to pay for using reflection and external assemblies. The whole story can be forgotten if you link the shape-classes at compile-time. * Remember; the Netron graphlib is more a toolkit than a all-in solution to all situations. * However, the current implementation will cover, I beleive, 90% of the needs. */ #endregion ArrayList list = new ArrayList(); for(int p =0; p<dt.Value.Count; p++) //loop over the collection elements { DataType dat = dt.Value[p] as DataType; //take a collection element if(dat.IsCollection) //is it itself a collection? { string[] str = new string[dat.Value.Count]; for(int l=0;l<dat.Value.Count;l++) { if((dat.Value[l] as DataType).Value.Count>0) str[l] = (string) (dat.Value[l] as DataType).Value[0]; else str[l] = string.Empty; } list.Add(str); } else { list.Add(new string[]{(string) dat.Value[0]}); } } object o; o = pi.PropertyType.GetConstructor(new Type[]{typeof(ArrayList)}).Invoke(new Object[]{list}); pi.SetValue(shape,o,null); Trace.WriteLine("'" + dt.Name + "' is an array type","NMLSeriliazer.Deserialize"); } } else pi.SetValue(shape,dt.Value,null); Trace.WriteLine("'" + dt.Name + "' deserialized.","NMLSeriliazer.Deserialize"); break; } } catch(Exception exc) { Trace.WriteLine("Failed '" + dt.Name +"': " + exc.Message,"NMLSeriliazer.Deserialize"); continue;//just try to make the best out of it } } } #endregion } else if (node.Data[m] is ConnectorType) { #region Handle connector data ct = node.Data[m] as ConnectorType; foreach(Connector c in shape.Connectors) if(c.Name==ct.Name) { c.UID = new Guid(ct.UID); break; } #endregion } } #endregion //at this point the shape is fully deserialized //but we still need to assign the ambient properties, //i.e. the properties associated to the current hosting of the control if(shape !=null) { shape.Site = site; shape.PostDeserialization(); shape.Font = site.Font; //shape.FitSize(false); abs.Shapes.Add(shape); //keep the references to the connectors, to be used when creating the connections foreach(Connector cor in shape.Connectors) connectors.Add(cor.UID.ToString(),cor); } } else if(g.Items[k] is ConnectionType) { #region handle the edge //we cannot create the connection here since not all shapes have been instantiated yet //keep the edges in temp collection, treated in next loop et = g.Items[k] as ConnectionType; con = new Connection(site); con.Font = site.Font; con.UID = new Guid(et.ID); Trace.WriteLine("Connection: " + et.ID,"NMLSeriliazer.Deserialize"); #region use the attribs to reconstruct the props for(int m=0; m<et.Data.Count;m++) //loop over the serialized data { if(et.Data[m] is DataType) { #region Handle data node, same as the shape dt = et.Data[m] as DataType; if(dt==null) continue; foreach (PropertyInfo pi in con.GetType().GetProperties()) { if (Attribute.IsDefined(pi, typeof(GraphMLDataAttribute))) { try { if(pi.Name==dt.Name) { if(dt.Name=="LinePath") { //the LinePath will not work without non-null From and To, so set it afterwards linePath = dt.Value[0].ToString(); } else if(pi.GetIndexParameters().Length==0) { if(pi.PropertyType.Equals(typeof(int))) pi.SetValue(con,Convert.ToInt32(dt.Value[0]),null); else if(pi.PropertyType.Equals(typeof(Color))) //Color is stored as an integer pi.SetValue(con,Color.FromArgb(int.Parse(dt.Value[0].ToString())),null); else if(pi.PropertyType.Equals(typeof(string))) pi.SetValue(con,(string)(dt.Value[0]),null); else if(pi.PropertyType.Equals(typeof(bool))) pi.SetValue(con,Convert.ToBoolean(dt.Value[0]),null); else if(pi.PropertyType.Equals(typeof(Guid))) pi.SetValue(con,new Guid((string) dt.Value[0]),null); else if(pi.PropertyType.Equals(typeof(float))) pi.SetValue(con, Convert.ToSingle(dt.Value[0]),null); else if (pi.PropertyType.Equals(typeof(ConnectionWeight))) pi.SetValue(con, Enum.Parse(typeof(ConnectionWeight),dt.Value[0].ToString()),null); else if (pi.PropertyType.Equals(typeof(System.Drawing.Drawing2D.DashStyle))) pi.SetValue(con, Enum.Parse(typeof(System.Drawing.Drawing2D.DashStyle),dt.Value[0].ToString()),null); } else pi.SetValue(con,dt.Value,null); Trace.WriteLine("'" + dt.Name + "' deserialized.","NMLSeriliazer.Deserialize"); break; } } catch(Exception exc) { Trace.WriteLine("Failed '" + dt.Name +"': " + exc.Message,"NMLSeriliazer.Deserialize"); continue;//just try to make the best out of it } } } #endregion } } #endregion ftc.Add(new FromTo(et.Sourceport,et.Targetport, con)); #endregion } } catch(Exception exc) { Trace.WriteLine(exc.Message,"NMLSeriliazer.Deserialize"); continue; } }//loop over items in the graph-XML #endregion #region now for the edges; //loop over the FromTo collections and pick up the corresponding connectors for(int k=0; k<ftc.Count; k++) { try { con = ftc[k].Connection; con.From = connectors[ftc[k].From] as Connector; con.To = connectors[ftc[k].To] as Connector; con.From.Connections.Add(con);//if From is null we'll fail in the catch and continue con.LinePath = linePath; //only setable after the From and To are found con.To.Connections.Add(con); abs.Insert(con); Trace.WriteLine("Connection '" + con.UID + "' added.","NMLSeriliazer.Deserialize"); } catch(Exception exc) { Trace.WriteLine("Connection failed: " + exc.Message,"NMLSeriliazer.Deserialize"); continue; //make the best of it } } #endregion // for(int n=0; n<pcs.Count; n++) // { // from = pcs[n].ChildShape; // to = abs.Shapes[pcs[n].Parent]; // con = new Connection(from, to ); // abs.Connections.Add(con); // con.site = site; // if(pcs[n].ChildShape.visible) // con.visible = true; // from.connection = con; //a lot of crossing...to make life easy really // from.parentNode =to; // to.childNodes.Add(from); // // // } return abs; }
/// <summary> /// Starts the serialization process. Takes the abstract of the graph and /// constructs a NMLType proxy-like object which will be serialized via the /// standard .Net XmlSerializer process. /// </summary> /// <param name="writer">An XmlWriter</param> /// <param name="g">The GraphAbstract object to be serialized</param> public void Serialize(XmlWriter writer, GraphAbstract g ) { try { //the root of the NML NMLType nml = new NMLType(); //add the version node nml.Version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); //the graph node GraphType graph = new GraphType(); nml.Graph = graph; //add the graph information graph.GraphInformation = new GraphInformationType(g.GraphInformation); //serialize the shapes foreach ( Shape s in g.Shapes ) graph.Items.Add(SerializeNode(s)); //serialize the connections foreach(Connection c in g.Connections) graph.Items.Add(SerializeEdge(c)); // foreach(DictionaryEntry de in keyList) // { // nml.Key.Add(BuildKeyType((String)de.Key)); // } // serialize XmlSerializer ser = new XmlSerializer(typeof(NMLType)); ser.Serialize(writer,nml); } catch(Exception exc) { site.OutputInfo(exc.Message, "NMLSerializer.Serialize", OutputInfoLevels.Exception); } catch { site.OutputInfo("Non-CLS exception caught.", "BinarySerializer.Serialize", OutputInfoLevels.Exception); } finally { } }
/// <summary> /// Deserializes the graphtype, here's where all the smart stuff happens /// </summary> /// <param name="gml">the graphtype which acts as an intermediate storage between XML and the GraphAbstract /// </param> /// <returns></returns> private GraphAbstract Deserialize(NMLType gml) { GraphAbstract abs = new GraphAbstract(); #region Load the graph information GraphType g = gml.Graph; abs.GraphInformation = g.GraphInformation.ToGraphInformation(); #endregion Shape shape = null; ShapeType node; DataType dt; ConnectorType ct; Connection con = null; ConnectionType et; string linePath = string.Empty; //see the split deserialization of the connection FromToCollection ftc = new FromToCollection(); //temporary store for from-to relations of connections Hashtable connectors = new Hashtable(); //temporary collection of connector #region Loop over all items for (int k = 0; k < g.Items.Count; k++) //loop over all serialized items { try { if (g.Items[k] is ShapeType) { Trace.WriteLine("Node: " + (g.Items[k] as ShapeType).UID, "NMLSerializer.Deserialize"); node = g.Items[k] as ShapeType; #region find out which type of shape needs to be instantiated if (node != null && node.InstanceKey != string.Empty) { shape = GetShape(node.InstanceKey); } if (shape == null) { Trace.WriteLine("...but failed to instantiate the appropriate shape (missing or not loaded library?", "NMLSerializer.Deserialize"); continue; } #endregion #region use the attribs again to reconstruct the props for (int m = 0; m < node.Data.Count; m++) //loop over the serialized data { if (node.Data[m] is DataType) { #region Handle data node dt = node.Data[m] as DataType; if (dt == null) { continue; } foreach (PropertyInfo pi in shape.GetType().GetProperties()) { if (Attribute.IsDefined(pi, typeof(GraphMLDataAttribute))) { try { if (pi.Name == dt.Name) { if (pi.GetIndexParameters().Length == 0) { if (pi.PropertyType.Equals(typeof(int))) { pi.SetValue(shape, Convert.ToInt32(dt.Value[0]), null); } else if (pi.PropertyType.Equals(typeof(Color))) //Color is stored as an integer { pi.SetValue(shape, Color.FromArgb(int.Parse(dt.Value[0].ToString())), null); } else if (pi.PropertyType.Equals(typeof(string))) { pi.SetValue(shape, (string)(dt.Value[0]), null); } else if (pi.PropertyType.Equals(typeof(bool))) { pi.SetValue(shape, Convert.ToBoolean(dt.Value[0]), null); } else if (pi.PropertyType.Equals(typeof(Guid))) { pi.SetValue(shape, new Guid((string)dt.Value[0]), null); } else if (pi.PropertyType.Equals(typeof(float))) { pi.SetValue(shape, Convert.ToSingle(dt.Value[0]), null); } else if (pi.PropertyType.BaseType.Equals(typeof(Enum))) //yesyes, you can imp/exp enum types too { pi.SetValue(shape, Enum.Parse(pi.PropertyType, dt.Value[0].ToString()), null); } else if (dt.IsCollection) { #region Some explanations /* OK, here's the deal. * This part is related to the possibility to imp/exp property-collections from/to NML. * However, since (see more specifically the ClassShape) the collections will usually be defined * where the shapes is defined, i.e. in an external assembly, the collection-type is unknown. * Importing/exporting collections to NML is itsel a problem and in this part of the code the data is reflected again. * To bypass the problem that the collection-type is unknown I hereby assume that the collection can be built up again via * a public constructor of the collection with argument 'ArrayList'. In the ArrayList the collection-elements are of type string[] * whereby the order of the strings reflect the order of the GraphMLData-tagged properties of the collection elements. * For example, the ClassPropertiesCollection has the required constructor and the ClassProperty object is instantiated via the string[] elements in the * ArrayList. * Of course, this brings some restriction but it's for the moment the most flexible way I have found. * If the ClassProperty would itself have a property inheriting from CollectionBase this will not work...one has to draw a line somewhere. * It's the price to pay for using reflection and external assemblies. The whole story can be forgotten if you link the shape-classes at compile-time. * Remember; the Netron graphlib is more a toolkit than a all-in solution to all situations. * However, the current implementation will cover, I beleive, 90% of the needs. */ #endregion ArrayList list = new ArrayList(); for (int p = 0; p < dt.Value.Count; p++) //loop over the collection elements { DataType dat = dt.Value[p] as DataType; //take a collection element if (dat.IsCollection) //is it itself a collection? { string[] str = new string[dat.Value.Count]; for (int l = 0; l < dat.Value.Count; l++) { if ((dat.Value[l] as DataType).Value.Count > 0) { str[l] = (string)(dat.Value[l] as DataType).Value[0]; } else { str[l] = string.Empty; } } list.Add(str); } else { list.Add(new string[] { (string)dat.Value[0] }); } } object o; o = pi.PropertyType.GetConstructor(new Type[] { typeof(ArrayList) }).Invoke(new Object[] { list }); pi.SetValue(shape, o, null); Trace.WriteLine("'" + dt.Name + "' is an array type", "NMLSeriliazer.Deserialize"); } } else { pi.SetValue(shape, dt.Value, null); } Trace.WriteLine("'" + dt.Name + "' deserialized.", "NMLSeriliazer.Deserialize"); break; } } catch (Exception exc) { Trace.WriteLine("Failed '" + dt.Name + "': " + exc.Message, "NMLSeriliazer.Deserialize"); continue; //just try to make the best out of it } } } #endregion } else if (node.Data[m] is ConnectorType) { #region Handle connector data ct = node.Data[m] as ConnectorType; foreach (Connector c in shape.Connectors) { if (c.Name == ct.Name) { c.UID = new Guid(ct.UID); break; } } #endregion } } #endregion //at this point the shape is fully deserialized //but we still need to assign the ambient properties, //i.e. the properties associated to the current hosting of the control if (shape != null) { shape.Site = site; shape.PostDeserialization(); shape.Font = site.Font; //shape.FitSize(false); abs.Shapes.Add(shape); //keep the references to the connectors, to be used when creating the connections foreach (Connector cor in shape.Connectors) { connectors.Add(cor.UID.ToString(), cor); } } } else if (g.Items[k] is ConnectionType) { #region handle the edge //we cannot create the connection here since not all shapes have been instantiated yet //keep the edges in temp collection, treated in next loop et = g.Items[k] as ConnectionType; con = new Connection(site); con.Font = site.Font; con.UID = new Guid(et.ID); Trace.WriteLine("Connection: " + et.ID, "NMLSeriliazer.Deserialize"); #region use the attribs to reconstruct the props for (int m = 0; m < et.Data.Count; m++) //loop over the serialized data { if (et.Data[m] is DataType) { #region Handle data node, same as the shape dt = et.Data[m] as DataType; if (dt == null) { continue; } foreach (PropertyInfo pi in con.GetType().GetProperties()) { if (Attribute.IsDefined(pi, typeof(GraphMLDataAttribute))) { try { if (pi.Name == dt.Name) { if (dt.Name == "LinePath") { //the LinePath will not work without non-null From and To, so set it afterwards linePath = dt.Value[0].ToString(); } else if (pi.GetIndexParameters().Length == 0) { if (pi.PropertyType.Equals(typeof(int))) { pi.SetValue(con, Convert.ToInt32(dt.Value[0]), null); } else if (pi.PropertyType.Equals(typeof(Color))) //Color is stored as an integer { pi.SetValue(con, Color.FromArgb(int.Parse(dt.Value[0].ToString())), null); } else if (pi.PropertyType.Equals(typeof(string))) { pi.SetValue(con, (string)(dt.Value[0]), null); } else if (pi.PropertyType.Equals(typeof(bool))) { pi.SetValue(con, Convert.ToBoolean(dt.Value[0]), null); } else if (pi.PropertyType.Equals(typeof(Guid))) { pi.SetValue(con, new Guid((string)dt.Value[0]), null); } else if (pi.PropertyType.Equals(typeof(float))) { pi.SetValue(con, Convert.ToSingle(dt.Value[0]), null); } else if (pi.PropertyType.Equals(typeof(ConnectionWeight))) { pi.SetValue(con, Enum.Parse(typeof(ConnectionWeight), dt.Value[0].ToString()), null); } } else { pi.SetValue(con, dt.Value, null); } Trace.WriteLine("'" + dt.Name + "' deserialized.", "NMLSeriliazer.Deserialize"); break; } } catch (Exception exc) { Trace.WriteLine("Failed '" + dt.Name + "': " + exc.Message, "NMLSeriliazer.Deserialize"); continue; //just try to make the best out of it } } } #endregion } } #endregion ftc.Add(new FromTo(et.Sourceport, et.Targetport, con)); #endregion } } catch (Exception exc) { Trace.WriteLine(exc.Message, "NMLSeriliazer.Deserialize"); continue; } } //loop over items in the graph-XML #endregion #region now for the edges; //loop over the FromTo collections and pick up the corresponding connectors for (int k = 0; k < ftc.Count; k++) { try { con = ftc[k].Connection; con.From = connectors[ftc[k].From] as Connector; con.To = connectors[ftc[k].To] as Connector; con.From.Connections.Add(con); //if From is null we'll fail in the catch and continue con.LinePath = linePath; //only setable after the From and To are found con.To.Connections.Add(con); abs.Insert(con); Trace.WriteLine("Connection '" + con.UID + "' added.", "NMLSeriliazer.Deserialize"); } catch (Exception exc) { Trace.WriteLine("Connection failed: " + exc.Message, "NMLSeriliazer.Deserialize"); continue; //make the best of it } } #endregion // for(int n=0; n<pcs.Count; n++) // { // from = pcs[n].ChildShape; // to = abs.Shapes[pcs[n].Parent]; // con = new Connection(from, to ); // abs.Connections.Add(con); // con.site = site; // if(pcs[n].ChildShape.visible) // con.visible = true; // from.connection = con; //a lot of crossing...to make life easy really // from.parentNode =to; // to.childNodes.Add(from); // // // } return(abs); }