Skip to content

Commit

Permalink
v2.0
Browse files Browse the repository at this point in the history
Add XmlSeq type and move functions and methods from Map type.  Also, remove probably unused XmlWriter methods that return 'raw' value; a potentially breaking change.
  • Loading branch information
clbanning committed Jul 4, 2019
1 parent 4638d1b commit 9bb810d
Show file tree
Hide file tree
Showing 12 changed files with 88 additions and 79 deletions.
4 changes: 2 additions & 2 deletions badxml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func TestBadXmlSeq(t *testing.T) {
t.Fatalf("err: didn't find xmlStartElement")
}
fmt.Printf("m: %v\n", m)
j, _ := m.XmlSeq()
j, _ := m.Xml()
fmt.Println("m:", string(j))
}

Expand All @@ -63,6 +63,6 @@ func TestBadXmlSeqReader(t *testing.T) {
t.Fatalf("err: didn't find xmlStartElement")
}
fmt.Printf("m: %v\n", m)
j, _ := m.XmlSeq()
j, _ := m.Xml()
fmt.Println("m:", string(j))
}
4 changes: 2 additions & 2 deletions bom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func TestBomDataSeq(t *testing.T) {
t.Fatalf("err: didn't find xml.StartElement")
}
fmt.Printf("m: %v\n", m)
j, _ := m.XmlSeq()
j, _ := m.Xml()
fmt.Println("m:", string(j))
}

