stdlib_quote.go raw

   1  // Copied and trimmed down from https://github.com/golang/go/blob/e3769299cd3484e018e0e2a6e1b95c2b18ce4f41/src/strconv/quote.go
   2  // We want to use the standard library's private "quoteWith" function rather than write our own so that we get robust unicode support.
   3  // Every private function called by quoteWith was copied.
   4  // There are 2 modifications to simplify the code:
   5  // 1. The unicode.IsPrint function was substituted for the custom implementation of IsPrint
   6  // 2. All code paths reachable only when ASCIIonly or grphicOnly are set to true were removed.
   7  
   8  // Copyright 2009 The Go Authors. All rights reserved.
   9  // Use of this source code is governed by a BSD-style
  10  // license that can be found in the LICENSE file.
  11  
  12  package yaml
  13  
  14  import (
  15  	"unicode"
  16  	"unicode/utf8"
  17  )
  18  
  19  const (
  20  	lowerhex = "0123456789abcdef"
  21  )
  22  
  23  func quoteWith(s string, quote byte) string {
  24  	return string(appendQuotedWith(make([]byte, 0, 3*len(s)/2), s, quote))
  25  }
  26  
  27  func appendQuotedWith(buf []byte, s string, quote byte) []byte {
  28  	// Often called with big strings, so preallocate. If there's quoting,
  29  	// this is conservative but still helps a lot.
  30  	if cap(buf)-len(buf) < len(s) {
  31  		nBuf := make([]byte, len(buf), len(buf)+1+len(s)+1)
  32  		copy(nBuf, buf)
  33  		buf = nBuf
  34  	}
  35  	buf = append(buf, quote)
  36  	for width := 0; len(s) > 0; s = s[width:] {
  37  		r := rune(s[0])
  38  		width = 1
  39  		if r >= utf8.RuneSelf {
  40  			r, width = utf8.DecodeRuneInString(s)
  41  		}
  42  		if width == 1 && r == utf8.RuneError {
  43  			buf = append(buf, `\x`...)
  44  			buf = append(buf, lowerhex[s[0]>>4])
  45  			buf = append(buf, lowerhex[s[0]&0xF])
  46  			continue
  47  		}
  48  		buf = appendEscapedRune(buf, r, quote)
  49  	}
  50  	buf = append(buf, quote)
  51  	return buf
  52  }
  53  
  54  func appendEscapedRune(buf []byte, r rune, quote byte) []byte {
  55  	var runeTmp [utf8.UTFMax]byte
  56  	if r == rune(quote) || r == '\\' { // always backslashed
  57  		buf = append(buf, '\\')
  58  		buf = append(buf, byte(r))
  59  		return buf
  60  	}
  61  	if unicode.IsPrint(r) {
  62  		n := utf8.EncodeRune(runeTmp[:], r)
  63  		buf = append(buf, runeTmp[:n]...)
  64  		return buf
  65  	}
  66  	switch r {
  67  	case '\a':
  68  		buf = append(buf, `\a`...)
  69  	case '\b':
  70  		buf = append(buf, `\b`...)
  71  	case '\f':
  72  		buf = append(buf, `\f`...)
  73  	case '\n':
  74  		buf = append(buf, `\n`...)
  75  	case '\r':
  76  		buf = append(buf, `\r`...)
  77  	case '\t':
  78  		buf = append(buf, `\t`...)
  79  	case '\v':
  80  		buf = append(buf, `\v`...)
  81  	default:
  82  		switch {
  83  		case r < ' ':
  84  			buf = append(buf, `\x`...)
  85  			buf = append(buf, lowerhex[byte(r)>>4])
  86  			buf = append(buf, lowerhex[byte(r)&0xF])
  87  		case r > utf8.MaxRune:
  88  			r = 0xFFFD
  89  			fallthrough
  90  		case r < 0x10000:
  91  			buf = append(buf, `\u`...)
  92  			for s := 12; s >= 0; s -= 4 {
  93  				buf = append(buf, lowerhex[r>>uint(s)&0xF])
  94  			}
  95  		default:
  96  			buf = append(buf, `\U`...)
  97  			for s := 28; s >= 0; s -= 4 {
  98  				buf = append(buf, lowerhex[r>>uint(s)&0xF])
  99  			}
 100  		}
 101  	}
 102  	return buf
 103  }
 104