github.com/anthropics/anthropic-sdk-go@v1.70.1/internal/encoding/json/indent.go (about)

     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  package json
     6  
     7  import (
     8  	"bytes"
     9  )
    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, encOpts{})
    47  	dst.Write(b)
    48  	return err
    49  }
    50  
    51  func appendCompact(dst, src []byte, opts encOpts) ([]byte, error) {
    52  	// EDIT(begin): optimize for skipCompaction
    53  	if opts.skipCompaction {
    54  		dst = append(dst, src...)
    55  		return dst, nil
    56  	}
    57  
    58  	escape := opts.escapeHTML
    59  	// EDIT(end)
    60  
    61  	origLen := len(dst)
    62  	scan := newScanner()
    63  	defer freeScanner(scan)
    64  	start := 0
    65  	for i, c := range src {
    66  		if escape && (c == '<' || c == '>' || c == '&') {
    67  			if start < i {
    68  				dst = append(dst, src[start:i]...)
    69  			}
    70  			dst = append(dst, '\\', 'u', '0', '0', hex[c>>4], hex[c&0xF])
    71  			start = i + 1
    72  		}
    73  		// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
    74  		if escape && c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
    75  			if start < i {
    76  				dst = append(dst, src[start:i]...)
    77  			}
    78  			dst = append(dst, '\\', 'u', '2', '0', '2', hex[src[i+2]&0xF])
    79  			start = i + 3
    80  		}
    81  		v := scan.step(scan, c)
    82  		if v >= scanSkipSpace {
    83  			if v == scanError {
    84  				break
    85  			}
    86  			if start < i {
    87  				dst = append(dst, src[start:i]...)
    88  			}
    89  			start = i + 1
    90  		}
    91  	}
    92  	if scan.eof() == scanError {
    93  		return dst[:origLen], scan.err
    94  	}
    95  	if start < len(src) {
    96  		dst = append(dst, src[start:]...)
    97  	}
    98  	return dst, nil
    99  }
   100  
   101  func appendNewline(dst []byte, prefix, indent string, depth int) []byte {
   102  	dst = append(dst, '\n')
   103  	dst = append(dst, prefix...)
   104  	for i := 0; i < depth; i++ {
   105  		dst = append(dst, indent...)
   106  	}
   107  	return dst
   108  }
   109  
   110  // indentGrowthFactor specifies the growth factor of indenting JSON input.
   111  // Empirically, the growth factor was measured to be between 1.4x to 1.8x
   112  // for some set of compacted JSON with the indent being a single tab.
   113  // Specify a growth factor slightly larger than what is observed
   114  // to reduce probability of allocation in appendIndent.
   115  // A factor no higher than 2 ensures that wasted space never exceeds 50%.
   116  const indentGrowthFactor = 2
   117  
   118  // Indent appends to dst an indented form of the JSON-encoded src.
   119  // Each element in a JSON object or array begins on a new,
   120  // indented line beginning with prefix followed by one or more
   121  // copies of indent according to the indentation nesting.
   122  // The data appended to dst does not begin with the prefix nor
   123  // any indentation, to make it easier to embed inside other formatted JSON data.
   124  // Although leading space characters (space, tab, carriage return, newline)
   125  // at the beginning of src are dropped, trailing space characters
   126  // at the end of src are preserved and copied to dst.
   127  // For example, if src has no trailing spaces, neither will dst;
   128  // if src ends in a trailing newline, so will dst.
   129  func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
   130  	dst.Grow(indentGrowthFactor * len(src))
   131  	b := dst.AvailableBuffer()
   132  	b, err := appendIndent(b, src, prefix, indent)
   133  	dst.Write(b)
   134  	return err
   135  }
   136  
   137  func appendIndent(dst, src []byte, prefix, indent string) ([]byte, error) {
   138  	origLen := len(dst)
   139  	scan := newScanner()
   140  	defer freeScanner(scan)
   141  	needIndent := false
   142  	depth := 0
   143  	for _, c := range src {
   144  		scan.bytes++
   145  		v := scan.step(scan, c)
   146  		if v == scanSkipSpace {
   147  			continue
   148  		}
   149  		if v == scanError {
   150  			break
   151  		}
   152  		if needIndent && v != scanEndObject && v != scanEndArray {
   153  			needIndent = false
   154  			depth++
   155  			dst = appendNewline(dst, prefix, indent, depth)
   156  		}
   157  
   158  		// Emit semantically uninteresting bytes
   159  		// (in particular, punctuation in strings) unmodified.
   160  		if v == scanContinue {
   161  			dst = append(dst, c)
   162  			continue
   163  		}
   164  
   165  		// Add spacing around real punctuation.
   166  		switch c {
   167  		case '{', '[':
   168  			// delay indent so that empty object and array are formatted as {} and [].
   169  			needIndent = true
   170  			dst = append(dst, c)
   171  		case ',':
   172  			dst = append(dst, c)
   173  			dst = appendNewline(dst, prefix, indent, depth)
   174  		case ':':
   175  			dst = append(dst, c, ' ')
   176  		case '}', ']':
   177  			if needIndent {
   178  				// suppress indent in empty object/array
   179  				needIndent = false
   180  			} else {
   181  				depth--
   182  				dst = appendNewline(dst, prefix, indent, depth)
   183  			}
   184  			dst = append(dst, c)
   185  		default:
   186  			dst = append(dst, c)
   187  		}
   188  	}
   189  	if scan.eof() == scanError {
   190  		return dst[:origLen], scan.err
   191  	}
   192  	return dst, nil
   193  }