Satsuma
a delicious .NET graph library
 All Classes Namespaces Files Functions Variables Enumerations Enumerator Properties Pages
IO.GraphML.cs
Go to the documentation of this file.
1 #region License
2 /*This file is part of Satsuma Graph Library
3 Copyright © 2013 Balázs Szalkai
4 
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8 
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12 
13  1. The origin of this software must not be misrepresented; you must not
14  claim that you wrote the original software. If you use this software
15  in a product, an acknowledgment in the product documentation would be
16  appreciated but is not required.
17 
18  2. Altered source versions must be plainly marked as such, and must not be
19  misrepresented as being the original software.
20 
21  3. This notice may not be removed or altered from any source
22  distribution.*/
23 #endregion
24 
25 using System;
26 using System.Collections.Generic;
27 using System.Xml;
28 using System.Globalization;
29 using System.IO;
30 using System.Linq;
31 using System.Xml.Linq;
32 
33 namespace Satsuma.IO.GraphML
34 {
37  public enum PropertyDomain
38  {
39  All, Node, Arc, Graph
40  }
41 
52  public abstract class GraphMLProperty
53  {
56  public string Name { get; set; }
58  public PropertyDomain Domain { get; set; }
62  public string Id { get; set; }
63 
64  protected GraphMLProperty()
65  {
66  Domain = PropertyDomain.All;
67  }
68 
70  protected static string DomainToGraphML(PropertyDomain domain)
71  {
72  switch (domain)
73  {
74  case PropertyDomain.Node: return "node";
75  case PropertyDomain.Arc: return "edge";
76  case PropertyDomain.Graph: return "graph";
77  default: return "all";
78  }
79  }
80 
83  protected static PropertyDomain ParseDomain(string s)
84  {
85  switch (s)
86  {
87  case "node": return PropertyDomain.Node;
88  case "edge": return PropertyDomain.Arc;
89  case "graph": return PropertyDomain.Graph;
90  default: return PropertyDomain.All;
91  }
92  }
93 
95  protected virtual void LoadFromKeyElement(XElement xKey)
96  {
97  var attrName = xKey.Attribute("attr.name");
98  Name = (attrName == null ? null : attrName.Value);
99  Domain = ParseDomain(xKey.Attribute("for").Value);
100  Id = xKey.Attribute("id").Value;
101 
102  var _default = Utils.ElementLocal(xKey, "default");
103  ReadData(_default, null);
104  }
105 
108  public virtual XElement GetKeyElement()
109  {
110  XElement xKey = new XElement(GraphMLFormat.xmlns + "key");
111  xKey.SetAttributeValue("attr.name", Name);
112  xKey.SetAttributeValue("for", DomainToGraphML(Domain));
113  xKey.SetAttributeValue("id", Id);
114 
115  XElement xDefault = WriteData(null);
116  if (xDefault != null)
117  {
118  xDefault.Name = GraphMLFormat.xmlns + "default";
119  xKey.Add(xDefault);
120  }
121 
122  return xKey;
123  }
124 
131  public abstract void ReadData(XElement x, object key);
136  public abstract XElement WriteData(object key);
137  }
138 
140  public abstract class DictionaryProperty<T> : GraphMLProperty, IClearable
141  {
143  public bool HasDefaultValue { get; set; }
145  public T DefaultValue { get; set; }
149  public Dictionary<object, T> Values { get; private set; }
150 
151  protected DictionaryProperty() : base()
152  {
153  HasDefaultValue = false;
154  Values = new Dictionary<object, T>();
155  }
156 
158  public void Clear()
159  {
160  HasDefaultValue = false;
161  Values.Clear();
162  }
163 
169  public bool TryGetValue(object key, out T result)
170  {
171  if (Values.TryGetValue(key, out result)) return true;
172  if (HasDefaultValue)
173  {
174  result = DefaultValue;
175  return true;
176  }
177  result = default(T);
178  return false;
179  }
180 
181  public T this[object key]
182  {
183  get
184  {
185  T result;
186  TryGetValue(key, out result);
187  return result;
188  }
189  }
190 
191  public override void ReadData(XElement x, object key)
192  {
193  if (x == null)
194  {
195  // erase
196  if (key == null) HasDefaultValue = false; else Values.Remove(key);
197  }
198  else
199  {
200  // load
201  T value = ReadValue(x);
202  if (key == null)
203  {
204  HasDefaultValue = true;
205  DefaultValue = value;
206  }
207  else Values[key] = value;
208  }
209  }
210 
211  public override XElement WriteData(object key)
212  {
213  if (key == null)
214  {
215  return HasDefaultValue ? WriteValue(DefaultValue) : null;
216  }
217  else
218  {
219  T value;
220  if (!Values.TryGetValue(key, out value)) return null;
221  return WriteValue(value);
222  }
223  }
224 
229  protected abstract T ReadValue(XElement x);
232  protected abstract XElement WriteValue(T value);
233  }
234 
236  public enum StandardType
237  {
239  }
240 
262  public sealed class StandardProperty<T> : DictionaryProperty<T>
263  {
265  private static readonly StandardType Type = ParseType(typeof(T));
267  private static readonly string TypeString = TypeToGraphML(Type);
268 
269  public StandardProperty()
270  : base()
271  { }
272 
275  internal StandardProperty(XElement xKey)
276  : this()
277  {
278  var attrType = xKey.Attribute("attr.type");
279  if (attrType == null || attrType.Value != TypeString)
280  throw new ArgumentException("Key not compatible with property.");
281  LoadFromKeyElement(xKey);
282  }
283 
285  private static StandardType ParseType(Type t)
286  {
287  if (t == typeof(bool)) return StandardType.Bool;
288  if (t == typeof(double)) return StandardType.Double;
289  if (t == typeof(float)) return StandardType.Float;
290  if (t == typeof(int)) return StandardType.Int;
291  if (t == typeof(long)) return StandardType.Long;
292  if (t == typeof(string)) return StandardType.String;
293  throw new ArgumentException("Invalid type for a standard GraphML property.");
294  }
295 
297  private static string TypeToGraphML(StandardType type)
298  {
299  switch (type)
300  {
301  case StandardType.Bool: return "boolean"; // !
302  case StandardType.Double: return "double";
303  case StandardType.Float: return "float";
304  case StandardType.Int: return "int";
305  case StandardType.Long: return "long";
306  default: return "string";
307  }
308  }
309 
310  private static object ParseValue(string value)
311  {
312  switch (Type)
313  {
314  case StandardType.Bool: return value == "true";
315  case StandardType.Double: return double.Parse(value, CultureInfo.InvariantCulture);
316  case StandardType.Float: return float.Parse(value, CultureInfo.InvariantCulture);
317  case StandardType.Int: return int.Parse(value, CultureInfo.InvariantCulture);
318  case StandardType.Long: return long.Parse(value, CultureInfo.InvariantCulture);
319  default: return value;
320  }
321  }
322 
323  private static string ValueToGraphML(object value)
324  {
325  switch (Type)
326  {
327  case StandardType.Bool: return (bool)value ? "true" : "false";
328  case StandardType.Double: return ((double)value).ToString(CultureInfo.InvariantCulture);
329  case StandardType.Float: return ((float)value).ToString(CultureInfo.InvariantCulture);
330  case StandardType.Int: return ((int)value).ToString(CultureInfo.InvariantCulture);
331  case StandardType.Long: return ((long)value).ToString(CultureInfo.InvariantCulture);
332  default: return value.ToString();
333  }
334  }
335 
336  public override XElement GetKeyElement()
337  {
338  XElement x = base.GetKeyElement();
339  x.SetAttributeValue("attr.type", TypeString);
340  return x;
341  }
342 
343  protected override T ReadValue(XElement x)
344  {
345  return (T)ParseValue(x.Value);
346  }
347 
348  protected override XElement WriteValue(T value)
349  {
350  return new XElement("dummy", ValueToGraphML(value));
351  }
352  }
353 
355  public enum NodeShape
356  {
357  Rectangle, RoundRect, Ellipse, Parallelogram,
358  Hexagon, Triangle, Rectangle3D, Octagon,
359  Diamond, Trapezoid, Trapezoid2
360  }
361 
364  public sealed class NodeGraphics
365  {
367  public double X { get; set; }
369  public double Y { get; set; }
371  public double Width { get; set; }
373  public double Height { get; set; }
375  public NodeShape Shape { get; set; }
376 
377  public NodeGraphics()
378  {
379  X = Y = 0;
380  Width = Height = 10;
381  Shape = NodeShape.Rectangle;
382  }
383 
384  private readonly string[] nodeShapeToString = { "rectangle", "roundrectangle", "ellipse", "parallelogram",
385  "hexagon", "triangle", "rectangle3d", "octagon",
386  "diamond", "trapezoid", "trapezoid2"};
387 
389  private NodeShape ParseShape(string s)
390  {
391  return (NodeShape)(Math.Max(0, Array.IndexOf(nodeShapeToString, s)));
392  }
393 
395  private string ShapeToGraphML(NodeShape shape)
396  {
397  return nodeShapeToString[(int)shape];
398  }
399 
401  public NodeGraphics(XElement xData)
402  {
403  XElement xGeometry = Utils.ElementLocal(xData, "Geometry");
404  if (xGeometry != null)
405  {
406  X = double.Parse(xGeometry.Attribute("x").Value, CultureInfo.InvariantCulture);
407  Y = double.Parse(xGeometry.Attribute("y").Value, CultureInfo.InvariantCulture);
408  Width = double.Parse(xGeometry.Attribute("width").Value, CultureInfo.InvariantCulture);
409  Height = double.Parse(xGeometry.Attribute("height").Value, CultureInfo.InvariantCulture);
410  }
411  XElement xShape = Utils.ElementLocal(xData, "Shape");
412  if (xShape != null)
413  Shape = ParseShape(xShape.Attribute("type").Value);
414  }
415 
417  public XElement ToXml()
418  {
419  return new XElement("dummy",
420  new XElement(GraphMLFormat.xmlnsY + "ShapeNode",
421  new XElement(GraphMLFormat.xmlnsY + "Geometry",
422  new XAttribute("x", X.ToString(CultureInfo.InvariantCulture)),
423  new XAttribute("y", Y.ToString(CultureInfo.InvariantCulture)),
424  new XAttribute("width", Width.ToString(CultureInfo.InvariantCulture)),
425  new XAttribute("height", Height.ToString(CultureInfo.InvariantCulture))),
426  new XElement(GraphMLFormat.xmlnsY + "Shape",
427  new XAttribute("type", ShapeToGraphML(Shape)))
428  )
429  );
430  }
431 
432  public override string ToString()
433  {
434  return ToXml().ToString();
435  }
436  }
437 
455  public sealed class NodeGraphicsProperty : DictionaryProperty<NodeGraphics>
456  {
458  : base()
459  {
460  Domain = PropertyDomain.Node;
461  }
462 
465  internal NodeGraphicsProperty(XElement xKey)
466  : this()
467  {
468  var attrYFilesType = xKey.Attribute("yfiles.type");
469  if (attrYFilesType == null || attrYFilesType.Value != "nodegraphics")
470  throw new ArgumentException("Key not compatible with property.");
471  LoadFromKeyElement(xKey);
472  }
473 
474  public override XElement GetKeyElement()
475  {
476  XElement x = base.GetKeyElement();
477  x.SetAttributeValue("yfiles.type", "nodegraphics");
478  return x;
479  }
480 
481  protected override NodeGraphics ReadValue(XElement x)
482  {
483  return new NodeGraphics(x);
484  }
485 
486  protected override XElement WriteValue(NodeGraphics value)
487  {
488  return value.ToXml();
489  }
490  }
491 
537  public sealed class GraphMLFormat
538  {
539  internal static readonly XNamespace xmlns = "http://graphml.graphdrawing.org/xmlns";
540  private static readonly XNamespace xmlnsXsi = "http://www.w3.org/2001/XMLSchema-instance"; // xmlns:xsi
541  internal static readonly XNamespace xmlnsY = "http://www.yworks.com/xml/graphml"; // xmlns:y
542  private static readonly XNamespace xmlnsYed = "http://www.yworks.com/xml/yed/3"; // xmlns:yed
543  private const string xsiSchemaLocation = "http://graphml.graphdrawing.org/xmlns\n" + // xsi:schemaLocation
544  "http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd";
545 
550  public IGraph Graph { get; set; }
554  public Dictionary<Node,string> NodeId { get; set; }
557  public Dictionary<Arc, string> ArcId { get; set; }
559  public IList<GraphMLProperty> Properties { get; private set; }
560 
561  private readonly List<Func<XElement, GraphMLProperty>> PropertyLoaders;
562 
563  public GraphMLFormat()
564  {
565  Properties = new List<GraphMLProperty>();
566  PropertyLoaders = new List<Func<XElement, GraphMLProperty>>
567  {
568  x => new StandardProperty<bool>(x),
569  x => new StandardProperty<double>(x),
570  x => new StandardProperty<float>(x),
571  x => new StandardProperty<int>(x),
572  x => new StandardProperty<long>(x),
573  x => new StandardProperty<string>(x),
574  x => new NodeGraphicsProperty(x)
575  };
576  }
577 
587  public void RegisterPropertyLoader(Func<XElement, GraphMLProperty> loader)
588  {
589  PropertyLoaders.Add(loader);
590  }
591 
592  private static void ReadProperties(Dictionary<string, GraphMLProperty> propertyById, XElement x, object obj)
593  {
594  foreach (var xData in Utils.ElementsLocal(x, "data"))
595  {
596  GraphMLProperty p;
597  if (propertyById.TryGetValue(xData.Attribute("key").Value, out p))
598  p.ReadData(xData, obj);
599  }
600  }
601 
603  public void Load(XDocument doc)
604  {
605  // Namespaces are ignored so we can load broken documents.
606  if (Graph == null) Graph = new CustomGraph();
607  IBuildableGraph buildableGraph = (IBuildableGraph)Graph;
608  buildableGraph.Clear();
609  XElement xGraphML = doc.Root;
610 
611  // load properties
612  Properties.Clear();
613  Dictionary<string, GraphMLProperty> propertyById = new Dictionary<string, GraphMLProperty>();
614  foreach (var xKey in Utils.ElementsLocal(xGraphML, "key"))
615  {
616  foreach (var handler in PropertyLoaders)
617  {
618  try
619  {
620  GraphMLProperty p = handler(xKey);
621  Properties.Add(p);
622  propertyById[p.Id] = p;
623  break;
624  }
625  catch (ArgumentException) { }
626  }
627  }
628 
629  // load graph
630  XElement xGraph = Utils.ElementLocal(xGraphML, "graph");
631  Directedness defaultDirectedness = (xGraph.Attribute("edgedefault").Value == "directed" ?
632  Directedness.Directed : Directedness.Undirected);
633  ReadProperties(propertyById, xGraph, Graph);
634  // load nodes
635  NodeId = new Dictionary<Node, string>();
636  Dictionary<string, Node> nodeById = new Dictionary<string, Node>();
637  foreach (var xNode in Utils.ElementsLocal(xGraph, "node"))
638  {
639  Node node = buildableGraph.AddNode();
640  string id = xNode.Attribute("id").Value;
641  NodeId[node] = id;
642  nodeById[id] = node;
643  ReadProperties(propertyById, xNode, node);
644  }
645  // load arcs
646  ArcId = new Dictionary<Arc, string>();
647  foreach (var xArc in Utils.ElementsLocal(xGraph, "edge"))
648  {
649  Node u = nodeById[xArc.Attribute("source").Value];
650  Node v = nodeById[xArc.Attribute("target").Value];
651 
652  Directedness dir = defaultDirectedness;
653  XAttribute dirAttr = xArc.Attribute("directed");
654  if (dirAttr != null) dir = (dirAttr.Value == "true" ? Directedness.Directed : Directedness.Undirected);
655 
656  Arc arc = buildableGraph.AddArc(u, v, dir);
657  XAttribute xId = xArc.Attribute("id");
658  if (xId != null)
659  ArcId[arc] = xId.Value;
660  ReadProperties(propertyById, xArc, arc);
661  }
662  }
663 
665  public void Load(XmlReader xml)
666  {
667  XDocument doc = XDocument.Load(xml);
668  Load(doc);
669  }
670 
673  public void Load(TextReader reader)
674  {
675  using (XmlReader xml = XmlReader.Create(reader))
676  Load(xml);
677  }
678 
680  public void Load(string filename)
681  {
682  using (StreamReader reader = new StreamReader(filename))
683  Load(reader);
684  }
685 
686  private void DefinePropertyValues(XmlWriter xml, object obj)
687  {
688  foreach (var p in Properties)
689  {
690  XElement x = p.WriteData(obj);
691  if (x == null) continue;
692  x.Name = GraphMLFormat.xmlns + "data";
693  x.SetAttributeValue("key", p.Id);
694  x.WriteTo(xml);
695  }
696  }
697 
702  public void AddStandardNodeProperty<T>(string name, Func<Node,T> getValueForNode)
703  {
705  prop.Domain = PropertyDomain.Node;
706  prop.Name = name;
707  foreach (var node in Graph.Nodes())
708  prop.Values[node] = getValueForNode(node);
709  Properties.Add(prop);
710  }
711 
716  public void AddStandardArcProperty<T>(string name, Func<Arc, T> getValueForArc)
717  {
719  prop.Domain = PropertyDomain.Arc;
720  prop.Name = name;
721  foreach (var arc in Graph.Arcs())
722  prop.Values[arc] = getValueForArc(arc);
723  Properties.Add(prop);
724  }
725 
727  private void Save(XmlWriter xml)
728  {
729  xml.WriteStartDocument();
730  xml.WriteStartElement("graphml", xmlns.NamespaceName);
731  xml.WriteAttributeString("xmlns", "xsi", null, xmlnsXsi.NamespaceName);
732  xml.WriteAttributeString("xmlns", "y", null, xmlnsY.NamespaceName);
733  xml.WriteAttributeString("xmlns", "yed", null, xmlnsYed.NamespaceName);
734  xml.WriteAttributeString("xsi", "schemaLocation", null, xsiSchemaLocation);
735 
736  for (int i = 0; i < Properties.Count; i++)
737  {
738  var p = Properties[i];
739  p.Id = "d" + i;
740  p.GetKeyElement().WriteTo(xml);
741  }
742 
743  xml.WriteStartElement("graph", xmlns.NamespaceName);
744  xml.WriteAttributeString("id", "G");
745  xml.WriteAttributeString("edgedefault", "directed");
746  xml.WriteAttributeString("parse.nodes", Graph.NodeCount().ToString(CultureInfo.InvariantCulture));
747  xml.WriteAttributeString("parse.edges", Graph.ArcCount().ToString(CultureInfo.InvariantCulture));
748  xml.WriteAttributeString("parse.order", "nodesfirst");
749  DefinePropertyValues(xml, Graph);
750 
751  Dictionary<string, Node> nodeById = new Dictionary<string, Node>();
752  if (NodeId == null)
753  NodeId = new Dictionary<Node,string>();
754  foreach (var kv in NodeId)
755  {
756  if (nodeById.ContainsKey(kv.Value))
757  throw new Exception("Duplicate node id " + kv.Value);
758  nodeById[kv.Value] = kv.Key;
759  }
760  foreach (var node in Graph.Nodes())
761  {
762  string id;
763  NodeId.TryGetValue(node, out id);
764  if (id == null)
765  {
766  id = node.Id.ToString(CultureInfo.InvariantCulture);
767  while (nodeById.ContainsKey(id))
768  id += '_';
769  NodeId[node] = id;
770  nodeById[id] = node;
771  }
772 
773  xml.WriteStartElement("node", xmlns.NamespaceName);
774  xml.WriteAttributeString("id", id);
775  DefinePropertyValues(xml, node);
776  xml.WriteEndElement(); // node
777  }
778 
779  foreach (var arc in Graph.Arcs())
780  {
781  string id;
782  if (ArcId != null)
783  ArcId.TryGetValue(arc, out id);
784  else
785  id = null;
786 
787  xml.WriteStartElement("edge", xmlns.NamespaceName);
788  if (id != null) xml.WriteAttributeString("id", id);
789  if (Graph.IsEdge(arc)) xml.WriteAttributeString("directed", "false");
790  xml.WriteAttributeString("source", NodeId[Graph.U(arc)]);
791  xml.WriteAttributeString("target", NodeId[Graph.V(arc)]);
792  DefinePropertyValues(xml, arc);
793  xml.WriteEndElement(); // edge
794  }
795 
796  xml.WriteEndElement(); // graph
797  xml.WriteEndElement(); // graphml
798  }
799 
802  public void Save(TextWriter writer)
803  {
804  using (XmlWriter xml = XmlWriter.Create(writer))
805  Save(xml);
806  }
807 
809  public void Save(string filename)
810  {
811  using (StreamWriter writer = new StreamWriter(filename))
812  Save(writer);
813  }
814  }
815 }