diff --git a/node.go b/node.go index 62d0425..ebe5be3 100644 --- a/node.go +++ b/node.go @@ -67,10 +67,14 @@ func (n *Node) InnerText() string { return b.String() } +// xmlTextEscaper escapes the characters that are not allowed to appear +// literally in XML character data. +var xmlTextEscaper = strings.NewReplacer("&", "&", "<", "<", ">", ">") + func outputXML(b *strings.Builder, n *Node, level int, skip bool) { level++ if n.Type == TextNode { - b.WriteString(fmt.Sprintf("%v", n.value)) + b.WriteString(xmlTextEscaper.Replace(fmt.Sprintf("%v", n.value))) return } if v := reflect.ValueOf(n.value); v.Kind() == reflect.Slice { diff --git a/node_test.go b/node_test.go index cc1fb1f..a171c6e 100644 --- a/node_test.go +++ b/node_test.go @@ -1,6 +1,7 @@ package jsonquery import ( + "encoding/xml" "reflect" "strings" "testing" @@ -161,3 +162,27 @@ func TestLargeFloat(t *testing.T) { t.Fatalf("expected %v but %v", "365823929453", n.InnerText()) } } + +func TestOutputXMLEscapesText(t *testing.T) { + doc, err := parseString(`{"a": "x < y && y > z", "b": ["", "AT&T"]}`) + if err != nil { + t.Fatal(err) + } + out := doc.OutputXML() + want := `x < y && y > z<i>AT&T` + if out != want { + t.Fatalf("OutputXML() = %s, want %s", out, want) + } + + // The output must be well-formed and decode back to the original text. + var v struct { + A string `xml:"a"` + B []string `xml:"b"` + } + if err := xml.Unmarshal([]byte(out), &v); err != nil { + t.Fatalf("OutputXML() is not well-formed XML: %v", err) + } + if v.A != "x < y && y > z" || len(v.B) != 2 || v.B[0] != "" || v.B[1] != "AT&T" { + t.Fatalf("decoded %+v", v) + } +}