Error handling #10

This commit is contained in:
Yasuhiro Hatta 2016-10-24 01:22:23 +09:00
parent 28c54df853
commit 597272db9b
7 changed files with 67 additions and 40 deletions

View File

@ -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.

View File

@ -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
} }
} }

View File

@ -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 {

View File

@ -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
} }

View File

@ -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,

View File

@ -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

View File

@ -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
} }
} }