// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package xml import ( "bytes" "fmt" "io" "reflect" "strings" "testing" "unicode/utf8" ) type toks struct { earlyEOF bool t []Token } func (t *toks) Token() (Token, error) { if len(t.t) == 0 { return nil, io.EOF } var tok Token tok, t.t = t.t[0], t.t[1:] if t.earlyEOF && len(t.t) == 0 { return tok, io.EOF } return tok, nil } func TestDecodeEOF(t *testing.T) { start := StartElement{Name: Name{Local: "test"}} tests := []struct { name string tokens []Token ok bool }{ { name: "OK", tokens: []Token{ start, start.End(), }, ok: true, }, { name: "Malformed", tokens: []Token{ start, StartElement{Name: Name{Local: "bad"}}, start.End(), }, ok: false, }, } for _, tc := range tests { for _, eof := range []bool{true, false} { name := fmt.Sprintf("%s/earlyEOF=%v", tc.name, eof) t.Run(name, func(t *testing.T) { d := NewTokenDecoder(&toks{ earlyEOF: eof, t: tc.tokens, }) err := d.Decode(&struct { XMLName Name `xml:"test"` }{}) if tc.ok && err != nil { t.Fatalf("d.Decode: expected nil error, got %v", err) } if _, ok := err.(*SyntaxError); !tc.ok && !ok { t.Errorf("d.Decode: expected syntax error, got %v", err) } }) } } } type toksNil struct { returnEOF bool t []Token } func (t *toksNil) Token() (Token, error) { if len(t.t) == 0 { if !t.returnEOF { // Return nil, nil before returning an EOF. It's legal, but // discouraged. t.returnEOF = true return nil, nil } return nil, io.EOF } var tok Token tok, t.t = t.t[0], t.t[1:] return tok, nil } func TestDecodeNilToken(t *testing.T) { for _, strict := range []bool{true, false} { name := fmt.Sprintf("Strict=%v", strict) t.Run(name, func(t *testing.T) { start := StartElement{Name: Name{Local: "test"}} bad := StartElement{Name: Name{Local: "bad"}} d := NewTokenDecoder(&toksNil{ // Malformed t: []Token{start, bad, start.End()}, }) d.Strict = strict err := d.Decode(&struct { XMLName Name `xml:"test"` }{}) if _, ok := err.(*SyntaxError); !ok { t.Errorf("d.Decode: expected syntax error, got %v", err) } }) } } const testInput = `
", "p", "nowrap"}, {"
", "p", "nowrap"}, {"", "input", "checked"}, {"", "input", "checked"}, } for _, test := range tests { d := NewDecoder(strings.NewReader(test[0])) d.Strict = false token, err := d.Token() if _, ok := err.(*SyntaxError); ok { t.Errorf("Unexpected error: %v", err) } if token.(StartElement).Name.Local != test[1] { t.Errorf("Unexpected tag name: %v", token.(StartElement).Name.Local) } attr := token.(StartElement).Attr[0] if attr.Value != test[2] { t.Errorf("Unexpected attribute value: %v", attr.Value) } if attr.Name.Local != test[2] { t.Errorf("Unexpected attribute name: %v", attr.Name.Local) } } } func TestCopyTokenCharData(t *testing.T) { data := []byte("same data") var tok1 Token = CharData(data) tok2 := CopyToken(tok1) if !reflect.DeepEqual(tok1, tok2) { t.Error("CopyToken(CharData) != CharData") } data[1] = 'o' if reflect.DeepEqual(tok1, tok2) { t.Error("CopyToken(CharData) uses same buffer.") } } func TestCopyTokenStartElement(t *testing.T) { elt := StartElement{Name{"", "hello"}, []Attr{{Name{"", "lang"}, "en"}}} var tok1 Token = elt tok2 := CopyToken(tok1) if tok1.(StartElement).Attr[0].Value != "en" { t.Error("CopyToken overwrote Attr[0]") } if !reflect.DeepEqual(tok1, tok2) { t.Error("CopyToken(StartElement) != StartElement") } tok1.(StartElement).Attr[0] = Attr{Name{"", "lang"}, "de"} if reflect.DeepEqual(tok1, tok2) { t.Error("CopyToken(CharData) uses same buffer.") } } func TestCopyTokenComment(t *testing.T) { data := []byte("") var tok1 Token = Comment(data) tok2 := CopyToken(tok1) if !reflect.DeepEqual(tok1, tok2) { t.Error("CopyToken(Comment) != Comment") } data[1] = 'o' if reflect.DeepEqual(tok1, tok2) { t.Error("CopyToken(Comment) uses same buffer.") } } func TestSyntaxErrorLineNum(t *testing.T) { testInput := "
Foo
\n\n
Bar>\n"
d := NewDecoder(strings.NewReader(testInput))
var err error
for _, err = d.Token(); err == nil; _, err = d.Token() {
}
synerr, ok := err.(*SyntaxError)
if !ok {
t.Error("Expected SyntaxError.")
}
if synerr.Line != 3 {
t.Error("SyntaxError didn't have correct line number.")
}
}
func TestTrailingRawToken(t *testing.T) {
input := `
`))
d2 := NewTokenDecoder(d)
if d != d2 {
t.Error("NewTokenDecoder did not detect underlying Decoder")
}
}
func TestWrapDecoder(t *testing.T) {
d := NewDecoder(strings.NewReader(`[Re-enter Clown with a letter, and FABIAN]
`))
m := tokenMap(func(t Token) Token {
switch tok := t.(type) {
case StartElement:
if tok.Name.Local == "quote" {
tok.Name.Local = "blocking"
return tok
}
case EndElement:
if tok.Name.Local == "quote" {
tok.Name.Local = "blocking"
return tok
}
}
return t
})
d = NewTokenDecoder(m(d))
o := struct {
XMLName Name `xml:"blocking"`
Chardata string `xml:",chardata"`
}{}
if err := d.Decode(&o); err != nil {
t.Fatal("Got unexpected error while decoding:", err)
}
if o.Chardata != "[Re-enter Clown with a letter, and FABIAN]" {
t.Fatalf("Got unexpected chardata: `%s`\n", o.Chardata)
}
}
type tokReader struct{}
func (tokReader) Token() (Token, error) {
return StartElement{}, nil
}
type Failure struct{}
func (Failure) UnmarshalXML(*Decoder, StartElement) error {
return nil
}
func TestTokenUnmarshaler(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Error("Unexpected panic using custom token unmarshaler")
}
}()
d := NewTokenDecoder(tokReader{})
d.Decode(&Failure{})
}
func testRoundTrip(t *testing.T, input string) {
d := NewDecoder(strings.NewReader(input))
var tokens []Token
var buf bytes.Buffer
e := NewEncoder(&buf)
for {
tok, err := d.Token()
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("invalid input: %v", err)
}
if err := e.EncodeToken(tok); err != nil {
t.Fatalf("failed to re-encode input: %v", err)
}
tokens = append(tokens, CopyToken(tok))
}
if err := e.Flush(); err != nil {
t.Fatal(err)
}
d = NewDecoder(&buf)
for {
tok, err := d.Token()
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("failed to decode output: %v", err)
}
if len(tokens) == 0 {
t.Fatalf("unexpected token: %#v", tok)
}
a, b := tokens[0], tok
if !reflect.DeepEqual(a, b) {
t.Fatalf("token mismatch: %#v vs %#v", a, b)
}
tokens = tokens[1:]
}
if len(tokens) > 0 {
t.Fatalf("lost tokens: %#v", tokens)
}
}
func TestRoundTrip(t *testing.T) {
tests := map[string]string{
"trailing colon": `
abc
`
func BenchmarkHTMLAutoClose(b *testing.B) {
b.RunParallel(func(p *testing.PB) {
for p.Next() {
d := NewDecoder(strings.NewReader(testInputHTMLAutoClose))
d.Strict = false
d.AutoClose = HTMLAutoClose
d.Entity = HTMLEntity
for {
_, err := d.Token()
if err != nil {
if err == io.EOF {
break
}
b.Fatalf("unexpected error: %v", err)
}
}
}
})
}
func TestHTMLAutoClose(t *testing.T) {
wantTokens := []Token{
ProcInst{"xml", []byte(`version="1.0" encoding="UTF-8"`)},
CharData("\n"),
StartElement{Name{"", "br"}, []Attr{}},
EndElement{Name{"", "br"}},
CharData("\n"),
StartElement{Name{"", "br"}, []Attr{}},
EndElement{Name{"", "br"}},
StartElement{Name{"", "br"}, []Attr{}},
EndElement{Name{"", "br"}},
CharData("\n"),
StartElement{Name{"", "br"}, []Attr{}},
EndElement{Name{"", "br"}},
StartElement{Name{"", "br"}, []Attr{}},
EndElement{Name{"", "br"}},
CharData("\n"),
StartElement{Name{"", "br"}, []Attr{}},
EndElement{Name{"", "br"}},
CharData("\n"),
StartElement{Name{"", "BR"}, []Attr{}},
EndElement{Name{"", "BR"}},
CharData("\n"),
StartElement{Name{"", "BR"}, []Attr{}},
EndElement{Name{"", "BR"}},
StartElement{Name{"", "BR"}, []Attr{}},
EndElement{Name{"", "BR"}},
CharData("\n"),
StartElement{Name{"", "Br"}, []Attr{}},
EndElement{Name{"", "Br"}},
CharData("\n"),
StartElement{Name{"", "BR"}, []Attr{}},
EndElement{Name{"", "BR"}},
StartElement{Name{"", "span"}, []Attr{{Name: Name{"", "id"}, Value: "test"}}},
CharData("abc"),
EndElement{Name{"", "span"}},
StartElement{Name{"", "br"}, []Attr{}},
EndElement{Name{"", "br"}},
StartElement{Name{"", "br"}, []Attr{}},
EndElement{Name{"", "br"}},
}
d := NewDecoder(strings.NewReader(testInputHTMLAutoClose))
d.Strict = false
d.AutoClose = HTMLAutoClose
d.Entity = HTMLEntity
var haveTokens []Token
for {
tok, err := d.Token()
if err != nil {
if err == io.EOF {
break
}
t.Fatalf("unexpected error: %v", err)
}
haveTokens = append(haveTokens, CopyToken(tok))
}
if len(haveTokens) != len(wantTokens) {
t.Errorf("tokens count mismatch: have %d, want %d", len(haveTokens), len(wantTokens))
}
for i, want := range wantTokens {
if i >= len(haveTokens) {
t.Errorf("token[%d] expected %#v, have no token", i, want)
} else {
have := haveTokens[i]
if !reflect.DeepEqual(have, want) {
t.Errorf("token[%d] mismatch:\nhave: %#v\nwant: %#v", i, have, want)
}
}
}
}