summaryrefslogtreecommitdiffstats
path: root/src/encoding/json/indent.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
commitccd992355df7192993c666236047820244914598 (patch)
treef00fea65147227b7743083c6148396f74cd66935 /src/encoding/json/indent.go
parentInitial commit. (diff)
downloadgolang-1.21-ccd992355df7192993c666236047820244914598.tar.xz
golang-1.21-ccd992355df7192993c666236047820244914598.zip
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/encoding/json/indent.go')
-rw-r--r--src/encoding/json/indent.go174
1 files changed, 174 insertions, 0 deletions
diff --git a/src/encoding/json/indent.go b/src/encoding/json/indent.go
new file mode 100644
index 0000000..26bb5d2
--- /dev/null
+++ b/src/encoding/json/indent.go
@@ -0,0 +1,174 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package json
+
+import "bytes"
+
+// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029
+// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029
+// so that the JSON will be safe to embed inside HTML <script> tags.
+// For historical reasons, web browsers don't honor standard HTML
+// escaping within <script> tags, so an alternative JSON encoding must be used.
+func HTMLEscape(dst *bytes.Buffer, src []byte) {
+ dst.Grow(len(src))
+ dst.Write(appendHTMLEscape(dst.AvailableBuffer(), src))
+}
+
+func appendHTMLEscape(dst, src []byte) []byte {
+ // The characters can only appear in string literals,
+ // so just scan the string one byte at a time.
+ start := 0
+ for i, c := range src {
+ if c == '<' || c == '>' || c == '&' {
+ dst = append(dst, src[start:i]...)
+ dst = append(dst, '\\', 'u', '0', '0', hex[c>>4], hex[c&0xF])
+ start = i + 1
+ }
+ // Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
+ if c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
+ dst = append(dst, src[start:i]...)
+ dst = append(dst, '\\', 'u', '2', '0', '2', hex[src[i+2]&0xF])
+ start = i + len("\u2029")
+ }
+ }
+ return append(dst, src[start:]...)
+}
+
+// Compact appends to dst the JSON-encoded src with
+// insignificant space characters elided.
+func Compact(dst *bytes.Buffer, src []byte) error {
+ dst.Grow(len(src))
+ b := dst.AvailableBuffer()
+ b, err := appendCompact(b, src, false)
+ dst.Write(b)
+ return err
+}
+
+func appendCompact(dst, src []byte, escape bool) ([]byte, error) {
+ origLen := len(dst)
+ scan := newScanner()
+ defer freeScanner(scan)
+ start := 0
+ for i, c := range src {
+ if escape && (c == '<' || c == '>' || c == '&') {
+ dst = append(dst, src[start:i]...)
+ dst = append(dst, '\\', 'u', '0', '0', hex[c>>4], hex[c&0xF])
+ start = i + 1
+ }
+ // Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
+ if escape && c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
+ dst = append(dst, src[start:i]...)
+ dst = append(dst, '\\', 'u', '2', '0', '2', hex[src[i+2]&0xF])
+ start = i + len("\u2029")
+ }
+ v := scan.step(scan, c)
+ if v >= scanSkipSpace {
+ if v == scanError {
+ break
+ }
+ dst = append(dst, src[start:i]...)
+ start = i + 1
+ }
+ }
+ if scan.eof() == scanError {
+ return dst[:origLen], scan.err
+ }
+ dst = append(dst, src[start:]...)
+ return dst, nil
+}
+
+func appendNewline(dst []byte, prefix, indent string, depth int) []byte {
+ dst = append(dst, '\n')
+ dst = append(dst, prefix...)
+ for i := 0; i < depth; i++ {
+ dst = append(dst, indent...)
+ }
+ return dst
+}
+
+// indentGrowthFactor specifies the growth factor of indenting JSON input.
+// Empirically, the growth factor was measured to be between 1.4x to 1.8x
+// for some set of compacted JSON with the indent being a single tab.
+// Specify a growth factor slightly larger than what is observed
+// to reduce probability of allocation in appendIndent.
+// A factor no higher than 2 ensures that wasted space never exceeds 50%.
+const indentGrowthFactor = 2
+
+// Indent appends to dst an indented form of the JSON-encoded src.
+// Each element in a JSON object or array begins on a new,
+// indented line beginning with prefix followed by one or more
+// copies of indent according to the indentation nesting.
+// The data appended to dst does not begin with the prefix nor
+// any indentation, to make it easier to embed inside other formatted JSON data.
+// Although leading space characters (space, tab, carriage return, newline)
+// at the beginning of src are dropped, trailing space characters
+// at the end of src are preserved and copied to dst.
+// For example, if src has no trailing spaces, neither will dst;
+// if src ends in a trailing newline, so will dst.
+func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
+ dst.Grow(indentGrowthFactor * len(src))
+ b := dst.AvailableBuffer()
+ b, err := appendIndent(b, src, prefix, indent)
+ dst.Write(b)
+ return err
+}
+
+func appendIndent(dst, src []byte, prefix, indent string) ([]byte, error) {
+ origLen := len(dst)
+ scan := newScanner()
+ defer freeScanner(scan)
+ needIndent := false
+ depth := 0
+ for _, c := range src {
+ scan.bytes++
+ v := scan.step(scan, c)
+ if v == scanSkipSpace {
+ continue
+ }
+ if v == scanError {
+ break
+ }
+ if needIndent && v != scanEndObject && v != scanEndArray {
+ needIndent = false
+ depth++
+ dst = appendNewline(dst, prefix, indent, depth)
+ }
+
+ // Emit semantically uninteresting bytes
+ // (in particular, punctuation in strings) unmodified.
+ if v == scanContinue {
+ dst = append(dst, c)
+ continue
+ }
+
+ // Add spacing around real punctuation.
+ switch c {
+ case '{', '[':
+ // delay indent so that empty object and array are formatted as {} and [].
+ needIndent = true
+ dst = append(dst, c)
+ case ',':
+ dst = append(dst, c)
+ dst = appendNewline(dst, prefix, indent, depth)
+ case ':':
+ dst = append(dst, c, ' ')
+ case '}', ']':
+ if needIndent {
+ // suppress indent in empty object/array
+ needIndent = false
+ } else {
+ depth--
+ dst = appendNewline(dst, prefix, indent, depth)
+ }
+ dst = append(dst, c)
+ default:
+ dst = append(dst, c)
+ }
+ }
+ if scan.eof() == scanError {
+ return dst[:origLen], scan.err
+ }
+ return dst, nil
+}