Skip to content

Commit 74c396b

Browse files
committed
Better handle null string value through GraphML serialization.
1 parent 23c9ee0 commit 74c396b

3 files changed

Lines changed: 98 additions & 38 deletions

File tree

src/QuikGraph.Serialization/GraphML/GraphMLDeserializer.cs

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -168,16 +168,17 @@ private static Delegate CreateReadDelegate(
168168
var method = new DynamicMethod(
169169
$"{DynamicMethodPrefix}Read{elementType.Name}",
170170
typeof(void),
171-
// reader, namespaceUri
172171
new[] { typeof(XmlReader), typeof(string), elementType },
173172
elementType.Module);
174173
ILGenerator generator = method.GetILGenerator();
175174

175+
// Ldarg_0 = reader
176+
// Ldarg_1 = namespace URI
177+
// Ldarg_2 = element
178+
176179
generator.DeclareLocal(typeof(string));
177180

178-
generator.Emit(OpCodes.Ldarg_0);
179-
generator.Emit(OpCodes.Ldstr, "key");
180-
EmitCall(generator, Metadata.GetAttributeMethod);
181+
GetXmlAttribute("key");
181182
generator.Emit(OpCodes.Stloc_0);
182183

183184
// We need to create the switch for each property
@@ -210,7 +211,7 @@ private static Delegate CreateReadDelegate(
210211
if (setMethod is null)
211212
throw new InvalidOperationException($"Property {property.DeclaringType}.{property.Name} has no setter.");
212213

213-
// reader.ReadXXX + SetField
214+
// element.xxx = reader.ReadXXX
214215
generator.Emit(OpCodes.Ldarg_2); // element
215216
generator.Emit(OpCodes.Ldarg_0); // reader
216217
generator.Emit(OpCodes.Ldstr, "data");
@@ -233,6 +234,17 @@ private static Delegate CreateReadDelegate(
233234

234235
// Let's bake the method
235236
return method.CreateDelegate(delegateType);
237+
238+
#region Local function
239+
240+
void GetXmlAttribute(string attributeName)
241+
{
242+
generator.Emit(OpCodes.Ldarg_0);
243+
generator.Emit(OpCodes.Ldstr, attributeName);
244+
EmitCall(generator, Metadata.GetAttributeMethod);
245+
}
246+
247+
#endregion
236248
}
237249
}
238250

@@ -368,28 +380,23 @@ private void ReadElements()
368380
}
369381
}
370382

371-
private void ReadEdge([NotNull] IDictionary<string, TVertex> vertices)
383+
private void ReadVertex([NotNull] IDictionary<string, TVertex> vertices)
372384
{
373385
Debug.Assert(vertices != null);
374386
Debug.Assert(
375387
_reader.NodeType == XmlNodeType.Element
376-
&& _reader.Name == EdgeTag
388+
&& _reader.Name == NodeTag
377389
&& _reader.NamespaceURI == _graphMLNamespace);
378390

379391
// Get subtree
380392
using (XmlReader subReader = _reader.ReadSubtree())
381393
{
382394
// Read id
383395
string id = ReadAttributeValue(_reader, IdAttribute);
384-
string sourceId = ReadAttributeValue(_reader, SourceAttribute);
385-
if (!vertices.TryGetValue(sourceId, out TVertex source))
386-
throw new ArgumentException($"Could not find vertex {sourceId}.");
387-
string targetId = ReadAttributeValue(_reader, TargetAttribute);
388-
if (!vertices.TryGetValue(targetId, out TVertex target))
389-
throw new ArgumentException($"Could not find vertex {targetId}.");
390-
391-
TEdge edge = _edgeFactory(source, target, id);
392-
ReadDelegateCompiler.SetEdgeDefault(edge);
396+
// Create new vertex
397+
TVertex vertex = _vertexFactory(id);
398+
// Apply defaults
399+
ReadDelegateCompiler.SetVertexDefault(vertex);
393400

394401
// Read data
395402
while (subReader.Read())
@@ -398,31 +405,38 @@ private void ReadEdge([NotNull] IDictionary<string, TVertex> vertices)
398405
&& subReader.Name == DataTag
399406
&& subReader.NamespaceURI == _graphMLNamespace)
400407
{
401-
ReadDelegateCompiler.EdgeAttributesReader(subReader, _graphMLNamespace, edge);
408+
ReadDelegateCompiler.VertexAttributesReader(subReader, _graphMLNamespace, vertex);
402409
}
403410
}
404411

