~john-koepi/ubuntu/trusty/golang/default

« back to all changes in this revision

Viewing changes to src/pkg/csv/reader.go

  • Committer: Bazaar Package Importer
  • Author(s): Ondřej Surý
  • Date: 2011-08-03 17:04:59 UTC
  • mfrom: (14.1.2 sid)
  • Revision ID: james.westby@ubuntu.com-20110803170459-wzd99m3567y80ila
Tags: 1:59-1
* Imported Upstream version 59
* Refresh patches to a new release
* Fix FTBFS on ARM (Closes: #634270)
* Update version.bash to work with Debian packaging and not hg
  repository

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2011 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 csv reads and writes comma-separated values (CSV) files.
 
6
//
 
7
// A csv file contains zero or more records of one or more fields per record.
 
8
// Each record is separated by the newline character. The final record may
 
9
// optionally be followed by a newline character.
 
10
//
 
11
//      field1,field2,field3
 
12
//
 
13
// White space is considered part of a field.
 
14
//
 
15
// Carriage returns before newline characters are silently removed.
 
16
//
 
17
// Blank lines are ignored.  A line with only whitespace characters (excluding
 
18
// the ending newline character) is not considered a blank line.
 
19
//
 
20
// Fields which start and stop with the quote character " are called
 
21
// quoted-fields.  The beginning and ending quote are not part of the
 
22
// field.
 
23
//
 
24
// The source:
 
25
//
 
26
//      normal string,"quoted-field"
 
27
//
 
28
// results in the fields
 
29
//
 
30
//      {`normal string`, `quoted-field`}
 
31
//
 
32
// Within a quoted-field a quote character followed by a second quote
 
33
// character is considered a single quote.
 
34
//
 
35
//      "the ""word"" is true","a ""quoted-field"""
 
36
//
 
37
// results in
 
38
//
 
39
//      {`the "word" is true`, `a "quoted-field"`}
 
40
//
 
41
// Newlines and commas may be included in a quoted-field
 
42
//
 
43
//      "Multi-line
 
44
//      field","comma is ,"
 
45
//
 
46
// results in
 
47
//
 
48
//      {`Multi-line
 
49
//      field`, `comma is ,`}
 
50
package csv
 
51
 
 
52
import (
 
53
        "bufio"
 
54
        "bytes"
 
55
        "fmt"
 
56
        "io"
 
57
        "os"
 
58
        "unicode"
 
59
)
 
60
 
 
61
// A ParseError is returned for parsing errors.
 
62
// The first line is 1.  The first column is 0.
 
63
type ParseError struct {
 
64
        Line   int      // Line where the error occurred
 
65
        Column int      // Column (rune index) where the error occurred
 
66
        Error  os.Error // The actual error
 
67
}
 
68
 
 
69
func (e *ParseError) String() string {
 
70
        return fmt.Sprintf("line %d, column %d: %s", e.Line, e.Column, e.Error)
 
71
}
 
72
 
 
73
// These are the errors that can be returned in ParseError.Error
 
74
var (
 
75
        ErrTrailingComma = os.NewError("extra delimiter at end of line")
 
76
        ErrBareQuote     = os.NewError("bare \" in non-quoted-field")
 
77
        ErrQuote         = os.NewError("extraneous \" in field")
 
78
        ErrFieldCount    = os.NewError("wrong number of fields in line")
 
79
)
 
80
 
 
81
// A Reader reads records from a CSV-encoded file.
 
82
//
 
83
// As returned by NewReader, a Reader expects input conforming to RFC 4180.
 
84
// The exported fields can be changed to customize the details before the
 
85
// first call to Read or ReadAll.
 
86
//
 
87
// Comma is the field delimiter.  It defaults to ','.
 
88
//
 
89
// Comment, if not 0, is the comment character. Lines beginning with the
 
90
// Comment character is ignored.
 
91
//
 
92
// If FieldsPerRecord is positive, Read requires each record to
 
93
// have the given number of fields.  If FieldsPerRecord is 0, Read sets it to
 
94
// the number of fields in the first record, so that future records must
 
95
// have the same field count.
 
96
//
 
97
// If LazyQuotes is true, a quote may appear in an unquoted field and a
 
98
// non-doubled quote may appear in a quoted field.
 
99
//
 
100
// If TrailingComma is true, the last field may be a unquoted empty field.
 
101
//
 
102
// If TrimLeadingSpace is true, leading white space in a field is ignored.
 
103
type Reader struct {
 
104
        Comma            int  // Field delimiter (set to ',' by NewReader)
 
105
        Comment          int  // Comment character for start of line
 
106
        FieldsPerRecord  int  // Number of expected fields per record
 
107
        LazyQuotes       bool // Allow lazy quotes
 
108
        TrailingComma    bool // Allow trailing comma
 
109
        TrimLeadingSpace bool // Trim leading space
 
110
        line             int
 
111
        column           int
 
112
        r                *bufio.Reader
 
113
        field            bytes.Buffer
 
114
}
 
115
 
 
116
// NewReader returns a new Reader that reads from r.
 
117
func NewReader(r io.Reader) *Reader {
 
118
        return &Reader{
 
119
                Comma: ',',
 
120
                r:     bufio.NewReader(r),
 
121
        }
 
122
}
 
123
 
 
124
// error creates a new ParseError based on err.
 
125
func (r *Reader) error(err os.Error) os.Error {
 
126
        return &ParseError{
 
127
                Line:   r.line,
 
128
                Column: r.column,
 
129
                Error:  err,
 
130
        }
 
131
}
 
132
 
 
133
// Read reads one record from r.  The record is a slice of strings with each
 
134
// string representing one field.
 
135
func (r *Reader) Read() (record []string, err os.Error) {
 
136
        for {
 
137
                record, err = r.parseRecord()
 
138
                if record != nil {
 
139
                        break
 
140
                }
 
141
                if err != nil {
 
142
                        return nil, err
 
143
                }
 
144
        }
 
145
 
 
146
        if r.FieldsPerRecord > 0 {
 
147
                if len(record) != r.FieldsPerRecord {
 
148
                        r.column = 0 // report at start of record
 
149
                        return record, r.error(ErrFieldCount)
 
150
                }
 
151
        } else if r.FieldsPerRecord == 0 {
 
152
                r.FieldsPerRecord = len(record)
 
153
        }
 
154
        return record, nil
 
155
}
 
156
 
 
157
// ReadAll reads all the remaining records from r.
 
158
// Each record is a slice of fields.
 
159
func (r *Reader) ReadAll() (records [][]string, err os.Error) {
 
160
        for {
 
161
                record, err := r.Read()
 
162
                if err == os.EOF {
 
163
                        return records, nil
 
164
                }
 
165
                if err != nil {
 
166
                        return nil, err
 
167
                }
 
168
                records = append(records, record)
 
169
        }
 
170
        panic("unreachable")
 
171
}
 
172
 
 
173
// readRune reads one rune from r, folding \r\n to \n and keeping track
 
174
// of our far into the line we have read.  r.column will point to the start
 
175
// of this rune, not the end of this rune.
 
176
func (r *Reader) readRune() (int, os.Error) {
 
177
        rune, _, err := r.r.ReadRune()
 
178
 
 
179
        // Handle \r\n here.  We make the simplifying assumption that
 
180
        // anytime \r is followed by \n that it can be folded to \n.
 
181
        // We will not detect files which contain both \r\n and bare \n.
 
182
        if rune == '\r' {
 
183
                rune, _, err = r.r.ReadRune()
 
184
                if err == nil {
 
185
                        if rune != '\n' {
 
186
                                r.r.UnreadRune()
 
187
                                rune = '\r'
 
188
                        }
 
189
                }
 
190
        }
 
191
        r.column++
 
192
        return rune, err
 
193
}
 
194
 
 
195
// unreadRune puts the last rune read from r back.
 
196
func (r *Reader) unreadRune() {
 
197
        r.r.UnreadRune()
 
198
        r.column--
 
199
}
 
200
 
 
201
// skip reads runes up to and including the rune delim or until error.
 
202
func (r *Reader) skip(delim int) os.Error {
 
203
        for {
 
204
                rune, err := r.readRune()
 
205
                if err != nil {
 
206
                        return err
 
207
                }
 
208
                if rune == delim {
 
209
                        return nil
 
210
                }
 
211
        }
 
212
        panic("unreachable")
 
213
}
 
214
 
 
215
// parseRecord reads and parses a single csv record from r.
 
216
func (r *Reader) parseRecord() (fields []string, err os.Error) {
 
217
        // Each record starts on a new line.  We increment our line
 
218
        // number (lines start at 1, not 0) and set column to -1
 
219
        // so as we increment in readRune it points to the character we read.
 
220
        r.line++
 
221
        r.column = -1
 
222
 
 
223
        // Peek at the first rune.  If it is an error we are done.
 
224
        // If we are support comments and it is the comment character
 
225
        // the skip to the end of line.
 
226
 
 
227
        rune, _, err := r.r.ReadRune()
 
228
        if err != nil {
 
229
                return nil, err
 
230
        }
 
231
 
 
232
        if r.Comment != 0 && rune == r.Comment {
 
233
                return nil, r.skip('\n')
 
234
        }
 
235
        r.r.UnreadRune()
 
236
 
 
237
        // At this point we have at least one field.
 
238
        for {
 
239
                haveField, delim, err := r.parseField()
 
240
                if haveField {
 
241
                        fields = append(fields, r.field.String())
 
242
                }
 
243
                if delim == '\n' || err == os.EOF {
 
244
                        return fields, err
 
245
                } else if err != nil {
 
246
                        return nil, err
 
247
                }
 
248
        }
 
249
        panic("unreachable")
 
250
}
 
251
 
 
252
 
 
253
// parseField parses the next field in the record.  The read field is
 
254
// located in r.field.  Delim is the first character not part of the field
 
255
// (r.Comma or '\n').
 
256
func (r *Reader) parseField() (haveField bool, delim int, err os.Error) {
 
257
        r.field.Reset()
 
258
 
 
259
        rune, err := r.readRune()
 
260
        if err != nil {
 
261
                // If we have EOF and are not at the start of a line
 
262
                // then we return the empty field.  We have already
 
263
                // checked for trailing commas if needed.
 
264
                if err == os.EOF && r.column != 0 {
 
265
                        return true, 0, err
 
266
                }
 
267
                return false, 0, err
 
268
        }
 
269
 
 
270
        if r.TrimLeadingSpace {
 
271
                for unicode.IsSpace(rune) {
 
272
                        rune, err = r.readRune()
 
273
                        if err != nil {
 
274
                                return false, 0, err
 
275
                        }
 
276
                }
 
277
        }
 
278
 
 
279
        switch rune {
 
280
        case r.Comma:
 
281
                // will check below
 
282
 
 
283
        case '\n':
 
284
                // We are a trailing empty field or a blank linke
 
285
                if r.column == 0 {
 
286
                        return false, rune, nil
 
287
                }
 
288
                return true, rune, nil
 
289
 
 
290
        case '"':
 
291
                // quoted field
 
292
        Quoted:
 
293
                for {
 
294
                        rune, err = r.readRune()
 
295
                        if err != nil {
 
296
                                if err == os.EOF {
 
297
                                        if r.LazyQuotes {
 
298
                                                return true, 0, err
 
299
                                        }
 
300
                                        return false, 0, r.error(ErrQuote)
 
301
                                }
 
302
                                return false, 0, err
 
303
                        }
 
304
                        switch rune {
 
305
                        case '"':
 
306
                                rune, err = r.readRune()
 
307
                                if err != nil || rune == r.Comma {
 
308
                                        break Quoted
 
309
                                }
 
310
                                if rune == '\n' {
 
311
                                        return true, rune, nil
 
312
                                }
 
313
                                if rune != '"' {
 
314
                                        if !r.LazyQuotes {
 
315
                                                r.column--
 
316
                                                return false, 0, r.error(ErrQuote)
 
317
                                        }
 
318
                                        // accept the bare quote
 
319
                                        r.field.WriteRune('"')
 
320
                                }
 
321
                        case '\n':
 
322
                                r.line++
 
323
                                r.column = -1
 
324
                        }
 
325
                        r.field.WriteRune(rune)
 
326
                }
 
327
 
 
328
        default:
 
329
                // unquoted field
 
330
                for {
 
331
                        r.field.WriteRune(rune)
 
332
                        rune, err = r.readRune()
 
333
                        if err != nil || rune == r.Comma {
 
334
                                break
 
335
                        }
 
336
                        if rune == '\n' {
 
337
                                return true, rune, nil
 
338
                        }
 
339
                        if !r.LazyQuotes && rune == '"' {
 
340
                                return false, 0, r.error(ErrBareQuote)
 
341
                        }
 
342
                }
 
343
        }
 
344
 
 
345
        if err != nil {
 
346
                if err == os.EOF {
 
347
                        return true, 0, err
 
348
                }
 
349
                return false, 0, err
 
350
        }
 
351
 
 
352
        if !r.TrailingComma {
 
353
                // We don't allow trailing commas.  See if we
 
354
                // are at the end of the line (being mindful
 
355
                // of triming spaces
 
356
                c := r.column
 
357
                rune, err = r.readRune()
 
358
                if r.TrimLeadingSpace {
 
359
                        for unicode.IsSpace(rune) {
 
360
                                rune, err = r.readRune()
 
361
                                if err != nil {
 
362
                                        break
 
363
                                }
 
364
                        }
 
365
                }
 
366
                if err == os.EOF || rune == '\n' {
 
367
                        r.column = c // report the comma
 
368
                        return false, 0, r.error(ErrTrailingComma)
 
369
                }
 
370
                r.unreadRune()
 
371
        }
 
372
        return true, rune, nil
 
373
}