From a3219f36148abfe184728808c922bf432d51ee53 Mon Sep 17 00:00:00 2001 From: Graeme Brooks-Crawford Date: Tue, 22 Nov 2022 11:58:23 +0000 Subject: [PATCH 1/3] Support overriding default prefix, #, in Map keys - SetGlobalKeyMapPrefix(). --- global_map_prefix_test.go | 114 ++++++++++++++++++++++++++++++++++++++ go.mod | 3 - keyvalues2_test.go | 8 +-- xml.go | 34 ++++++++++-- xml_test.go | 4 +- xmlseq.go | 86 ++++++++++++++-------------- 6 files changed, 192 insertions(+), 57 deletions(-) create mode 100644 global_map_prefix_test.go delete mode 100644 go.mod diff --git a/global_map_prefix_test.go b/global_map_prefix_test.go new file mode 100644 index 0000000..958094b --- /dev/null +++ b/global_map_prefix_test.go @@ -0,0 +1,114 @@ +package mxj + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +var whiteSpaceDataSeqTest2 = []byte(` + + William T. Gaddis + The Recognitions + One of the great seminal American novels of the 20th century. + + + Austin Tappan Wright + Islandia + An example of earlier 20th century American utopian fiction. + + + John Hawkes + The Beetle Leg + A lyrical novel about the construction of Ft. Peck Dam in Montana. + +`) + +func TestSetGlobalKeyMapPrefix(t *testing.T) { + prefixList := []struct { + name string + value string + }{ + { + name: "Testing with % as Map Key Prefix", + value: "%", + }, + { + name: "Testing with _ as Map Key Prefix", + value: "_", + }, + { + name: "Testing with - as Map Key Prefix", + value: "-", + }, + { + name: "Testing with & as Map Key Prefix", + value: "&", + }, + } + + for _, prefix := range prefixList { + t.Run(prefix.name, func(t *testing.T) { + + // Testing MapSeq(Ordering) with whitespace and byte equivalence + DisableTrimWhiteSpace(true) + SetGlobalKeyMapPrefix(prefix.value) + + m, err := NewMapFormattedXmlSeq(whiteSpaceDataSeqTest2) + if err != nil { + t.Fatal(err) + } + + m1 := MapSeq(m) + x, err := m1.XmlIndent("", " ") + if err != nil { + t.Fatal(err) + } + + if string(x) != string(whiteSpaceDataSeqTest2) { + t.Fatalf("expected\n'%s' \ngot \n'%s'", whiteSpaceDataSeqTest2, x) + } + DisableTrimWhiteSpace(false) + + // Testing Map with whitespace and deep equivalence + DisableTrimWhiteSpace(true) + m3, err := NewMapXml(whiteSpaceDataSeqTest2) + if err != nil { + t.Fatal(err) + } + + m4 := Map(m3) + + if !cmp.Equal(m3, m4) { + t.Errorf("Maps unmatched using %s", prefix.value) + } + DisableTrimWhiteSpace(false) + + // Testing MapSeq(Ordering) without whitespace and byte equivalence + m5, err := NewMapFormattedXmlSeq(whiteSpaceDataSeqTest2) + if err != nil { + t.Fatal(err) + } + + m6 := MapSeq(m5) + + if !cmp.Equal(m5, m6) { + t.Errorf("Maps unmatched using %s", prefix.value) + } + + // Testing Map without whitespace and deep equivalence + m7, err := NewMapXml(whiteSpaceDataSeqTest2) + if err != nil { + t.Fatal(err) + } + + m8 := Map(m7) + + if !cmp.Equal(m7, m8) { + t.Errorf("Maps unmatched using %s", prefix.value) + } + }) + + } + +} diff --git a/go.mod b/go.mod deleted file mode 100644 index 7bd84fe..0000000 --- a/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/clbanning/mxj/v2 - -go 1.15 diff --git a/keyvalues2_test.go b/keyvalues2_test.go index 81f3290..a80b764 100644 --- a/keyvalues2_test.go +++ b/keyvalues2_test.go @@ -27,8 +27,8 @@ func TestSetSubkeyFieldSeparator(t *testing.T) { if len(vals) != 1 { t.Fatal(":len(vals);", len(vals), vals) } - if vals[0].(map[string]interface{})["#text"].(string) != "value 2" { - t.Fatal(":expecting: value 2; got:", vals[0].(map[string]interface{})["#text"]) + if vals[0].(map[string]interface{})[textK].(string) != "value 2" { + t.Fatal(":expecting: value 2; got:", vals[0].(map[string]interface{})[textK]) } SetFieldSeparator("|") @@ -40,8 +40,8 @@ func TestSetSubkeyFieldSeparator(t *testing.T) { if len(vals) != 1 { t.Fatal("|len(vals);", len(vals), vals) } - if vals[0].(map[string]interface{})["#text"].(string) != "value 2" { - t.Fatal("|expecting: value 2; got:", vals[0].(map[string]interface{})["#text"]) + if vals[0].(map[string]interface{})[textK].(string) != "value 2" { + t.Fatal("|expecting: value 2; got:", vals[0].(map[string]interface{})[textK]) } } diff --git a/xml.go b/xml.go index 9eaff84..b72a146 100644 --- a/xml.go +++ b/xml.go @@ -22,6 +22,30 @@ import ( "time" ) +var ( + textK = "#text" + seqK = "#seq" + commentK = "#comment" + attrK = "#attr" + directiveK = "#directive" + procinstK = "#procinst" + targetK = "#target" + instK = "#inst" +) + +// Support overriding default Map keys prefix + +func SetGlobalKeyMapPrefix(s string) { + textK = strings.ReplaceAll(textK, textK[0:1], s) + seqK = strings.ReplaceAll(seqK, seqK[0:1], s) + commentK = strings.ReplaceAll(commentK, commentK[0:1], s) + directiveK = strings.ReplaceAll(directiveK, directiveK[0:1], s) + procinstK = strings.ReplaceAll(procinstK, procinstK[0:1], s) + targetK = strings.ReplaceAll(targetK, targetK[0:1], s) + instK = strings.ReplaceAll(instK, instK[0:1], s) + attrK = strings.ReplaceAll(attrK, attrK[0:1], s) +} + // ------------------- NewMapXml & NewMapXmlReader ... ------------------------- // If XmlCharsetReader != nil, it will be used to decode the XML, if required. @@ -441,7 +465,7 @@ func xmlToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[stri val.(map[string]interface{})["_seq"] = seq // will overwrite an "_seq" XML tag seq++ case interface{}: // a non-nil simple element: string, float64, bool - v := map[string]interface{}{"#text": val} + v := map[string]interface{}{textK: val} v["_seq"] = seq seq++ val = v @@ -479,7 +503,7 @@ func xmlToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[stri } else if len(n) == 1 && len(na) > 0 { // it's a simple element w/ no attributes w/ subelements for _, v := range n { - na["#text"] = v + na[textK] = v } n[skey] = na } @@ -492,7 +516,7 @@ func xmlToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[stri } if len(tt) > 0 { if len(na) > 0 || decodeSimpleValuesAsMap { - na["#text"] = cast(tt, r, "#text") + na[textK] = cast(tt, r, textK) } else if skey != "" { n[skey] = cast(tt, r, skey) } else { @@ -1115,7 +1139,7 @@ func marshalMapToXmlIndent(doIndent bool, b *bytes.Buffer, key string, value int // simple element? Note: '#text" is an invalid XML tag. isComplex := false - if v, ok := vv["#text"]; ok && n+1 == lenvv { + if v, ok := vv[textK]; ok && n+1 == lenvv { // just the value and attributes switch v.(type) { case string: @@ -1179,7 +1203,7 @@ func marshalMapToXmlIndent(doIndent bool, b *bytes.Buffer, key string, value int elemlist := make([][2]interface{}, len(vv)) n = 0 for k, v := range vv { - if k == "#text" { + if k == textK { // simple element handled above continue } diff --git a/xml_test.go b/xml_test.go index 9660942..3edac2b 100644 --- a/xml_test.go +++ b/xml_test.go @@ -23,7 +23,7 @@ func TestNewMapXml(t *testing.T) { want := Map{"root2": map[string]interface{}{ "newtag": - map[string]interface{}{"-newattr": "some_attr_value", "#text":"something more"}, + map[string]interface{}{"-newattr": "some_attr_value", textK:"something more"}, "list": map[string]interface{}{"-listattr":"val", "item":[]interface{}{"1", "2"}}, }} @@ -48,7 +48,7 @@ func TestAttrHyphenFalse(t *testing.T) { want := Map{"root2": map[string]interface{}{ "newtag": - map[string]interface{}{"newattr": "some_attr_value", "#text":"something more"}, + map[string]interface{}{"newattr": "some_attr_value", textK:"something more"}, "list": map[string]interface{}{"listattr":"val", "item":[]interface{}{"1", "2"}}, }} diff --git a/xmlseq.go b/xmlseq.go index ca981c2..4360acc 100644 --- a/xmlseq.go +++ b/xmlseq.go @@ -237,12 +237,12 @@ func xmlSeqToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[s v.Value = escapeChars(v.Value) } if len(v.Name.Space) > 0 { - aa[v.Name.Space+`:`+v.Name.Local] = map[string]interface{}{"#text": cast(v.Value, r, ""), "#seq": i} + aa[v.Name.Space+`:`+v.Name.Local] = map[string]interface{}{textK: cast(v.Value, r, ""), seqK: i} } else { - aa[v.Name.Local] = map[string]interface{}{"#text": cast(v.Value, r, ""), "#seq": i} + aa[v.Name.Local] = map[string]interface{}{textK: cast(v.Value, r, ""), seqK: i} } } - na["#attr"] = aa + na[attrK] = aa } } @@ -310,10 +310,10 @@ func xmlSeqToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[s // where all the "list" subelements are decoded into an array. switch val.(type) { case map[string]interface{}: - val.(map[string]interface{})["#seq"] = seq + val.(map[string]interface{})[seqK] = seq seq++ case interface{}: // a non-nil simple element: string, float64, bool - v := map[string]interface{}{"#text": val, "#seq": seq} + v := map[string]interface{}{textK: val, seqK: seq} seq++ val = v } @@ -380,42 +380,42 @@ func xmlSeqToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[s } if len(tt) > 0 { // every simple element is a #text and has #seq associated with it - na["#text"] = cast(tt, r, "") - na["#seq"] = seq + na[textK] = cast(tt, r, "") + na[seqK] = seq seq++ } case xml.Comment: if n == nil { // no root 'key' - n = map[string]interface{}{"#comment": string(t.(xml.Comment))} + n = map[string]interface{}{commentK: string(t.(xml.Comment))} return n, NoRoot } cm := make(map[string]interface{}, 2) - cm["#text"] = string(t.(xml.Comment)) - cm["#seq"] = seq + cm[textK] = string(t.(xml.Comment)) + cm[seqK] = seq seq++ - na["#comment"] = cm + na[commentK] = cm case xml.Directive: if n == nil { // no root 'key' - n = map[string]interface{}{"#directive": string(t.(xml.Directive))} + n = map[string]interface{}{directiveK: string(t.(xml.Directive))} return n, NoRoot } dm := make(map[string]interface{}, 2) - dm["#text"] = string(t.(xml.Directive)) - dm["#seq"] = seq + dm[textK] = string(t.(xml.Directive)) + dm[seqK] = seq seq++ - na["#directive"] = dm + na[directiveK] = dm case xml.ProcInst: if n == nil { - na = map[string]interface{}{"#target": t.(xml.ProcInst).Target, "#inst": string(t.(xml.ProcInst).Inst)} - n = map[string]interface{}{"#procinst": na} + na = map[string]interface{}{targetK: t.(xml.ProcInst).Target, instK: string(t.(xml.ProcInst).Inst)} + n = map[string]interface{}{procinstK: na} return n, NoRoot } pm := make(map[string]interface{}, 3) - pm["#target"] = t.(xml.ProcInst).Target - pm["#inst"] = string(t.(xml.ProcInst).Inst) - pm["#seq"] = seq + pm[targetK] = t.(xml.ProcInst).Target + pm[instK] = string(t.(xml.ProcInst).Inst) + pm[seqK] = seq seq++ - na["#procinst"] = pm + na[procinstK] = pm default: // noop - shouldn't ever get here, now, since we handle all token types } @@ -605,7 +605,7 @@ func mapToXmlSeqIndent(doIndent bool, s *string, key string, value interface{}, if doIndent { *s += p.padding } - if key != "#comment" && key != "#directive" && key != "#procinst" { + if key != commentK && key != directiveK && key != procinstK { *s += `<` + key } } @@ -613,27 +613,27 @@ func mapToXmlSeqIndent(doIndent bool, s *string, key string, value interface{}, case map[string]interface{}: val := value.(map[string]interface{}) - if key == "#comment" { - *s += `` + if key == commentK { + *s += `` noEndTag = true break } - if key == "#directive" { - *s += `` + if key == directiveK { + *s += `` noEndTag = true break } - if key == "#procinst" { - *s += `` + if key == procinstK { + *s += `` noEndTag = true break } haveAttrs := false // process attributes first - if v, ok := val["#attr"].(map[string]interface{}); ok { + if v, ok := val[attrK].(map[string]interface{}); ok { // First, unroll the map[string]interface{} into a []keyval array. // Then sequence it. kv := make([]keyval, len(v)) @@ -646,21 +646,21 @@ func mapToXmlSeqIndent(doIndent bool, s *string, key string, value interface{}, // Now encode the attributes in original decoding sequence, using keyval array. for _, a := range kv { vv := a.v.(map[string]interface{}) - switch vv["#text"].(type) { + switch vv[textK].(type) { case string: if xmlEscapeChars { - ss = escapeChars(vv["#text"].(string)) + ss = escapeChars(vv[textK].(string)) } else { - ss = vv["#text"].(string) + ss = vv[textK].(string) } *s += ` ` + a.k + `="` + ss + `"` case float64, bool, int, int32, int64, float32: - *s += ` ` + a.k + `="` + fmt.Sprintf("%v", vv["#text"]) + `"` + *s += ` ` + a.k + `="` + fmt.Sprintf("%v", vv[textK]) + `"` case []byte: if xmlEscapeChars { - ss = escapeChars(string(vv["#text"].([]byte))) + ss = escapeChars(string(vv[textK].([]byte))) } else { - ss = string(vv["#text"].([]byte)) + ss = string(vv[textK].([]byte)) } *s += ` ` + a.k + `="` + ss + `"` default: @@ -672,8 +672,8 @@ func mapToXmlSeqIndent(doIndent bool, s *string, key string, value interface{}, // simple element? // every map value has, at least, "#seq" and, perhaps, "#text" and/or "#attr" - _, seqOK := val["#seq"] // have key - if v, ok := val["#text"]; ok && ((len(val) == 3 && haveAttrs) || (len(val) == 2 && !haveAttrs)) && seqOK { + _, seqOK := val[seqK] // have key + if v, ok := val[textK]; ok && ((len(val) == 3 && haveAttrs) || (len(val) == 2 && !haveAttrs)) && seqOK { if stmp, ok := v.(string); ok && stmp != "" { if xmlEscapeChars { stmp = escapeChars(stmp) @@ -694,10 +694,10 @@ func mapToXmlSeqIndent(doIndent bool, s *string, key string, value interface{}, // 'kv' will hold everything that needs to be written kv := make([]keyval, 0) for k, v := range val { - if k == "#attr" { // already processed + if k == attrK { // already processed continue } - if k == "#seq" { // ignore - just for sorting + if k == seqK { // ignore - just for sorting continue } switch v.(type) { @@ -870,16 +870,16 @@ func (e elemListSeq) Less(i, j int) bool { var iseq, jseq int var fiseq, fjseq float64 var ok bool - if iseq, ok = e[i].v.(map[string]interface{})["#seq"].(int); !ok { - if fiseq, ok = e[i].v.(map[string]interface{})["#seq"].(float64); ok { + if iseq, ok = e[i].v.(map[string]interface{})[seqK].(int); !ok { + if fiseq, ok = e[i].v.(map[string]interface{})[seqK].(float64); ok { iseq = int(fiseq) } else { iseq = 9999999 } } - if jseq, ok = e[j].v.(map[string]interface{})["#seq"].(int); !ok { - if fjseq, ok = e[j].v.(map[string]interface{})["#seq"].(float64); ok { + if jseq, ok = e[j].v.(map[string]interface{})[seqK].(int); !ok { + if fjseq, ok = e[j].v.(map[string]interface{})[seqK].(float64); ok { jseq = int(fjseq) } else { jseq = 9999999 From 5bae5e03a82d58ba7c438803a7c96998c4062177 Mon Sep 17 00:00:00 2001 From: Graeme Brooks-Crawford Date: Wed, 23 Nov 2022 20:07:38 +0000 Subject: [PATCH 2/3] updated variable to new dynamic type and updatevalues_test to reflect map key vars if they've been changed --- leafnode.go | 2 +- updatevalues_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/leafnode.go b/leafnode.go index cf413eb..1bc814f 100644 --- a/leafnode.go +++ b/leafnode.go @@ -44,7 +44,7 @@ func (mv Map) LeafNodes(no_attr ...bool) []LeafNode { func getLeafNodes(path, node string, mv interface{}, l *[]LeafNode, noattr bool) { // if stripping attributes, then also strip "#text" key - if !noattr || node != "#text" { + if !noattr || node != textK { if path != "" && node[:1] != "[" { path += "." } diff --git a/updatevalues_test.go b/updatevalues_test.go index 09db137..bfa8d44 100644 --- a/updatevalues_test.go +++ b/updatevalues_test.go @@ -86,11 +86,11 @@ func TestUpdateValuesForPath_Author(t *testing.T) { } fmt.Println("\nnewDoc:", string(newDoc)) fmt.Println("m:", m) - fmt.Println("m.UpdateValuesForPath(\"#text:maybe not so simple element\", \"tag\")") - n, _ = m.UpdateValuesForPath("#text:maybe not so simple element", "tag") + fmt.Println("m.UpdateValuesForPath(\"" + textK + ":maybe not so simple element\", \"tag\")") + n, _ = m.UpdateValuesForPath(textK + ":maybe not so simple element", "tag") fmt.Println("n:", n, "m:", m) - fmt.Println("m.UpdateValuesForPath(\"#text:simple element again\", \"*\")") - n, _ = m.UpdateValuesForPath("#text:simple element again", "*") + fmt.Println("m.UpdateValuesForPath(\"" + textK + ":simple element again\", \"*\")") + n, _ = m.UpdateValuesForPath(textK + ":simple element again", "*") fmt.Println("n:", n, "m:", m) /* From f3c235f904d59581163bc3291ed7ea009fc87638 Mon Sep 17 00:00:00 2001 From: Graeme Brooks-Crawford Date: Wed, 23 Nov 2022 20:13:08 +0000 Subject: [PATCH 3/3] Added SetGlobalKeyMapPrefix(#) to the end of test function TestSetGlobalKeyMapPrefix() --- global_map_prefix_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/global_map_prefix_test.go b/global_map_prefix_test.go index 958094b..15d377a 100644 --- a/global_map_prefix_test.go +++ b/global_map_prefix_test.go @@ -110,5 +110,6 @@ func TestSetGlobalKeyMapPrefix(t *testing.T) { }) } + SetGlobalKeyMapPrefix("#") }