-
Notifications
You must be signed in to change notification settings - Fork 0
/
xml.go
130 lines (113 loc) · 3.27 KB
/
xml.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
package bbn
import (
"bytes"
"encoding/xml"
"fmt"
"strconv"
"strings"
)
type bifXmlWrapper struct {
Network networkXml `xml:"NETWORK"`
}
type networkXml struct {
Name string `xml:"NAME"`
Variables []variableXml `xml:"VARIABLE"`
Definitions []definitionXml `xml:"DEFINITION"`
}
type variableXml struct {
Name string `xml:"NAME"`
Type string `xml:"TYPE,attr"`
Outcomes []string `xml:"OUTCOME"`
Properties []string `xml:"PROPERTY"`
}
type definitionXml struct {
For string `xml:"FOR"`
Given []string `xml:"GIVEN"`
Table string `xml:"TABLE"`
}
// FromBIFXML creates a [Network] from XML. See also [FromFile].
func FromBIFXML(content []byte) (*Network, error) {
reader := bytes.NewReader(content)
decoder := xml.NewDecoder(reader)
bifNet := bifXmlWrapper{}
err := decoder.Decode(&bifNet)
if err != nil {
return nil, err
}
defs := map[string]*definitionXml{}
for i := range bifNet.Network.Definitions {
def := &bifNet.Network.Definitions[i]
defs[def.For] = def
}
variables := make([]Variable, len(bifNet.Network.Variables))
factors := []Factor{}
for i, variable := range bifNet.Network.Variables {
def := defs[variable.Name]
columns := len(variable.Outcomes)
tableValues := strings.Fields(def.Table)
if columns > 0 {
if len(tableValues)%columns != 0 {
return nil, fmt.Errorf("number of values in table for node '%s' does not match expected number", variable.Name)
}
}
position, err := parsePosition(&variable)
if err != nil {
return nil, err
}
tp, ok := nodeTypes[variable.Type]
if !ok {
return nil, fmt.Errorf("unknown node type %s", variable.Type)
}
variables[i] = Variable{
Name: variable.Name,
NodeType: tp,
Outcomes: variable.Outcomes,
Position: position,
}
var table []float64
if len(tableValues) > 0 {
table = make([]float64, len(tableValues))
for i := range table {
v, err := strconv.ParseFloat(tableValues[i], 64)
if err != nil {
return nil, fmt.Errorf("error parsing table value in node '%s' to float", variable.Name)
}
table[i] = v
}
}
factors = append(factors, Factor{
For: variable.Name,
Given: def.Given,
Table: table,
})
}
return New(bifNet.Network.Name, "", variables, factors)
}
// Search and parse position property in format `position = (x, y)`
func parsePosition(variable *variableXml) ([2]int, error) {
position := [2]int{}
for _, prob := range variable.Properties {
parts := strings.Split(prob, "=")
if len(parts) != 2 || strings.TrimSpace(parts[0]) != "position" {
continue
}
value := strings.TrimSpace(parts[1])
value = strings.Trim(value, "()")
parts = strings.Split(value, ",")
if len(parts) != 2 {
return position, fmt.Errorf("syntax error in property 'position' of node '%s'", variable.Name)
}
var err error
position[0], err = strconv.Atoi(strings.TrimSpace(parts[0]))
if err != nil {
return position, fmt.Errorf("error parsing '%s' to integer in property 'position' of node '%s'", parts[0], variable.Name)
}
position[1], err = strconv.Atoi(strings.TrimSpace(parts[1]))
if err != nil {
return position, fmt.Errorf("error parsing '%s' to integer in property 'position' of node '%s'", parts[1], variable.Name)
}
position[0] /= 2
position[1] /= 12
}
return position, nil
}