diff --git a/.swift-version b/.swift-version index 5186d07..bf77d54 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -4.0 +4.2 diff --git a/.travis.yml b/.travis.yml index 1e7d064..45b1fdf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: swift -osx_image: xcode9.3 +osx_image: xcode10.1 script: - set -o pipefail && xcodebuild test -scheme CSV-macOS after_success: diff --git a/CSV.xcodeproj/project.pbxproj b/CSV.xcodeproj/project.pbxproj index 520af73..21623ee 100755 --- a/CSV.xcodeproj/project.pbxproj +++ b/CSV.xcodeproj/project.pbxproj @@ -20,6 +20,9 @@ 0E7F657B1EF6437E00E1E1A0 /* Version1Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F657A1EF6437E00E1E1A0 /* Version1Tests.swift */; }; 0E7F657C1EF6437E00E1E1A0 /* Version1Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F657A1EF6437E00E1E1A0 /* Version1Tests.swift */; }; 0E7F657D1EF6437E00E1E1A0 /* Version1Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F657A1EF6437E00E1E1A0 /* Version1Tests.swift */; }; + 0E87607B219D992900C6C7FA /* BinaryReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E87607A219D992900C6C7FA /* BinaryReaderTests.swift */; }; + 0E87607C219D992900C6C7FA /* BinaryReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E87607A219D992900C6C7FA /* BinaryReaderTests.swift */; }; + 0E87607D219D992900C6C7FA /* BinaryReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E87607A219D992900C6C7FA /* BinaryReaderTests.swift */; }; 0E959C62208B611F005F8D01 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E959C5B208B611F005F8D01 /* CSVError.swift */; }; 0E959C63208B611F005F8D01 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E959C5B208B611F005F8D01 /* CSVError.swift */; }; 0E959C64208B611F005F8D01 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E959C5B208B611F005F8D01 /* CSVError.swift */; }; @@ -48,17 +51,17 @@ 0E959C7B208B611F005F8D01 /* CSVReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E959C61208B611F005F8D01 /* CSVReader.swift */; }; 0E959C7C208B611F005F8D01 /* CSVReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E959C61208B611F005F8D01 /* CSVReader.swift */; }; 0E959C7D208B611F005F8D01 /* CSVReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E959C61208B611F005F8D01 /* CSVReader.swift */; }; - 0EDF8ED71DDB73520068056A /* CSVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECD1DDB73370068056A /* CSVTests.swift */; }; + 0EDF8ED71DDB73520068056A /* CSVReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECD1DDB73370068056A /* CSVReaderTests.swift */; }; 0EDF8ED81DDB73520068056A /* LineBreakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECE1DDB73370068056A /* LineBreakTests.swift */; }; 0EDF8ED91DDB73520068056A /* ReadmeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */; }; 0EDF8EDA1DDB73520068056A /* TrimFieldsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ED01DDB73370068056A /* TrimFieldsTests.swift */; }; 0EDF8EDB1DDB73520068056A /* UnicodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ED11DDB73370068056A /* UnicodeTests.swift */; }; - 0EDF8EDC1DDB73520068056A /* CSVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECD1DDB73370068056A /* CSVTests.swift */; }; + 0EDF8EDC1DDB73520068056A /* CSVReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECD1DDB73370068056A /* CSVReaderTests.swift */; }; 0EDF8EDD1DDB73520068056A /* LineBreakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECE1DDB73370068056A /* LineBreakTests.swift */; }; 0EDF8EDE1DDB73520068056A /* ReadmeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */; }; 0EDF8EDF1DDB73520068056A /* TrimFieldsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ED01DDB73370068056A /* TrimFieldsTests.swift */; }; 0EDF8EE01DDB73520068056A /* UnicodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ED11DDB73370068056A /* UnicodeTests.swift */; }; - 0EDF8EE11DDB73530068056A /* CSVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECD1DDB73370068056A /* CSVTests.swift */; }; + 0EDF8EE11DDB73530068056A /* CSVReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECD1DDB73370068056A /* CSVReaderTests.swift */; }; 0EDF8EE21DDB73530068056A /* LineBreakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECE1DDB73370068056A /* LineBreakTests.swift */; }; 0EDF8EE31DDB73530068056A /* ReadmeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */; }; 0EDF8EE41DDB73530068056A /* TrimFieldsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ED01DDB73370068056A /* TrimFieldsTests.swift */; }; @@ -105,6 +108,7 @@ 0E7E8CE81D0BCD0B0057A1C1 /* CSV.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CSV.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7E8CF11D0BCD0B0057A1C1 /* CSVTests-tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CSVTests-tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7F657A1EF6437E00E1E1A0 /* Version1Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Version1Tests.swift; sourceTree = ""; }; + 0E87607A219D992900C6C7FA /* BinaryReaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BinaryReaderTests.swift; sourceTree = ""; }; 0E959C5B208B611F005F8D01 /* CSVError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVError.swift; sourceTree = ""; }; 0E959C5C208B611F005F8D01 /* CSVWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVWriter.swift; sourceTree = ""; }; 0E959C5D208B611F005F8D01 /* CSV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSV.swift; sourceTree = ""; }; @@ -112,7 +116,7 @@ 0E959C5F208B611F005F8D01 /* UnicodeIterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeIterator.swift; sourceTree = ""; }; 0E959C60208B611F005F8D01 /* Endian.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Endian.swift; sourceTree = ""; }; 0E959C61208B611F005F8D01 /* CSVReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVReader.swift; sourceTree = ""; }; - 0EDF8ECD1DDB73370068056A /* CSVTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVTests.swift; sourceTree = ""; }; + 0EDF8ECD1DDB73370068056A /* CSVReaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVReaderTests.swift; sourceTree = ""; }; 0EDF8ECE1DDB73370068056A /* LineBreakTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineBreakTests.swift; sourceTree = ""; }; 0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadmeTests.swift; sourceTree = ""; }; 0EDF8ED01DDB73370068056A /* TrimFieldsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrimFieldsTests.swift; sourceTree = ""; }; @@ -236,7 +240,8 @@ 0EA37AC81DD8C0B900F5B274 /* CSVTests */ = { isa = PBXGroup; children = ( - 0EDF8ECD1DDB73370068056A /* CSVTests.swift */, + 0E87607A219D992900C6C7FA /* BinaryReaderTests.swift */, + 0EDF8ECD1DDB73370068056A /* CSVReaderTests.swift */, 3C89219D21484153004AA78A /* CSVReader+DecodableTests.swift */, 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */, 0EDF8ECE1DDB73370068056A /* LineBreakTests.swift */, @@ -419,7 +424,7 @@ 0E7E8C781D0BC7BB0057A1C1 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0730; + LastSwiftUpdateCheck = 1010; LastUpgradeCheck = 0930; ORGANIZATIONNAME = yaslab; TargetAttributes = { @@ -459,6 +464,7 @@ hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = 0E7E8C771D0BC7BB0057A1C1; productRefGroup = 0E7E8C821D0BC7BB0057A1C1 /* Products */; @@ -566,10 +572,11 @@ files = ( 0EDF8EDE1DDB73520068056A /* ReadmeTests.swift in Sources */, 3C89219F21484154004AA78A /* CSVReader+DecodableTests.swift in Sources */, - 0EDF8EDC1DDB73520068056A /* CSVTests.swift in Sources */, + 0EDF8EDC1DDB73520068056A /* CSVReaderTests.swift in Sources */, 0EDF8EDF1DDB73520068056A /* TrimFieldsTests.swift in Sources */, 0E7F657C1EF6437E00E1E1A0 /* Version1Tests.swift in Sources */, 0EDF8EE01DDB73520068056A /* UnicodeTests.swift in Sources */, + 0E87607C219D992900C6C7FA /* BinaryReaderTests.swift in Sources */, 0EDF8EDD1DDB73520068056A /* LineBreakTests.swift in Sources */, 0E54021C1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */, ); @@ -609,10 +616,11 @@ files = ( 0EDF8ED91DDB73520068056A /* ReadmeTests.swift in Sources */, 3C89219E21484154004AA78A /* CSVReader+DecodableTests.swift in Sources */, - 0EDF8ED71DDB73520068056A /* CSVTests.swift in Sources */, + 0EDF8ED71DDB73520068056A /* CSVReaderTests.swift in Sources */, 0EDF8EDA1DDB73520068056A /* TrimFieldsTests.swift in Sources */, 0E7F657B1EF6437E00E1E1A0 /* Version1Tests.swift in Sources */, 0EDF8EDB1DDB73520068056A /* UnicodeTests.swift in Sources */, + 0E87607B219D992900C6C7FA /* BinaryReaderTests.swift in Sources */, 0EDF8ED81DDB73520068056A /* LineBreakTests.swift in Sources */, 0E54021B1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */, ); @@ -638,10 +646,11 @@ files = ( 0EDF8EE31DDB73530068056A /* ReadmeTests.swift in Sources */, 3C8921A021484154004AA78A /* CSVReader+DecodableTests.swift in Sources */, - 0EDF8EE11DDB73530068056A /* CSVTests.swift in Sources */, + 0EDF8EE11DDB73530068056A /* CSVReaderTests.swift in Sources */, 0EDF8EE41DDB73530068056A /* TrimFieldsTests.swift in Sources */, 0E7F657D1EF6437E00E1E1A0 /* Version1Tests.swift in Sources */, 0EDF8EE51DDB73530068056A /* UnicodeTests.swift in Sources */, + 0E87607D219D992900C6C7FA /* BinaryReaderTests.swift in Sources */, 0EDF8EE21DDB73530068056A /* LineBreakTests.swift in Sources */, 0E54021D1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */, ); @@ -722,6 +731,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -774,6 +784,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.9; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -796,7 +807,6 @@ PRODUCT_BUNDLE_IDENTIFIER = net.yaslab.CSV; PRODUCT_NAME = CSV; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -816,7 +826,6 @@ PRODUCT_NAME = CSV; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; }; name = Release; }; @@ -828,7 +837,6 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "net.yaslab.CSVTests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -841,7 +849,6 @@ PRODUCT_BUNDLE_IDENTIFIER = "net.yaslab.CSVTests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; }; name = Release; }; @@ -861,7 +868,6 @@ PRODUCT_NAME = CSV; SDKROOT = watchos; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.0; }; @@ -884,7 +890,6 @@ SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.0; }; @@ -908,7 +913,6 @@ PRODUCT_NAME = CSV; SDKROOT = macosx; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -931,7 +935,6 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; }; name = Release; }; @@ -947,7 +950,6 @@ PRODUCT_BUNDLE_IDENTIFIER = "net.yaslab.CSVTests-macOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -964,7 +966,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; }; name = Release; }; @@ -983,7 +984,6 @@ PRODUCT_NAME = CSV; SDKROOT = appletvos; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 9.0; }; @@ -1005,7 +1005,6 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 9.0; }; @@ -1020,7 +1019,6 @@ PRODUCT_BUNDLE_IDENTIFIER = "net.yaslab.CSVTests-tvOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.2; }; name = Debug; @@ -1035,7 +1033,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.2; }; name = Release; diff --git a/Sources/CSV/BinaryReader.swift b/Sources/CSV/BinaryReader.swift index a48254b..f5227df 100644 --- a/Sources/CSV/BinaryReader.swift +++ b/Sources/CSV/BinaryReader.swift @@ -8,35 +8,60 @@ import Foundation -internal let utf8BOM: [UInt8] = [0xef, 0xbb, 0xbf] -internal let utf16BigEndianBOM: [UInt8] = [0xfe, 0xff] -internal let utf16LittleEndianBOM: [UInt8] = [0xff, 0xfe] -internal let utf32BigEndianBOM: [UInt8] = [0x00, 0x00, 0xfe, 0xff] -internal let utf32LittleEndianBOM: [UInt8] = [0xff, 0xfe, 0x00, 0x00] - -private func readBOM(buffer: UnsafePointer, length: Int) -> (Endian, Int)? { - if length >= 4 { - if memcmp(buffer, utf32BigEndianBOM, 4) == 0 { - return (.big, 4) +enum UnicodeBOM { + // UTF-8 + static let utf8: [UInt8] = [0xef, 0xbb, 0xbf] + // UTF-16 BE + static let utf16BE: [UInt8] = [0xfe, 0xff] + // UTF-16 LE + static let utf16LE: [UInt8] = [0xff, 0xfe] + // UTF-32 BE + static let utf32BE: [UInt8] = [0x00, 0x00, 0xfe, 0xff] + // UTF-32 LE + static let utf32LE: [UInt8] = [0xff, 0xfe, 0x00, 0x00] +} + +extension UnicodeBOM { + + fileprivate static func readBOM(buffer: UnsafePointer, count: Int) -> (Endian, Int)? { + if count >= 4 { + // UTF-32 BE + if compare(buffer: buffer, bom: UnicodeBOM.utf32BE) { + return (.big, UnicodeBOM.utf32BE.count) + } + // UTF-32 LE + if compare(buffer: buffer, bom: UnicodeBOM.utf32LE) { + return (.little, UnicodeBOM.utf32LE.count) + } } - if memcmp(buffer, utf32LittleEndianBOM, 4) == 0 { - return (.little, 4) + if count >= 3 { + // UTF-8 + if compare(buffer: buffer, bom: UnicodeBOM.utf8) { + return (.unknown, UnicodeBOM.utf8.count) + } } - } - if length >= 3 { - if memcmp(buffer, utf8BOM, 3) == 0 { - return (.unknown, 3) + if count >= 2 { + // UTF-16 BE + if compare(buffer: buffer, bom: UnicodeBOM.utf16BE) { + return (.big, UnicodeBOM.utf16BE.count) + } + // UTF-16 LE + if compare(buffer: buffer, bom: UnicodeBOM.utf16LE) { + return (.little, UnicodeBOM.utf16LE.count) + } } + return nil } - if length >= 2 { - if memcmp(buffer, utf16BigEndianBOM, 2) == 0 { - return (.big, 2) - } - if memcmp(buffer, utf16LittleEndianBOM, 2) == 0 { - return (.little, 2) + + private static func compare(buffer: UnsafePointer, bom: [UInt8]) -> Bool { + for i in 0 ..< bom.count { + guard buffer[i] == bom[i] else { + return false + } } + return true } - return nil + } internal class BinaryReader { @@ -45,16 +70,16 @@ internal class BinaryReader { private let endian: Endian private let closeOnDeinit: Bool - private var buffer = malloc(4).assumingMemoryBound(to: UInt8.self) - - private var tempBuffer = malloc(4).assumingMemoryBound(to: UInt8.self) - private let tempBufferSize = 4 - private var tempBufferOffset = 0 + private let _buffer: UnsafeMutablePointer + private let _capacity: Int + private var _count: Int = 0 + private var _position: Int = 0 internal init( stream: InputStream, endian: Endian, - closeOnDeinit: Bool) throws { + closeOnDeinit: Bool, + bufferSize: Int = Int(UInt16.max)) throws { var endian = endian @@ -65,14 +90,22 @@ internal class BinaryReader { throw CSVError.cannotOpenFile } - let readCount = stream.read(tempBuffer, maxLength: tempBufferSize) - if let (e, l) = readBOM(buffer: tempBuffer, length: readCount) { + _buffer = UnsafeMutablePointer.allocate(capacity: bufferSize) + _capacity = bufferSize + _count = stream.read(_buffer, maxLength: _capacity) + if _count < 0 { + throw CSVError.cannotReadFile + } + + var position = 0 + if let (e, l) = UnicodeBOM.readBOM(buffer: _buffer, count: _count) { if endian != .unknown && endian != e { throw CSVError.stringEndianMismatch } endian = e - tempBufferOffset = l + position = l } + _position = position self.stream = stream self.endian = endian @@ -83,58 +116,64 @@ internal class BinaryReader { if closeOnDeinit && stream.streamStatus != .closed { stream.close() } - free(buffer) - free(tempBuffer) + _buffer.deallocate() } internal var hasBytesAvailable: Bool { + if _count - _position > 0 { + return true + } return stream.hasBytesAvailable } + @inline(__always) private func readStream(_ buffer: UnsafeMutablePointer, maxLength: Int) throws -> Int { - if stream.streamStatus != .open { - throw CSVError.cannotReadFile - } - - var i = 0 - while tempBufferOffset < tempBufferSize { - buffer[i] = tempBuffer[tempBufferOffset] - i += 1 - tempBufferOffset += 1 - if i >= maxLength { - return i + var count = 0 + for i in 0 ..< maxLength { + if _position >= _count { + let result = stream.read(_buffer, maxLength: _capacity) + if result < 0 { + if let error = stream.streamError { + throw CSVError.streamErrorHasOccurred(error: error) + } else { + throw CSVError.cannotReadFile + } + } + _count = result + _position = 0 + if result == 0 { + break + } } + buffer[i] = _buffer[_position] + _position += 1 + count += 1 } - return stream.read(buffer + i, maxLength: maxLength - i) + return count } internal func readUInt8() throws -> UInt8 { let bufferSize = 1 - let length = try readStream(buffer, maxLength: bufferSize) - if length < 0 { - throw CSVError.streamErrorHasOccurred(error: stream.streamError!) - } - if length != bufferSize { + var buffer: UInt8 = 0 + if try readStream(&buffer, maxLength: bufferSize) != bufferSize { throw CSVError.cannotReadFile } - return buffer[0] + return buffer } internal func readUInt16() throws -> UInt16 { let bufferSize = 2 - let length = try readStream(buffer, maxLength: bufferSize) - if length < 0 { - throw CSVError.streamErrorHasOccurred(error: stream.streamError!) - } - if length != bufferSize { + let buffer = UnsafeMutablePointer.allocate(capacity: bufferSize) + defer { buffer.deallocate() } + if try readStream(buffer, maxLength: bufferSize) != bufferSize { throw CSVError.stringEncodingMismatch } return try buffer.withMemoryRebound(to: UInt16.self, capacity: 1) { switch endian { case .big: - return UInt16(bigEndian: $0[0]) + return UInt16(bigEndian: $0.pointee) case .little: - return UInt16(littleEndian: $0[0]) + return UInt16(littleEndian: $0.pointee) default: throw CSVError.stringEndianMismatch } @@ -143,19 +182,17 @@ internal class BinaryReader { internal func readUInt32() throws -> UInt32 { let bufferSize = 4 - let length = try readStream(buffer, maxLength: bufferSize) - if length < 0 { - throw CSVError.streamErrorHasOccurred(error: stream.streamError!) - } - if length != bufferSize { + let buffer = UnsafeMutablePointer.allocate(capacity: bufferSize) + defer { buffer.deallocate() } + if try readStream(buffer, maxLength: bufferSize) != bufferSize { throw CSVError.stringEncodingMismatch } return try buffer.withMemoryRebound(to: UInt32.self, capacity: 1) { switch endian { case .big: - return UInt32(bigEndian: $0[0]) + return UInt32(bigEndian: $0.pointee) case .little: - return UInt32(littleEndian: $0[0]) + return UInt32(littleEndian: $0.pointee) default: throw CSVError.stringEndianMismatch } diff --git a/Sources/CSV/CSVReader.swift b/Sources/CSV/CSVReader.swift index 63fd944..9342725 100644 --- a/Sources/CSV/CSVReader.swift +++ b/Sources/CSV/CSVReader.swift @@ -53,7 +53,6 @@ public class CSVReader { public fileprivate (set) var error: Error? fileprivate var back: UnicodeScalar? - fileprivate var fieldBuffer = String.UnicodeScalarView() fileprivate var currentRowIndex: Int = 0 fileprivate var currentFieldIndex: Int = 0 @@ -281,7 +280,7 @@ extension CSVReader { } private func readField(quoted: Bool) -> (String, Bool) { - fieldBuffer.removeAll(keepingCapacity: true) + var fieldBuffer = String.UnicodeScalarView() while let c = moveNext() { if quoted { diff --git a/Sources/CSV/CSVWriter.swift b/Sources/CSV/CSVWriter.swift index f0c76fa..75df5c4 100644 --- a/Sources/CSV/CSVWriter.swift +++ b/Sources/CSV/CSVWriter.swift @@ -89,7 +89,7 @@ extension CSVWriter { let config = Configuration(delimiter: delimiter, newline: newline) try self.init(stream: stream, configuration: config) { (scalar: UnicodeScalar) throws in - var error: CSVError? = nil + var error: CSVError? codecType.encode(scalar) { (code: UInt8) in var code = code let count = stream.write(&code, maxLength: 1) @@ -113,7 +113,7 @@ extension CSVWriter { let config = Configuration(delimiter: delimiter, newline: newline) try self.init(stream: stream, configuration: config) { (scalar: UnicodeScalar) throws in - var error: CSVError? = nil + var error: CSVError? codecType.encode(scalar) { (code: UInt16) in var code = (endian == .big) ? code.bigEndian : code.littleEndian withUnsafeBytes(of: &code) { (buffer) -> Void in @@ -140,7 +140,7 @@ extension CSVWriter { let config = Configuration(delimiter: delimiter, newline: newline) try self.init(stream: stream, configuration: config) { (scalar: UnicodeScalar) throws in - var error: CSVError? = nil + var error: CSVError? codecType.encode(scalar) { (code: UInt32) in var code = (endian == .big) ? code.bigEndian : code.littleEndian withUnsafeBytes(of: &code) { (buffer) -> Void in diff --git a/Tests/CSVTests/BinaryReaderTests.swift b/Tests/CSVTests/BinaryReaderTests.swift new file mode 100644 index 0000000..f5e3c6d --- /dev/null +++ b/Tests/CSVTests/BinaryReaderTests.swift @@ -0,0 +1,113 @@ +// +// BinaryReaderTests.swift +// CSV +// +// Created by Yasuhiro Hatta on 2018/11/15. +// Copyright © 2018 yaslab. All rights reserved. +// + +import XCTest +@testable import CSV + +class BinaryReaderTests: XCTestCase { + + static let allTests = [ + ("testReadUInt8WithSmallBuffer", testReadUInt8WithSmallBuffer), + ("testReadUInt16BEWithSmallBuffer", testReadUInt16BEWithSmallBuffer), + ("testReadUInt16LEWithSmallBuffer", testReadUInt16LEWithSmallBuffer), + ("testReadUInt32BEWithSmallBuffer", testReadUInt32BEWithSmallBuffer), + ("testReadUInt32LEWithSmallBuffer", testReadUInt32LEWithSmallBuffer) + ] + + private func random(_ count: Int) -> [UInt8] { + var array = [UInt8]() + for _ in 0 ..< count { + array.append(UInt8.random(in: .min ... .max)) + } + return array + } + + func testReadUInt8WithSmallBuffer() { + let bytes = [0xcc] + random(99) + let stream = InputStream(data: Data(bytes: bytes)) + + do { + let reader = try BinaryReader(stream: stream, endian: .unknown, closeOnDeinit: true, bufferSize: 7) + for expected in bytes { + let actual = try reader.readUInt8() + XCTAssertEqual(actual, expected) + } + } catch { + XCTFail("\(error)") + } + } + + func testReadUInt16BEWithSmallBuffer() { + let bytes = [0xcc] + random(99) + let stream = InputStream(data: Data(bytes: bytes)) + + do { + let reader = try BinaryReader(stream: stream, endian: .big, closeOnDeinit: true, bufferSize: 7) + for i in stride(from: 0, to: bytes.count, by: 2) { + let expected = (UInt16(bytes[i]) << 8) | UInt16(bytes[i + 1]) + let actual = try reader.readUInt16() + XCTAssertEqual(actual, expected) + } + } catch { + XCTFail("\(error)") + } + } + + func testReadUInt16LEWithSmallBuffer() { + let bytes = [0xcc] + random(99) + let stream = InputStream(data: Data(bytes: bytes)) + + do { + let reader = try BinaryReader(stream: stream, endian: .little, closeOnDeinit: true, bufferSize: 7) + for i in stride(from: 0, to: bytes.count, by: 2) { + let expected = UInt16(bytes[i]) | (UInt16(bytes[i + 1]) << 8) + let actual = try reader.readUInt16() + XCTAssertEqual(actual, expected) + } + } catch { + XCTFail("\(error)") + } + } + + func testReadUInt32BEWithSmallBuffer() { + let bytes = [0xcc] + random(99) + let stream = InputStream(data: Data(bytes: bytes)) + + do { + let reader = try BinaryReader(stream: stream, endian: .big, closeOnDeinit: true, bufferSize: 7) + for i in stride(from: 0, to: bytes.count, by: 4) { + let expected = + (UInt32(bytes[i ]) << 24) | (UInt32(bytes[i + 1]) << 16) | + (UInt32(bytes[i + 2]) << 8) | (UInt32(bytes[i + 3]) ) + let actual = try reader.readUInt32() + XCTAssertEqual(actual, expected) + } + } catch { + XCTFail("\(error)") + } + } + + func testReadUInt32LEWithSmallBuffer() { + let bytes = [0xcc] + random(99) + let stream = InputStream(data: Data(bytes: bytes)) + + do { + let reader = try BinaryReader(stream: stream, endian: .little, closeOnDeinit: true, bufferSize: 7) + for i in stride(from: 0, to: bytes.count, by: 4) { + let expected = + (UInt32(bytes[i ]) ) | (UInt32(bytes[i + 1]) << 8) | + (UInt32(bytes[i + 2]) << 16) | (UInt32(bytes[i + 3]) << 24) + let actual = try reader.readUInt32() + XCTAssertEqual(actual, expected) + } + } catch { + XCTFail("\(error)") + } + } + +} diff --git a/Tests/CSVTests/CSVTests.swift b/Tests/CSVTests/CSVReaderTests.swift similarity index 99% rename from Tests/CSVTests/CSVTests.swift rename to Tests/CSVTests/CSVReaderTests.swift index c38eb36..0d43b94 100644 --- a/Tests/CSVTests/CSVTests.swift +++ b/Tests/CSVTests/CSVReaderTests.swift @@ -1,5 +1,5 @@ // -// CSVTests.swift +// CSVReaderTests.swift // CSV // // Created by Yasuhiro Hatta on 2016/06/11. @@ -9,7 +9,7 @@ import XCTest @testable import CSV -class CSVTests: XCTestCase { +class CSVReaderTests: XCTestCase { static let allTests = [ ("testOneLine", testOneLine), diff --git a/Tests/CSVTests/UnicodeTests.swift b/Tests/CSVTests/UnicodeTests.swift index 0bba796..11d9768 100644 --- a/Tests/CSVTests/UnicodeTests.swift +++ b/Tests/CSVTests/UnicodeTests.swift @@ -26,7 +26,7 @@ class UnicodeTests: XCTestCase { let csvString = "abab,,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\"," let encoding = String.Encoding.utf8 var mutableData = Data() - mutableData.append(utf8BOM, count: utf8BOM.count) + mutableData.append(contentsOf: UnicodeBOM.utf8) mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData) let csv = try! CSVReader(stream: stream, codecType: UTF8.self) @@ -51,7 +51,7 @@ class UnicodeTests: XCTestCase { let csvString = "abab,,cdcd,efef\r\n😆zxcv,asdf,\"qw\"\"er\"," let encoding = String.Encoding.utf16BigEndian var mutableData = Data() - mutableData.append(utf16BigEndianBOM, count: utf16BigEndianBOM.count) + mutableData.append(contentsOf: UnicodeBOM.utf16BE) mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData as Data) let csv = try! CSVReader(stream: stream, codecType: UTF16.self, endian: .big) @@ -64,7 +64,7 @@ class UnicodeTests: XCTestCase { let csvString = "abab,,cdcd,efef\r\nzxcv😆,asdf,\"qw\"\"er\"," let encoding = String.Encoding.utf16LittleEndian var mutableData = Data() - mutableData.append(utf16LittleEndianBOM, count: utf16LittleEndianBOM.count) + mutableData.append(contentsOf: UnicodeBOM.utf16LE) mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData as Data) let csv = try! CSVReader(stream: stream, codecType: UTF16.self, endian: .little) @@ -89,7 +89,7 @@ class UnicodeTests: XCTestCase { let csvString = "abab,,cd😆cd,efef\r\nzxcv,asdf,\"qw\"\"er\"," let encoding = String.Encoding.utf32BigEndian var mutableData = Data() - mutableData.append(utf32BigEndianBOM, count: utf32BigEndianBOM.count) + mutableData.append(contentsOf: UnicodeBOM.utf32BE) mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData as Data) let csv = try! CSVReader(stream: stream, codecType: UTF32.self, endian: .big) @@ -102,7 +102,7 @@ class UnicodeTests: XCTestCase { let csvString = "abab,,cdcd,ef😆ef\r\nzxcv,asdf,\"qw\"\"er\"," let encoding = String.Encoding.utf32LittleEndian var mutableData = Data() - mutableData.append(utf32LittleEndianBOM, count: utf32LittleEndianBOM.count) + mutableData.append(contentsOf: UnicodeBOM.utf32LE) mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData as Data) let csv = try! CSVReader(stream: stream, codecType: UTF32.self, endian: .little) diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 15d3947..04ead86 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -10,7 +10,8 @@ import XCTest @testable import CSVTests XCTMain([ - testCase(CSVTests.allTests), + testCase(BinaryReaderTests.allTests), + testCase(CSVReaderTests.allTests), testCase(CSVWriterTests.allTests), testCase(LineBreakTests.allTests), testCase(ReadmeTests.allTests),