1
// Copyright 2015 Unknwon
3
// Licensed under the Apache License, Version 2.0 (the "License"): you may
4
// not use this file except in compliance with the License. You may obtain
5
// a copy of the License at
7
// http://www.apache.org/licenses/LICENSE-2.0
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
// License for the specific language governing permissions and limitations
30
_TOKEN_INVALID tokenType = iota
43
func newParser(r io.Reader) *parser {
45
buf: bufio.NewReader(r),
47
comment: &bytes.Buffer{},
51
// BOM handles header of BOM-UTF8 format.
52
// http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
53
func (p *parser) BOM() error {
54
mask, err := p.buf.Peek(3)
55
if err != nil && err != io.EOF {
57
} else if len(mask) < 3 {
59
} else if mask[0] == 239 && mask[1] == 187 && mask[2] == 191 {
65
func (p *parser) readUntil(delim byte) ([]byte, error) {
66
data, err := p.buf.ReadBytes(delim)
77
func cleanComment(in []byte) ([]byte, bool) {
78
i := bytes.IndexAny(in, "#;")
85
func readKeyName(in []byte) (string, int, error) {
88
// Check if key name surrounded by quotes.
91
if len(line) > 6 && string(line[0:3]) == `"""` {
96
} else if line[0] == '`' {
102
if len(keyQuote) > 0 {
103
startIdx := len(keyQuote)
104
// FIXME: fail case -> """"""name"""=value
105
pos := strings.Index(line[startIdx:], keyQuote)
107
return "", -1, fmt.Errorf("missing closing key quote: %s", line)
111
// Find key-value delimiter
112
i := strings.IndexAny(line[pos+startIdx:], "=:")
114
return "", -1, fmt.Errorf("key-value delimiter not found: %s", line)
117
return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
120
endIdx = strings.IndexAny(line, "=:")
122
return "", -1, fmt.Errorf("key-value delimiter not found: %s", line)
124
return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
127
func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
129
data, err := p.readUntil('\n')
135
pos := strings.LastIndex(next, valQuote)
139
comment, has := cleanComment([]byte(next[pos:]))
141
p.comment.Write(bytes.TrimSpace(comment))
147
return "", fmt.Errorf("missing closing key quote from '%s' to '%s'", line, next)
153
func (p *parser) readContinuationLines(val string) (string, error) {
155
data, err := p.readUntil('\n')
159
next := strings.TrimSpace(string(data))
165
if val[len(val)-1] != '\\' {
168
val = val[:len(val)-1]
173
// hasSurroundedQuote check if and only if the first and last characters
174
// are quotes \" or \'.
175
// It returns false if any other parts also contain same kind of quotes.
176
func hasSurroundedQuote(in string, quote byte) bool {
177
return len(in) > 2 && in[0] == quote && in[len(in)-1] == quote &&
178
strings.IndexByte(in[1:], quote) == len(in)-2
181
func (p *parser) readValue(in []byte) (string, error) {
182
line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
188
if len(line) > 3 && string(line[0:3]) == `"""` {
190
} else if line[0] == '`' {
194
if len(valQuote) > 0 {
195
startIdx := len(valQuote)
196
pos := strings.LastIndex(line[startIdx:], valQuote)
197
// Check for multi-line value
199
return p.readMultilines(line, line[startIdx:], valQuote)
202
return line[startIdx : pos+startIdx], nil
205
// Won't be able to reach here if value only contains whitespace.
206
line = strings.TrimSpace(line)
208
// Check continuation lines
209
if line[len(line)-1] == '\\' {
210
return p.readContinuationLines(line[:len(line)-1])
213
i := strings.IndexAny(line, "#;")
215
p.comment.WriteString(line[i:])
216
line = strings.TrimSpace(line[:i])
219
// Trim single quotes
220
if hasSurroundedQuote(line, '\'') ||
221
hasSurroundedQuote(line, '"') {
222
line = line[1 : len(line)-1]
227
// parse parses data through an io.Reader.
228
func (f *File) parse(reader io.Reader) (err error) {
229
p := newParser(reader)
230
if err = p.BOM(); err != nil {
231
return fmt.Errorf("BOM: %v", err)
234
// Ignore error because default section name is never empty string.
235
section, _ := f.NewSection(DEFAULT_SECTION)
239
line, err = p.readUntil('\n')
244
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
250
if line[0] == '#' || line[0] == ';' {
251
// Note: we do not care ending line break,
252
// it is needed for adding second line,
253
// so just clean it once at the end when set to value.
254
p.comment.Write(line)
260
// Read to the next ']' (TODO: support quoted strings)
261
closeIdx := bytes.IndexByte(line, ']')
263
return fmt.Errorf("unclosed section: %s", line)
266
section, err = f.NewSection(string(line[1:closeIdx]))
271
comment, has := cleanComment(line[closeIdx+1:])
273
p.comment.Write(comment)
276
section.Comment = strings.TrimSpace(p.comment.String())
278
// Reset aotu-counter and comments
284
kname, offset, err := readKeyName(line)
293
kname = "#" + strconv.Itoa(p.count)
297
key, err := section.NewKey(kname, "")
301
key.isAutoIncr = isAutoIncr
303
value, err := p.readValue(line[offset:])
308
key.Comment = strings.TrimSpace(p.comment.String())