Source file src/encoding/json/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  
     5  //go:build !goexperiment.jsonv2
     6  
     7  package json
     8  
     9  import "bytes"
    10  
    11  // HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029
    12  // characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029
    13  // so that the JSON will be safe to embed inside HTML <script> tags.
    14  // For historical reasons, web browsers don't honor standard HTML
    15  // escaping within <script> tags, so an alternative JSON encoding must be used.
    16  func HTMLEscape(dst *bytes.Buffer, src []byte) {
    17  	dst.Grow(len(src))
    18  	dst.Write(appendHTMLEscape(dst.AvailableBuffer(), src))
    19  }
    20  
    21  func appendHTMLEscape(dst, src []byte) []byte {
    22  	// The characters can only appear in string literals,
    23  	// so just scan the string one byte at a time.
    24  	start := 0
    25  	for i, c := range src {
    26  		if c == '<' || c == '>' || c == '&' {
    27  			dst = append(dst, src[start:i]...)
    28  			dst = append(dst, '\\', 'u', '0', '0', hex[c>>4], hex[c&0xF])
    29  			start = i + 1
    30  		}
    31  		// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
    32  		if c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
    33  			dst = append(dst, src[start:i]...)
    34  			dst = append(dst, '\\', 'u', '2', '0', '2', hex[src[i+2]&0xF])
    35  			start = i + len("\u2029")
    36  		}
    37  	}
    38  	return append(dst, src[start:]...)
    39  }
    40  
    41  // Compact appends to dst the JSON-encoded src with
    42  // insignificant space characters elided.
    43  func Compact(dst *bytes.Buffer, src []byte) error {
    44  	dst.Grow(len(src))
    45  	b := dst.AvailableBuffer()
    46  	b, err := appendCompact(b, src, false)
    47  	dst.Write(b)
    48  	return err
    49  }
    50  
    51  func appendCompact(dst, src []byte, escape bool) ([]byte, error) {
    52  	origLen := len(dst)
    53  	scan := newScanner()
    54  	defer freeScanner(scan)
    55  	start := 0
    56  	for i, c := range src {
    57  		if escape && (c == '<' || c == '>' || c == '&') {
    58  			if start < i {
    59  				dst = append(dst, src[start:i]...)
    60  			}
    61  			dst = append(dst, '\\', 'u', '0', '0', hex[c>>4], hex[c&0xF])
    62  			start = i + 1
    63  		}
    64  		// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
    65  		if escape && c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
    66  			if start < i {
    67  				dst = append(dst, src[start:i]...)
    68  			}
    69  			dst = append(dst, '\\', 'u', '2', '0', '2', hex[src[i+2]&0xF])
    70  			start = i + 3
    71  		}
    72  		v := scan.step(scan, c)
    73  		if v >= scanSkipSpace {
    74  			if v == scanError {
    75  				break
    76  			}
    77  			if start < i {
    78  				dst = append(dst, src[start:i]...)
    79  			}
    80  			start = i + 1
    81  		}
    82  	}
    83  	if scan.eof() == scanError {
    84  		return dst[:origLen], scan.err
    85  	}
    86  	if start < len(src) {
    87  		dst = append(dst, src[start:]...)
    88  	}
    89  	return dst, nil
    90  }
    91  
    92  func appendNewline(dst []byte, prefix, indent string, depth int) []byte {
    93  	dst = append(dst, '\n')
    94  	dst = append(dst, prefix...)
    95  	for i := 0; i < depth; i++ {
    96  		dst = append(dst, indent...)
    97  	}
    98  	return dst
    99  }
   100  
   101  // indentGrowthFactor specifies the growth factor of indenting JSON input.
   102  // Empirically, the growth factor was measured to be between 1.4x to 1.8x
   103  // for some set of compacted JSON with the indent being a single tab.
   104  // Specify a growth factor slightly larger than what is observed
   105  // to reduce probability of allocation in appendIndent.
   106  // A factor no higher than 2 ensures that wasted space never exceeds 50%.
   107  const indentGrowthFactor = 2
   108  
   109  // Indent appends to dst an indented form of the JSON-encoded src.
   110  // Each element in a JSON object or array begins on a new,
   111  // indented line beginning with prefix followed by one or more
   112  // copies of indent according to the indentation nesting.
   113  // The data appended to dst does not begin with the prefix nor
   114  // any indentation, to make it easier to embed inside other formatted JSON data.
   115  // Although leading space characters (space, tab, carriage return, newline)
   116  // at the beginning of src are dropped, trailing space characters
   117  // at the end of src are preserved and copied to dst.
   118  // For example, if src has no trailing spaces, neither will dst;
   119  // if src ends in a trailing newline, so will dst.
   120  func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
   121  	dst.Grow(indentGrowthFactor * len(src))
   122  	b := dst.AvailableBuffer()
   123  	b, err := appendIndent(b, src, prefix, indent)
   124  	dst.Write(b)
   125  	return err
   126  }
   127  
   128  func appendIndent(dst, src []byte, prefix, indent string) ([]byte, error) {
   129  	origLen := len(dst)
   130  	scan := newScanner()
   131  	defer freeScanner(scan)
   132  	needIndent := false
   133  	depth := 0
   134  	for _, c := range src {
   135  		scan.bytes++
   136  		v := scan.step(scan, c)
   137  		if v == scanSkipSpace {
   138  			continue
   139  		}
   140  		if v == scanError {
   141  			break
   142  		}
   143  		if needIndent && v != scanEndObject && v != scanEndArray {
   144  			needIndent = false
   145  			depth++
   146  			dst = appendNewline(dst, prefix, indent, depth)
   147  		}
   148  
   149  		// Emit semantically uninteresting bytes
   150  		// (in particular, punctuation in strings) unmodified.
   151  		if v == scanContinue {
   152  			dst = append(dst, c)
   153  			continue
   154  		}
   155  
   156  		// Add spacing around real punctuation.
   157  		switch c {
   158  		case '{', '[':
   159  			// delay indent so that empty object and array are formatted as {} and [].
   160  			needIndent = true
   161  			dst = append(dst, c)
   162  		case ',':
   163  			dst = append(dst, c)
   164  			dst = appendNewline(dst, prefix, indent, depth)
   165  		case ':':
   166  			dst = append(dst, c, ' ')
   167  		case '}', ']':
   168  			if needIndent {
   169  				// suppress indent in empty object/array
   170  				needIndent = false
   171  			} else {
   172  				depth--
   173  				dst = appendNewline(dst, prefix, indent, depth)
   174  			}
   175  			dst = append(dst, c)
   176  		default:
   177  			dst = append(dst, c)
   178  		}
   179  	}
   180  	if scan.eof() == scanError {
   181  		return dst[:origLen], scan.err
   182  	}
   183  	return dst, nil
   184  }
   185  

View as plain text