diff --git a/README.md b/README.md index e63185e..80cb66d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # render [![wercker status](https://app.wercker.com/status/fcf6b26a1b41f53540200b1949b48dec "wercker status")](https://app.wercker.com/project/bykey/fcf6b26a1b41f53540200b1949b48dec) -Martini middleware/handler for easily rendering serialized JSON and HTML template responses. +Martini middleware/handler for easily rendering serialized JSON, XML, and HTML template responses. [API Reference](http://godoc.org/github.com/martini-contrib/render) @@ -47,6 +47,7 @@ m.Use(render.Renderer(render.Options{ Delims: render.Delims{"{[{", "}]}"}, // Sets delimiters to the specified strings. Charset: "UTF-8", // Sets encoding for json and html content-types. Default is "UTF-8". IndentJSON: true, // Output human readable JSON + IndentXML: true, // Output human readable XML HTMLContentType: "application/xhtml+xml", // Output XHTML content type instead of default "text/html" })) // ... diff --git a/render.go b/render.go index b932983..4e84177 100644 --- a/render.go +++ b/render.go @@ -26,6 +26,7 @@ package render import ( "bytes" "encoding/json" + "encoding/xml" "fmt" "html/template" "io" @@ -41,10 +42,11 @@ import ( const ( ContentType = "Content-Type" ContentLength = "Content-Length" + ContentBinary = "application/octet-stream" ContentJSON = "application/json" ContentHTML = "text/html" ContentXHTML = "application/xhtml+xml" - ContentBinary = "application/octet-stream" + ContentXML = "text/xml" defaultCharset = "UTF-8" ) @@ -65,6 +67,8 @@ type Render interface { JSON(status int, v interface{}) // HTML renders a html template specified by the name and writes the result and given status to the http.ResponseWriter. HTML(status int, name string, v interface{}, htmlOpt ...HTMLOptions) + // XML writes the given status and XML serialized version of the given value to the http.ResponseWriter. + XML(status int, v interface{}) // Data writes the raw byte array to the http.ResponseWriter. Data(status int, v []byte) // Error is a convenience function that writes an http status to the http.ResponseWriter. @@ -103,8 +107,12 @@ type Options struct { Charset string // Outputs human readable JSON IndentJSON bool + // Outputs human readable XML + IndentXML bool // Prefixes the JSON output with the given bytes. PrefixJSON []byte + // Prefixes the XML output with the given bytes. + PrefixXML []byte // Allows changing of output to XHTML instead of HTML. Default is "text/html" HTMLContentType string } @@ -266,6 +274,28 @@ func (r *renderer) HTML(status int, name string, binding interface{}, htmlOpt .. io.Copy(r, out) } +func (r *renderer) XML(status int, v interface{}) { + var result []byte + var err error + if r.opt.IndentXML { + result, err = xml.MarshalIndent(v, "", " ") + } else { + result, err = xml.Marshal(v) + } + if err != nil { + http.Error(r, err.Error(), 500) + return + } + + // json rendered fine, write out the result + r.Header().Set(ContentType, ContentXML+r.compiledCharset) + r.WriteHeader(status) + if len(r.opt.PrefixXML) > 0 { + r.Write(r.opt.PrefixXML) + } + r.Write(result) +} + func (r *renderer) Data(status int, v []byte) { if r.Header().Get(ContentType) == "" { r.Header().Set(ContentType, ContentBinary) diff --git a/render_test.go b/render_test.go index 0ea459c..2777177 100644 --- a/render_test.go +++ b/render_test.go @@ -1,6 +1,7 @@ package render import ( + "encoding/xml" "html/template" "net/http" "net/http/httptest" @@ -16,6 +17,12 @@ type Greeting struct { Two string `json:"two"` } +type GreetingXML struct { + XMLName xml.Name `xml:"greeting"` + One string `xml:"one,attr"` + Two string `xml:"two,attr"` +} + func Test_Render_JSON(t *testing.T) { m := martini.Classic() m.Use(Renderer(Options{ @@ -56,7 +63,7 @@ func Test_Render_JSON_Prefix(t *testing.T) { expect(t, res.Code, 300) expect(t, res.Header().Get(ContentType), ContentJSON+"; charset=UTF-8") - expect(t, res.Body.String(), prefix + `{"one":"hello","two":"world"}`) + expect(t, res.Body.String(), prefix+`{"one":"hello","two":"world"}`) } func Test_Render_Indented_JSON(t *testing.T) { @@ -83,6 +90,70 @@ func Test_Render_Indented_JSON(t *testing.T) { }`) } +func Test_Render_XML(t *testing.T) { + m := martini.Classic() + m.Use(Renderer(Options{ + // nothing here to configure + })) + + // routing + m.Get("/foobar", func(r Render) { + r.XML(300, GreetingXML{One: "hello", Two: "world"}) + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/foobar", nil) + + m.ServeHTTP(res, req) + + expect(t, res.Code, 300) + expect(t, res.Header().Get(ContentType), ContentXML+"; charset=UTF-8") + expect(t, res.Body.String(), ``) +} + +func Test_Render_XML_Prefix(t *testing.T) { + m := martini.Classic() + prefix := ")]}',\n" + m.Use(Renderer(Options{ + PrefixXML: []byte(prefix), + })) + + // routing + m.Get("/foobar", func(r Render) { + r.XML(300, GreetingXML{One: "hello", Two: "world"}) + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/foobar", nil) + + m.ServeHTTP(res, req) + + expect(t, res.Code, 300) + expect(t, res.Header().Get(ContentType), ContentXML+"; charset=UTF-8") + expect(t, res.Body.String(), prefix+``) +} + +func Test_Render_Indented_XML(t *testing.T) { + m := martini.Classic() + m.Use(Renderer(Options{ + IndentXML: true, + })) + + // routing + m.Get("/foobar", func(r Render) { + r.XML(300, GreetingXML{One: "hello", Two: "world"}) + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/foobar", nil) + + m.ServeHTTP(res, req) + + expect(t, res.Code, 300) + expect(t, res.Header().Get(ContentType), ContentXML+"; charset=UTF-8") + expect(t, res.Body.String(), ``) +} + func Test_Render_Bad_HTML(t *testing.T) { m := martini.Classic() m.Use(Renderer(Options{