blob: 1eae1f3a9916d46501366f212759c72bc230cf5a [file] [log] [blame]
Joe Tsai27c2a762018-08-01 16:48:18 -07001// Copyright 2018 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 text
6
7import (
8 "bytes"
9 "strings"
10
Joe Tsai01ab2962018-09-21 17:44:00 -070011 "github.com/golang/protobuf/v2/internal/errors"
Joe Tsai27c2a762018-08-01 16:48:18 -070012)
13
14// Marshal serializes v as the proto text format, where v must be a Message.
15// In the proto text format, the top-level value is always a message where the
16// delimiters are elided.
17//
18// If indent is a non-empty string, it causes every entry in a List or Message
19// to be preceded by the indent and trailed by a newline.
20//
21// If delims is not the zero value, it controls the delimiter characters used
22// for messages (e.g., "{}" vs "<>").
23//
24// If outputASCII is true, strings will be serialized in such a way that
25// multi-byte UTF-8 sequences are escaped. This property ensures that the
26// overall output is ASCII (as opposed to UTF-8).
27func Marshal(v Value, indent string, delims [2]byte, outputASCII bool) ([]byte, error) {
28 p := encoder{}
29 if len(indent) > 0 {
30 if strings.Trim(indent, " \t") != "" {
31 return nil, errors.New("indent may only be composed of space and tab characters")
32 }
33 p.indent = indent
34 p.newline = "\n"
35 }
36 switch delims {
37 case [2]byte{0, 0}:
38 p.delims = [2]byte{'{', '}'}
39 case [2]byte{'{', '}'}, [2]byte{'<', '>'}:
40 p.delims = delims
41 default:
42 return nil, errors.New("delimiters may only be \"{}\" or \"<>\"")
43 }
44 p.outputASCII = outputASCII
45
46 err := p.marshalMessage(v, false)
47 if !p.nerr.Merge(err) {
48 return nil, err
49 }
50 if len(indent) > 0 {
51 return append(bytes.TrimRight(p.out, "\n"), '\n'), p.nerr.E
52 }
53 return p.out, p.nerr.E
54}
55
56type encoder struct {
57 nerr errors.NonFatal
58 out []byte
59
60 indent string
61 indents []byte
62 newline string // set to "\n" if len(indent) > 0
63 delims [2]byte
64 outputASCII bool
65}
66
67func (p *encoder) marshalList(v Value) error {
68 if v.Type() != List {
69 return errors.New("invalid type %v, expected list", v.Type())
70 }
71 elems := v.List()
72 p.out = append(p.out, '[')
73 p.indents = append(p.indents, p.indent...)
74 if len(elems) > 0 {
75 p.out = append(p.out, p.newline...)
76 }
77 for i, elem := range elems {
78 p.out = append(p.out, p.indents...)
79 if err := p.marshalValue(elem); !p.nerr.Merge(err) {
80 return err
81 }
82 if i < len(elems)-1 {
83 p.out = append(p.out, ',')
84 }
85 p.out = append(p.out, p.newline...)
86 }
87 p.indents = p.indents[:len(p.indents)-len(p.indent)]
88 if len(elems) > 0 {
89 p.out = append(p.out, p.indents...)
90 }
91 p.out = append(p.out, ']')
92 return nil
93}
94
95func (p *encoder) marshalMessage(v Value, emitDelims bool) error {
96 if v.Type() != Message {
97 return errors.New("invalid type %v, expected message", v.Type())
98 }
99 items := v.Message()
100 if emitDelims {
101 p.out = append(p.out, p.delims[0])
102 p.indents = append(p.indents, p.indent...)
103 if len(items) > 0 {
104 p.out = append(p.out, p.newline...)
105 }
106 }
107 for i, item := range items {
108 p.out = append(p.out, p.indents...)
109 if err := p.marshalKey(item[0]); !p.nerr.Merge(err) {
110 return err
111 }
112 p.out = append(p.out, ':')
113 if len(p.indent) > 0 {
114 p.out = append(p.out, ' ')
115 }
116 if err := p.marshalValue(item[1]); !p.nerr.Merge(err) {
117 return err
118 }
119 if i < len(items)-1 && len(p.indent) == 0 {
120 p.out = append(p.out, ' ')
121 }
122 p.out = append(p.out, p.newline...)
123 }
124 if emitDelims {
125 p.indents = p.indents[:len(p.indents)-len(p.indent)]
126 if len(items) > 0 {
127 p.out = append(p.out, p.indents...)
128 }
129 p.out = append(p.out, p.delims[1])
130 }
131 return nil
132}
133
134func (p *encoder) marshalKey(v Value) error {
135 switch v.Type() {
136 case String:
137 var err error
138 p.out = append(p.out, '[')
139 if len(urlRegexp.FindString(v.str)) == len(v.str) {
140 p.out = append(p.out, v.str...)
141 } else {
142 err = p.marshalString(v)
143 }
144 p.out = append(p.out, ']')
145 return err
146 case Uint:
147 return p.marshalNumber(v)
148 case Name:
149 s, _ := v.Name()
150 p.out = append(p.out, s...)
151 return nil
152 default:
153 return errors.New("invalid type %v to encode key", v.Type())
154 }
155}
156
157func (p *encoder) marshalValue(v Value) error {
158 switch v.Type() {
159 case Bool, Int, Uint, Float:
160 return p.marshalNumber(v)
161 case String:
162 return p.marshalString(v)
163 case List:
164 return p.marshalList(v)
165 case Message:
166 return p.marshalMessage(v, true)
167 case Name:
168 s, _ := v.Name()
169 p.out = append(p.out, s...)
170 return nil
171 default:
172 return errors.New("invalid type %v to encode value", v.Type())
173 }
174}