405-
_graph.AddEdge(edge);
412+
// Add to graph
413+
_graph.AddVertex(vertex);
414+
vertices.Add(id, vertex);
406415
}
407416
}
408417

409-
private void ReadVertex([NotNull] IDictionary<string, TVertex> vertices)
418+
private void ReadEdge([NotNull] IDictionary<string, TVertex> vertices)
410419
{
411420
Debug.Assert(vertices != null);
412421
Debug.Assert(
413422
_reader.NodeType == XmlNodeType.Element
414-
&& _reader.Name == NodeTag
423+
&& _reader.Name == EdgeTag
415424
&& _reader.NamespaceURI == _graphMLNamespace);
416425

417426
// Get subtree
418427
using (XmlReader subReader = _reader.ReadSubtree())
419428
{
420429
// Read id
421430
string id = ReadAttributeValue(_reader, IdAttribute);
422-
// Create new vertex
423-
TVertex vertex = _vertexFactory(id);
424-
// Apply defaults
425-
ReadDelegateCompiler.SetVertexDefault(vertex);
431+
string sourceId = ReadAttributeValue(_reader, SourceAttribute);
432+
if (!vertices.TryGetValue(sourceId, out TVertex source))
433+
throw new ArgumentException($"Could not find vertex {sourceId}.");
434+
string targetId = ReadAttributeValue(_reader, TargetAttribute);
435+
if (!vertices.TryGetValue(targetId, out TVertex target))
436+
throw new ArgumentException($"Could not find vertex {targetId}.");
437+
438+
TEdge edge = _edgeFactory(source, target, id);
439+
ReadDelegateCompiler.SetEdgeDefault(edge);
426440

427441
// Read data
428442
while (subReader.Read())
@@ -431,13 +445,11 @@ private void ReadVertex([NotNull] IDictionary<string, TVertex> vertices)
431445
&& subReader.Name == DataTag
432446
&& subReader.NamespaceURI == _graphMLNamespace)
433447
{
434-
ReadDelegateCompiler.VertexAttributesReader(subReader, _graphMLNamespace, vertex);
448+
ReadDelegateCompiler.EdgeAttributesReader(subReader, _graphMLNamespace, edge);
435449
}
436450
}
437451

438-
// Add to graph
439-
_graph.AddVertex(vertex);
440-
vertices.Add(id, vertex);
452+
_graph.AddEdge(edge);
441453
}
442454
}
443455

@@ -495,7 +507,7 @@ private static Dictionary<Type, MethodInfo> InitializeReadMethods()
495507
[typeof(long)] = readerType.GetMethod(nameof(XmlReader.ReadElementContentAsLong), new[] { typeof(string), typeof(string) }),
496508
[typeof(float)] = readerType.GetMethod(nameof(XmlReader.ReadElementContentAsFloat), new[] { typeof(string), typeof(string) }),
497509
[typeof(double)] = readerType.GetMethod(nameof(XmlReader.ReadElementContentAsDouble), new[] { typeof(string), typeof(string) }),
498-
[typeof(string)] = readerType.GetMethod(nameof(XmlReader.ReadElementContentAsString), new[] { typeof(string), typeof(string) }),
510+
[typeof(string)] = readerExtensionsType.GetMethod(nameof(XmlReaderExtensions.ReadElementAsNullableString)),
499511

500512
// Extensions
501513
[typeof(bool[])] = readerExtensionsType.GetMethod(nameof(XmlReaderExtensions.ReadElementContentAsBooleanArray)),

