indent.go

1// Copyright 2010 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package json
6
7import (
8	"bytes"
9)
10
11// Compact appends to dst the JSON-encoded src with
12// insignificant space characters elided.
13func Compact(dst *bytes.Buffer, src []byte) error {
14	return compact(dst, src, false)
15}
16
17func compact(dst *bytes.Buffer, src []byte, escape bool) error {
18	origLen := dst.Len()
19	scan := newScanner()
20	defer freeScanner(scan)
21	start := 0
22	for i, c := range src {
23		if escape && (c == '<' || c == '>' || c == '&') {
24			if start < i {
25				dst.Write(src[start:i])
26			}
27			dst.WriteString(`\u00`)
28			dst.WriteByte(hex[c>>4])
29			dst.WriteByte(hex[c&0xF])
30			start = i + 1
31		}
32		// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
33		if escape && c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
34			if start < i {
35				dst.Write(src[start:i])
36			}
37			dst.WriteString(`\u202`)
38			dst.WriteByte(hex[src[i+2]&0xF])
39			start = i + 3
40		}
41		v := scan.step(scan, c)
42		if v >= scanSkipSpace {
43			if v == scanError {
44				break
45			}
46			if start < i {
47				dst.Write(src[start:i])
48			}
49			start = i + 1
50		}
51	}
52	if scan.eof() == scanError {
53		dst.Truncate(origLen)
54		return scan.err
55	}
56	if start < len(src) {
57		dst.Write(src[start:])
58	}
59	return nil
60}
61
62func newline(dst *bytes.Buffer, prefix, indent string, depth int) {
63	dst.WriteByte('\n')
64	dst.WriteString(prefix)
65	for i := 0; i < depth; i++ {
66		dst.WriteString(indent)
67	}
68}
69
70// Indent appends to dst an indented form of the JSON-encoded src.
71// Each element in a JSON object or array begins on a new,
72// indented line beginning with prefix followed by one or more
73// copies of indent according to the indentation nesting.
74// The data appended to dst does not begin with the prefix nor
75// any indentation, to make it easier to embed inside other formatted JSON data.
76// Although leading space characters (space, tab, carriage return, newline)
77// at the beginning of src are dropped, trailing space characters
78// at the end of src are preserved and copied to dst.
79// For example, if src has no trailing spaces, neither will dst;
80// if src ends in a trailing newline, so will dst.
81func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
82	origLen := dst.Len()
83	scan := newScanner()
84	defer freeScanner(scan)
85	needIndent := false
86	depth := 0
87	for _, c := range src {
88		scan.bytes++
89		v := scan.step(scan, c)
90		if v == scanSkipSpace {
91			continue
92		}
93		if v == scanError {
94			break
95		}
96		if needIndent && v != scanEndObject && v != scanEndArray {
97			needIndent = false
98			depth++
99			newline(dst, prefix, indent, depth)
100		}
101
102		// Emit semantically uninteresting bytes
103		// (in particular, punctuation in strings) unmodified.
104		if v == scanContinue {
105			dst.WriteByte(c)
106			continue
107		}
108
109		// Add spacing around real punctuation.
110		switch c {
111		case '{', '[':
112			// delay indent so that empty object and array are formatted as {} and [].
113			needIndent = true
114			dst.WriteByte(c)
115
116		case ',':
117			dst.WriteByte(c)
118			newline(dst, prefix, indent, depth)
119
120		case ':':
121			dst.WriteByte(c)
122			dst.WriteByte(' ')
123
124		case '}', ']':
125			if needIndent {
126				// suppress indent in empty object/array
127				needIndent = false
128			} else {
129				depth--
130				newline(dst, prefix, indent, depth)
131			}
132			dst.WriteByte(c)
133
134		default:
135			dst.WriteByte(c)
136		}
137	}
138	if scan.eof() == scanError {
139		dst.Truncate(origLen)
140		return scan.err
141	}
142	return nil
143}