-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreader.go
More file actions
109 lines (96 loc) · 2.94 KB
/
Copy pathreader.go
File metadata and controls
109 lines (96 loc) · 2.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package marc
import (
"bytes"
"io"
"strconv"
)
// ReaderOption configures a Reader; shares option constructors with NewRecord
// (leader/fields/data options are ignored by the reader).
type ReaderOption = RecordOption
// Reader iterates over MARC21 records in transmission format read from an
// io.Reader. Ported from pymarc.reader.MARCReader.
//
// It is permissive: a bad record yields (nil, err) from Next but does not stop
// iteration, unless the error is fatal (the record's length/boundary could not
// be determined), in which case every subsequent Next call returns io.EOF.
type Reader struct {
src io.Reader
toUnicode bool
forceUTF8 bool
hideUTF8Warnings bool
utf8Handling string
fileEncoding string
currentChunk []byte
fatal bool
}
// NewReader builds a Reader over r (or over raw bytes via NewReaderFromBytes).
func NewReader(r io.Reader, opts ...ReaderOption) *Reader {
cfg := &recordConfig{toUnicode: true, utf8Handling: "strict", fileEncoding: "iso8859-1"}
for _, opt := range opts {
opt(cfg)
}
return &Reader{
src: r,
toUnicode: cfg.toUnicode,
forceUTF8: cfg.forceUTF8,
hideUTF8Warnings: cfg.hideUTF8Warnings,
utf8Handling: cfg.utf8Handling,
fileEncoding: cfg.fileEncoding,
}
}
// NewReaderFromBytes builds a Reader over an in-memory MARC blob.
func NewReaderFromBytes(data []byte, opts ...ReaderOption) *Reader {
return NewReader(bytes.NewReader(data), opts...)
}
// CurrentChunk returns the raw bytes of the most recently attempted record.
func (rd *Reader) CurrentChunk() []byte { return rd.currentChunk }
// Next reads and decodes the next record. It returns (nil, io.EOF) once the
// underlying reader is exhausted, or once a fatal boundary error has occurred.
// A non-fatal decode error is returned as (nil, err); the reader remains usable
// for subsequent Next calls.
func (rd *Reader) Next() (*Record, error) {
if rd.fatal {
return nil, io.EOF
}
rd.currentChunk = nil
first5 := make([]byte, 5)
n, err := io.ReadFull(rd.src, first5)
if n == 0 && err == io.EOF {
return nil, io.EOF
}
if err != nil {
rd.currentChunk = first5[:n]
rd.fatal = true
return nil, ErrTruncatedRecord
}
rd.currentChunk = first5
length, convErr := strconv.Atoi(string(first5))
if convErr != nil {
rd.fatal = true
return nil, ErrRecordLengthInvalid
}
rest := make([]byte, length-5)
n2, err2 := io.ReadFull(rd.src, rest)
chunk := append(first5, rest[:n2]...)
rd.currentChunk = chunk
if err2 != nil || len(chunk) < length {
rd.fatal = true
return nil, ErrTruncatedRecord
}
if chunk[len(chunk)-1] != EndOfRecord {
rd.fatal = true
return nil, ErrEndOfRecordNotFound
}
rec, err := NewRecord(
WithData(chunk),
WithToUnicode(rd.toUnicode),
WithForceUTF8(rd.forceUTF8),
WithHideUTF8Warnings(rd.hideUTF8Warnings),
WithUTF8Handling(rd.utf8Handling),
WithFileEncoding(rd.fileEncoding),
)
if err != nil {
return nil, err
}
return rec, nil
}