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}