Skip to content

Commit

Permalink
Add XML rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
mdlayher committed May 11, 2014
1 parent b65287d commit 7ed3cb3
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 3 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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)

Expand Down Expand Up @@ -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"
}))
// ...
Expand Down
32 changes: 31 additions & 1 deletion render.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ package render
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"html/template"
"io"
Expand All @@ -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"
)

Expand All @@ -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.
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down
73 changes: 72 additions & 1 deletion render_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package render

import (
"encoding/xml"
"html/template"
"net/http"
"net/http/httptest"
Expand All @@ -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{
Expand Down Expand Up @@ -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) {
Expand All @@ -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(), `<greeting one="hello" two="world"></greeting>`)
}

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+`<greeting one="hello" two="world"></greeting>`)
}

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(), `<greeting one="hello" two="world"></greeting>`)
}

func Test_Render_Bad_HTML(t *testing.T) {
m := martini.Classic()
m.Use(Renderer(Options{
Expand Down

0 comments on commit 7ed3cb3

Please sign in to comment.