diff --git a/README.md b/README.md
index e63185e..80cb66d 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# render [](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{