Expand All @@ -83,6 +83,6 @@ func TestBomDataSeqReader(t *testing.T) {
t.Fatalf("err: didn't find xml.StartElement")
}
fmt.Printf("m: %v\n", m)
j, _ := m.XmlSeq()
j, _ := m.Xml()
fmt.Println("m:", string(j))
}
4 changes: 3 additions & 1 deletion doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ Related Packages:
checkxml: github.com/clbanning/checkxml provides functions for validating XML data.
Notes:
2019.01.21: DecodeSimpleValuesAsMap - decode to map[<tag>:map["#text":<value>]] rather than map[<tag>:<value>]
2019.07.04: v2.0 - remove unnecessary methods - mv.XmlWriterRaw, mv.XmlIndentWriterRaw - for Map and MapSeq.
2019.07.04: Add MapSeq type and move associated functions and methods from Map to MapSeq.
2019.01.21: DecodeSimpleValuesAsMap - decode to map[<tag>:map["#text":<value>]] rather than map[<tag>:<value>].
2018.04.18: mv.Xml/mv.XmlIndent encodes non-map[string]interface{} map values - map[string]string, map[int]uint, etc.
2018.03.29: mv.Gob/NewMapGob support gob encoding/decoding of Maps.
2018.03.26: Added mxj/x2j-wrapper sub-package for migrating from legacy x2j package.
Expand Down
4 changes: 2 additions & 2 deletions escapechars_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func TestXMLSeqEscapeChars(t *testing.T) {
XMLEscapeChars(true)
defer XMLEscapeChars(false)

x, err := m.XmlSeqIndent("", " ")
x, err := m.XmlIndent("", " ")
if err != nil {
t.Fatal(err)
}
Expand All @@ -105,7 +105,7 @@ func TestXMLSeqEscapeChars2(t *testing.T) {
XMLEscapeChars(true)
defer XMLEscapeChars(false)

x, err := m.XmlSeqIndent("", " ")
x, err := m.XmlIndent("", " ")
if err != nil {
t.Fatal(err)
}
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ For over a year I've wanted to refactor the XML-to-map[string]interface{} decode

<h4>Notices</h4>

2019.07.04: v2.0 - remove unnecessary methods - mv.XmlWriterRaw, mv.XmlIndentWriterRaw - for Map and MapSeq.
2019.07.04: Add MapSeq type and move associated functions and methods from Map to MapSeq.
2019.01.21: DecodeSimpleValuesAsMap - decode to map[<tag>:map["#text":<value>]] rather than map[<tag>:<value>]
2018.04.18: mv.Xml/mv.XmlIndent encodes non-map[string]interface{} map values - map[string]string, map[int]uint, etc.
2018.03.29: mv.Gob/NewMapGob support gob encoding/decoding of Maps.
Expand Down
4 changes: 2 additions & 2 deletions snakecase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ func TestStakeCase(t *testing.T) {
</software_information>
</rpc_reply>`

m, err = NewMapXmlSeq([]byte(data1))
ms, err := NewMapXmlSeq([]byte(data1))
if err != nil {
t.Fatal(err)
}

x, err = m.XmlSeqIndent("", "")
x, err = ms.XmlIndent("", "")
if err != nil {
t.Fatal(err)
}
Expand Down
6 changes: 5 additions & 1 deletion xml.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2012-2016, 2018 Charles Banning. All rights reserved.
// Copyright 2012-2016, 2018-2019 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file

Expand Down Expand Up @@ -653,6 +653,7 @@ func (mv Map) XmlWriter(xmlWriter io.Writer, rootTag ...string) error {

// Writes the Map as XML on the Writer. []byte is the raw XML that was written.
// See Xml() for encoding rules.
/*
func (mv Map) XmlWriterRaw(xmlWriter io.Writer, rootTag ...string) ([]byte, error) {
x, err := mv.Xml(rootTag...)
if err != nil {
Expand All @@ -662,6 +663,7 @@ func (mv Map) XmlWriterRaw(xmlWriter io.Writer, rootTag ...string) ([]byte, erro
_, err = xmlWriter.Write(x)
return x, err
}
*/

// Writes the Map as pretty XML on the Writer.
// See Xml() for encoding rules.
Expand All @@ -677,6 +679,7 @@ func (mv Map) XmlIndentWriter(xmlWriter io.Writer, prefix, indent string, rootTa

// Writes the Map as pretty XML on the Writer. []byte is the raw XML that was written.
// See Xml() for encoding rules.
/*
func (mv Map) XmlIndentWriterRaw(xmlWriter io.Writer, prefix, indent string, rootTag ...string) ([]byte, error) {
x, err := mv.XmlIndent(prefix, indent, rootTag...)
if err != nil {
Expand All @@ -686,6 +689,7 @@ func (mv Map) XmlIndentWriterRaw(xmlWriter io.Writer, prefix, indent string, roo
_, err = xmlWriter.Write(x)
return x, err
}
*/

// -------------------- END: mv.Xml & mv.XmlWriter -------------------------------

Expand Down
2 changes: 1 addition & 1 deletion xml3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func TestOnlyAttributesSeq(t *testing.T) {
if err != nil {
t.Fatal(err)
}
xml, err := dom.XmlSeqIndent("", " ")
xml, err := dom.XmlIndent("", " ")
if err != nil {
t.Fatal(err)
}
Expand Down
3 changes: 1 addition & 2 deletions xml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func TestXmlWriter(t *testing.T) {
mv := Map{"tag1": "some data", "tag2": "more data", "boolean": true, "float": 3.14159625}
w := new(bytes.Buffer)

raw, err := mv.XmlWriterRaw(w, "myRootTag")
err := mv.XmlWriter(w, "myRootTag")
if err != nil {
t.Fatal("err:", err.Error())
}
Expand All @@ -190,7 +190,6 @@ func TestXmlWriter(t *testing.T) {
t.Fatal("err:", err.Error())
}

fmt.Println("XmlWriter, raw:", string(raw))
fmt.Println("XmlWriter, b :", string(b))
}

Expand Down
100 changes: 46 additions & 54 deletions xmlseq.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2012-2016 Charles Banning. All rights reserved.
// Copyright 2012-2016, 2019 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file

Expand All @@ -17,20 +17,23 @@ import (
"strings"
)

// MapSeq is like Map but contains seqencing indices to allow recovering the original order of
// the XML elements when the map[string]interface{} is marshaled. (Also, element attributes are
// stored as a map["#attr"][]map[<tag>]map[<attr_key>]map[string]interface{} value instead of
// denoting the keys with a prefix character.
type MapSeq map[string]interface{}

// NoRoot is returned by NewXmlSeq, etc., when a comment, directive or procinstr element is parsed
// in the XML data stream and the element is not contained in an XML object with a root element.
var NoRoot = errors.New("no root key")
var NO_ROOT = NoRoot // maintain backwards compatibility

// ------------------- NewMapXmlSeq & NewMapXmlSeqReader ... -------------------------

// This is only useful if you want to re-encode the Map as XML using mv.XmlSeq(), etc., to preserve the original structure.
// The xml.Decoder.RawToken method is used to parse the XML, so there is no checking for appropriate xml.EndElement values;
// thus, it is assumed that the XML is valid.
//
// NewMapXmlSeq - convert a XML doc into a Map with elements id'd with decoding sequence int - #seq.
// NewMapXmlSeq converts a XML doc into a MapSeq value with elements id'd with decoding sequence key represented
// as map["#seq"]<int value>.
// If the optional argument 'cast' is 'true', then values will be converted to boolean or float64 if possible.
// NOTE: "#seq" key/value pairs are removed on encoding with mv.XmlSeq() / mv.XmlSeqIndent().
// NOTE: "#seq" key/value pairs are removed on encoding with msv.Xml() / msv.XmlIndent().
// • attributes are a map - map["#attr"]map["attr_key"]map[string]interface{}{"#text":<aval>, "#seq":<num>}
// • all simple elements are decoded as map["#text"]interface{} with a "#seq" k:v pair, as well.
// • lists always decode as map["list_tag"][]map[string]interface{} where the array elements are maps that
Expand All @@ -52,7 +55,7 @@ var NO_ROOT = NoRoot // maintain backwards compatibility
// newtag :
// #seq :[int] 1
// #text :[string] value 2
// It will encode in proper sequence even though the Map representation merges all "ltag" elements in an array.
// It will encode in proper sequence even though the MapSeq representation merges all "ltag" elements in an array.
// • comments - "<!--comment-->" - are decoded as map["#comment"]map["#text"]"cmnt_text" with a "#seq" k:v pair.
// • directives - "<!text>" - are decoded as map["#directive"]map[#text"]"directive_text" with a "#seq" k:v pair.
// • process instructions - "<?instr?>" - are decoded as map["#procinst"]interface{} where the #procinst value
Expand All @@ -70,24 +73,22 @@ var NO_ROOT = NoRoot // maintain backwards compatibility
// 3. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
//
// NAME SPACES:
// 1. Keys in the Map value that are parsed from a <name space prefix>:<local name> tag preserve the
// 1. Keys in the MapSeq value that are parsed from a <name space prefix>:<local name> tag preserve the
// "<prefix>:" notation rather than stripping it as with NewMapXml().
// 2. Attribute keys for name space prefix declarations preserve "xmlns:<prefix>" notation.
//
// ERRORS:
// 1. If a NoRoot error, "no root key," is returned, check the initial map key for a "#comment",
// "#directive" or #procinst" key.
func NewMapXmlSeq(xmlVal []byte, cast ...bool) (Map, error) {
func NewMapXmlSeq(xmlVal []byte, cast ...bool) (MapSeq, error) {
var r bool
if len(cast) == 1 {
r = cast[0]
}
return xmlSeqToMap(xmlVal, r)
}

// This is only useful if you want to re-encode the Map as XML using mv.XmlSeq(), etc., to preserve the original structure.
//
// Get next XML doc from an io.Reader as a Map value. Returns Map value.
// NewMpaXmlSeqReader returns next XML doc from an io.Reader as a MapSeq value.
// NOTES:
// 1. The 'xmlReader' will be parsed looking for an xml.StartElement, xml.Comment, etc., so BOM and other
// extraneous xml.CharData will be ignored unless io.EOF is reached first.
Expand All @@ -98,7 +99,7 @@ func NewMapXmlSeq(xmlVal []byte, cast ...bool) (Map, error) {
// ERRORS:
// 1. If a NoRoot error, "no root key," is returned, check the initial map key for a "#comment",
// "#directive" or #procinst" key.
func NewMapXmlSeqReader(xmlReader io.Reader, cast ...bool) (Map, error) {
func NewMapXmlSeqReader(xmlReader io.Reader, cast ...bool) (MapSeq, error) {
var r bool
if len(cast) == 1 {
r = cast[0]
Expand All @@ -115,9 +116,8 @@ func NewMapXmlSeqReader(xmlReader io.Reader, cast ...bool) (Map, error) {
return xmlSeqReaderToMap(xmlReader, r)
}

// This is only useful if you want to re-encode the Map as XML using mv.XmlSeq(), etc., to preserve the original structure.
//
// Get next XML doc from an io.Reader as a Map value. Returns Map value and slice with the raw XML.
// NewMapXmlSeqReaderRaw returns the next XML doc from an io.Reader as a MapSeq value.
// Returns MapSeq value, slice with the raw XML, and any error.
// NOTES:
// 1. Due to the implementation of xml.Decoder, the raw XML off the reader is buffered to []byte
// using a ByteReader. If the io.Reader is an os.File, there may be significant performance impact.
Expand All @@ -132,9 +132,9 @@ func NewMapXmlSeqReader(xmlReader io.Reader, cast ...bool) (Map, error) {
// 5. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
//
// ERRORS:
// 1. If a NoRoot error, "no root key," is returned, check the initial map key for a "#comment",
// 1. If a NoRoot error, "no root key," is returned, check if the initial map key is "#comment",
// "#directive" or #procinst" key.
func NewMapXmlSeqReaderRaw(xmlReader io.Reader, cast ...bool) (Map, []byte, error) {
func NewMapXmlSeqReaderRaw(xmlReader io.Reader, cast ...bool) (MapSeq, []byte, error) {
var r bool
if len(cast) == 1 {
r = cast[0]
Expand Down Expand Up @@ -394,12 +394,9 @@ func xmlSeqToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[s

// --------------------- mv.XmlSeq & mv.XmlSeqWriter -------------------------

// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Encode a Map as XML with elements sorted on #seq. The companion of NewMapXmlSeq().
// Xml encodes a MapSeq as XML with elements sorted on #seq. The companion of NewMapXmlSeq().
// The following rules apply.
// - The key label "#text" is treated as the value for a simple element with attributes.
// - The "#seq" key is used to seqence the subelements or attributes but is ignored for writing.
// - The "#seq" key value is used to seqence the subelements or attributes only.
// - The "#attr" map key identifies the map of attribute map[string]interface{} values with "#text" key.
// - The "#comment" map key identifies a comment in the value "#text" map entry - <!--comment-->.
// - The "#directive" map key identifies a directive in the value "#text" map entry - <!directive>.
Expand All @@ -413,7 +410,7 @@ func xmlSeqToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[s
// - Elements with only attribute values or are null are terminated using "/>" unless XmlGoEmptyElemSystax() called.
// - If len(mv) == 1 and no rootTag is provided, then the map key is used as the root tag, possible.
// Thus, `{ "key":"value" }` encodes as "<key>value</key>".
func (mv Map) XmlSeq(rootTag ...string) ([]byte, error) {
func (mv MapSeq) Xml(rootTag ...string) ([]byte, error) {
m := map[string]interface{}(mv)
var err error
s := new(string)
Expand Down Expand Up @@ -449,12 +446,10 @@ done:
// The following implementation is provided only for symmetry with NewMapXmlReader[Raw]
// The names will also provide a key for the number of return arguments.

// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Writes the Map as XML on the Writer.
// See XmlSeq() for encoding rules.
func (mv Map) XmlSeqWriter(xmlWriter io.Writer, rootTag ...string) error {
x, err := mv.XmlSeq(rootTag...)
// XmlWriter Writes the MapSeq value as XML on the Writer.
// See MapSeq.Xml() for encoding rules.
func (mv MapSeq) XmlWriter(xmlWriter io.Writer, rootTag ...string) error {
x, err := mv.Xml(rootTag...)
if err != nil {
return err
}
Expand All @@ -463,26 +458,24 @@ func (mv Map) XmlSeqWriter(xmlWriter io.Writer, rootTag ...string) error {
return err
}

// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Writes the Map as XML on the Writer. []byte is the raw XML that was written.
// See XmlSeq() for encoding rules.
func (mv Map) XmlSeqWriterRaw(xmlWriter io.Writer, rootTag ...string) ([]byte, error) {
x, err := mv.XmlSeq(rootTag...)
// XmlWriteRaw writes the MapSeq value as XML on the Writer. []byte is the raw XML that was written.
// See Map.XmlSeq() for encoding rules.
/*
func (mv MapSeq) XmlWriterRaw(xmlWriter io.Writer, rootTag ...string) ([]byte, error) {
x, err := mv.Xml(rootTag...)
if err != nil {
return x, err
}
_, err = xmlWriter.Write(x)
return x, err
}
*/

// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Writes the Map as pretty XML on the Writer.
// See Xml() for encoding rules.
func (mv Map) XmlSeqIndentWriter(xmlWriter io.Writer, prefix, indent string, rootTag ...string) error {
x, err := mv.XmlSeqIndent(prefix, indent, rootTag...)
// XmlIndentWriter writes the MapSeq value as pretty XML on the Writer.
// See MapSeq.Xml() for encoding rules.
func (mv MapSeq) XmlIndentWriter(xmlWriter io.Writer, prefix, indent string, rootTag ...string) error {
x, err := mv.XmlIndent(prefix, indent, rootTag...)
if err != nil {
return err
}
Expand All @@ -491,11 +484,10 @@ func (mv Map) XmlSeqIndentWriter(xmlWriter io.Writer, prefix, indent string, roo
return err
}

// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Writes the Map as pretty XML on the Writer. []byte is the raw XML that was written.
// See XmlSeq() for encoding rules.
func (mv Map) XmlSeqIndentWriterRaw(xmlWriter io.Writer, prefix, indent string, rootTag ...string) ([]byte, error) {
// XmlIndentWriterRaw writes the Map as pretty XML on the Writer. []byte is the raw XML that was written.
// See Map.XmlSeq() for encoding rules.
/*
func (mv MapSeq) XmlIndentWriterRaw(xmlWriter io.Writer, prefix, indent string, rootTag ...string) ([]byte, error) {
x, err := mv.XmlSeqIndent(prefix, indent, rootTag...)
if err != nil {
return x, err
Expand All @@ -504,16 +496,15 @@ func (mv Map) XmlSeqIndentWriterRaw(xmlWriter io.Writer, prefix, indent string,
_, err = xmlWriter.Write(x)
return x, err
}
*/

// -------------------- END: mv.Xml & mv.XmlWriter -------------------------------

// ---------------------- XmlSeqIndent ----------------------------

// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Encode a map[string]interface{} as a pretty XML string.
// See mv.XmlSeq() for encoding rules.
func (mv Map) XmlSeqIndent(prefix, indent string, rootTag ...string) ([]byte, error) {
// XmlIndent encodes a map[string]interface{} as a pretty XML string.
// See MapSeq.XmlSeq() for encoding rules.
func (mv MapSeq) XmlIndent(prefix, indent string, rootTag ...string) ([]byte, error) {
m := map[string]interface{}(mv)

var err error
Expand Down Expand Up @@ -842,10 +833,11 @@ func (e elemListSeq) Less(i, j int) bool {
// =============== https://groups.google.com/forum/#!topic/golang-nuts/lHPOHD-8qio

// BeautifyXml (re)formats an XML doc similar to Map.XmlIndent().
// It preserves comments, directives and process instructions,
func BeautifyXml(b []byte, prefix, indent string) ([]byte, error) {
x, err := NewMapXmlSeq(b)
if err != nil {
return nil, err
}
return x.XmlSeqIndent(prefix, indent)
return x.XmlIndent(prefix, indent)
}
Loading

0 comments on commit 9bb810d

Please sign in to comment.