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 131 132
|
package storage
import (
"bytes"
"fmt"
"github.com/tomwright/dasel/v2"
"github.com/tomwright/dasel/v2/dencoding"
"strings"
"github.com/clbanning/mxj/v2"
"golang.org/x/net/html/charset"
)
func init() {
// Required for https://github.com/TomWright/dasel/issues/61
mxj.XMLEscapeCharsDecoder(true)
// Required for https://github.com/TomWright/dasel/issues/164
mxj.XmlCharsetReader = charset.NewReaderLabel
registerReadParser([]string{"xml"}, []string{".xml"}, &XMLParser{})
registerWriteParser([]string{"xml"}, []string{".xml"}, &XMLParser{})
}
// XMLParser is a Parser implementation to handle xml files.
type XMLParser struct {
}
// FromBytes returns some data that is represented by the given bytes.
func (p *XMLParser) FromBytes(byteData []byte, options ...ReadWriteOption) (dasel.Value, error) {
if byteData == nil {
return dasel.Value{}, fmt.Errorf("cannot parse nil xml data")
}
if len(byteData) == 0 || strings.TrimSpace(string(byteData)) == "" {
return dasel.Value{}, nil
}
data, err := mxj.NewMapXml(byteData)
if err != nil {
return dasel.Value{}, fmt.Errorf("could not unmarshal data: %w", err)
}
return dasel.ValueOf(map[string]interface{}(data)).WithMetadata("isSingleDocument", true), nil
}
// ToBytes returns a slice of bytes that represents the given value.
func (p *XMLParser) ToBytes(value dasel.Value, options ...ReadWriteOption) ([]byte, error) {
buf := new(bytes.Buffer)
prettyPrint := true
colourise := false
indent := " "
for _, o := range options {
switch o.Key {
case OptionIndent:
if value, ok := o.Value.(string); ok {
indent = value
}
case OptionPrettyPrint:
if value, ok := o.Value.(bool); ok {
prettyPrint = value
}
case OptionColourise:
if value, ok := o.Value.(bool); ok {
colourise = value
}
}
}
writeMap := func(val interface{}) error {
var m map[string]interface{}
switch v := val.(type) {
case *dencoding.Map:
m = v.UnorderedData()
case map[string]any:
m = v
default:
_, err := buf.Write([]byte(fmt.Sprintf("%v\n", val)))
return err
}
mv := mxj.New()
for k, v := range m {
mv[k] = v
}
var byteData []byte
var err error
if prettyPrint {
byteData, err = mv.XmlIndent("", indent)
} else {
byteData, err = mv.Xml()
}
if err != nil {
return err
}
buf.Write(byteData)
buf.Write([]byte("\n"))
return nil
}
switch {
case value.Metadata("isSingleDocument") == true:
if err := writeMap(value.Interface()); err != nil {
return nil, err
}
case value.Metadata("isMultiDocument") == true:
for i := 0; i < value.Len(); i++ {
if err := writeMap(value.Index(i).Interface()); err != nil {
return nil, err
}
}
case value.IsDencodingMap():
dm := value.Interface().(*dencoding.Map)
if err := writeMap(dm.UnorderedData()); err != nil {
return nil, err
}
default:
if err := writeMap(value.Interface()); err != nil {
return nil, err
}
}
if colourise {
if err := ColouriseBuffer(buf, "xml"); err != nil {
return nil, fmt.Errorf("could not colourise output: %w", err)
}
}
return buf.Bytes(), nil
}
|