Error handling #10
This commit is contained in:
parent
28c54df853
commit
597272db9b
|
@ -16,6 +16,8 @@ internal let defaultWhitespaces = CharacterSet.whitespaces
|
||||||
/// No overview available.
|
/// No overview available.
|
||||||
public struct CSVConfiguration {
|
public struct CSVConfiguration {
|
||||||
|
|
||||||
|
public var errorHandler: ((Error, Int, Int) -> Void)? = nil
|
||||||
|
|
||||||
/// `true` if the CSV has a header row, otherwise `false`. Default: `false`.
|
/// `true` if the CSV has a header row, otherwise `false`. Default: `false`.
|
||||||
public let hasHeaderRow: Bool
|
public let hasHeaderRow: Bool
|
||||||
/// No overview available.
|
/// No overview available.
|
||||||
|
|
|
@ -164,21 +164,23 @@ internal class BinaryReader {
|
||||||
|
|
||||||
extension BinaryReader {
|
extension BinaryReader {
|
||||||
|
|
||||||
internal struct UInt8Iterator: Sequence, IteratorProtocol {
|
internal class UInt8Iterator: Sequence, IteratorProtocol {
|
||||||
|
|
||||||
private let reader: BinaryReader
|
private let reader: BinaryReader
|
||||||
|
internal var errorHandler: ((Error) -> Void)? = nil
|
||||||
|
|
||||||
fileprivate init(reader: BinaryReader) {
|
fileprivate init(reader: BinaryReader) {
|
||||||
self.reader = reader
|
self.reader = reader
|
||||||
}
|
}
|
||||||
|
|
||||||
internal mutating func next() -> UInt8? {
|
internal func next() -> UInt8? {
|
||||||
if !reader.hasBytesAvailable {
|
if !reader.hasBytesAvailable {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
return try reader.readUInt8()
|
return try reader.readUInt8()
|
||||||
} catch {
|
} catch {
|
||||||
|
errorHandler?(error)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,21 +195,23 @@ extension BinaryReader {
|
||||||
|
|
||||||
extension BinaryReader {
|
extension BinaryReader {
|
||||||
|
|
||||||
internal struct UInt16Iterator: Sequence, IteratorProtocol {
|
internal class UInt16Iterator: Sequence, IteratorProtocol {
|
||||||
|
|
||||||
private let reader: BinaryReader
|
private let reader: BinaryReader
|
||||||
|
internal var errorHandler: ((Error) -> Void)? = nil
|
||||||
|
|
||||||
fileprivate init(reader: BinaryReader) {
|
fileprivate init(reader: BinaryReader) {
|
||||||
self.reader = reader
|
self.reader = reader
|
||||||
}
|
}
|
||||||
|
|
||||||
internal mutating func next() -> UInt16? {
|
internal func next() -> UInt16? {
|
||||||
if !reader.hasBytesAvailable {
|
if !reader.hasBytesAvailable {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
return try reader.readUInt16()
|
return try reader.readUInt16()
|
||||||
} catch {
|
} catch {
|
||||||
|
errorHandler?(error)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -222,21 +226,23 @@ extension BinaryReader {
|
||||||
|
|
||||||
extension BinaryReader {
|
extension BinaryReader {
|
||||||
|
|
||||||
internal struct UInt32Iterator: Sequence, IteratorProtocol {
|
internal class UInt32Iterator: Sequence, IteratorProtocol {
|
||||||
|
|
||||||
private let reader: BinaryReader
|
private let reader: BinaryReader
|
||||||
|
internal var errorHandler: ((Error) -> Void)? = nil
|
||||||
|
|
||||||
fileprivate init(reader: BinaryReader) {
|
fileprivate init(reader: BinaryReader) {
|
||||||
self.reader = reader
|
self.reader = reader
|
||||||
}
|
}
|
||||||
|
|
||||||
internal mutating func next() -> UInt32? {
|
internal func next() -> UInt32? {
|
||||||
if !reader.hasBytesAvailable {
|
if !reader.hasBytesAvailable {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
return try reader.readUInt32()
|
return try reader.readUInt32()
|
||||||
} catch {
|
} catch {
|
||||||
|
errorHandler?(error)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ extension CSV {
|
||||||
/// - parameter stream: An `InputStream` object. If the stream is not open,
|
/// - parameter stream: An `InputStream` object. If the stream is not open,
|
||||||
/// initializer opens automatically.
|
/// initializer opens automatically.
|
||||||
/// - parameter config: CSV configuration.
|
/// - parameter config: CSV configuration.
|
||||||
public init(
|
public convenience init(
|
||||||
stream: InputStream,
|
stream: InputStream,
|
||||||
config: CSVConfiguration = CSVConfiguration()) throws {
|
config: CSVConfiguration = CSVConfiguration()) throws {
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ extension CSV {
|
||||||
///
|
///
|
||||||
/// - parameter string: An CSV string.
|
/// - parameter string: An CSV string.
|
||||||
/// - parameter config: CSV configuration.
|
/// - parameter config: CSV configuration.
|
||||||
public init(
|
public convenience init(
|
||||||
string: String,
|
string: String,
|
||||||
config: CSVConfiguration = CSVConfiguration()) throws {
|
config: CSVConfiguration = CSVConfiguration()) throws {
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
extension CSV: IteratorProtocol, Sequence {
|
extension CSV: IteratorProtocol, Sequence {
|
||||||
|
|
||||||
/// No overview available.
|
/// No overview available.
|
||||||
public mutating func next() -> Row? {
|
public func next() -> Row? {
|
||||||
guard let row = readRow() else {
|
guard let row = readRow() else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,16 @@ private let CR = UnicodeScalar(UInt8(0x0d)) // "\r"
|
||||||
private let DQUOTE = UnicodeScalar(UInt8(0x22)) // "\""
|
private let DQUOTE = UnicodeScalar(UInt8(0x22)) // "\""
|
||||||
|
|
||||||
/// No overview available.
|
/// No overview available.
|
||||||
public struct CSV {
|
public class CSV {
|
||||||
|
|
||||||
private var iterator: AnyIterator<UnicodeScalar>
|
private var iterator: AnyIterator<UnicodeScalar>
|
||||||
private let config: CSVConfiguration
|
private let config: CSVConfiguration
|
||||||
|
|
||||||
private var back: UnicodeScalar? = nil
|
private var back: UnicodeScalar? = nil
|
||||||
|
|
||||||
|
private var currentRowIndex: Int = 0
|
||||||
|
private var currentFieldIndex: Int = 0
|
||||||
|
|
||||||
/// CSV header row. To set a value for this property,
|
/// CSV header row. To set a value for this property,
|
||||||
/// you set `true` to `hasHeaerRow` in initializer.
|
/// you set `true` to `hasHeaerRow` in initializer.
|
||||||
public private(set) var headerRow: [String]? = nil
|
public private(set) var headerRow: [String]? = nil
|
||||||
|
@ -46,18 +49,18 @@ public struct CSV {
|
||||||
/// initializer opens automatically.
|
/// initializer opens automatically.
|
||||||
/// - parameter codecType: A `UnicodeCodec` type for `stream`.
|
/// - parameter codecType: A `UnicodeCodec` type for `stream`.
|
||||||
/// - parameter config: CSV configuration.
|
/// - parameter config: CSV configuration.
|
||||||
public init<T: UnicodeCodec>(
|
public convenience init<T: UnicodeCodec>(
|
||||||
stream: InputStream,
|
stream: InputStream,
|
||||||
codecType: T.Type,
|
codecType: T.Type,
|
||||||
config: CSVConfiguration = CSVConfiguration()
|
config: CSVConfiguration = CSVConfiguration()
|
||||||
) throws where T.CodeUnit == UInt8 {
|
) throws where T.CodeUnit == UInt8 {
|
||||||
|
|
||||||
let reader = try BinaryReader(stream: stream, endian: .unknown, closeOnDeinit: true)
|
let reader = try BinaryReader(stream: stream, endian: .unknown, closeOnDeinit: true)
|
||||||
let iterator = UnicodeIterator(
|
let input = reader.makeUInt8Iterator()
|
||||||
input: reader.makeUInt8Iterator(),
|
let iterator = UnicodeIterator(input: input, inputEncodingType: codecType)
|
||||||
inputEncodingType: codecType
|
|
||||||
)
|
|
||||||
try self.init(iterator: iterator, config: config)
|
try self.init(iterator: iterator, config: config)
|
||||||
|
input.errorHandler = self.errorHandler
|
||||||
|
iterator.errorHandler = self.errorHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an instance with `InputStream`.
|
/// Create an instance with `InputStream`.
|
||||||
|
@ -67,7 +70,7 @@ public struct CSV {
|
||||||
/// - parameter codecType: A `UnicodeCodec` type for `stream`.
|
/// - parameter codecType: A `UnicodeCodec` type for `stream`.
|
||||||
/// - parameter endian: Endian to use when reading a stream. Default: `.big`.
|
/// - parameter endian: Endian to use when reading a stream. Default: `.big`.
|
||||||
/// - parameter config: CSV configuration.
|
/// - parameter config: CSV configuration.
|
||||||
public init<T: UnicodeCodec>(
|
public convenience init<T: UnicodeCodec>(
|
||||||
stream: InputStream,
|
stream: InputStream,
|
||||||
codecType: T.Type,
|
codecType: T.Type,
|
||||||
endian: Endian = .big,
|
endian: Endian = .big,
|
||||||
|
@ -75,11 +78,11 @@ public struct CSV {
|
||||||
) throws where T.CodeUnit == UInt16 {
|
) throws where T.CodeUnit == UInt16 {
|
||||||
|
|
||||||
let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true)
|
let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true)
|
||||||
let iterator = UnicodeIterator(
|
let input = reader.makeUInt16Iterator()
|
||||||
input: reader.makeUInt16Iterator(),
|
let iterator = UnicodeIterator(input: input, inputEncodingType: codecType)
|
||||||
inputEncodingType: codecType
|
|
||||||
)
|
|
||||||
try self.init(iterator: iterator, config: config)
|
try self.init(iterator: iterator, config: config)
|
||||||
|
input.errorHandler = self.errorHandler
|
||||||
|
iterator.errorHandler = self.errorHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an instance with `InputStream`.
|
/// Create an instance with `InputStream`.
|
||||||
|
@ -89,7 +92,7 @@ public struct CSV {
|
||||||
/// - parameter codecType: A `UnicodeCodec` type for `stream`.
|
/// - parameter codecType: A `UnicodeCodec` type for `stream`.
|
||||||
/// - parameter endian: Endian to use when reading a stream. Default: `.big`.
|
/// - parameter endian: Endian to use when reading a stream. Default: `.big`.
|
||||||
/// - parameter config: CSV configuration.
|
/// - parameter config: CSV configuration.
|
||||||
public init<T: UnicodeCodec>(
|
public convenience init<T: UnicodeCodec>(
|
||||||
stream: InputStream,
|
stream: InputStream,
|
||||||
codecType: T.Type,
|
codecType: T.Type,
|
||||||
endian: Endian = .big,
|
endian: Endian = .big,
|
||||||
|
@ -97,16 +100,18 @@ public struct CSV {
|
||||||
) throws where T.CodeUnit == UInt32 {
|
) throws where T.CodeUnit == UInt32 {
|
||||||
|
|
||||||
let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true)
|
let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true)
|
||||||
let iterator = UnicodeIterator(
|
let input = reader.makeUInt32Iterator()
|
||||||
input: reader.makeUInt32Iterator(),
|
let iterator = UnicodeIterator(input: input, inputEncodingType: codecType)
|
||||||
inputEncodingType: codecType
|
|
||||||
)
|
|
||||||
try self.init(iterator: iterator, config: config)
|
try self.init(iterator: iterator, config: config)
|
||||||
|
input.errorHandler = self.errorHandler
|
||||||
|
iterator.errorHandler = self.errorHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Parse CSV
|
// MARK: - Parse CSV
|
||||||
|
|
||||||
internal mutating func readRow() -> [String]? {
|
internal func readRow() -> [String]? {
|
||||||
|
currentFieldIndex = 0
|
||||||
|
|
||||||
var c = moveNext()
|
var c = moveNext()
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -140,13 +145,18 @@ public struct CSV {
|
||||||
if end {
|
if end {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentFieldIndex += 1
|
||||||
|
|
||||||
c = moveNext()
|
c = moveNext()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentRowIndex += 1
|
||||||
|
|
||||||
return row
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
private mutating func readField(quoted: Bool) -> (String, Bool) {
|
private func readField(quoted: Bool) -> (String, Bool) {
|
||||||
var field = ""
|
var field = ""
|
||||||
|
|
||||||
while let c = moveNext() {
|
while let c = moveNext() {
|
||||||
|
@ -206,7 +216,7 @@ public struct CSV {
|
||||||
return (field, true)
|
return (field, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private mutating func moveNext() -> UnicodeScalar? {
|
private func moveNext() -> UnicodeScalar? {
|
||||||
if back != nil {
|
if back != nil {
|
||||||
defer {
|
defer {
|
||||||
back = nil
|
back = nil
|
||||||
|
@ -216,13 +226,15 @@ public struct CSV {
|
||||||
return iterator.next()
|
return iterator.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
private func errorHandler(error: Error) {
|
||||||
|
config.errorHandler?(error, currentRowIndex, currentFieldIndex)
|
||||||
|
}
|
||||||
|
|
||||||
extension CSV {
|
// MARK: - deprecated
|
||||||
|
|
||||||
/// Unavailable.
|
/// Unavailable.
|
||||||
@available(*, unavailable, message: "Use init(stream:codecType:config:) instead")
|
@available(*, unavailable, message: "Use init(stream:codecType:config:) instead")
|
||||||
public init<T: UnicodeCodec>(
|
public convenience init<T: UnicodeCodec>(
|
||||||
stream: InputStream,
|
stream: InputStream,
|
||||||
codecType: T.Type,
|
codecType: T.Type,
|
||||||
hasHeaderRow: Bool = defaultHasHeaderRow,
|
hasHeaderRow: Bool = defaultHasHeaderRow,
|
||||||
|
@ -245,7 +257,7 @@ extension CSV {
|
||||||
|
|
||||||
/// Unavailable.
|
/// Unavailable.
|
||||||
@available(*, unavailable, message: "Use init(stream:codecType:endian:config:) instead")
|
@available(*, unavailable, message: "Use init(stream:codecType:endian:config:) instead")
|
||||||
public init<T: UnicodeCodec>(
|
public convenience init<T: UnicodeCodec>(
|
||||||
stream: InputStream,
|
stream: InputStream,
|
||||||
codecType: T.Type,
|
codecType: T.Type,
|
||||||
endian: Endian = .big,
|
endian: Endian = .big,
|
||||||
|
@ -269,7 +281,7 @@ extension CSV {
|
||||||
|
|
||||||
/// Unavailable.
|
/// Unavailable.
|
||||||
@available(*, unavailable, message: "Use init(stream:codecType:endian:config:) instead")
|
@available(*, unavailable, message: "Use init(stream:codecType:endian:config:) instead")
|
||||||
public init<T: UnicodeCodec>(
|
public convenience init<T: UnicodeCodec>(
|
||||||
stream: InputStream,
|
stream: InputStream,
|
||||||
codecType: T.Type,
|
codecType: T.Type,
|
||||||
endian: Endian = .big,
|
endian: Endian = .big,
|
||||||
|
@ -293,7 +305,7 @@ extension CSV {
|
||||||
|
|
||||||
/// Unavailable.
|
/// Unavailable.
|
||||||
@available(*, unavailable, message: "Use init(stream:config:) instead")
|
@available(*, unavailable, message: "Use init(stream:config:) instead")
|
||||||
public init(
|
public convenience init(
|
||||||
stream: InputStream,
|
stream: InputStream,
|
||||||
hasHeaderRow: Bool = defaultHasHeaderRow,
|
hasHeaderRow: Bool = defaultHasHeaderRow,
|
||||||
trimFields: Bool = defaultTrimFields,
|
trimFields: Bool = defaultTrimFields,
|
||||||
|
@ -309,7 +321,7 @@ extension CSV {
|
||||||
|
|
||||||
/// Unavailable.
|
/// Unavailable.
|
||||||
@available(*, unavailable, message: "Use init(string:config:) instead")
|
@available(*, unavailable, message: "Use init(string:config:) instead")
|
||||||
public init(
|
public convenience init(
|
||||||
string: String,
|
string: String,
|
||||||
hasHeaderRow: Bool = defaultHasHeaderRow,
|
hasHeaderRow: Bool = defaultHasHeaderRow,
|
||||||
trimFields: Bool = defaultTrimFields,
|
trimFields: Bool = defaultTrimFields,
|
||||||
|
|
|
@ -16,6 +16,8 @@ public enum CSVError: Error {
|
||||||
/// No overview available.
|
/// No overview available.
|
||||||
case streamErrorHasOccurred(error: Error)
|
case streamErrorHasOccurred(error: Error)
|
||||||
/// No overview available.
|
/// No overview available.
|
||||||
|
case unicodeDecoding
|
||||||
|
/// No overview available.
|
||||||
case cannotReadHeaderRow
|
case cannotReadHeaderRow
|
||||||
/// No overview available.
|
/// No overview available.
|
||||||
case stringEncodingMismatch
|
case stringEncodingMismatch
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
// Copyright © 2016 yaslab. All rights reserved.
|
// Copyright © 2016 yaslab. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
internal struct UnicodeIterator<
|
internal class UnicodeIterator<
|
||||||
Input: IteratorProtocol,
|
Input: IteratorProtocol,
|
||||||
InputEncoding: UnicodeCodec>
|
InputEncoding: UnicodeCodec>
|
||||||
: IteratorProtocol
|
: IteratorProtocol
|
||||||
|
@ -14,17 +14,22 @@ internal struct UnicodeIterator<
|
||||||
|
|
||||||
private var input: Input
|
private var input: Input
|
||||||
private var inputEncoding: InputEncoding
|
private var inputEncoding: InputEncoding
|
||||||
|
internal var errorHandler: ((Error) -> Void)? = nil
|
||||||
|
|
||||||
internal init(input: Input, inputEncodingType: InputEncoding.Type) {
|
internal init(input: Input, inputEncodingType: InputEncoding.Type) {
|
||||||
self.input = input
|
self.input = input
|
||||||
self.inputEncoding = inputEncodingType.init()
|
self.inputEncoding = inputEncodingType.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal mutating func next() -> UnicodeScalar? {
|
internal func next() -> UnicodeScalar? {
|
||||||
switch inputEncoding.decode(&input) {
|
switch inputEncoding.decode(&input) {
|
||||||
case .scalarValue(let c): return c
|
case .scalarValue(let c):
|
||||||
case .emptyInput: return nil
|
return c
|
||||||
case .error: return nil
|
case .emptyInput:
|
||||||
|
return nil
|
||||||
|
case .error:
|
||||||
|
errorHandler?(CSVError.unicodeDecoding)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue