diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:25:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:25:22 +0000 |
commit | f6ad4dcef54c5ce997a4bad5a6d86de229015700 (patch) | |
tree | 7cfa4e31ace5c2bd95c72b154d15af494b2bcbef /src/encoding/json/indent.go | |
parent | Initial commit. (diff) | |
download | golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.tar.xz golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.zip |
Adding upstream version 1.22.1.upstream/1.22.1
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.go | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/src/encoding/json/indent.go b/src/encoding/json/indent.go new file mode 100644 index 0000000..01bfdf6 --- /dev/null +++ b/src/encoding/json/indent.go @@ -0,0 +1,182 @@ +// 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 == '&') { + if start < i { + 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 { + if start < i { + dst = append(dst, src[start:i]...) + } + dst = append(dst, '\\', 'u', '2', '0', '2', hex[src[i+2]&0xF]) + start = i + 3 + } + v := scan.step(scan, c) + if v >= scanSkipSpace { + if v == scanError { + break + } + if start < i { + dst = append(dst, src[start:i]...) + } + start = i + 1 + } + } + if scan.eof() == scanError { + return dst[:origLen], scan.err + } + if start < len(src) { + 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 +} |