github.com/tomwright/dasel@v1.27.3/storage/xml.go (about)

     1  package storage
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/clbanning/mxj/v2"
     9  	"golang.org/x/net/html/charset"
    10  )
    11  
    12  func init() {
    13  	// Required for https://github.com/TomWright/dasel/issues/61
    14  	mxj.XMLEscapeCharsDecoder(true)
    15  
    16  	// Required for https://github.com/TomWright/dasel/issues/164
    17  	mxj.XmlCharsetReader = charset.NewReaderLabel
    18  
    19  	registerReadParser([]string{"xml"}, []string{".xml"}, &XMLParser{})
    20  	registerWriteParser([]string{"xml"}, []string{".xml"}, &XMLParser{})
    21  }
    22  
    23  // XMLParser is a Parser implementation to handle xml files.
    24  type XMLParser struct {
    25  }
    26  
    27  // FromBytes returns some data that is represented by the given bytes.
    28  func (p *XMLParser) FromBytes(byteData []byte) (interface{}, error) {
    29  	if byteData == nil {
    30  		return nil, fmt.Errorf("cannot parse nil xml data")
    31  	}
    32  	if len(byteData) == 0 || strings.TrimSpace(string(byteData)) == "" {
    33  		return nil, nil
    34  	}
    35  	data, err := mxj.NewMapXml(byteData)
    36  	if err != nil {
    37  		return data, fmt.Errorf("could not unmarshal data: %w", err)
    38  	}
    39  	return &BasicSingleDocument{
    40  		Value: map[string]interface{}(data),
    41  	}, nil
    42  }
    43  
    44  // ToBytes returns a slice of bytes that represents the given value.
    45  func (p *XMLParser) ToBytes(value interface{}, options ...ReadWriteOption) ([]byte, error) {
    46  	buf := new(bytes.Buffer)
    47  
    48  	prettyPrint := true
    49  	colourise := false
    50  	indent := "  "
    51  
    52  	for _, o := range options {
    53  		switch o.Key {
    54  		case OptionIndent:
    55  			if value, ok := o.Value.(string); ok {
    56  				indent = value
    57  			}
    58  		case OptionPrettyPrint:
    59  			if value, ok := o.Value.(bool); ok {
    60  				prettyPrint = value
    61  			}
    62  		case OptionColourise:
    63  			if value, ok := o.Value.(bool); ok {
    64  				colourise = value
    65  			}
    66  		}
    67  	}
    68  
    69  	writeMap := func(val interface{}) error {
    70  		if m, ok := val.(map[string]interface{}); ok {
    71  			mv := mxj.New()
    72  			for k, v := range m {
    73  				mv[k] = v
    74  			}
    75  
    76  			var byteData []byte
    77  			var err error
    78  			if prettyPrint {
    79  				byteData, err = mv.XmlIndent("", indent)
    80  			} else {
    81  				byteData, err = mv.Xml()
    82  			}
    83  
    84  			if err != nil {
    85  				return err
    86  			}
    87  			buf.Write(byteData)
    88  			buf.Write([]byte("\n"))
    89  			return nil
    90  		}
    91  		buf.Write([]byte(fmt.Sprintf("%v\n", val)))
    92  		return nil
    93  	}
    94  
    95  	switch d := value.(type) {
    96  	case SingleDocument:
    97  		if err := writeMap(d.Document()); err != nil {
    98  			return nil, err
    99  		}
   100  	case MultiDocument:
   101  		for _, dd := range d.Documents() {
   102  			if err := writeMap(dd); err != nil {
   103  				return nil, err
   104  			}
   105  		}
   106  	default:
   107  		if err := writeMap(d); err != nil {
   108  			return nil, err
   109  		}
   110  	}
   111  
   112  	if colourise {
   113  		if err := ColouriseBuffer(buf, "xml"); err != nil {
   114  			return nil, fmt.Errorf("could not colourise output: %w", err)
   115  		}
   116  	}
   117  
   118  	return buf.Bytes(), nil
   119  }