-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathgraphml.go
864 lines (782 loc) · 27 KB
/
graphml.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
// Package graphml implements marshaling and unmarshalling of GraphML XML documents.
package graphml
import (
"encoding/xml"
"errors"
"fmt"
"io"
"reflect"
"sort"
"strconv"
)
// NotAValue The Not value of data attribute to substitute with default one if present
var NotAValue interface{} = nil
// KeyForElement The elements where data-function can be attached
type KeyForElement string
const (
// KeyForGraphML the data-function is for root GraphML element only
KeyForGraphML KeyForElement = "graphml"
// KeyForGraph the data-function is for Graph element only
KeyForGraph KeyForElement = "graph"
// KeyForNode the data-function is for Node element only
KeyForNode KeyForElement = "node"
// KeyForEdge the data-function is for Edge element only
KeyForEdge KeyForElement = "edge"
// KeyForAll the data-function is for all elements
KeyForAll KeyForElement = "all"
)
// DataType The GraphML data types
type DataType string
const (
// BooleanType boolean (reflect.Bool)
BooleanType DataType = "boolean"
// IntType single integer precision (reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16)
IntType DataType = "int"
// LongType double integer precision (reflect.Int64, reflect.Uint32)
LongType DataType = "long"
// FloatType single float precision (reflect.Float32)
FloatType DataType = "float"
// DoubleType double float precision (reflect.Float64)
DoubleType DataType = "double"
// StringType string value (reflect.String)
StringType DataType = "string"
)
// EdgeDirection The edge direction
type EdgeDirection int
const (
// EdgeDirectionDefault edge direction is not specified
EdgeDirectionDefault EdgeDirection = iota
// EdgeDirectionDirected edge is directed
EdgeDirectionDirected
// EdgeDirectionUndirected edge is undirected
EdgeDirectionUndirected
)
const (
edgeDirectionDirected = "directed"
edgeDirectionUndirected = "undirected"
)
// GraphML The root element
type GraphML struct {
// The name of root element
XMLName xml.Name `xml:"graphml"`
// The name space definitions
XmlNS string `xml:"xmlns,attr"`
// The XML schema definition
XmlnsXsi string `xml:"xmlns:xsi,attr"`
XsiSchemaLocation string `xml:"xsi:schemaLocation,attr"`
// Provides human readable description
Description string `xml:"desc,omitempty"`
// The custom keys describing data-functions used in this or other elements
Keys []*Key `xml:"key,omitempty"`
// The data associated with root element
Data []*Data `xml:"data,omitempty"`
// The graph objects encapsulated
Graphs []*Graph `xml:"graph,omitempty"`
// The map to look for keys by their standard identifiers (see keyIdentifier(name string, target KeyForElement))
keysByIdentifier map[string]*Key
// The map to look for keys by their IDs. Useful for fast reverse mapping of Data -> Key -> Attribute Name/Type
keysById map[string]*Key
// The default key type to use when no key type specified
keyTypeDefault DataType
}
// Key the data function declaration.
// In GraphML there may be data-functions attached to graphs, nodes, ports, edges, hyperedges and endpoint
// and to the whole collection of graphs described by the content of <graphml>. These functions are declared by <key>
// elements (children of <graphml>) and defined by <data> elements. Occurrence: <graphml>.
type Key struct {
// The ID of this key element (in form dX, where X denotes the number of occurrences of the key element before the current one)
ID string `xml:"id,attr"`
// The name of element this key is for (graphml|graph|node|edge|hyperedge|port|endpoint|all)
Target KeyForElement `xml:"for,attr,omitempty"`
// The name of data-function associated with this key
Name string `xml:"attr.name,attr"`
// The type of input to the data-function associated with this key. (Allowed values: boolean, int, long, float, double, string)
KeyType DataType `xml:"attr.type,attr"`
// Provides human readable description
Description string `xml:"desc,omitempty"`
// The default value
DefaultValue string `xml:"default,omitempty"`
}
// Data the data function definition.
// In GraphML there may be data-functions attached to graphs, nodes, ports, edges, hyperedges and endpoint and to the
// whole collection of graphs described by the content of <graphml>. These functions are declared by <key> elements
// (children of <graphml>) and defined by <data> elements. Occurrence: <graphml>, <graph>, <node>, <port>, <edge>,
// <hyperedge>, and <endpoint>.
type Data struct {
// The ID of this data element (in form dX, where X denotes the number of occurrences of the data element before the current one)
ID string `xml:"id,attr,omitempty"`
// The ID of <key> element for this data element
Key string `xml:"key,attr"`
// The data value associated with this element
Value string `xml:",chardata"`
}
// Graph Describes one graph in this document. Occurrence: <graphml>, <node>, <edge>, <hyperedge>.
type Graph struct {
// The ID of this graph element (in form gX, where X denotes the number of occurrences of the graph element before the current one)
ID string `xml:"id,attr"`
// The default edge direction (directed|undirected)
EdgeDefault string `xml:"edgedefault,attr"`
// Provides human readable description
Description string `xml:"desc,omitempty"`
// The nodes associated with this graph
Nodes []*Node `xml:"node,omitempty"`
// The edges associated with this graph and connecting nodes
Edges []*Edge `xml:"edge,omitempty"`
// The data associated with this node
Data []*Data `xml:"data,omitempty"`
// The parent GraphML
parent *GraphML
// The map of nodes, indexed by their ID
nodesMap map[string]*Node
// The map of edges by connected nodes
edgesMap map[string]*Edge
// The default edge direction flag
edgesDirection EdgeDirection
}
// Node Describes one node in the <graph> containing this <node>. Occurrence: <graph>.
type Node struct {
// The ID of this node element (in form nX, where X denotes the number of occurrences of the node element before the current one)
ID string `xml:"id,attr"`
// Provides human readable description
Description string `xml:"desc,omitempty"`
// The data associated with this node
Data []*Data `xml:"data,omitempty"`
// The reference to the parent graph for reverse mapping
graph *Graph
}
// Edge Describes an edge in the <graph> which contains this <edge>. Occurrence: <graph>.
type Edge struct {
// The ID of this edge element (in form eX, where X is the number of edge elements before this one)
ID string `xml:"id,attr"`
// The source node ID
Source string `xml:"source,attr"`
// The target node ID
Target string `xml:"target,attr"`
// The direction type of this edge (true - directed, false - undirected)
Directed string `xml:"directed,attr,omitempty"`
// Provides human readable description
Description string `xml:"desc,omitempty"`
// The data associated with this edge
Data []*Data `xml:"data,omitempty"`
// The reference to the parent graph for reverse mapping
graph *Graph
}
// NewGraphMLWithDefaultKeyType creates new GraphML instance with provided description and default data type of the key.
func NewGraphMLWithDefaultKeyType(description string, keyTypeDefault DataType) *GraphML {
gml := GraphML{
Description: description,
Keys: make([]*Key, 0),
Data: make([]*Data, 0),
Graphs: make([]*Graph, 0),
XmlNS: "http://graphml.graphdrawing.org/xmlns",
XmlnsXsi: "http://www.w3.org/2001/XMLSchema-instance",
XsiSchemaLocation: "http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd",
keysByIdentifier: make(map[string]*Key),
keysById: make(map[string]*Key),
keyTypeDefault: keyTypeDefault,
}
return &gml
}
// NewGraphML creates new GraphML instance with provided description.
func NewGraphML(description string) *GraphML {
return NewGraphMLWithDefaultKeyType(description, StringType)
}
// NewGraphMLWithAttributes creates new GraphML instance with given attributes
func NewGraphMLWithAttributes(description string, attributes map[string]interface{}) (gml *GraphML, err error) {
gml = NewGraphML(description)
// add attributes
if gml.Data, err = gml.createDataAttributes(attributes, KeyForGraphML); err != nil {
return nil, err
}
return gml, nil
}
// Encode encodes GraphML into provided Writer. If withIndent set then each element begins on a new indented line.
func (gml *GraphML) Encode(w io.Writer, withIndent bool) error {
enc := xml.NewEncoder(w)
if withIndent {
enc.Indent(" ", " ")
}
err := enc.Encode(gml)
if err == nil {
err = enc.Flush()
}
return err
}
// Decode decodes GraphML from provided Reader
func (gml *GraphML) Decode(r io.Reader) error {
dec := xml.NewDecoder(r)
err := dec.Decode(gml)
if err != nil {
return err
}
// populate auxiliary data structure
for _, key := range gml.Keys {
if key.KeyType == "" {
key.KeyType = gml.keyTypeDefault
}
if key.Target == "" {
key.Target = KeyForAll
}
gml.keysByIdentifier[keyIdentifier(key.Name, key.Target)] = key
gml.keysById[key.ID] = key
}
for _, gr := range gml.Graphs {
gr.parent = gml
if gr.EdgeDefault == edgeDirectionDirected {
gr.edgesDirection = EdgeDirectionDirected
} else if gr.EdgeDefault == edgeDirectionUndirected {
gr.edgesDirection = EdgeDirectionUndirected
}
// populate edges map and link them to their graph
gr.edgesMap = make(map[string]*Edge)
for _, e := range gr.Edges {
gr.edgesMap[edgeIdentifier(e.Source, e.Target)] = e
e.graph = gr
}
// populate nodes map and link them to their graph
gr.nodesMap = make(map[string]*Node)
for _, n := range gr.Nodes {
gr.nodesMap[n.ID] = n
n.graph = gr
}
}
return err
}
// RegisterKey registers data function with GraphML instance
func (gml *GraphML) RegisterKey(target KeyForElement, name, description string, keyType reflect.Kind, defaultValue interface{}) (key *Key, err error) {
if key := gml.GetKey(name, target); key != nil {
return nil, errors.New(fmt.Sprintf("key with given name already registered: %s", name))
}
id := gml.nextKeyId()
key = &Key{
ID: id,
Target: target,
Name: name,
Description: description,
}
// add key type (boolean, int, long, float, double, string)
if key.KeyType, err = typeNameForKind(keyType); err != nil {
return nil, err
}
// store default value
if defaultValue != nil {
if key.DefaultValue, err = stringValueIfSupported(defaultValue, key.KeyType); err != nil {
return nil, err
}
}
// store key
gml.addKey(key)
return key, nil
}
func (gml *GraphML) nextKeyId() string {
count := len(gml.Keys)
var id string
for found := true; found; _, found = gml.keysById[id] {
id = fmt.Sprintf("d%d", count)
count++
}
return id
}
// RemoveKeyByName removes data key with specified name from target element.
// Returns error if key is not found in target element.
func (gml *GraphML) RemoveKeyByName(target KeyForElement, name string) error {
if key := gml.GetKey(name, target); key == nil {
return errors.New("key no found")
} else {
return gml.RemoveKey(key)
}
}
// RemoveKey removes a key from the GraphML and all the associated attributes
// in all the target elements.
// Returns error if key is not found in any target element.
func (gml *GraphML) RemoveKey(key *Key) error {
var found bool
var i int
var k *Key
for i, k = range gml.Keys {
if key == k {
found = true
break
}
}
if !found {
return errors.New("key not found")
}
gml.Keys = append(gml.Keys[:i], gml.Keys[i+1:]...)
delete(gml.keysById, key.ID)
delete(gml.keysByIdentifier, keyIdentifier(key.Name, key.Target))
if key.Target == KeyForAll || key.Target == KeyForGraphML {
gml.RemoveAttribute(key.ID)
}
if key.Target == KeyForGraphML {
return nil
}
for _, graph := range gml.Graphs {
if key.Target == KeyForAll || key.Target == KeyForGraph {
graph.RemoveAttribute(key.ID)
}
if key.Target == KeyForAll || key.Target == KeyForNode {
for _, node := range graph.Nodes {
node.RemoveAttribute(key.ID)
}
}
if key.Target == KeyForAll || key.Target == KeyForEdge {
for _, edge := range graph.Edges {
edge.RemoveAttribute(key.ID)
}
}
}
return nil
}
// GetKey looks for registered keys with specified name for a given target element. If specific target has no
// registered key then common target (KeyForAll) will be checked next. Returns Key (either specific or common) or nil.
func (gml *GraphML) GetKey(name string, target KeyForElement) *Key {
if key, ok := gml.keysByIdentifier[keyIdentifier(name, target)]; ok {
// found element specific data-function
return key
} else if key, ok = gml.keysByIdentifier[keyIdentifier(name, KeyForAll)]; ok {
// found common data-function with given name
return key
}
return nil
}
// AddGraph creates new Graph and add it to the root GraphML
func (gml *GraphML) AddGraph(description string, edgeDefault EdgeDirection, attributes map[string]interface{}) (graph *Graph, err error) {
var edgeDirection string
switch edgeDefault {
case EdgeDirectionDirected:
edgeDirection = edgeDirectionDirected
case EdgeDirectionUndirected:
edgeDirection = edgeDirectionUndirected
default:
return nil, errors.New("default edge direction must be provided")
}
id := gml.nextGraphId()
graph = &Graph{
ID: id,
EdgeDefault: edgeDirection,
Description: description,
Nodes: make([]*Node, 0),
Edges: make([]*Edge, 0),
parent: gml,
nodesMap: make(map[string]*Node),
edgesMap: make(map[string]*Edge),
edgesDirection: edgeDefault,
}
// add attributes
if graph.Data, err = gml.createDataAttributes(attributes, KeyForGraph); err != nil {
return nil, err
}
// store graph in parent
gml.Graphs = append(gml.Graphs, graph)
return graph, nil
}
func (gml *GraphML) nextGraphId() string {
count := len(gml.Graphs)
var id string
for found := true; found; {
id = fmt.Sprintf("g%d", count)
found = false
for _, g := range gml.Graphs {
if g.ID == id {
found = true
count++
break
}
}
}
return id
}
// AddNode adds node to the graph with provided additional attributes and description
func (gr *Graph) AddNode(attributes map[string]interface{}, description string) (node *Node, err error) {
id := gr.nextNodeId()
node = &Node{
ID: id,
Description: description,
Data: make([]*Data, 0),
}
// add attributes
if node.Data, err = gr.parent.createDataAttributes(attributes, KeyForNode); err != nil {
return nil, err
}
// add node
node.graph = gr
gr.Nodes = append(gr.Nodes, node)
gr.nodesMap[node.ID] = node
return node, nil
}
func (gr *Graph) nextNodeId() string {
count := len(gr.Nodes)
var id string
for found := true; found; _, found = gr.nodesMap[id] {
id = fmt.Sprintf("n%d", count)
count++
}
return id
}
// GetNode method to test if node with given id exists. If node exists it will be returned, otherwise nil returned
func (gr *Graph) GetNode(id string) *Node {
if node, ok := gr.nodesMap[id]; ok {
return node
}
return nil
}
// AddEdge adds edge to the graph which connects two its nodes with provided additional attributes and description
func (gr *Graph) AddEdge(source, target *Node, attributes map[string]interface{}, edgeDirection EdgeDirection, description string) (edge *Edge, err error) {
// test if edge already exists
edgeIdentification := edgeIdentifier(source.ID, target.ID)
exists := false
if _, exists = gr.edgesMap[edgeIdentification]; !exists && (edgeDirection == EdgeDirectionUndirected || gr.edgesDirection == EdgeDirectionUndirected) {
// check other direction for undirected edge or graph types
edgeIdentification = edgeIdentifier(target.ID, source.ID)
_, exists = gr.edgesMap[edgeIdentification]
}
if exists {
return nil, errors.New("edge already added to the graph")
}
id := gr.nextEdgeId()
edge = &Edge{
ID: id,
Source: source.ID,
Target: target.ID,
Description: description,
}
switch edgeDirection {
case EdgeDirectionDirected:
edge.Directed = "true"
case EdgeDirectionUndirected:
edge.Directed = "false"
default:
// ignore
}
// add attributes
if edge.Data, err = gr.parent.createDataAttributes(attributes, KeyForEdge); err != nil {
return nil, err
}
// add edge
edge.graph = gr
gr.Edges = append(gr.Edges, edge)
gr.edgesMap[edgeIdentifier(source.ID, target.ID)] = edge
return edge, nil
}
func (gr *Graph) nextEdgeId() string {
count := len(gr.Edges)
var id string
for found := true; found; {
id = fmt.Sprintf("e%d", count)
found = false
for _, e := range gr.Edges {
if e.ID == id {
found = true
count++
break
}
}
}
return id
}
// GetEdge method to test if edge exists between given nodes. If edge exists it will be returned, otherwise nil returned
func (gr *Graph) GetEdge(sourceId, targetId string) *Edge {
edgeIdentification := edgeIdentifier(sourceId, targetId)
if edge, ok := gr.edgesMap[edgeIdentification]; ok {
return edge
}
return nil
}
// SourceNode method to get the source node struct. If it exists it will be returned, otherwise nil returned
func (e *Edge) SourceNode() *Node {
return e.graph.GetNode(e.Source)
}
// TargetNode method to get the target node struct. If it exists it will be returned, otherwise nil returned
func (e *Edge) TargetNode() *Node {
return e.graph.GetNode(e.Target)
}
// RemoveAttribute removes the attribute associated with the given key ID from
// the data of this GraphML.
func (gml *GraphML) RemoveAttribute(key string) {
gml.Data = removeAttributeFromData(gml.Data, key)
}
// RemoveAttribute removes the attribute associated with the given key ID from
// the data of this graph.
func (gr *Graph) RemoveAttribute(key string) {
gr.Data = removeAttributeFromData(gr.Data, key)
}
// RemoveAttribute removes the attribute associated with the given key ID from
// the data of this node.
func (n *Node) RemoveAttribute(key string) {
n.Data = removeAttributeFromData(n.Data, key)
}
// RemoveAttribute removes the attribute associated with the given key ID from
// the data of this edge.
func (e *Edge) RemoveAttribute(key string) {
e.Data = removeAttributeFromData(e.Data, key)
}
// removeAttributeFromData removes the attribute associated with the given key ID from
// the given data.
func removeAttributeFromData(data []*Data, key string) []*Data {
var i int
var d *Data
for i, d = range data {
if d.Key == key {
return append(data[:i], data[i+1:]...)
}
}
return data
}
// SetAttribute sets the value of the attribute associated with the given key ID
// in the data of this GraphML.
func (gml *GraphML) SetAttribute(key string, val interface{}) (err error) {
gml.Data, err = gml.setAttributeForData(gml.Data, KeyForGraphML, key, val)
return
}
// SetAttribute sets the value of the attribute associated with the given key ID
// in the data of this graph.
func (gr *Graph) SetAttribute(key string, val interface{}) (err error) {
gr.Data, err = gr.parent.setAttributeForData(gr.Data, KeyForGraph, key, val)
return
}
// SetAttribute sets the value of the attribute associated with the given key ID
// in the data of this node.
func (n *Node) SetAttribute(key string, val interface{}) (err error) {
n.Data, err = n.graph.parent.setAttributeForData(n.Data, KeyForNode, key, val)
return
}
// SetAttribute sets the value of the attribute associated with the given key ID
// in the data of this edge.
func (e *Edge) SetAttribute(key string, val interface{}) (err error) {
e.Data, err = e.graph.parent.setAttributeForData(e.Data, KeyForEdge, key, val)
return
}
// setAttributeForData sets the value of the attribute associated with
// the given key ID in the given data.
func (gml *GraphML) setAttributeForData(data []*Data, target KeyForElement, key string, val interface{}) ([]*Data, error) {
newData, err := gml.createDataAttribute(val, key, target)
if err != nil {
return data, err
}
for i, d := range data {
if d.Key == newData.Key {
data[i] = newData
return data, nil
}
}
return append(data, newData), nil
}
// GetAttributes return data attributes map associated with GraphML
func (gml *GraphML) GetAttributes() (map[string]interface{}, error) {
return attributesForData(gml.Data, KeyForGraphML, gml)
}
// GetAttributes return data attributes map associated with Graph
func (gr *Graph) GetAttributes() (map[string]interface{}, error) {
return attributesForData(gr.Data, KeyForGraph, gr.parent)
}
// GetAttributes returns data attributes map associated with Node
func (n *Node) GetAttributes() (map[string]interface{}, error) {
return attributesForData(n.Data, KeyForNode, n.graph.parent)
}
// GetAttributes returns data attributes map associated with Edge
func (e *Edge) GetAttributes() (map[string]interface{}, error) {
return attributesForData(e.Data, KeyForEdge, e.graph.parent)
}
// builds attributes map for specified data array
func attributesForData(data []*Data, target KeyForElement, gml *GraphML) (map[string]interface{}, error) {
attr := make(map[string]interface{})
for _, d := range data {
key, ok := gml.keysById[d.Key]
if !ok {
return nil, errors.New(fmt.Sprintf("failed to find attribute name/type by id: %s", d.Key))
}
// use data value or default value
dataValue := d.Value
if dataValue == "" && key.KeyType != StringType {
if key.DefaultValue != "" {
dataValue = key.DefaultValue
} else {
return nil, errors.New(fmt.Sprintf("data has no value and key id: %s has no default value", d.Key))
}
}
if value, err := valueByType(dataValue, key.KeyType, gml.keyTypeDefault); err != nil {
return nil, err
} else {
attr[key.Name] = value
}
}
// fill defaults for undefined keys
for _, k := range keysForElement(gml.Keys, target) {
if k.DefaultValue == "" && k.KeyType != StringType {
continue
}
if _, ok := attr[k.Name]; !ok {
val, err := valueByType(k.DefaultValue, k.KeyType, gml.keyTypeDefault)
if err != nil {
return nil, errors.New("could not parse default value for key id: " + k.ID)
}
attr[k.Name] = val
}
}
return attr, nil
}
// appends given key
func (gml *GraphML) addKey(key *Key) {
gml.Keys = append(gml.Keys, key)
keyIdentifier := keyIdentifier(key.Name, key.Target)
gml.keysByIdentifier[keyIdentifier] = key
gml.keysById[key.ID] = key
}
// Creates data-functions from given attributes and appends definitions of created functions to the provided data list.
func (gml *GraphML) createDataAttributes(attributes map[string]interface{}, target KeyForElement) (data []*Data, err error) {
// make sure that attributes are sorted in predictable order
names := make([]string, 0, len(attributes))
for key := range attributes {
names = append(names, key)
}
sort.Strings(names)
// create data functions
data = make([]*Data, len(attributes))
count := 0
for _, key := range names {
val := attributes[key]
if d, err := gml.createDataAttribute(val, key, target); err != nil {
// failed
return nil, err
} else {
data[count] = d
}
count++
}
return data, nil
}
// createDataAttribute creates a single data object with given value, key name and target.
// If there is no key with this name and target, a new one is registered.
func (gml *GraphML) createDataAttribute(value interface{}, key string, target KeyForElement) (data *Data, err error) {
keyFunc := gml.GetKey(key, target)
if keyFunc == nil {
// register new Key
if keyFunc, err = gml.RegisterKey(target, key, "", reflect.TypeOf(value).Kind(), nil); err != nil {
// failed
return nil, err
}
}
return createDataWithKey(value, keyFunc)
}
// Creates data object with specified name, value and for provided Key
func createDataWithKey(value interface{}, key *Key) (data *Data, err error) {
data = &Data{
Key: key.ID,
}
// add value
if value != NotAValue {
if data.Value, err = stringValueIfSupported(value, key.KeyType); err == nil {
return data, nil
}
} else if key.Target == KeyForAll && len(key.DefaultValue) > 0 {
// use default value
data.Value = key.DefaultValue
} else {
// raise error
return nil, errors.New(fmt.Sprintf("empty attribute without default value: %s", key.Name))
}
return data, nil
}
// returns standard edge identifier based on provided iDs of connected nodes
func edgeIdentifier(source, target string) string {
return fmt.Sprintf("%s<->%s", source, target)
}
// returns standard key identifier based on provided name and target
func keyIdentifier(name string, target KeyForElement) string {
return fmt.Sprintf("%s_for_%s", name, target)
}
// Returns type name for a given kind
func typeNameForKind(kind reflect.Kind) (DataType, error) {
var keyType DataType
switch kind {
case reflect.Bool:
keyType = BooleanType
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16:
keyType = IntType
case reflect.Int64, reflect.Uint32:
keyType = LongType
case reflect.Float32:
keyType = FloatType
case reflect.Float64:
keyType = DoubleType
case reflect.String:
keyType = StringType
default:
return "unsupported", errors.New("unsupported data type for key")
}
return keyType, nil
}
// Converts provided value to string if it's supported by this keyType
func stringValueIfSupported(value interface{}, keyType DataType) (string, error) {
res := "unsupported"
// check that key and value types compatible
switch keyType {
case BooleanType:
if reflect.TypeOf(value).Kind() != reflect.Bool {
return res, errors.New("default value has wrong data type when boolean expected")
}
case IntType, LongType:
if defTypeName, err := typeNameForKind(reflect.TypeOf(value).Kind()); err != nil {
return res, err
} else if !(defTypeName == IntType || defTypeName == LongType) {
return res, errors.New(
fmt.Sprintf("default value has wrong data type when int/long expected: %s", defTypeName))
}
case FloatType, DoubleType:
if defTypeName, err := typeNameForKind(reflect.TypeOf(value).Kind()); err != nil {
return res, err
} else if !(defTypeName == FloatType || defTypeName == DoubleType) {
return res, errors.New(
fmt.Sprintf("default value has wrong data type when float/double expected: %s", defTypeName))
}
case StringType:
if defTypeName, err := typeNameForKind(reflect.TypeOf(value).Kind()); err != nil {
return res, err
} else if defTypeName != StringType {
return res, errors.New(
fmt.Sprintf("default value has wrong data type when string expected: %s", defTypeName))
}
}
return fmt.Sprint(value), nil
}
// Converts provided string value to the specified data type
func valueByType(val string, keyType DataType, keyTypeDefault DataType) (interface{}, error) {
switch keyType {
case BooleanType:
return strconv.ParseBool(val)
case IntType, LongType:
if iVal, err := strconv.ParseInt(val, 10, 64); err != nil {
return nil, err
} else if keyType == IntType {
return int(iVal), nil
} else {
return iVal, nil
}
case FloatType, DoubleType:
if fVal, err := strconv.ParseFloat(val, 64); err != nil {
return nil, err
} else if keyType == FloatType {
return float32(fVal), nil
} else {
return fVal, nil
}
case StringType:
return val, nil
default:
if keyTypeDefault == "" {
return val, nil
}
// try once more with default key type
return valueByType(val, keyTypeDefault, keyTypeDefault)
}
}
// keysForElement returns all the keys from allKeys that apply to a certain element
func keysForElement(allKeys []*Key, target KeyForElement) (keys []*Key) {
for _, k := range allKeys {
if k.Target == target || k.Target == KeyForAll {
keys = append(keys, k)
}
}
return keys
}