| // Copyright 2018 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 text | |
| import ( | |
| "math" | |
| "math/bits" | |
| "strconv" | |
| "strings" | |
| "unicode/utf8" | |
| "google.golang.org/protobuf/internal/detrand" | |
| "google.golang.org/protobuf/internal/errors" | |
| ) | |
| // encType represents an encoding type. | |
| type encType uint8 | |
| const ( | |
| _ encType = (1 << iota) / 2 | |
| name | |
| scalar | |
| messageOpen | |
| messageClose | |
| ) | |
| // Encoder provides methods to write out textproto constructs and values. The user is | |
| // responsible for producing valid sequences of constructs and values. | |
| type Encoder struct { | |
| encoderState | |
| indent string | |
| delims [2]byte | |
| outputASCII bool | |
| } | |
| type encoderState struct { | |
| lastType encType | |
| indents []byte | |
| out []byte | |
| } | |
| // NewEncoder returns an Encoder. | |
| // | |
| // If indent is a non-empty string, it causes every entry in a List or Message | |
| // to be preceded by the indent and trailed by a newline. | |
| // | |
| // If delims is not the zero value, it controls the delimiter characters used | |
| // for messages (e.g., "{}" vs "<>"). | |
| // | |
| // If outputASCII is true, strings will be serialized in such a way that | |
| // multi-byte UTF-8 sequences are escaped. This property ensures that the | |
| // overall output is ASCII (as opposed to UTF-8). | |
| func NewEncoder(buf []byte, indent string, delims [2]byte, outputASCII bool) (*Encoder, error) { | |
| e := &Encoder{ | |
| encoderState: encoderState{out: buf}, | |
| } | |
| if len(indent) > 0 { | |
| if strings.Trim(indent, " \t") != "" { | |
| return nil, errors.New("indent may only be composed of space and tab characters") | |
| } | |
| e.indent = indent | |
| } | |
| switch delims { | |
| case [2]byte{0, 0}: | |
| e.delims = [2]byte{'{', '}'} | |
| case [2]byte{'{', '}'}, [2]byte{'<', '>'}: | |
| e.delims = delims | |
| default: | |
| return nil, errors.New("delimiters may only be \"{}\" or \"<>\"") | |
| } | |
| e.outputASCII = outputASCII | |
| return e, nil | |
| } | |
| // Bytes returns the content of the written bytes. | |
| func (e *Encoder) Bytes() []byte { | |
| return e.out | |
| } | |
| // StartMessage writes out the '{' or '<' symbol. | |
| func (e *Encoder) StartMessage() { | |
| e.prepareNext(messageOpen) | |
| e.out = append(e.out, e.delims[0]) | |
| } | |
| // EndMessage writes out the '}' or '>' symbol. | |
| func (e *Encoder) EndMessage() { | |
| e.prepareNext(messageClose) | |
| e.out = append(e.out, e.delims[1]) | |
| } | |
| // WriteName writes out the field name and the separator ':'. | |
| func (e *Encoder) WriteName(s string) { | |
| e.prepareNext(name) | |
| e.out = append(e.out, s...) | |
| e.out = append(e.out, ':') | |
| } | |
| // WriteBool writes out the given boolean value. | |
| func (e *Encoder) WriteBool(b bool) { | |
| if b { | |
| e.WriteLiteral("true") | |
| } else { | |
| e.WriteLiteral("false") | |
| } | |
| } | |
| // WriteString writes out the given string value. | |
| func (e *Encoder) WriteString(s string) { | |
| e.prepareNext(scalar) | |
| e.out = appendString(e.out, s, e.outputASCII) | |
| } | |
| func appendString(out []byte, in string, outputASCII bool) []byte { | |
| out = append(out, '"') | |
| i := indexNeedEscapeInString(in) | |
| in, out = in[i:], append(out, in[:i]...) | |
| for len(in) > 0 { | |
| switch r, n := utf8.DecodeRuneInString(in); { | |
| case r == utf8.RuneError && n == 1: | |
| // We do not report invalid UTF-8 because strings in the text format | |
| // are used to represent both the proto string and bytes type. | |
| r = rune(in[0]) | |
| fallthrough | |
| case r < ' ' || r == '"' || r == '\\' || r == 0x7f: | |
| out = append(out, '\\') | |
| switch r { | |
| case '"', '\\': | |
| out = append(out, byte(r)) | |
| case '\n': | |
| out = append(out, 'n') | |
| case '\r': | |
| out = append(out, 'r') | |
| case '\t': | |
| out = append(out, 't') | |
| default: | |
| out = append(out, 'x') | |
| out = append(out, "00"[1+(bits.Len32(uint32(r))-1)/4:]...) | |
| out = strconv.AppendUint(out, uint64(r), 16) | |
| } | |
| in = in[n:] | |
| case r >= utf8.RuneSelf && (outputASCII || r <= 0x009f): | |
| out = append(out, '\\') | |
| if r <= math.MaxUint16 { | |
| out = append(out, 'u') | |
| out = append(out, "0000"[1+(bits.Len32(uint32(r))-1)/4:]...) | |
| out = strconv.AppendUint(out, uint64(r), 16) | |
| } else { | |
| out = append(out, 'U') | |
| out = append(out, "00000000"[1+(bits.Len32(uint32(r))-1)/4:]...) | |
| out = strconv.AppendUint(out, uint64(r), 16) | |
| } | |
| in = in[n:] | |
| default: | |
| i := indexNeedEscapeInString(in[n:]) | |
| in, out = in[n+i:], append(out, in[:n+i]...) | |
| } | |
| } | |
| out = append(out, '"') | |
| return out | |
| } | |
| // indexNeedEscapeInString returns the index of the character that needs | |
| // escaping. If no characters need escaping, this returns the input length. | |
| func indexNeedEscapeInString(s string) int { | |
| for i := 0; i < len(s); i++ { | |
| if c := s[i]; c < ' ' || c == '"' || c == '\'' || c == '\\' || c >= 0x7f { | |
| return i | |
| } | |
| } | |
| return len(s) | |
| } | |
| // WriteFloat writes out the given float value for given bitSize. | |
| func (e *Encoder) WriteFloat(n float64, bitSize int) { | |
| e.prepareNext(scalar) | |
| e.out = appendFloat(e.out, n, bitSize) | |
| } | |
| func appendFloat(out []byte, n float64, bitSize int) []byte { | |
| switch { | |
| case math.IsNaN(n): | |
| return append(out, "nan"...) | |
| case math.IsInf(n, +1): | |
| return append(out, "inf"...) | |
| case math.IsInf(n, -1): | |
| return append(out, "-inf"...) | |
| default: | |
| return strconv.AppendFloat(out, n, 'g', -1, bitSize) | |
| } | |
| } | |
| // WriteInt writes out the given signed integer value. | |
| func (e *Encoder) WriteInt(n int64) { | |
| e.prepareNext(scalar) | |
| e.out = strconv.AppendInt(e.out, n, 10) | |
| } | |
| // WriteUint writes out the given unsigned integer value. | |
| func (e *Encoder) WriteUint(n uint64) { | |
| e.prepareNext(scalar) | |
| e.out = strconv.AppendUint(e.out, n, 10) | |
| } | |
| // WriteLiteral writes out the given string as a literal value without quotes. | |
| // This is used for writing enum literal strings. | |
| func (e *Encoder) WriteLiteral(s string) { | |
| e.prepareNext(scalar) | |
| e.out = append(e.out, s...) | |
| } | |
| // prepareNext adds possible space and indentation for the next value based | |
| // on last encType and indent option. It also updates e.lastType to next. | |
| func (e *Encoder) prepareNext(next encType) { | |
| defer func() { | |
| e.lastType = next | |
| }() | |
| // Single line. | |
| if len(e.indent) == 0 { | |
| // Add space after each field before the next one. | |
| if e.lastType&(scalar|messageClose) != 0 && next == name { | |
| e.out = append(e.out, ' ') | |
| // Add a random extra space to make output unstable. | |
| if detrand.Bool() { | |
| e.out = append(e.out, ' ') | |
| } | |
| } | |
| return | |
| } | |
| // Multi-line. | |
| switch { | |
| case e.lastType == name: | |
| e.out = append(e.out, ' ') | |
| // Add a random extra space after name: to make output unstable. | |
| if detrand.Bool() { | |
| e.out = append(e.out, ' ') | |
| } | |
| case e.lastType == messageOpen && next != messageClose: | |
| e.indents = append(e.indents, e.indent...) | |
| e.out = append(e.out, '\n') | |
| e.out = append(e.out, e.indents...) | |
| case e.lastType&(scalar|messageClose) != 0: | |
| if next == messageClose { | |
| e.indents = e.indents[:len(e.indents)-len(e.indent)] | |
| } | |
| e.out = append(e.out, '\n') | |
| e.out = append(e.out, e.indents...) | |
| } | |
| } | |
| // Snapshot returns the current snapshot for use in Reset. | |
| func (e *Encoder) Snapshot() encoderState { | |
| return e.encoderState | |
| } | |
| // Reset resets the Encoder to the given encoderState from a Snapshot. | |
| func (e *Encoder) Reset(es encoderState) { | |
| e.encoderState = es | |
| } | |
| // AppendString appends the escaped form of the input string to b. | |
| func AppendString(b []byte, s string) []byte { | |
| return appendString(b, s, false) | |
| } |