Skip to content

Commit

Permalink
tpl/encoding: Add noHTMLEscape option to jsonify
Browse files Browse the repository at this point in the history
  • Loading branch information
bep committed Oct 24, 2022
1 parent 2ef60db commit 09e1011
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 11 deletions.
11 changes: 11 additions & 0 deletions docs/content/en/functions/jsonify.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ more copies of *indent* according to the indentation nesting.
{{ dict "title" .Title "content" .Plain | jsonify (dict "prefix" " " "indent" " ") }}
```

## Jsonify options

indent ("")
: Indendation to use.

prefix ("")
: Indentation prefix.

noHTMLEscape (false)
: Disable escaping of problematic HTML characters inside JSON quoted strings. The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e to avoid certain safety problems that can arise when embedding JSON in HTML.

See also the `.PlainWords`, `.Plain`, and `.RawContent` [page variables][pagevars].

[pagevars]: /variables/page/
42 changes: 34 additions & 8 deletions tpl/encoding/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import (
"errors"
"html/template"

bp "github.com/gohugoio/hugo/bufferpool"

"github.com/gohugoio/hugo/common/maps"
"github.com/mitchellh/mapstructure"
"github.com/spf13/cast"
)

Expand Down Expand Up @@ -60,24 +63,27 @@ func (ns *Namespace) Base64Encode(content any) (string, error) {
// to the indentation nesting.
func (ns *Namespace) Jsonify(args ...any) (template.HTML, error) {
var (
b []byte
err error
b []byte
err error
obj any
opts jsonifyOpts
)

switch len(args) {
case 0:
return "", nil
case 1:
b, err = json.Marshal(args[0])
obj = args[0]
case 2:
var opts map[string]string

opts, err = maps.ToStringMapStringE(args[0])
var m map[string]any
m, err = maps.ToStringMapE(args[0])
if err != nil {
break
}

b, err = json.MarshalIndent(args[1], opts["prefix"], opts["indent"])
if err = mapstructure.WeakDecode(m, &opts); err != nil {
break
}
obj = args[1]
default:
err = errors.New("too many arguments to jsonify")
}
Expand All @@ -86,5 +92,25 @@ func (ns *Namespace) Jsonify(args ...any) (template.HTML, error) {
return "", err
}

buff := bp.GetBuffer()
defer bp.PutBuffer(buff)
e := json.NewEncoder(buff)
e.SetEscapeHTML(!opts.NoHTMLEscape)
e.SetIndent(opts.Prefix, opts.Indent)
if err = e.Encode(obj); err != nil {
return "", err
}
b = buff.Bytes()
// See https://github.com/golang/go/issues/37083
// Hugo changed from MarshalIndent/Marshal. To make the output
// the same, we need to trim the trailing newline.
b = b[:len(b)-1]

return template.HTML(b), nil
}

type jsonifyOpts struct {
Prefix string
Indent string
NoHTMLEscape bool
}
9 changes: 6 additions & 3 deletions tpl/encoding/encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func TestJsonify(t *testing.T) {
c := qt.New(t)
ns := New()

for _, test := range []struct {
for i, test := range []struct {
opts any
v any
expect any
Expand All @@ -91,6 +91,9 @@ func TestJsonify(t *testing.T) {
{map[string]string{"indent": "<i>"}, []string{"a", "b"}, template.HTML("[\n<i>\"a\",\n<i>\"b\"\n]")},
{map[string]string{"prefix": "<p>"}, []string{"a", "b"}, template.HTML("[\n<p>\"a\",\n<p>\"b\"\n<p>]")},
{map[string]string{"prefix": "<p>", "indent": "<i>"}, []string{"a", "b"}, template.HTML("[\n<p><i>\"a\",\n<p><i>\"b\"\n<p>]")},
{map[string]string{"indent": "<i>"}, []string{"a", "b"}, template.HTML("[\n<i>\"a\",\n<i>\"b\"\n]")},
{map[string]any{"noHTMLEscape": false}, []string{"<a>", "<b>"}, template.HTML("[\"\\u003ca\\u003e\",\"\\u003cb\\u003e\"]")},
{map[string]any{"noHTMLEscape": true}, []string{"<a>", "<b>"}, template.HTML("[\"<a>\",\"<b>\"]")},
{nil, tstNoStringer{}, template.HTML("{}")},
{nil, nil, template.HTML("null")},
// errors
Expand All @@ -108,11 +111,11 @@ func TestJsonify(t *testing.T) {
result, err := ns.Jsonify(args...)

if b, ok := test.expect.(bool); ok && !b {
c.Assert(err, qt.Not(qt.IsNil))
c.Assert(err, qt.Not(qt.IsNil), qt.Commentf("#%d", i))
continue
}

c.Assert(err, qt.IsNil)
c.Assert(result, qt.Equals, test.expect)
c.Assert(result, qt.Equals, test.expect, qt.Commentf("#%d", i))
}
}

0 comments on commit 09e1011

Please sign in to comment.