package util import ( "encoding/json" "encoding/xml" "fmt" "io" ) // Copyright 2021 Patrick Gundlach // Permission is hereby granted, free of charge, to any person obtaining a copy of this software // and associated documentation files (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, merge, publish, distribute, // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE // AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT // OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // encodeString encodes str as an element named entry with the attribute key="" if key is not empty. func encodeString(enc *xml.Encoder, str string, key string) error { stringElt := xml.StartElement{Name: xml.Name{Local: "entry"}} var err error if key != "" { stringElt.Attr = []xml.Attr{{Name: xml.Name{Local: "key"}, Value: key}} } if err = enc.EncodeToken(stringElt); err != nil { return err } if err = enc.EncodeToken(xml.CharData([]byte(str))); err != nil { return err } return enc.EncodeToken(stringElt.End()) } // ToXML reads the JSON file from jsonInput and writes to xmlOutput. func ToXML(jsonInput io.Reader, xmlOutput io.Writer) error { var err error dec := json.NewDecoder(jsonInput) enc := xml.NewEncoder(xmlOutput) root := xml.StartElement{} root.Name = xml.Name{Local: "Root"} mapElt := xml.StartElement{Name: xml.Name{Local: "map"}} aryElt := xml.StartElement{Name: xml.Name{Local: "array"}} // inMap is a stack with the last element shows if we are currently // in a map. This is to insert key=".." attributes. inMap := []bool{false} err = enc.EncodeToken(root) if err != nil { return err } var key string encodeLoop: for { tok, err := dec.Token() if err != nil && err != io.EOF { return err } if tok == nil { break encodeLoop } switch t := tok.(type) { case json.Delim: switch t { case '{': inMap = append(inMap, true) if key == "" { if enc.EncodeToken(mapElt); err != nil { return err } } else { attr := xml.Attr{Name: xml.Name{Local: "key"}, Value: key} se := mapElt.Copy() se.Attr = []xml.Attr{attr} if err = enc.EncodeToken(se); err != nil { return err } } key = "" case '[': inMap = append(inMap, false) if key == "" { if err = enc.EncodeToken(aryElt); err != nil { return err } } else { attr := xml.Attr{Name: xml.Name{Local: "key"}, Value: key} se := aryElt.Copy() se.Attr = []xml.Attr{attr} if err = enc.EncodeToken(se); err != nil { return err } } key = "" case ']': inMap = inMap[:len(inMap)-1] if key != "" { if err = encodeString(enc, key, ""); err != nil { return err } key = "" } if err = enc.EncodeToken(aryElt.End()); err != nil { return err } case '}': inMap = inMap[:len(inMap)-1] if key != "" { if err = encodeString(enc, key, ""); err != nil { return err } key = "" } if err = enc.EncodeToken(mapElt.End()); err != nil { return err } } case string: if inMap[len(inMap)-1] { if key != "" { if err = encodeString(enc, t, key); err != nil { return err } key = "" } else { key = t } } else { if err = encodeString(enc, t, ""); err != nil { return err } } case float64, bool: if err = encodeString(enc, fmt.Sprint(t), key); err != nil { return err } key = "" default: panic("not implemented") } } if err = enc.EncodeToken(root.End()); err != nil { return err } return enc.Flush() }