From 5703709574870bb23dc588f3eda88118223da0cf Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Sat, 17 May 2025 14:29:04 +0200 Subject: [PATCH 1/3] Xcql produces output to io.Writer Uses Go's own XML encoding of characters. --- cmd/cql-cli/main.go | 9 +++++++- cql/parser_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++- cql/xcql.go | 43 +++++++++++++++++-------------------- 3 files changed, 78 insertions(+), 26 deletions(-) diff --git a/cmd/cql-cli/main.go b/cmd/cql-cli/main.go index 5b358c4..92dc640 100644 --- a/cmd/cql-cli/main.go +++ b/cmd/cql-cli/main.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "encoding/json" "encoding/xml" "flag" @@ -36,7 +37,13 @@ func main() { case "struct": fmt.Printf("%+v\n", query) case "xcql": - fmt.Print((&cql.Xcql{}).Marshal(query, 2)) + buf := bytes.NewBuffer(nil) + err := (&cql.Xcql{}).Write(query, 2, buf) + if err != nil { + fmt.Fprintln(os.Stderr, "ERROR", err) + } else { + fmt.Printf("%s\n", buf) + } default: fmt.Fprintln(os.Stderr, "Unknown output format:", outFmt) os.Exit(1) diff --git a/cql/parser_test.go b/cql/parser_test.go index 41956c6..698fc7a 100644 --- a/cql/parser_test.go +++ b/cql/parser_test.go @@ -1,6 +1,8 @@ package cql import ( + "bytes" + "errors" "strings" "testing" ) @@ -507,6 +509,24 @@ func TestParseXml(t *testing.T) { ok: false, expect: "EOF expected at position 3", }, + { + name: "invalid", + input: "\"\x05\"", + ok: true, + expect: ` + + +cql.serverChoice + += + +` + "\xef\xbf\xbd" + // replacement character, #FFFD + ` + + + +`, + }, } { t.Run(testcase.name, func(t *testing.T) { node, err := p.Parse(testcase.input) @@ -515,7 +535,12 @@ func TestParseXml(t *testing.T) { t.Fatalf("expected OK for query %s . Got error: %s", testcase.input, err) } var xcql Xcql - xml := xcql.Marshal(node, testcase.tab) + bytes := bytes.NewBuffer(nil) + err := xcql.Write(node, testcase.tab, bytes) + if err != nil { + t.Fatalf("error marshalling query %s: %s", testcase.input, err) + } + xml := bytes.String() if xml != testcase.expect { t.Fatalf("Different XML for query %s\nExpect:\n%s\nGot:\n%s", testcase.input, testcase.expect, xml) } @@ -1055,3 +1080,28 @@ func TestBoolClauseString(t *testing.T) { t.Fatalf("expected:\n%s\nwas:\n%s", in, out) } } + +type FailWriter struct{} + +func (f *FailWriter) Write(p []byte) (n int, err error) { + return 0, errors.New("write error") +} + +func TestXcqlFailWriter(t *testing.T) { + var p Parser + query, err := p.Parse("a") + if err != nil { + t.Fatalf("parse error: %s", err) + } + var xcql Xcql + err = xcql.Write(query, 0, &FailWriter{}) + if err == nil { + t.Fatalf("expected error but got nil") + } + xcql.err = nil + xcql.cdata("hello") + err = xcql.err + if err == nil { + t.Fatalf("expected error but got nil") + } +} diff --git a/cql/xcql.go b/cql/xcql.go index 09b4e12..83101ac 100644 --- a/cql/xcql.go +++ b/cql/xcql.go @@ -1,40 +1,35 @@ package cql import ( - "strings" - "unicode/utf8" + "encoding/xml" + "io" ) type Xcql struct { - sb strings.Builder + w io.Writer + err error tab int } func (xcql *Xcql) cdata(msg string) { - pos := 0 - for pos < len(msg) { - r, w := utf8.DecodeRuneInString(msg[pos:]) - switch r { - case utf8.RuneError: - return - case '&': - xcql.sb.WriteString("&") - case '<': - xcql.sb.WriteString("<") - case '>': - xcql.sb.WriteString(">") - default: - xcql.sb.WriteRune(r) - } - pos += w + err := xml.EscapeText(xcql.w, []byte(msg)) + if err != nil && xcql.err == nil { + xcql.err = err + } +} + +func (xcql *Xcql) write(msg string) { + _, err := xcql.w.Write([]byte(msg)) + if err != nil && xcql.err == nil { + xcql.err = err } } func (xcql *Xcql) pr(level int, msg string) { for i := 0; i < level*xcql.tab; i++ { - xcql.sb.WriteString(" ") + xcql.write(" ") } - xcql.sb.WriteString(msg) + xcql.write(msg) } func (xcql *Xcql) toXmlMod(modifiers []Modifier, level int) { @@ -150,11 +145,11 @@ func (xcql *Xcql) toXmlSort(query Query, level int) { } } -func (xcql *Xcql) Marshal(query Query, tab int) string { - xcql.sb.Reset() +func (xcql *Xcql) Write(query Query, tab int, w io.Writer) error { + xcql.w = w xcql.tab = tab xcql.pr(0, "\n") xcql.toXmlSort(query, 1) xcql.pr(0, "\n") - return xcql.sb.String() + return xcql.err } From 938447802831a03d89a7bec9485835fa1b64d694 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Sat, 17 May 2025 18:13:30 +0200 Subject: [PATCH 2/3] Pass standard output writer --- cmd/cql-cli/main.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cmd/cql-cli/main.go b/cmd/cql-cli/main.go index 92dc640..084f9af 100644 --- a/cmd/cql-cli/main.go +++ b/cmd/cql-cli/main.go @@ -1,7 +1,6 @@ package main import ( - "bytes" "encoding/json" "encoding/xml" "flag" @@ -37,12 +36,10 @@ func main() { case "struct": fmt.Printf("%+v\n", query) case "xcql": - buf := bytes.NewBuffer(nil) - err := (&cql.Xcql{}).Write(query, 2, buf) + os.Stdout.WriteString("\n") + err := (&cql.Xcql{}).Write(query, 2, os.Stdout) if err != nil { fmt.Fprintln(os.Stderr, "ERROR", err) - } else { - fmt.Printf("%s\n", buf) } default: fmt.Fprintln(os.Stderr, "Unknown output format:", outFmt) From a27ed863e0f90d743497f898938db6096aa13c84 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Sat, 17 May 2025 18:21:20 +0200 Subject: [PATCH 3/3] Offer Xcql.MarshalIndent --- cql/parser_test.go | 6 ++---- cql/xcql.go | 7 +++++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cql/parser_test.go b/cql/parser_test.go index 698fc7a..fffd3f3 100644 --- a/cql/parser_test.go +++ b/cql/parser_test.go @@ -1,7 +1,6 @@ package cql import ( - "bytes" "errors" "strings" "testing" @@ -535,12 +534,11 @@ func TestParseXml(t *testing.T) { t.Fatalf("expected OK for query %s . Got error: %s", testcase.input, err) } var xcql Xcql - bytes := bytes.NewBuffer(nil) - err := xcql.Write(node, testcase.tab, bytes) + bytes, err := xcql.MarshalIndent(node, testcase.tab) if err != nil { t.Fatalf("error marshalling query %s: %s", testcase.input, err) } - xml := bytes.String() + xml := string(bytes) if xml != testcase.expect { t.Fatalf("Different XML for query %s\nExpect:\n%s\nGot:\n%s", testcase.input, testcase.expect, xml) } diff --git a/cql/xcql.go b/cql/xcql.go index 83101ac..c566de4 100644 --- a/cql/xcql.go +++ b/cql/xcql.go @@ -1,6 +1,7 @@ package cql import ( + "bytes" "encoding/xml" "io" ) @@ -153,3 +154,9 @@ func (xcql *Xcql) Write(query Query, tab int, w io.Writer) error { xcql.pr(0, "\n") return xcql.err } + +func (xcql *Xcql) MarshalIndent(query Query, tab int) ([]byte, error) { + buf := new(bytes.Buffer) + err := xcql.Write(query, tab, buf) + return buf.Bytes(), err +}