src/QuikGraph.Serialization/GraphML/GraphMLSerializer.cs

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,12 @@ private static void EmitWriteProperty(PropertySerializationInfo info, [NotNull]
9292
if (!Metadata.TryGetWriteValueMethod(property.PropertyType, out MethodInfo writeMethod))
9393
throw new NotSupportedException($"Property {property.DeclaringType}.{property.Name} type is not supported.");
9494

95-
var defaultValueAttribute =
96-
Attribute.GetCustomAttribute(property, typeof(DefaultValueAttribute)) as DefaultValueAttribute;
95+
// Ldarg_0 = writer
96+
// Ldarg_1 = element
97+
98+
var defaultValueAttribute = Attribute.GetCustomAttribute(
99+
property,
100+
typeof(DefaultValueAttribute)) as DefaultValueAttribute;
97101
if (defaultValueAttribute != null)
98102
{
99103
@default = generator.DefineLabel();
@@ -119,15 +123,11 @@ private static void EmitWriteProperty(PropertySerializationInfo info, [NotNull]
119123
EmitCall(generator, Metadata.WriteStartElementMethod);
120124

121125
// writer.WriteStartAttribute("key");
122-
generator.Emit(OpCodes.Ldarg_0);
123-
generator.Emit(OpCodes.Ldstr, "key");
124-
generator.Emit(OpCodes.Ldstr, info.Name);
125-
EmitCall(generator, Metadata.WriteAttributeStringMethod);
126+
WriteXmlAttribute("key", info.Name);
126127

127-
// writer.WriteValue(v.xxx);
128+
// writer.WriteValue(element.xxx);
128129
generator.Emit(OpCodes.Ldarg_0);
129-
generator.Emit(OpCodes.Ldarg_1);
130-
EmitCall(generator, getMethod);
130+
GetFieldValue();
131131
EmitCall(generator, writeMethod);
132132

133133
// writer.WriteEndElement()
@@ -138,6 +138,28 @@ private static void EmitWriteProperty(PropertySerializationInfo info, [NotNull]
138138
{
139139
generator.MarkLabel(@default);
140140
}
141+
142+
#region Local functions
143+
144+
void GetFieldValue()
145+
{
146+
generator.Emit(OpCodes.Ldarg_1);
147+
EmitCall(generator, getMethod);
148+
}
149+
150+
void WriteXmlAttribute(string attributeName, string attributeValue)
151+
{
152+
Debug.Assert(generator != null);
153+
Debug.Assert(attributeName != null);
154+
Debug.Assert(attributeValue != null);
155+
156+
generator.Emit(OpCodes.Ldarg_0);
157+
generator.Emit(OpCodes.Ldstr, attributeName);
158+
generator.Emit(OpCodes.Ldstr, attributeValue);
159+
EmitCall(generator, Metadata.WriteAttributeStringMethod);
160+
}
161+
162+
#endregion
141163
}
142164

143165
[NotNull]

src/QuikGraph.Serialization/GraphML/XmlReaderExtensions.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,32 @@ namespace QuikGraph.Serialization
99
/// </summary>
1010
public static class XmlReaderExtensions
1111
{
12+
/// <summary>
13+
/// Reads a named element as a string.
14+
/// </summary>
15+
/// <param name="xmlReader">XML reader.</param>
16+
/// <param name="localName">Node name.</param>
17+
/// <param name="namespaceURI">XML namespace.</param>
18+
/// <returns>Boolean array.</returns>
19+
/// <exception cref="T:System.ArgumentNullException"><paramref name="localName"/> is <see langword="null"/>.</exception>
20+
/// <exception cref="T:System.ArgumentNullException"><paramref name="namespaceURI"/> is <see langword="null"/>.</exception>
21+
/// <exception cref="T:System.ArgumentException"><paramref name="localName"/> is empty.</exception>
22+
[Pure]
23+
[CanBeNull]
24+
public static string ReadElementAsNullableString(
25+
[NotNull] XmlReader xmlReader,
26+
[NotNull] string localName,
27+
[NotNull] string namespaceURI)
28+
{
29+
bool isNull = xmlReader.IsEmptyElement;
30+
string str = xmlReader.ReadElementContentAsString(localName, namespaceURI);
31+
if (isNull)
32+
{
33+
str = null;
34+
}
35+
return str;
36+
}
37+
1238
/// <summary>
1339
/// Reads the content of a named element as an array of booleans.
1440
/// </summary>

0 commit comments

Comments
 (0)