parser.go
1package waypoint
2
3import (
4 "encoding/json"
5 "fmt"
6 "git.kilimanjaro.io/ygo"
7)
8
9// ExtractWaypoints performs a depth-first search on the XmlFragment at key "main"
10// and returns all elements with tag "waypoint" as Waypoint structs.
11func ExtractWaypoints(doc *ygo.Doc) ([]Waypoint, error) {
12 // Get the fragment OUTSIDE the transaction (following ygo pattern)
13 frag, err := doc.GetXmlFragment("main")
14 if err != nil {
15 return nil, fmt.Errorf("failed to get xml fragment 'main': %w", err)
16 }
17 defer frag.Destroy()
18
19 var waypoints []Waypoint
20
21 err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
22 // Iterate over all children of the fragment using ChildIter
23 childIter, err := frag.ChildIter(txn)
24 if err != nil {
25 return fmt.Errorf("failed to get child iterator: %w", err)
26 }
27
28 for {
29 child, err := childIter.Next()
30 if err != nil {
31 return fmt.Errorf("failed to get next child: %w", err)
32 }
33 if child == nil {
34 break
35 }
36
37 // Process element children
38 if child.Tag() == ygo.TagXmlElem {
39 elem, err := child.ToXmlElement()
40 child.Destroy()
41 if err != nil {
42 return fmt.Errorf("failed to convert to XmlElement: %w", err)
43 }
44
45 // Traverse this element and its children
46 err = dfsFindWaypoints(elem, txn, &waypoints)
47 elem.Destroy()
48 if err != nil {
49 return err
50 }
51 } else {
52 child.Destroy()
53 }
54 }
55
56 return nil
57 })
58
59 if err != nil {
60 return nil, err
61 }
62
63 return waypoints, nil
64}
65
66// dfsFindWaypoints recursively traverses XML elements depth-first,
67// collecting waypoints into the results slice.
68func dfsFindWaypoints(elem *ygo.XmlElement, txn *ygo.Transaction, results *[]Waypoint) error {
69 // Check if this element is a waypoint
70 tag, err := elem.Tag()
71 if err != nil {
72 return fmt.Errorf("failed to get element tag: %w", err)
73 }
74
75 if tag == "waypoint" {
76 // Extract waypoint attributes
77 wp, err := extractWaypoint(elem, txn)
78 if err != nil {
79 return fmt.Errorf("failed to extract waypoint: %w", err)
80 }
81 *results = append(*results, wp)
82 }
83
84 // Iterate over children and recurse
85 childIter, err := elem.ChildIter(txn)
86 if err != nil {
87 return fmt.Errorf("failed to get child iterator: %w", err)
88 }
89
90 for {
91 child, err := childIter.Next()
92 if err != nil {
93 return fmt.Errorf("failed to get next child: %w", err)
94 }
95 if child == nil {
96 break
97 }
98
99 // Only process XmlElement children (skip text nodes)
100 if child.Tag() != ygo.TagXmlElem {
101 child.Destroy()
102 continue
103 }
104
105 // Convert to XmlElement
106 childElem, err := child.ToXmlElement()
107 child.Destroy()
108 if err != nil {
109 return fmt.Errorf("failed to convert child to XmlElement: %w", err)
110 }
111
112 // Recurse
113 if err := dfsFindWaypoints(childElem, txn, results); err != nil {
114 childElem.Destroy()
115 return err
116 }
117 childElem.Destroy()
118 }
119
120 return nil
121}
122
123// extractWaypoint extracts attributes from an XmlElement into a Waypoint struct.
124func extractWaypoint(elem *ygo.XmlElement, txn *ygo.Transaction) (Waypoint, error) {
125 var wp Waypoint
126
127 // Get all attributes
128 attrIter, err := elem.AttrIter(txn)
129 if err != nil {
130 return wp, fmt.Errorf("failed to get attribute iterator: %w", err)
131 }
132 defer attrIter.Destroy()
133
134 for {
135 attr, err := attrIter.Next()
136 if err != nil {
137 return wp, fmt.Errorf("failed to get next attribute: %w", err)
138 }
139 if attr == nil {
140 break
141 }
142
143 // Use ToInterface to convert value to appropriate Go type
144 val, err := attr.Value.ToInterface()
145 if err != nil {
146 // If conversion fails, skip this attribute
147 attr.Destroy()
148 continue
149 }
150
151 // Map attributes to Waypoint fields
152 switch attr.Name {
153 case "type":
154 if v, ok := val.(string); ok {
155 wp.Type = v
156 }
157 case "id":
158 if v, ok := val.(string); ok {
159 wp.ID = v
160 }
161 case "label":
162 if v, ok := val.(string); ok {
163 wp.Label = v
164 }
165 case "gid":
166 if v, ok := val.(string); ok {
167 wp.GID = v
168 }
169 case "country":
170 if v, ok := val.(string); ok {
171 wp.Country = v
172 }
173 case "point":
174 // Handle point attribute - read raw JSON bytes and unmarshal directly
175 if jsonBytes, ok := attr.Value.JSONBytes(); ok {
176 var point []float64
177 if err := json.Unmarshal(jsonBytes, &point); err == nil && len(point) == 2 {
178 wp.Point = point
179 }
180 }
181 case "from":
182 // Handle from attribute - read raw JSON bytes and unmarshal directly
183 if jsonBytes, ok := attr.Value.JSONBytes(); ok {
184 var fromMap map[string]Route
185 if err := json.Unmarshal(jsonBytes, &fromMap); err == nil {
186 wp.From = fromMap
187 }
188 }
189 case "cons":
190 // Handle cons attribute - read raw JSON bytes and unmarshal directly
191 if jsonBytes, ok := attr.Value.JSONBytes(); ok {
192 var cons []string
193 if err := json.Unmarshal(jsonBytes, &cons); err == nil {
194 wp.Cons = cons
195 }
196 }
197 case "error":
198 // Handle error attribute - read raw JSON bytes and unmarshal directly
199 if jsonBytes, ok := attr.Value.JSONBytes(); ok {
200 var errors []string
201 if err := json.Unmarshal(jsonBytes, &errors); err == nil {
202 wp.Error = errors
203 }
204 }
205 case "nonroutable":
206 // Handle nonroutable attribute - stored as boolean
207 if v, ok := val.(bool); ok {
208 wp.Nonroutable = v
209 }
210 }
211
212 attr.Destroy()
213 }
214
215 return wp, nil
216}