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}