xml.go
1package ygo
2
3/*
4#include "libyrs.h"
5#include <stdlib.h>
6*/
7import "C"
8import (
9 "encoding/json"
10 "fmt"
11 "unsafe"
12)
13
14// XmlFragment represents a root-level XML container.
15type XmlFragment struct {
16 branch *C.Branch
17}
18
19// GetXmlFragment retrieves or creates a root-level YXmlFragment.
20func (d *Doc) GetXmlFragment(name string) (*XmlFragment, error) {
21 if d.ptr == nil {
22 return nil, ErrNilDocument
23 }
24 cName := C.CString(name)
25 defer C.free(unsafe.Pointer(cName))
26
27 branch := C.yxmlfragment(d.ptr, cName)
28 if branch == nil {
29 return nil, fmt.Errorf("failed to get or create xml fragment %q", name)
30 }
31
32 f := &XmlFragment{branch: branch}
33 return f, nil
34}
35
36// Destroy releases resources.
37func (f *XmlFragment) Destroy() {
38}
39
40// InsertElement creates and inserts an XML element at index.
41func (f *XmlFragment) InsertElement(txn *Transaction, index uint32, tag string) (*XmlElement, error) {
42 if f.branch == nil {
43 return nil, ErrNilBranch
44 }
45 if txn == nil || txn.ptr == nil {
46 return nil, ErrNilTransaction
47 }
48 if !txn.IsWriteable() {
49 return nil, ErrNotWriteable
50 }
51 cTag := C.CString(tag)
52 defer C.free(unsafe.Pointer(cTag))
53
54 branch := C.yxmlelem_insert_elem(f.branch, txn.ptr, C.uint32_t(index), cTag)
55 if branch == nil {
56 return nil, fmt.Errorf("failed to insert xml element %q", tag)
57 }
58
59 e := &XmlElement{branch: branch}
60 // REMOVED: runtime.SetFinalizer
61 return e, nil
62}
63
64// InsertText creates and inserts an XML text node at index.
65func (f *XmlFragment) InsertText(txn *Transaction, index uint32) (*XmlText, error) {
66 if f.branch == nil {
67 return nil, ErrNilBranch
68 }
69 if txn == nil || txn.ptr == nil {
70 return nil, ErrNilTransaction
71 }
72 if !txn.IsWriteable() {
73 return nil, ErrNotWriteable
74 }
75
76 branch := C.yxmlelem_insert_text(f.branch, txn.ptr, C.uint32_t(index))
77 if branch == nil {
78 return nil, fmt.Errorf("failed to insert xml text node")
79 }
80
81 t := &XmlText{branch: branch}
82 // REMOVED: runtime.SetFinalizer
83 return t, nil
84}
85
86// XmlElement represents an XML element with attributes and children.
87type XmlElement struct {
88 branch *C.Branch
89}
90
91// Destroy releases resources.
92func (e *XmlElement) Destroy() {
93 // REMOVED: runtime.SetFinalizer
94}
95
96// Tag returns the element's tag name.
97func (e *XmlElement) Tag() (string, error) {
98 if e.branch == nil {
99 return "", ErrNilBranch
100 }
101 return cStringToGoAndFree(C.yxmlelem_tag(e.branch)), nil
102}
103
104// String returns the element as an XML string.
105func (e *XmlElement) String(txn *Transaction) (string, error) {
106 if e.branch == nil {
107 return "", ErrNilBranch
108 }
109 if txn == nil || txn.ptr == nil {
110 return "", ErrNilTransaction
111 }
112 return cStringToGoAndFree(C.yxmlelem_string(e.branch, txn.ptr)), nil
113}
114
115// SetAttribute sets an attribute.
116func (e *XmlElement) SetAttribute(txn *Transaction, name string, value Input) error {
117 if e.branch == nil {
118 return ErrNilBranch
119 }
120 if txn == nil || txn.ptr == nil {
121 return ErrNilTransaction
122 }
123 if !txn.IsWriteable() {
124 return ErrNotWriteable
125 }
126 cName := C.CString(name)
127 defer C.free(unsafe.Pointer(cName))
128
129 C.yxmlelem_insert_attr(e.branch, txn.ptr, cName, &value.cInput)
130 return nil
131}
132
133// RemoveAttribute removes an attribute.
134func (e *XmlElement) RemoveAttribute(txn *Transaction, name string) error {
135 if e.branch == nil {
136 return ErrNilBranch
137 }
138 if txn == nil || txn.ptr == nil {
139 return ErrNilTransaction
140 }
141 if !txn.IsWriteable() {
142 return ErrNotWriteable
143 }
144 cName := C.CString(name)
145 defer C.free(unsafe.Pointer(cName))
146
147 C.yxmlelem_remove_attr(e.branch, txn.ptr, cName)
148 return nil
149}
150
151// ChildLen returns the number of child nodes.
152func (e *XmlElement) ChildLen(txn *Transaction) (uint32, error) {
153 if e.branch == nil {
154 return 0, ErrNilBranch
155 }
156 if txn == nil || txn.ptr == nil {
157 return 0, ErrNilTransaction
158 }
159 return uint32(C.yxmlelem_child_len(e.branch, txn.ptr)), nil
160}
161
162// InsertElement creates and inserts a child XML element at index.
163func (e *XmlElement) InsertElement(txn *Transaction, index uint32, tag string) (*XmlElement, error) {
164 if e.branch == nil {
165 return nil, ErrNilBranch
166 }
167 if txn == nil || txn.ptr == nil {
168 return nil, ErrNilTransaction
169 }
170 if !txn.IsWriteable() {
171 return nil, ErrNotWriteable
172 }
173 cTag := C.CString(tag)
174 defer C.free(unsafe.Pointer(cTag))
175
176 branch := C.yxmlelem_insert_elem(e.branch, txn.ptr, C.uint32_t(index), cTag)
177 if branch == nil {
178 return nil, fmt.Errorf("failed to insert child xml element %q", tag)
179 }
180
181 elem := &XmlElement{branch: branch}
182 return elem, nil
183}
184
185// InsertText creates and inserts a text node at index.
186func (e *XmlElement) InsertText(txn *Transaction, index uint32) (*XmlText, error) {
187 if e.branch == nil {
188 return nil, ErrNilBranch
189 }
190 if txn == nil || txn.ptr == nil {
191 return nil, ErrNilTransaction
192 }
193 if !txn.IsWriteable() {
194 return nil, ErrNotWriteable
195 }
196
197 branch := C.yxmlelem_insert_text(e.branch, txn.ptr, C.uint32_t(index))
198 if branch == nil {
199 return nil, fmt.Errorf("failed to insert xml text node")
200 }
201
202 t := &XmlText{branch: branch}
203 // REMOVED: runtime.SetFinalizer
204 return t, nil
205}
206
207// RemoveRange removes child nodes starting at index.
208func (e *XmlElement) RemoveRange(txn *Transaction, index, length uint32) error {
209 if e.branch == nil {
210 return ErrNilBranch
211 }
212 if txn == nil || txn.ptr == nil {
213 return ErrNilTransaction
214 }
215 if !txn.IsWriteable() {
216 return ErrNotWriteable
217 }
218 C.yxmlelem_remove_range(e.branch, txn.ptr, C.uint32_t(index), C.uint32_t(length))
219 return nil
220}
221
222// XmlText represents an XML text node with formatting.
223type XmlText struct {
224 branch *C.Branch
225}
226
227// Destroy releases resources.
228func (t *XmlText) Destroy() {
229 // REMOVED: runtime.SetFinalizer
230}
231
232// Len returns the text length.
233// Panics if the XmlText is nil or if txn is nil.
234func (t *XmlText) Len(txn *Transaction) uint32 {
235 if t.branch == nil {
236 panic("ygo: XmlText.Len called on nil XmlText")
237 }
238 if txn == nil || txn.ptr == nil {
239 panic("ygo: XmlText.Len called with nil transaction")
240 }
241 return uint32(C.yxmltext_len(t.branch, txn.ptr))
242}
243
244// String returns the text content.
245func (t *XmlText) String(txn *Transaction) (string, error) {
246 if t.branch == nil {
247 return "", ErrNilBranch
248 }
249 if txn == nil || txn.ptr == nil {
250 return "", ErrNilTransaction
251 }
252 return cStringToGoAndFree(C.yxmltext_string(t.branch, txn.ptr)), nil
253}
254
255// Insert inserts text at index.
256func (t *XmlText) Insert(txn *Transaction, index uint32, text string) error {
257 if t.branch == nil {
258 return ErrNilBranch
259 }
260 if txn == nil || txn.ptr == nil {
261 return ErrNilTransaction
262 }
263 if !txn.IsWriteable() {
264 return ErrNotWriteable
265 }
266 cText := C.CString(text)
267 defer C.free(unsafe.Pointer(cText))
268
269 C.yxmltext_insert(t.branch, txn.ptr, C.uint32_t(index), cText, nil)
270 return nil
271}
272
273// RemoveRange removes text starting at index.
274func (t *XmlText) RemoveRange(txn *Transaction, index, length uint32) error {
275 if t.branch == nil {
276 return ErrNilBranch
277 }
278 if txn == nil || txn.ptr == nil {
279 return ErrNilTransaction
280 }
281 if !txn.IsWriteable() {
282 return ErrNotWriteable
283 }
284 C.yxmltext_remove_range(t.branch, txn.ptr, C.uint32_t(index), C.uint32_t(length))
285 return nil
286}
287
288// Branch returns the underlying branch pointer (for advanced use).
289func (e *XmlElement) Branch() unsafe.Pointer {
290 return unsafe.Pointer(e.branch)
291}
292
293// Branch returns the underlying branch pointer (for advanced use).
294func (t *XmlText) Branch() unsafe.Pointer {
295 return unsafe.Pointer(t.branch)
296}
297
298// Branch returns the underlying branch pointer (for advanced use).
299func (f *XmlFragment) Branch() unsafe.Pointer {
300 return unsafe.Pointer(f.branch)
301}
302
303// XmlAttrIter iterates over XML element attributes.
304type XmlAttrIter struct {
305 ptr *C.YXmlAttrIter
306}
307
308// Destroy releases iterator resources.
309func (it *XmlAttrIter) Destroy() {
310 if it.ptr != nil {
311 C.yxmlattr_iter_destroy(it.ptr)
312 it.ptr = nil
313 }
314}
315
316// XmlAttr represents an XML attribute.
317type XmlAttr struct {
318 Name string
319 Value *Output
320 ptr *C.YXmlAttr // underlying C struct - nil after Destroy()
321}
322
323// Destroy releases resources associated with this attribute.
324// This MUST be called when done with the attribute to prevent memory leaks.
325func (a *XmlAttr) Destroy() {
326 if a.ptr != nil {
327 C.yxmlattr_destroy(a.ptr)
328 a.ptr = nil
329 }
330 // Note: a.Value is freed by yxmlattr_destroy via YXmlAttr's Drop impl,
331 // so we just nil the pointer here without calling Destroy()
332 if a.Value != nil {
333 a.Value.ptr = nil
334 a.Value = nil
335 }
336}
337
338// Next returns the next attribute. Returns nil when iteration is complete.
339func (it *XmlAttrIter) Next() (*XmlAttr, error) {
340 if it.ptr == nil {
341 return nil, ErrIteratorExhausted
342 }
343 attr := C.yxmlattr_iter_next(it.ptr)
344 if attr == nil {
345 return nil, nil // End of iteration
346 }
347
348 name := C.GoString(attr.name)
349 value := &Output{ptr: attr.value}
350
351 return &XmlAttr{Name: name, Value: value, ptr: attr}, nil
352}
353
354// AttrIter returns an iterator over the element's attributes.
355func (e *XmlElement) AttrIter(txn *Transaction) (*XmlAttrIter, error) {
356 if e.branch == nil {
357 return nil, ErrNilBranch
358 }
359 if txn == nil || txn.ptr == nil {
360 return nil, ErrNilTransaction
361 }
362 ptr := C.yxmlelem_attr_iter(e.branch, txn.ptr)
363 if ptr == nil {
364 return nil, fmt.Errorf("failed to create attribute iterator")
365 }
366 return &XmlAttrIter{ptr: ptr}, nil
367}
368
369// AttrIter returns an iterator over the text node's formatting attributes.
370func (t *XmlText) AttrIter(txn *Transaction) (*XmlAttrIter, error) {
371 if t.branch == nil {
372 return nil, ErrNilBranch
373 }
374 if txn == nil || txn.ptr == nil {
375 return nil, ErrNilTransaction
376 }
377 ptr := C.yxmltext_attr_iter(t.branch, txn.ptr)
378 if ptr == nil {
379 return nil, fmt.Errorf("failed to create text attribute iterator")
380 }
381 return &XmlAttrIter{ptr: ptr}, nil
382}
383
384// XmlChildIter iterates over XML element children.
385type XmlChildIter struct {
386 output *C.YOutput
387 txn *Transaction
388}
389
390// Destroy is a no-op - children are managed by parent.
391func (it *XmlChildIter) Destroy() {
392 // No-op, children are owned by parent
393}
394
395// Next returns the next child node. Returns nil when iteration is complete.
396func (it *XmlChildIter) Next() (*Output, error) {
397 if it.output == nil {
398 return nil, nil // End of iteration
399 }
400
401 // Save the current output for return
402 currentOutput := it.output
403
404 // Get the branch from current output to find next sibling
405 var currentBranch *C.Branch
406 tag := ValueTag(currentOutput.tag)
407 switch tag {
408 case TagXmlElem:
409 currentBranch = C.youtput_read_yxmlelem(currentOutput)
410 case TagXmlText:
411 currentBranch = C.youtput_read_yxmltext(currentOutput)
412 default:
413 // Can't continue iteration
414 it.output = nil
415 return &Output{ptr: currentOutput}, nil
416 }
417
418 // Get next sibling output
419 nextOutput := C.yxml_next_sibling(currentBranch, it.txn.ptr)
420
421 // Destroy the branch we extracted (we don't need it anymore)
422 // The branch is owned by the document, so we don't free it
423 // But we need to destroy the current output since we're returning a new Output wrapper
424
425 if nextOutput == nil {
426 it.output = nil
427 } else {
428 it.output = nextOutput
429 }
430
431 // Return the current output
432 return &Output{ptr: currentOutput}, nil
433}
434
435// ChildIter returns an iterator over the element's direct children.
436func (e *XmlElement) ChildIter(txn *Transaction) (*XmlChildIter, error) {
437 if e.branch == nil {
438 return nil, ErrNilBranch
439 }
440 if txn == nil || txn.ptr == nil {
441 return nil, ErrNilTransaction
442 }
443
444 // Get first child output
445 firstOutput := C.yxmlelem_first_child(e.branch)
446 if firstOutput == nil {
447 return &XmlChildIter{output: nil, txn: txn}, nil
448 }
449
450 // Return iterator with first output (no conversion needed now)
451 return &XmlChildIter{output: firstOutput, txn: txn}, nil
452}
453
454// FirstChild returns the first child of the fragment.
455func (f *XmlFragment) FirstChild(txn *Transaction) (*Output, error) {
456 if f.branch == nil {
457 return nil, ErrNilBranch
458 }
459 if txn == nil || txn.ptr == nil {
460 return nil, ErrNilTransaction
461 }
462
463 child := C.yxmlelem_first_child(f.branch)
464 if child == nil {
465 return nil, nil // No children
466 }
467 return &Output{ptr: child}, nil
468}
469
470// FragmentChildIter iterates over XmlFragment children.
471type FragmentChildIter struct {
472 output *C.YOutput
473 txn *Transaction
474}
475
476// Destroy is a no-op - children are managed by parent.
477func (it *FragmentChildIter) Destroy() {
478 // No-op, children are owned by parent
479}
480
481// Next returns the next child node. Returns nil when iteration is complete.
482func (it *FragmentChildIter) Next() (*Output, error) {
483 if it.output == nil {
484 return nil, nil // End of iteration
485 }
486
487 // Save the current output for return
488 currentOutput := it.output
489
490 // Get the branch from current output to find next sibling
491 var currentBranch *C.Branch
492 tag := ValueTag(currentOutput.tag)
493 switch tag {
494 case TagXmlElem:
495 currentBranch = C.youtput_read_yxmlelem(currentOutput)
496 case TagXmlText:
497 currentBranch = C.youtput_read_yxmltext(currentOutput)
498 default:
499 // Can't continue iteration
500 it.output = nil
501 return &Output{ptr: currentOutput}, nil
502 }
503
504 // Get next sibling output
505 nextOutput := C.yxml_next_sibling(currentBranch, it.txn.ptr)
506
507 if nextOutput == nil {
508 it.output = nil
509 } else {
510 it.output = nextOutput
511 }
512
513 // Return the current output
514 return &Output{ptr: currentOutput}, nil
515}
516
517// ChildIter returns an iterator over the fragment's direct children.
518func (f *XmlFragment) ChildIter(txn *Transaction) (*FragmentChildIter, error) {
519 if f.branch == nil {
520 return nil, ErrNilBranch
521 }
522 if txn == nil || txn.ptr == nil {
523 return nil, ErrNilTransaction
524 }
525
526 // Get first child output
527 firstOutput := C.yxmlelem_first_child(f.branch)
528 if firstOutput == nil {
529 return &FragmentChildIter{output: nil, txn: txn}, nil
530 }
531
532 // Return iterator with first output
533 return &FragmentChildIter{output: firstOutput, txn: txn}, nil
534}
535
536// MarshalJSONWithTxn implements JSON marshaling for XmlElement.
537// Returns JSON in the format: {"type": "tag", "attrs": {...}, "children": [...]}
538func (e *XmlElement) MarshalJSONWithTxn(txn *Transaction) ([]byte, error) {
539 if e.branch == nil {
540 return nil, ErrNilBranch
541 }
542 if txn == nil || txn.ptr == nil {
543 return nil, ErrNilTransaction
544 }
545
546 // Get tag name
547 tag, err := e.Tag()
548 if err != nil {
549 return nil, fmt.Errorf("failed to get tag: %w", err)
550 }
551
552 // Collect attributes
553 attrs := make(map[string]interface{})
554 attrIter, err := e.AttrIter(txn)
555 if err != nil {
556 return nil, err
557 }
558 defer attrIter.Destroy()
559
560 for {
561 attr, err := attrIter.Next()
562 if err != nil {
563 return nil, err
564 }
565 if attr == nil {
566 break
567 }
568
569 val, err := attr.Value.ToInterface()
570 if err != nil {
571 // Fallback: use string representation
572 if s, ok := attr.Value.String(); ok {
573 val = s
574 } else {
575 val = nil
576 }
577 }
578 attrs[attr.Name] = val
579 attr.Value.Destroy()
580 }
581
582 // Collect children
583 var children []interface{}
584 childIter, err := e.ChildIter(txn)
585 if err != nil {
586 return nil, err
587 }
588 defer childIter.Destroy()
589
590 for {
591 child, err := childIter.Next()
592 if err != nil {
593 return nil, err
594 }
595 if child == nil {
596 break
597 }
598
599 childJSON, err := e.marshalXmlChild(child, txn)
600 if err != nil {
601 child.Destroy()
602 return nil, err
603 }
604 children = append(children, childJSON)
605 child.Destroy()
606 }
607
608 // Build result
609 result := map[string]interface{}{
610 "type": tag,
611 "attrs": attrs,
612 "children": children,
613 }
614
615 return json.Marshal(result)
616}
617
618// marshalXmlChild marshals a single XML child node
619func (e *XmlElement) marshalXmlChild(out *Output, txn *Transaction) (interface{}, error) {
620 switch out.Tag() {
621 case TagXmlElem:
622 branch := C.youtput_read_yxmlelem(out.ptr)
623 if branch == nil {
624 return nil, fmt.Errorf("failed to read child element")
625 }
626 child := &XmlElement{branch: branch}
627 defer child.Destroy()
628
629 jsonBytes, err := child.MarshalJSONWithTxn(txn)
630 if err != nil {
631 return nil, err
632 }
633
634 var result map[string]interface{}
635 if err := json.Unmarshal(jsonBytes, &result); err != nil {
636 return nil, err
637 }
638 return result, nil
639
640 case TagXmlText:
641 branch := C.youtput_read_yxmltext(out.ptr)
642 if branch == nil {
643 return nil, fmt.Errorf("failed to read child text")
644 }
645 text := &XmlText{branch: branch}
646 defer text.Destroy()
647
648 content, err := text.String(txn)
649 if err != nil {
650 return nil, err
651 }
652
653 // Collect formatting attributes if any
654 attrs := make(map[string]interface{})
655 attrIter, err := text.AttrIter(txn)
656 if err == nil {
657 defer attrIter.Destroy()
658 for {
659 attr, err := attrIter.Next()
660 if err != nil {
661 break
662 }
663 if attr == nil {
664 break
665 }
666 val, _ := attr.Value.ToInterface()
667 attrs[attr.Name] = val
668 attr.Value.Destroy()
669 }
670 }
671
672 result := map[string]interface{}{
673 "type": "text",
674 "content": content,
675 }
676 if len(attrs) > 0 {
677 result["attrs"] = attrs
678 }
679 return result, nil
680
681 default:
682 return nil, fmt.Errorf("unsupported child type: %v", out.Tag())
683 }
684}
685
686// MarshalJSONWithTxn implements JSON marshaling for XmlFragment.
687// Returns an array of child elements since fragments don't have a tag name.
688func (f *XmlFragment) MarshalJSONWithTxn(txn *Transaction) ([]byte, error) {
689 if f.branch == nil {
690 return nil, ErrNilBranch
691 }
692 if txn == nil || txn.ptr == nil {
693 return nil, ErrNilTransaction
694 }
695
696 var children []interface{}
697
698 // Get first child
699 child, err := f.FirstChild(txn)
700 if err != nil {
701 return nil, err
702 }
703
704 for child != nil {
705 childJSON, err := f.marshalXmlFragmentChild(child, txn)
706 if err != nil {
707 child.Destroy()
708 return nil, err
709 }
710 children = append(children, childJSON)
711
712 // Get next sibling - need to extract branch from output
713 var currentBranch *C.Branch
714 switch child.Tag() {
715 case TagXmlElem:
716 currentBranch = C.youtput_read_yxmlelem(child.ptr)
717 case TagXmlText:
718 currentBranch = C.youtput_read_yxmltext(child.ptr)
719 }
720 child.Destroy()
721
722 if currentBranch == nil {
723 break
724 }
725
726 next := C.yxml_next_sibling(currentBranch, txn.ptr)
727 if next == nil {
728 break
729 }
730 child = &Output{ptr: next}
731 }
732
733 return json.Marshal(children)
734}
735
736// marshalXmlFragmentChild marshals a child from a fragment
737func (f *XmlFragment) marshalXmlFragmentChild(out *Output, txn *Transaction) (interface{}, error) {
738 // Same logic as XmlElement.marshalXmlChild
739 switch out.Tag() {
740 case TagXmlElem:
741 branch := C.youtput_read_yxmlelem(out.ptr)
742 if branch == nil {
743 return nil, fmt.Errorf("failed to read child element")
744 }
745 child := &XmlElement{branch: branch}
746 defer child.Destroy()
747
748 jsonBytes, err := child.MarshalJSONWithTxn(txn)
749 if err != nil {
750 return nil, err
751 }
752
753 var result map[string]interface{}
754 if err := json.Unmarshal(jsonBytes, &result); err != nil {
755 return nil, err
756 }
757 return result, nil
758
759 case TagXmlText:
760 branch := C.youtput_read_yxmltext(out.ptr)
761 if branch == nil {
762 return nil, fmt.Errorf("failed to read child text")
763 }
764 text := &XmlText{branch: branch}
765 defer text.Destroy()
766
767 content, err := text.String(txn)
768 if err != nil {
769 return nil, err
770 }
771
772 result := map[string]interface{}{
773 "type": "text",
774 "content": content,
775 }
776 return result, nil
777
778 default:
779 return nil, fmt.Errorf("unsupported child type: %v", out.Tag())
780 }
781}