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 "arc";
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 override void ReadData(XElement x, object key)
182  {
183  if (x == null)
184  {
185  // erase
186  if (key == null) HasDefaultValue = false; else Values.Remove(key);
187  }
188  else
189  {
190  // load
191  T value = ReadValue(x);
192  if (key == null)
193  {
194  HasDefaultValue = true;
195  DefaultValue = value;
196  }
197  else Values[key] = value;
198  }
199  }
200 
201  public override XElement WriteData(object key)
202  {
203  if (key == null)
204  {
205  return HasDefaultValue ? WriteValue(DefaultValue) : null;
206  }
207  else
208  {
209  T value;
210  if (!Values.TryGetValue(key, out value)) return null;
211  return WriteValue(value);
212  }
213  }
214 
219  protected abstract T ReadValue(XElement x);
222  protected abstract XElement WriteValue(T value);
223  }
224 
226  public enum StandardType
227  {
229  }
230 
252  public sealed class StandardProperty<T> : DictionaryProperty<T>
253  {
255  private static readonly StandardType Type = ParseType(typeof(T));
257  private static readonly string TypeString = TypeToGraphML(Type);
258 
259  public StandardProperty()
260  : base()
261  { }
262 
265  internal StandardProperty(XElement xKey)
266  : this()
267  {
268  var attrType = xKey.Attribute("attr.type");
269  if (attrType == null || attrType.Value != TypeString)
270  throw new ArgumentException("Key not compatible with property.");
271  LoadFromKeyElement(xKey);
272  }
273 
275  private static StandardType ParseType(Type t)
276  {
277  if (t == typeof(bool)) return StandardType.Bool;
278  if (t == typeof(double)) return StandardType.Double;
279  if (t == typeof(float)) return StandardType.Float;
280  if (t == typeof(int)) return StandardType.Int;
281  if (t == typeof(long)) return StandardType.Long;
282  if (t == typeof(string)) return StandardType.String;
283  throw new ArgumentException("Invalid type for a standard GraphML property.");
284  }
285 
287  private static string TypeToGraphML(StandardType type)
288  {
289  switch (type)
290  {
291  case StandardType.Bool: return "boolean"; // !
292  case StandardType.Double: return "double";
293  case StandardType.Float: return "float";
294  case StandardType.Int: return "int";
295  case StandardType.Long: return "long";
296  default: return "string";
297  }
298  }
299 
300  private static object ParseValue(string value)
301  {
302  switch (Type)
303  {
304  case StandardType.Bool: return value == "true";
305  case StandardType.Double: return double.Parse(value, CultureInfo.InvariantCulture);
306  case StandardType.Float: return float.Parse(value, CultureInfo.InvariantCulture);
307  case StandardType.Int: return int.Parse(value, CultureInfo.InvariantCulture);
308  case StandardType.Long: return long.Parse(value, CultureInfo.InvariantCulture);
309  default: return value;
310  }
311  }
312 
313  public override XElement GetKeyElement()
314  {
315  XElement x = base.GetKeyElement();
316  x.SetAttributeValue("attr.type", TypeString);
317  return x;
318  }
319 
320  protected override T ReadValue(XElement x)
321  {
322  return (T)ParseValue(x.Value);
323  }
324 
325  protected override XElement WriteValue(T value)
326  {
327  return new XElement("dummy", value.ToString());
328  }
329  }
330 
332  public enum NodeShape
333  {
334  Rectangle, RoundRect, Ellipse, Parallelogram,
335  Hexagon, Triangle, Rectangle3D, Octagon,
336  Diamond, Trapezoid, Trapezoid2
337  }
338 
341  public sealed class NodeGraphics
342  {
344  public double X { get; set; }
346  public double Y { get; set; }
348  public double Width { get; set; }
350  public double Height { get; set; }
352  public NodeShape Shape { get; set; }
353 
354  public NodeGraphics()
355  {
356  X = Y = 0;
357  Width = Height = 10;
358  Shape = NodeShape.Rectangle;
359  }
360 
361  private readonly string[] nodeShapeToString = { "rectangle", "roundrectangle", "ellipse", "parallelogram",
362  "hexagon", "triangle", "rectangle3d", "octagon",
363  "diamond", "trapezoid", "trapezoid2"};
364 
366  private NodeShape ParseShape(string s)
367  {
368  return (NodeShape)(Math.Max(0, Array.IndexOf(nodeShapeToString, s)));
369  }
370 
372  private string ShapeToGraphML(NodeShape shape)
373  {
374  return nodeShapeToString[(int)shape];
375  }
376 
378  public NodeGraphics(XElement xData)
379  {
380  XElement xGeometry = Utils.ElementLocal(xData, "Geometry");
381  if (xGeometry != null)
382  {
383  X = double.Parse(xGeometry.Attribute("x").Value, CultureInfo.InvariantCulture);
384  Y = double.Parse(xGeometry.Attribute("y").Value, CultureInfo.InvariantCulture);
385  Width = double.Parse(xGeometry.Attribute("width").Value, CultureInfo.InvariantCulture);
386  Height = double.Parse(xGeometry.Attribute("height").Value, CultureInfo.InvariantCulture);
387  }
388  XElement xShape = Utils.ElementLocal(xData, "Shape");
389  if (xShape != null)
390  Shape = ParseShape(xShape.Attribute("type").Value);
391  }
392 
394  public XElement ToXml()
395  {
396  return new XElement("dummy",
397  new XElement(GraphMLFormat.xmlnsY + "ShapeNode",
398  new XElement(GraphMLFormat.xmlnsY + "Geometry",
399  new XAttribute("x", X.ToString(CultureInfo.InvariantCulture)),
400  new XAttribute("y", Y.ToString(CultureInfo.InvariantCulture)),
401  new XAttribute("width", Width.ToString(CultureInfo.InvariantCulture)),
402  new XAttribute("height", Height.ToString(CultureInfo.InvariantCulture))),
403  new XElement(GraphMLFormat.xmlnsY + "Shape",
404  new XAttribute("type", ShapeToGraphML(Shape)))
405  )
406  );
407  }
408 
409  public override string ToString()
410  {
411  return ToXml().ToString();
412  }
413  }
414 
432  public sealed class NodeGraphicsProperty : DictionaryProperty<NodeGraphics>
433  {
435  : base()
436  {
437  Domain = PropertyDomain.Node;
438  }
439 
442  internal NodeGraphicsProperty(XElement xKey)
443  : this()
444  {
445  var attrYFilesType = xKey.Attribute("yfiles.type");
446  if (attrYFilesType == null || attrYFilesType.Value != "nodegraphics")
447  throw new ArgumentException("Key not compatible with property.");
448  LoadFromKeyElement(xKey);
449  }
450 
451  public override XElement GetKeyElement()
452  {
453  XElement x = base.GetKeyElement();
454  x.SetAttributeValue("yfiles.type", "nodegraphics");
455  return x;
456  }
457 
458  protected override NodeGraphics ReadValue(XElement x)
459  {
460  return new NodeGraphics(x);
461  }
462 
463  protected override XElement WriteValue(NodeGraphics value)
464  {
465  return value.ToXml();
466  }
467  }
468 
514  public sealed class GraphMLFormat
515  {
516  internal static readonly XNamespace xmlns = "http://graphml.graphdrawing.org/xmlns";
517  private static readonly XNamespace xmlnsXsi = "http://www.w3.org/2001/XMLSchema-instance"; // xmlns:xsi
518  internal static readonly XNamespace xmlnsY = "http://www.yworks.com/xml/graphml"; // xmlns:y
519  private static readonly XNamespace xmlnsYed = "http://www.yworks.com/xml/yed/3"; // xmlns:yed
520  private const string xsiSchemaLocation = "http://graphml.graphdrawing.org/xmlns\n" + // xsi:schemaLocation
521  "http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd";
522 
527  public IGraph Graph { get; set; }
529  public IList<GraphMLProperty> Properties { get; private set; }
530 
531  private readonly List<Func<XElement, GraphMLProperty>> PropertyLoaders;
532 
533  public GraphMLFormat()
534  {
535  Properties = new List<GraphMLProperty>();
536  PropertyLoaders = new List<Func<XElement, GraphMLProperty>>
537  {
538  x => new StandardProperty<bool>(x),
539  x => new StandardProperty<double>(x),
540  x => new StandardProperty<float>(x),
541  x => new StandardProperty<int>(x),
542  x => new StandardProperty<long>(x),
543  x => new StandardProperty<string>(x),
544  x => new NodeGraphicsProperty(x)
545  };
546  }
547 
557  public void RegisterPropertyLoader(Func<XElement, GraphMLProperty> loader)
558  {
559  PropertyLoaders.Add(loader);
560  }
561 
562  private static void ReadProperties(Dictionary<string, GraphMLProperty> propertyById, XElement x, object obj)
563  {
564  foreach (var xData in Utils.ElementsLocal(x, "data"))
565  {
566  GraphMLProperty p;
567  if (propertyById.TryGetValue(xData.Attribute("key").Value, out p))
568  p.ReadData(x, obj);
569  }
570  }
571 
573  public void Load(XDocument doc)
574  {
575  // Namespaces are ignored so we can load broken documents.
576  if (Graph == null) Graph = new CustomGraph();
577  IBuildableGraph buildableGraph = (IBuildableGraph)Graph;
578  buildableGraph.Clear();
579  XElement xGraphML = doc.Root;
580 
581  // load properties
582  Properties.Clear();
583  Dictionary<string, GraphMLProperty> propertyById = new Dictionary<string, GraphMLProperty>();
584  foreach (var xKey in Utils.ElementsLocal(xGraphML, "key"))
585  {
586  foreach (var handler in PropertyLoaders)
587  {
588  try
589  {
590  GraphMLProperty p = handler(xKey);
591  Properties.Add(p);
592  propertyById[p.Id] = p;
593  break;
594  }
595  catch (ArgumentException) { }
596  }
597  }
598 
599  // load graph
600  XElement xGraph = Utils.ElementLocal(xGraphML, "graph");
601  Directedness defaultDirectedness = (xGraph.Attribute("edgedefault").Value == "directed" ?
602  Directedness.Directed : Directedness.Undirected);
603  ReadProperties(propertyById, xGraph, Graph);
604  // load nodes
605  Dictionary<string, Node> nodeById = new Dictionary<string, Node>();
606  foreach (var xNode in Utils.ElementsLocal(xGraph, "node"))
607  {
608  Node node = buildableGraph.AddNode();
609  nodeById[xNode.Attribute("id").Value] = node;
610  ReadProperties(propertyById, xNode, node);
611  }
612  // load arcs
613  foreach (var xArc in Utils.ElementsLocal(xGraph, "edge"))
614  {
615  Node u = nodeById[xArc.Attribute("source").Value];
616  Node v = nodeById[xArc.Attribute("target").Value];
617 
618  Directedness dir = defaultDirectedness;
619  XAttribute dirAttr = xArc.Attribute("directed");
620  if (dirAttr != null) dir = (dirAttr.Value == "true" ? Directedness.Directed : Directedness.Undirected);
621 
622  Arc arc = buildableGraph.AddArc(u, v, dir);
623  ReadProperties(propertyById, xArc, arc);
624  }
625  }
626 
628  public void Load(XmlReader xml)
629  {
630  XDocument doc = XDocument.Load(xml);
631  Load(doc);
632  }
633 
636  public void Load(TextReader reader)
637  {
638  using (XmlReader xml = XmlReader.Create(reader))
639  Load(xml);
640  }
641 
643  public void Load(string filename)
644  {
645  using (StreamReader reader = new StreamReader(filename))
646  Load(reader);
647  }
648 
649  private void DefinePropertyValues(XmlWriter xml, object obj)
650  {
651  foreach (var p in Properties)
652  {
653  XElement x = p.WriteData(obj);
654  if (x == null) continue;
655  x.Name = GraphMLFormat.xmlns + "data";
656  x.SetAttributeValue("key", p.Id);
657  x.WriteTo(xml);
658  }
659  }
660 
662  private void Save(XmlWriter xml)
663  {
664  xml.WriteStartDocument();
665  xml.WriteStartElement("graphml", xmlns.NamespaceName);
666  xml.WriteAttributeString("xmlns", "xsi", null, xmlnsXsi.NamespaceName);
667  xml.WriteAttributeString("xmlns", "y", null, xmlnsY.NamespaceName);
668  xml.WriteAttributeString("xmlns", "yed", null, xmlnsYed.NamespaceName);
669  xml.WriteAttributeString("xsi", "schemaLocation", null, xsiSchemaLocation);
670 
671  for (int i = 0; i < Properties.Count; i++)
672  {
673  var p = Properties[i];
674  p.Id = "d" + i;
675  p.GetKeyElement().WriteTo(xml);
676  }
677 
678  xml.WriteStartElement("graph", xmlns.NamespaceName);
679  xml.WriteAttributeString("id", "G");
680  xml.WriteAttributeString("edgedefault", "directed");
681  xml.WriteAttributeString("parse.nodes", Graph.NodeCount().ToString(CultureInfo.InvariantCulture));
682  xml.WriteAttributeString("parse.edges", Graph.ArcCount().ToString(CultureInfo.InvariantCulture));
683  xml.WriteAttributeString("parse.order", "nodesfirst");
684  DefinePropertyValues(xml, Graph);
685  foreach (var node in Graph.Nodes())
686  {
687  xml.WriteStartElement("node", xmlns.NamespaceName);
688  xml.WriteAttributeString("id", node.Id.ToString(CultureInfo.InvariantCulture));
689  DefinePropertyValues(xml, node);
690  xml.WriteEndElement(); // node
691  }
692  foreach (var arc in Graph.Arcs())
693  {
694  xml.WriteStartElement("edge", xmlns.NamespaceName);
695  xml.WriteAttributeString("id", arc.Id.ToString(CultureInfo.InvariantCulture));
696  if (Graph.IsEdge(arc)) xml.WriteAttributeString("directed", "false");
697  xml.WriteAttributeString("source", Graph.U(arc).Id.ToString(CultureInfo.InvariantCulture));
698  xml.WriteAttributeString("target", Graph.V(arc).Id.ToString(CultureInfo.InvariantCulture));
699  DefinePropertyValues(xml, arc);
700  xml.WriteEndElement(); // edge
701  }
702  xml.WriteEndElement(); // graph
703  xml.WriteEndElement(); // graphml
704  }
705 
708  public void Save(TextWriter writer)
709  {
710  using (XmlWriter xml = XmlWriter.Create(writer))
711  Save(xml);
712  }
713 
715  public void Save(string filename)
716  {
717  using (StreamWriter writer = new StreamWriter(filename))
718  Save(writer);
719  }
720  }
721 }