diff --git a/.swift-version b/.swift-version index 9f55b2c..8c50098 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -3.0 +3.1 diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..c68f278 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,6 @@ +disabled_rules: + - force_cast + - force_try + - variable_name +included: + - Sources diff --git a/.travis.yml b/.travis.yml index 00660c2..4f959be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode8 +osx_image: xcode8.3 env: - LC_CTYPE=en_US.UTF-8 git: diff --git a/CSV.swift.podspec b/CSV.swift.podspec index 7a1198c..5ce431b 100644 --- a/CSV.swift.podspec +++ b/CSV.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'CSV.swift' - s.version = '1.1.2' + s.version = '2.0.0' s.license = 'MIT' s.summary = 'CSV reading library written in Swift.' s.homepage = 'https://github.com/yaslab/CSV.swift' diff --git a/CSV.xcodeproj/project.pbxproj b/CSV.xcodeproj/project.pbxproj index 23d1e1c..30fe0a0 100755 --- a/CSV.xcodeproj/project.pbxproj +++ b/CSV.xcodeproj/project.pbxproj @@ -11,29 +11,31 @@ 0E0F160F1D197DB800C92580 /* Endian.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F160D1D197DB800C92580 /* Endian.swift */; }; 0E0F16101D197DB800C92580 /* Endian.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F160D1D197DB800C92580 /* Endian.swift */; }; 0E0F16111D197DB800C92580 /* Endian.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F160D1D197DB800C92580 /* Endian.swift */; }; + 0E54021B1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; }; + 0E54021C1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; }; + 0E54021D1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; }; + 0E5402221EDA82220019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */; }; + 0E5402231EDA82220019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */; }; + 0E5402241EDA82220019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */; }; + 0E5402251EDA82230019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */; }; 0E7E8C8C1D0BC7BB0057A1C1 /* CSV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E7E8C811D0BC7BB0057A1C1 /* CSV.framework */; }; - 0E7E8CA11D0BC7F10057A1C1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */; }; + 0E7E8CA11D0BC7F10057A1C1 /* CSVReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */; }; 0E7E8CA21D0BC7F10057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; }; 0E7E8CA31D0BC7F10057A1C1 /* CSVVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 0E7E8CBE1D0BC9D70057A1C1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */; }; + 0E7E8CBE1D0BC9D70057A1C1 /* CSVReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */; }; 0E7E8CBF1D0BC9D70057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; }; 0E7E8CC01D0BC9D70057A1C1 /* CSVVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0E7E8CD01D0BCA2A0057A1C1 /* CSV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E7E8CC61D0BCA2A0057A1C1 /* CSV.framework */; }; - 0E7E8CE01D0BCA8E0057A1C1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */; }; + 0E7E8CE01D0BCA8E0057A1C1 /* CSVReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */; }; 0E7E8CE11D0BCA8E0057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; }; 0E7E8CE21D0BCA8E0057A1C1 /* CSVVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0E7E8CF21D0BCD0B0057A1C1 /* CSV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E7E8CE81D0BCD0B0057A1C1 /* CSV.framework */; }; - 0E7E8D001D0BCDCF0057A1C1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */; }; + 0E7E8D001D0BCDCF0057A1C1 /* CSVReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */; }; 0E7E8D011D0BCDCF0057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; }; 0E7E8D021D0BCDCF0057A1C1 /* CSVVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 0E9317D41D0DB2F200AC20A0 /* CSV+init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */; }; - 0E9317D51D0DB2F200AC20A0 /* CSV+init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */; }; - 0E9317D61D0DB2F200AC20A0 /* CSV+init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */; }; - 0E9317D71D0DB2F200AC20A0 /* CSV+init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */; }; - 0E9317D91D0DB30800AC20A0 /* CSV+subscript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D81D0DB30800AC20A0 /* CSV+subscript.swift */; }; - 0E9317DA1D0DB30800AC20A0 /* CSV+subscript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D81D0DB30800AC20A0 /* CSV+subscript.swift */; }; - 0E9317DB1D0DB30800AC20A0 /* CSV+subscript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D81D0DB30800AC20A0 /* CSV+subscript.swift */; }; - 0E9317DC1D0DB30800AC20A0 /* CSV+subscript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D81D0DB30800AC20A0 /* CSV+subscript.swift */; }; + 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 */; }; 0EA2AB7C1D183B45003EC967 /* BinaryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */; }; 0EA2AB7D1D183B45003EC967 /* BinaryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */; }; 0EA2AB7E1D183B45003EC967 /* BinaryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */; }; @@ -42,21 +44,25 @@ 0EA2AB821D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; }; 0EA2AB831D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; }; 0EA2AB841D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; }; - 0EA37ADD1DD8C12600F5B274 /* CSVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA37AC91DD8C0B900F5B274 /* CSVTests.swift */; }; - 0EA37ADE1DD8C12600F5B274 /* LineBreakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA37ACA1DD8C0B900F5B274 /* LineBreakTests.swift */; }; - 0EA37ADF1DD8C12600F5B274 /* ReadmeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA37ACB1DD8C0B900F5B274 /* ReadmeTests.swift */; }; - 0EA37AE01DD8C12600F5B274 /* TrimFieldsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA37ACC1DD8C0B900F5B274 /* TrimFieldsTests.swift */; }; - 0EA37AE11DD8C12600F5B274 /* UnicodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA37ACD1DD8C0B900F5B274 /* UnicodeTests.swift */; }; - 0EA37AE21DD8C12700F5B274 /* CSVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA37AC91DD8C0B900F5B274 /* CSVTests.swift */; }; - 0EA37AE31DD8C12700F5B274 /* LineBreakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA37ACA1DD8C0B900F5B274 /* LineBreakTests.swift */; }; - 0EA37AE41DD8C12700F5B274 /* ReadmeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA37ACB1DD8C0B900F5B274 /* ReadmeTests.swift */; }; - 0EA37AE51DD8C12700F5B274 /* TrimFieldsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA37ACC1DD8C0B900F5B274 /* TrimFieldsTests.swift */; }; - 0EA37AE61DD8C12700F5B274 /* UnicodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA37ACD1DD8C0B900F5B274 /* UnicodeTests.swift */; }; - 0EA37AE71DD8C12700F5B274 /* CSVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA37AC91DD8C0B900F5B274 /* CSVTests.swift */; }; - 0EA37AE81DD8C12700F5B274 /* LineBreakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA37ACA1DD8C0B900F5B274 /* LineBreakTests.swift */; }; - 0EA37AE91DD8C12700F5B274 /* ReadmeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA37ACB1DD8C0B900F5B274 /* ReadmeTests.swift */; }; - 0EA37AEA1DD8C12700F5B274 /* TrimFieldsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA37ACC1DD8C0B900F5B274 /* TrimFieldsTests.swift */; }; - 0EA37AEB1DD8C12700F5B274 /* UnicodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA37ACD1DD8C0B900F5B274 /* UnicodeTests.swift */; }; + 0EC628FE1EF675B300289554 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC628FD1EF675B300289554 /* CSV.swift */; }; + 0EC628FF1EF675FD00289554 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC628FD1EF675B300289554 /* CSV.swift */; }; + 0EC629001EF675FE00289554 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC628FD1EF675B300289554 /* CSV.swift */; }; + 0EC629011EF675FF00289554 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC628FD1EF675B300289554 /* CSV.swift */; }; + 0EDF8ED71DDB73520068056A /* CSVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECD1DDB73370068056A /* CSVTests.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 */; }; + 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 */; }; + 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 */; }; + 0EDF8EE51DDB73530068056A /* UnicodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ED11DDB73370068056A /* UnicodeTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -85,9 +91,11 @@ /* Begin PBXFileReference section */ 0E0F160D1D197DB800C92580 /* Endian.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Endian.swift; sourceTree = ""; }; + 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVWriterTests.swift; sourceTree = ""; }; + 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVWriter.swift; sourceTree = ""; }; 0E7E8C811D0BC7BB0057A1C1 /* CSV.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CSV.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7E8C8B1D0BC7BB0057A1C1 /* CSVTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CSVTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSV.swift; sourceTree = ""; }; + 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVReader.swift; sourceTree = ""; }; 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVError.swift; sourceTree = ""; }; 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSVVersion.h; sourceTree = ""; }; 0E7E8CAC1D0BC8610057A1C1 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -97,15 +105,15 @@ 0E7E8CCF1D0BCA2A0057A1C1 /* CSVTests-OSX.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CSVTests-OSX.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 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; }; - 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSV+init.swift"; sourceTree = ""; }; - 0E9317D81D0DB30800AC20A0 /* CSV+subscript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSV+subscript.swift"; sourceTree = ""; }; + 0E7F657A1EF6437E00E1E1A0 /* Version1Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Version1Tests.swift; sourceTree = ""; }; 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BinaryReader.swift; sourceTree = ""; }; 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeIterator.swift; sourceTree = ""; }; - 0EA37AC91DD8C0B900F5B274 /* CSVTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVTests.swift; sourceTree = ""; }; - 0EA37ACA1DD8C0B900F5B274 /* LineBreakTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineBreakTests.swift; sourceTree = ""; }; - 0EA37ACB1DD8C0B900F5B274 /* ReadmeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadmeTests.swift; sourceTree = ""; }; - 0EA37ACC1DD8C0B900F5B274 /* TrimFieldsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrimFieldsTests.swift; sourceTree = ""; }; - 0EA37ACD1DD8C0B900F5B274 /* UnicodeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeTests.swift; sourceTree = ""; }; + 0EC628FD1EF675B300289554 /* CSV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSV.swift; sourceTree = ""; }; + 0EDF8ECD1DDB73370068056A /* CSVTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVTests.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 = ""; }; + 0EDF8ED11DDB73370068056A /* UnicodeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -192,11 +200,11 @@ isa = PBXGroup; children = ( 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */, - 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */, - 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */, - 0E9317D81D0DB30800AC20A0 /* CSV+subscript.swift */, + 0EC628FD1EF675B300289554 /* CSV.swift */, 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */, + 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */, 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */, + 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */, 0E0F160D1D197DB800C92580 /* Endian.swift */, 0E7E8CAC1D0BC8610057A1C1 /* Info.plist */, 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */, @@ -216,11 +224,13 @@ 0EA37AC81DD8C0B900F5B274 /* CSVTests */ = { isa = PBXGroup; children = ( - 0EA37AC91DD8C0B900F5B274 /* CSVTests.swift */, - 0EA37ACA1DD8C0B900F5B274 /* LineBreakTests.swift */, - 0EA37ACB1DD8C0B900F5B274 /* ReadmeTests.swift */, - 0EA37ACC1DD8C0B900F5B274 /* TrimFieldsTests.swift */, - 0EA37ACD1DD8C0B900F5B274 /* UnicodeTests.swift */, + 0EDF8ECD1DDB73370068056A /* CSVTests.swift */, + 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */, + 0EDF8ECE1DDB73370068056A /* LineBreakTests.swift */, + 0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */, + 0EDF8ED01DDB73370068056A /* TrimFieldsTests.swift */, + 0EDF8ED11DDB73370068056A /* UnicodeTests.swift */, + 0E7F657A1EF6437E00E1E1A0 /* Version1Tests.swift */, ); path = CSVTests; sourceTree = ""; @@ -321,6 +331,7 @@ isa = PBXNativeTarget; buildConfigurationList = 0E7E8CD71D0BCA2A0057A1C1 /* Build configuration list for PBXNativeTarget "CSV-OSX" */; buildPhases = ( + 0E47EEBF1DBBC05700EBF783 /* Run Script () */, 0E7E8CC11D0BCA2A0057A1C1 /* Sources */, 0E7E8CC21D0BCA2A0057A1C1 /* Frameworks */, 0E7E8CC31D0BCA2A0057A1C1 /* Headers */, @@ -504,17 +515,34 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 0E47EEBF1DBBC05700EBF783 /* Run Script () */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script ()"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\n swiftlint autocorrect\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 0E7E8C7C1D0BC7BB0057A1C1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0E9317D51D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB821D183BA9003EC967 /* UnicodeIterator.swift in Sources */, - 0E9317DA1D0DB30800AC20A0 /* CSV+subscript.swift in Sources */, - 0E7E8CA11D0BC7F10057A1C1 /* CSV.swift in Sources */, + 0E5402241EDA82220019C3ED /* CSVWriter.swift in Sources */, + 0E7E8CA11D0BC7F10057A1C1 /* CSVReader.swift in Sources */, 0E0F160F1D197DB800C92580 /* Endian.swift in Sources */, 0E7E8CA21D0BC7F10057A1C1 /* CSVError.swift in Sources */, + 0EC628FF1EF675FD00289554 /* CSV.swift in Sources */, 0EA2AB7D1D183B45003EC967 /* BinaryReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -523,11 +551,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0EA37AE41DD8C12700F5B274 /* ReadmeTests.swift in Sources */, - 0EA37AE21DD8C12700F5B274 /* CSVTests.swift in Sources */, - 0EA37AE51DD8C12700F5B274 /* TrimFieldsTests.swift in Sources */, - 0EA37AE61DD8C12700F5B274 /* UnicodeTests.swift in Sources */, - 0EA37AE31DD8C12700F5B274 /* LineBreakTests.swift in Sources */, + 0EDF8EDE1DDB73520068056A /* ReadmeTests.swift in Sources */, + 0EDF8EDC1DDB73520068056A /* CSVTests.swift in Sources */, + 0EDF8EDF1DDB73520068056A /* TrimFieldsTests.swift in Sources */, + 0E7F657C1EF6437E00E1E1A0 /* Version1Tests.swift in Sources */, + 0EDF8EE01DDB73520068056A /* UnicodeTests.swift in Sources */, + 0EDF8EDD1DDB73520068056A /* LineBreakTests.swift in Sources */, + 0E54021C1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -535,12 +565,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0E9317D71D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB841D183BA9003EC967 /* UnicodeIterator.swift in Sources */, - 0E9317DC1D0DB30800AC20A0 /* CSV+subscript.swift in Sources */, - 0E7E8CBE1D0BC9D70057A1C1 /* CSV.swift in Sources */, + 0E5402221EDA82220019C3ED /* CSVWriter.swift in Sources */, + 0E7E8CBE1D0BC9D70057A1C1 /* CSVReader.swift in Sources */, 0E0F16111D197DB800C92580 /* Endian.swift in Sources */, 0E7E8CBF1D0BC9D70057A1C1 /* CSVError.swift in Sources */, + 0EC629011EF675FF00289554 /* CSV.swift in Sources */, 0EA2AB7F1D183B45003EC967 /* BinaryReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -549,12 +579,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0E9317D41D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB811D183BA9003EC967 /* UnicodeIterator.swift in Sources */, - 0E9317D91D0DB30800AC20A0 /* CSV+subscript.swift in Sources */, - 0E7E8CE01D0BCA8E0057A1C1 /* CSV.swift in Sources */, + 0E5402251EDA82230019C3ED /* CSVWriter.swift in Sources */, + 0E7E8CE01D0BCA8E0057A1C1 /* CSVReader.swift in Sources */, 0E0F160E1D197DB800C92580 /* Endian.swift in Sources */, 0E7E8CE11D0BCA8E0057A1C1 /* CSVError.swift in Sources */, + 0EC628FE1EF675B300289554 /* CSV.swift in Sources */, 0EA2AB7C1D183B45003EC967 /* BinaryReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -563,11 +593,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0EA37ADF1DD8C12600F5B274 /* ReadmeTests.swift in Sources */, - 0EA37ADD1DD8C12600F5B274 /* CSVTests.swift in Sources */, - 0EA37AE01DD8C12600F5B274 /* TrimFieldsTests.swift in Sources */, - 0EA37AE11DD8C12600F5B274 /* UnicodeTests.swift in Sources */, - 0EA37ADE1DD8C12600F5B274 /* LineBreakTests.swift in Sources */, + 0EDF8ED91DDB73520068056A /* ReadmeTests.swift in Sources */, + 0EDF8ED71DDB73520068056A /* CSVTests.swift in Sources */, + 0EDF8EDA1DDB73520068056A /* TrimFieldsTests.swift in Sources */, + 0E7F657B1EF6437E00E1E1A0 /* Version1Tests.swift in Sources */, + 0EDF8EDB1DDB73520068056A /* UnicodeTests.swift in Sources */, + 0EDF8ED81DDB73520068056A /* LineBreakTests.swift in Sources */, + 0E54021B1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -575,12 +607,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0E9317D61D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB831D183BA9003EC967 /* UnicodeIterator.swift in Sources */, - 0E9317DB1D0DB30800AC20A0 /* CSV+subscript.swift in Sources */, - 0E7E8D001D0BCDCF0057A1C1 /* CSV.swift in Sources */, + 0E5402231EDA82220019C3ED /* CSVWriter.swift in Sources */, + 0E7E8D001D0BCDCF0057A1C1 /* CSVReader.swift in Sources */, 0E0F16101D197DB800C92580 /* Endian.swift in Sources */, 0E7E8D011D0BCDCF0057A1C1 /* CSVError.swift in Sources */, + 0EC629001EF675FE00289554 /* CSV.swift in Sources */, 0EA2AB7E1D183B45003EC967 /* BinaryReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -589,11 +621,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0EA37AE91DD8C12700F5B274 /* ReadmeTests.swift in Sources */, - 0EA37AE71DD8C12700F5B274 /* CSVTests.swift in Sources */, - 0EA37AEA1DD8C12700F5B274 /* TrimFieldsTests.swift in Sources */, - 0EA37AEB1DD8C12700F5B274 /* UnicodeTests.swift in Sources */, - 0EA37AE81DD8C12700F5B274 /* LineBreakTests.swift in Sources */, + 0EDF8EE31DDB73530068056A /* ReadmeTests.swift in Sources */, + 0EDF8EE11DDB73530068056A /* CSVTests.swift in Sources */, + 0EDF8EE41DDB73530068056A /* TrimFieldsTests.swift in Sources */, + 0E7F657D1EF6437E00E1E1A0 /* Version1Tests.swift in Sources */, + 0EDF8EE51DDB73530068056A /* UnicodeTests.swift in Sources */, + 0EDF8EE21DDB73530068056A /* LineBreakTests.swift in Sources */, + 0E54021D1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/README.md b/README.md index 3f447d2..50e5a68 100644 --- a/README.md +++ b/README.md @@ -2,30 +2,33 @@ [![Build Status](https://travis-ci.org/yaslab/CSV.swift.svg?branch=master)](https://travis-ci.org/yaslab/CSV.swift) -CSV reading library written in Swift. +CSV reading and writing library written in Swift. -## Usage +## Usage for reading CSV -### From CSV string +### From string ```swift import CSV -for row in try! CSV(string: "1,foo\n2,bar") { +let csvString = "1,foo\n2,bar" +let csv = try! CSVReader(string: csvString) +while let row = csv.next() { print("\(row)") - // => ["1", "foo"] - // => ["2", "bar"] } +// => ["1", "foo"] +// => ["2", "bar"] ``` -### From file path +### From file ```swift import Foundation import CSV let stream = InputStream(fileAtPath: "/path/to/file.csv")! -for row in try! CSV(stream: stream) { +let csv = try! CSVReader(stream: stream) +while let row = csv.next() { print("\(row)") } ``` @@ -35,18 +38,18 @@ for row in try! CSV(stream: stream) { ```swift import CSV -let csv = try! CSV( - string: "id,name\n1,foo\n2,bar", - hasHeaderRow: true) // default: false +let csvString = "id,name\n1,foo\n2,bar" +let csv = try! CSVReader(string: csvString, + hasHeaderRow: true) // It must be true. let headerRow = csv.headerRow! print("\(headerRow)") // => ["id", "name"] -for row in csv { +while let row = csv.next() { print("\(row)") - // => ["1", "foo"] - // => ["2", "bar"] } +// => ["1", "foo"] +// => ["2", "bar"] ``` ### Get the field value using subscript @@ -54,11 +57,11 @@ for row in csv { ```swift import CSV -var csv = try! CSV( - string: "id,name\n1,foo", - hasHeaderRow: true) // It must be true. +let csvString = "id,name\n1,foo" +let csv = try! CSVReader(string: csvString, + hasHeaderRow: true) // It must be true. -while let _ = csv.next() { +while csv.next() != nil { print("\(csv["id"]!)") // => "1" print("\(csv["name"]!)") // => "foo" } @@ -72,10 +75,57 @@ If you use a file path, you can provide the character encoding to initializer. import Foundation import CSV -let csv = try! CSV( - stream: InputStream(fileAtPath: "/path/to/file.csv")!, - codecType: UTF16.self, - endian: .big) +let stream = InputStream(fileAtPath: "/path/to/file.csv")! +let csv = try! CSV(stream: stream, + codecType: UTF16.self, + endian: .big) +``` + +## Usage for writing CSV + +### Write to memory and get a CSV String + +```swift +import Foundation +import CSV + +let stream = OutputStream(toMemory: ()) +let csv = try! CSVWriter(stream: stream) + +// Write a row +try! csv.write(row: ["id", "name"]) + +// Write fields separately +csv.beginNewRow() +try! csv.write(field: "1") +try! csv.write(field: "foo") +csv.beginNewRow() +try! csv.write(field: "2") +try! csv.write(field: "bar") + +csv.stream.close() + +// Get a String +let csvData = stream.property(forKey: .dataWrittenToMemoryStreamKey) as! NSData +let csvString = String(data: Data(referencing: csvData), encoding: .utf8)! +print(csvString) +// => "id,name\n1,foo\n2,bar" +``` + +### Write to file + +```swift +import Foundation +import CSV + +let stream = OutputStream(toFileAtPath: "/path/to/file.csv", append: false)! +let csv = try! CSVWriter(stream: stream) + +try! csv.write(row: ["id", "name"]) +try! csv.write(row: ["1", "foo"]) +try! csv.write(row: ["1", "bar"]) + +csv.stream.close() ``` ## Installation @@ -83,13 +133,13 @@ let csv = try! CSV( ### CocoaPods ```ruby -pod 'CSV.swift', '~> 1.1' +pod 'CSV.swift', '~> 2.0' ``` ### Carthage ``` -github "yaslab/CSV.swift" ~> 1.1 +github "yaslab/CSV.swift" ~> 2.0 ``` ### Swift Package Manager @@ -100,7 +150,7 @@ import PackageDescription let package = Package( name: "PackageName", dependencies: [ - .Package(url: "https://github.com/yaslab/CSV.swift.git", majorVersion: 1, minor: 1) + .Package(url: "https://github.com/yaslab/CSV.swift.git", majorVersion: 2, minor: 0) ] ) ``` diff --git a/Sources/BinaryReader.swift b/Sources/BinaryReader.swift old mode 100755 new mode 100644 index 1872b32..a48254b --- a/Sources/BinaryReader.swift +++ b/Sources/BinaryReader.swift @@ -45,13 +45,17 @@ internal class BinaryReader { private let endian: Endian private let closeOnDeinit: Bool - private var buffer = [UInt8](repeating: 0, count: 4) + private var buffer = malloc(4).assumingMemoryBound(to: UInt8.self) - private var tempBuffer = [UInt8](repeating: 0, count: 4) + private var tempBuffer = malloc(4).assumingMemoryBound(to: UInt8.self) private let tempBufferSize = 4 private var tempBufferOffset = 0 - - internal init(stream: InputStream, endian: Endian = .unknown, closeOnDeinit: Bool = true) throws { + + internal init( + stream: InputStream, + endian: Endian, + closeOnDeinit: Bool) throws { + var endian = endian if stream.streamStatus == .notOpen { @@ -61,8 +65,8 @@ internal class BinaryReader { throw CSVError.cannotOpenFile } - let readCount = stream.read(&tempBuffer, maxLength: tempBufferSize) - if let (e, l) = readBOM(buffer: &tempBuffer, length: readCount) { + let readCount = stream.read(tempBuffer, maxLength: tempBufferSize) + if let (e, l) = readBOM(buffer: tempBuffer, length: readCount) { if endian != .unknown && endian != e { throw CSVError.stringEndianMismatch } @@ -74,11 +78,13 @@ internal class BinaryReader { self.endian = endian self.closeOnDeinit = closeOnDeinit } - + deinit { if closeOnDeinit && stream.streamStatus != .closed { stream.close() } + free(buffer) + free(tempBuffer) } internal var hasBytesAvailable: Bool { @@ -101,10 +107,10 @@ internal class BinaryReader { } return stream.read(buffer + i, maxLength: maxLength - i) } - + internal func readUInt8() throws -> UInt8 { let bufferSize = 1 - let length = try readStream(&buffer, maxLength: bufferSize) + let length = try readStream(buffer, maxLength: bufferSize) if length < 0 { throw CSVError.streamErrorHasOccurred(error: stream.streamError!) } @@ -113,17 +119,17 @@ internal class BinaryReader { } return buffer[0] } - + internal func readUInt16() throws -> UInt16 { let bufferSize = 2 - let length = try readStream(&buffer, maxLength: bufferSize) + let length = try readStream(buffer, maxLength: bufferSize) if length < 0 { throw CSVError.streamErrorHasOccurred(error: stream.streamError!) } if length != bufferSize { throw CSVError.stringEncodingMismatch } - return try UnsafePointer(buffer).withMemoryRebound(to: UInt16.self, capacity: 1) { + return try buffer.withMemoryRebound(to: UInt16.self, capacity: 1) { switch endian { case .big: return UInt16(bigEndian: $0[0]) @@ -134,17 +140,17 @@ internal class BinaryReader { } } } - + internal func readUInt32() throws -> UInt32 { let bufferSize = 4 - let length = try readStream(&buffer, maxLength: bufferSize) + let length = try readStream(buffer, maxLength: bufferSize) if length < 0 { throw CSVError.streamErrorHasOccurred(error: stream.streamError!) } - if length != 4 { + if length != bufferSize { throw CSVError.stringEncodingMismatch } - return try UnsafePointer(buffer).withMemoryRebound(to: UInt32.self, capacity: 1) { + return try buffer.withMemoryRebound(to: UInt32.self, capacity: 1) { switch endian { case .big: return UInt32(bigEndian: $0[0]) @@ -155,33 +161,34 @@ internal class BinaryReader { } } } - + } extension BinaryReader { - internal struct UInt8Iterator: Sequence, IteratorProtocol { - + internal class UInt8Iterator: Sequence, IteratorProtocol { + private let reader: BinaryReader - + internal var errorHandler: ((Error) -> Void)? + fileprivate init(reader: BinaryReader) { self.reader = reader } - - internal mutating func next() -> UInt8? { + + internal func next() -> UInt8? { if !reader.hasBytesAvailable { return nil } do { return try reader.readUInt8() - } - catch { + } catch { + errorHandler?(error) return nil } } - + } - + internal func makeUInt8Iterator() -> UInt8Iterator { return UInt8Iterator(reader: self) } @@ -189,53 +196,55 @@ extension BinaryReader { } extension BinaryReader { - - internal struct UInt16Iterator: Sequence, IteratorProtocol { - + + internal class UInt16Iterator: Sequence, IteratorProtocol { + private let reader: BinaryReader - + internal var errorHandler: ((Error) -> Void)? + fileprivate init(reader: BinaryReader) { self.reader = reader } - - internal mutating func next() -> UInt16? { + + internal func next() -> UInt16? { if !reader.hasBytesAvailable { return nil } do { return try reader.readUInt16() - } - catch { + } catch { + errorHandler?(error) return nil } } } - + internal func makeUInt16Iterator() -> UInt16Iterator { return UInt16Iterator(reader: self) } - + } extension BinaryReader { - internal struct UInt32Iterator: Sequence, IteratorProtocol { + internal class UInt32Iterator: Sequence, IteratorProtocol { private let reader: BinaryReader + internal var errorHandler: ((Error) -> Void)? fileprivate init(reader: BinaryReader) { self.reader = reader } - internal mutating func next() -> UInt32? { + internal func next() -> UInt32? { if !reader.hasBytesAvailable { return nil } do { return try reader.readUInt32() - } - catch { + } catch { + errorHandler?(error) return nil } } diff --git a/Sources/CSV+init.swift b/Sources/CSV+init.swift deleted file mode 100755 index 31e3f8b..0000000 --- a/Sources/CSV+init.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// CSV+init.swift -// CSV -// -// Created by Yasuhiro Hatta on 2016/06/13. -// Copyright © 2016 yaslab. All rights reserved. -// - -import Foundation - -extension CSV { - - // TODO: Documentation - /// No overview available. - public init( - stream: InputStream, - hasHeaderRow: Bool = defaultHasHeaderRow, - trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter) - throws - { - try self.init(stream: stream, codecType: UTF8.self, hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter) - } - -} - -extension CSV { - - // TODO: Documentation - /// No overview available. - public init( - string: String, - hasHeaderRow: Bool = defaultHasHeaderRow, - trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter) - throws - { - let iterator = string.unicodeScalars.makeIterator() - try self.init(iterator: iterator, hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter) - } - -} diff --git a/Sources/CSV+subscript.swift b/Sources/CSV+subscript.swift deleted file mode 100755 index 0f4be01..0000000 --- a/Sources/CSV+subscript.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// CSV+subscript.swift -// CSV -// -// Created by Yasuhiro Hatta on 2016/06/13. -// Copyright © 2016 yaslab. All rights reserved. -// - -extension CSV { - - // TODO: Documentation - /// No overview available. - public subscript(key: String) -> String? { - get { - guard let headerRow = headerRow, let currentRow = currentRow else { - return nil - } - guard let index = headerRow.index(of: key) else { - return nil - } - if index >= currentRow.count { - return nil - } - return currentRow[index] - } - } - -} diff --git a/Sources/CSV.swift b/Sources/CSV.swift old mode 100755 new mode 100644 index 2d66557..61ee942 --- a/Sources/CSV.swift +++ b/Sources/CSV.swift @@ -6,255 +6,6 @@ // Copyright © 2016 yaslab. All rights reserved. // -import Foundation - -private let LF = UnicodeScalar("\n")! -private let CR = UnicodeScalar("\r")! -private let DQUOTE = UnicodeScalar("\"")! - -internal let defaultHasHeaderRow = false -internal let defaultTrimFields = false -internal let defaultDelimiter = UnicodeScalar(",")! +public typealias CSV = CSVReader extension CSV: Sequence { } - -extension CSV: IteratorProtocol { - - // TODO: Documentation - /// No overview available. - public mutating func next() -> [String]? { - return readRow() - } - -} - -// TODO: Documentation -/// No overview available. -public struct CSV { - - private var iterator: AnyIterator - private let trimFields: Bool - private let delimiter: UnicodeScalar - - private var back: UnicodeScalar? = nil - - internal var currentRow: [String]? = nil - - /// CSV header row. To set a value for this property, you set `true` to `hasHeaerRow` in initializer. - public var headerRow: [String]? { return _headerRow } - private var _headerRow: [String]? = nil - - private let whitespaces: CharacterSet - - internal init( - iterator: T, - hasHeaderRow: Bool, - trimFields: Bool, - delimiter: UnicodeScalar) - throws - where T.Element == UnicodeScalar - { - self.iterator = AnyIterator(iterator) - self.trimFields = trimFields - self.delimiter = delimiter - - var whitespaces = CharacterSet.whitespaces - whitespaces.remove(delimiter) - self.whitespaces = whitespaces - - if hasHeaderRow { - guard let headerRow = next() else { - throw CSVError.cannotReadHeaderRow - } - _headerRow = headerRow - } - } - - /// Create an instance with `InputStream`. - /// - /// - parameter stream: An `InputStream` object. If the stream is not open, initializer opens automatically. - /// - parameter codecType: A `UnicodeCodec` type for `stream`. - /// - parameter hasHeaderRow: `true` if the CSV has a header row, otherwise `false`. Default: `false`. - /// - parameter delimiter: Default: `","`. - public init( - stream: InputStream, - codecType: T.Type, - hasHeaderRow: Bool = defaultHasHeaderRow, - trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter) - throws - where T.CodeUnit == UInt8 - { - let reader = try BinaryReader(stream: stream, endian: .unknown, closeOnDeinit: true) - let iterator = UnicodeIterator(input: reader.makeUInt8Iterator(), inputEncodingType: codecType) - try self.init(iterator: iterator, hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter) - } - - /// Create an instance with `InputStream`. - /// - /// - parameter stream: An `InputStream` object. If the stream is not open, initializer opens automatically. - /// - parameter codecType: A `UnicodeCodec` type for `stream`. - /// - parameter endian: Endian to use when reading a stream. Default: `.big`. - /// - parameter hasHeaderRow: `true` if the CSV has a header row, otherwise `false`. Default: `false`. - /// - parameter delimiter: Default: `","`. - public init( - stream: InputStream, - codecType: T.Type, - endian: Endian = .big, - hasHeaderRow: Bool = defaultHasHeaderRow, - trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter) - throws - where T.CodeUnit == UInt16 - { - let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) - let iterator = UnicodeIterator(input: reader.makeUInt16Iterator(), inputEncodingType: codecType) - try self.init(iterator: iterator, hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter) - } - - /// Create an instance with `InputStream`. - /// - /// - parameter stream: An `InputStream` object. If the stream is not open, initializer opens automatically. - /// - parameter codecType: A `UnicodeCodec` type for `stream`. - /// - parameter endian: Endian to use when reading a stream. Default: `.big`. - /// - parameter hasHeaderRow: `true` if the CSV has a header row, otherwise `false`. Default: `false`. - /// - parameter delimiter: Default: `","`. - public init( - stream: InputStream, - codecType: T.Type, - endian: Endian = .big, - hasHeaderRow: Bool = defaultHasHeaderRow, - trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter) - throws - where T.CodeUnit == UInt32 - { - let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) - let iterator = UnicodeIterator(input: reader.makeUInt32Iterator(), inputEncodingType: codecType) - try self.init(iterator: iterator, hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter) - } - - fileprivate mutating func readRow() -> [String]? { - currentRow = nil - - var next = moveNext() - if next == nil { - return nil - } - - var row = [String]() - var field: String - var end: Bool - while true { - if trimFields { - // Trim the leading spaces - while next != nil && whitespaces.contains(next!) { - next = moveNext() - } - } - - if next == nil { - (field, end) = ("", true) - } - else if next == DQUOTE { - (field, end) = readField(quoted: true) - } - else { - back = next - (field, end) = readField(quoted: false) - - if trimFields { - // Trim the trailing spaces - field = field.trimmingCharacters(in: whitespaces) - } - } - row.append(field) - if end { - break - } - next = moveNext() - } - - currentRow = row - return row - } - - private mutating func readField(quoted: Bool) -> (String, Bool) { - var field = "" - - var next = moveNext() - while let c = next { - if quoted { - if c == DQUOTE { - var cNext = moveNext() - - if trimFields { - // Trim the trailing spaces - while cNext != nil && whitespaces.contains(cNext!) { - cNext = moveNext() - } - } - - if cNext == nil || cNext == CR || cNext == LF { - if cNext == CR { - let cNextNext = moveNext() - if cNextNext != LF { - back = cNextNext - } - } - // END ROW - return (field, true) - } - else if cNext == delimiter { - // END FIELD - return (field, false) - } - else if cNext == DQUOTE { - // ESC - field.append(String(DQUOTE)) - } - else { - // ERROR? - field.append(String(c)) - } - } - else { - field.append(String(c)) - } - } - else { - if c == CR || c == LF { - if c == CR { - let cNext = moveNext() - if cNext != LF { - back = cNext - } - } - // END ROW - return (field, true) - } - else if c == delimiter { - // END FIELD - return (field, false) - } - else { - field.append(String(c)) - } - } - - next = moveNext() - } - - // END FILE - return (field, true) - } - - private mutating func moveNext() -> UnicodeScalar? { - if back != nil { - defer { back = nil } - return back - } - return iterator.next() - } - -} diff --git a/Sources/CSVError.swift b/Sources/CSVError.swift index 45c283f..a2df4cc 100644 --- a/Sources/CSVError.swift +++ b/Sources/CSVError.swift @@ -6,21 +6,24 @@ // Copyright © 2016 yaslab. All rights reserved. // -// TODO: Documentation /// No overview available. public enum CSVError: Error { - + /// No overview available. case cannotOpenFile /// No overview available. case cannotReadFile /// No overview available. + case cannotWriteStream + /// No overview available. case streamErrorHasOccurred(error: Error) /// No overview available. + case unicodeDecoding + /// No overview available. case cannotReadHeaderRow /// No overview available. case stringEncodingMismatch /// No overview available. case stringEndianMismatch - + } diff --git a/Sources/CSVReader.swift b/Sources/CSVReader.swift new file mode 100644 index 0000000..5530373 --- /dev/null +++ b/Sources/CSVReader.swift @@ -0,0 +1,399 @@ +// +// CSVReader.swift +// CSV +// +// Created by Yasuhiro Hatta on 2016/06/11. +// Copyright © 2016 yaslab. All rights reserved. +// + +import Foundation + +internal let LF: UnicodeScalar = "\n" +internal let CR: UnicodeScalar = "\r" +internal let DQUOTE: UnicodeScalar = "\"" + +internal let DQUOTE_STR: String = "\"" +internal let DQUOTE2_STR: String = "\"\"" + +internal let defaultHasHeaderRow = false +internal let defaultTrimFields = false +internal let defaultDelimiter: UnicodeScalar = "," +internal let defaultWhitespaces = CharacterSet.whitespaces + +/// No overview available. +public class CSVReader { + + /// No overview available. + public struct Configuration { + + /// `true` if the CSV has a header row, otherwise `false`. Default: `false`. + public var hasHeaderRow: Bool + /// No overview available. + public var trimFields: Bool + /// Default: `","`. + public var delimiter: UnicodeScalar + /// No overview available. + public var whitespaces: CharacterSet + + /// No overview available. + internal init( + hasHeaderRow: Bool, + trimFields: Bool, + delimiter: UnicodeScalar, + whitespaces: CharacterSet) { + + self.hasHeaderRow = hasHeaderRow + self.trimFields = trimFields + self.delimiter = delimiter + + var whitespaces = whitespaces + _ = whitespaces.remove(delimiter) + self.whitespaces = whitespaces + } + + } + + fileprivate var iterator: AnyIterator + public let configuration: Configuration + 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 + + /// CSV header row. To set a value for this property, + /// you set `true` to `headerRow` in initializer. + public private (set) var headerRow: [String]? + + public fileprivate (set) var currentRow: [String]? + + internal init( + iterator: T, + configuration: Configuration + ) throws where T.Element == UnicodeScalar { + + self.iterator = AnyIterator(iterator) + self.configuration = configuration + + if configuration.hasHeaderRow { + guard let headerRow = readRow() else { + throw CSVError.cannotReadHeaderRow + } + self.headerRow = headerRow + } + } + +} + +extension CSVReader { + + /// Create an instance with `InputStream`. + /// + /// - parameter stream: An `InputStream` object. If the stream is not open, + /// initializer opens automatically. + /// - parameter codecType: A `UnicodeCodec` type for `stream`. + /// - parameter hasHeaderRow: `true` if the CSV has a header row, otherwise `false`. Default: `false`. + /// - parameter delimiter: Default: `","`. + public convenience init( + stream: InputStream, + codecType: T.Type, + hasHeaderRow: Bool = defaultHasHeaderRow, + trimFields: Bool = defaultTrimFields, + delimiter: UnicodeScalar = defaultDelimiter, + whitespaces: CharacterSet = defaultWhitespaces + ) throws where T.CodeUnit == UInt8 { + + let reader = try BinaryReader(stream: stream, endian: .unknown, closeOnDeinit: true) + let input = reader.makeUInt8Iterator() + let iterator = UnicodeIterator(input: input, inputEncodingType: codecType) + let config = Configuration(hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter, + whitespaces: whitespaces) + try self.init(iterator: iterator, configuration: config) + input.errorHandler = { [unowned self] in self.errorHandler(error: $0) } + iterator.errorHandler = { [unowned self] in self.errorHandler(error: $0) } + } + + /// Create an instance with `InputStream`. + /// + /// - parameter stream: An `InputStream` object. If the stream is not open, + /// initializer opens automatically. + /// - parameter codecType: A `UnicodeCodec` type for `stream`. + /// - parameter endian: Endian to use when reading a stream. Default: `.big`. + /// - parameter hasHeaderRow: `true` if the CSV has a header row, otherwise `false`. Default: `false`. + /// - parameter delimiter: Default: `","`. + public convenience init( + stream: InputStream, + codecType: T.Type, + endian: Endian = .big, + hasHeaderRow: Bool = defaultHasHeaderRow, + trimFields: Bool = defaultTrimFields, + delimiter: UnicodeScalar = defaultDelimiter, + whitespaces: CharacterSet = defaultWhitespaces + ) throws where T.CodeUnit == UInt16 { + + let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) + let input = reader.makeUInt16Iterator() + let iterator = UnicodeIterator(input: input, inputEncodingType: codecType) + let config = Configuration(hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter, + whitespaces: whitespaces) + try self.init(iterator: iterator, configuration: config) + input.errorHandler = { [unowned self] in self.errorHandler(error: $0) } + iterator.errorHandler = { [unowned self] in self.errorHandler(error: $0) } + } + + /// Create an instance with `InputStream`. + /// + /// - parameter stream: An `InputStream` object. If the stream is not open, + /// initializer opens automatically. + /// - parameter codecType: A `UnicodeCodec` type for `stream`. + /// - parameter endian: Endian to use when reading a stream. Default: `.big`. + /// - parameter hasHeaderRow: `true` if the CSV has a header row, otherwise `false`. Default: `false`. + /// - parameter delimiter: Default: `","`. + public convenience init( + stream: InputStream, + codecType: T.Type, + endian: Endian = .big, + hasHeaderRow: Bool = defaultHasHeaderRow, + trimFields: Bool = defaultTrimFields, + delimiter: UnicodeScalar = defaultDelimiter, + whitespaces: CharacterSet = defaultWhitespaces + ) throws where T.CodeUnit == UInt32 { + + let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) + let input = reader.makeUInt32Iterator() + let iterator = UnicodeIterator(input: input, inputEncodingType: codecType) + let config = Configuration(hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter, + whitespaces: whitespaces) + try self.init(iterator: iterator, configuration: config) + input.errorHandler = { [unowned self] in self.errorHandler(error: $0) } + iterator.errorHandler = { [unowned self] in self.errorHandler(error: $0) } + } + + /// Create an instance with `InputStream`. + /// + /// - parameter stream: An `InputStream` object. If the stream is not open, + /// initializer opens automatically. + /// - parameter hasHeaderRow: `true` if the CSV has a header row, otherwise `false`. Default: `false`. + /// - parameter delimiter: Default: `","`. + public convenience init( + stream: InputStream, + hasHeaderRow: Bool = defaultHasHeaderRow, + trimFields: Bool = defaultTrimFields, + delimiter: UnicodeScalar = defaultDelimiter, + whitespaces: CharacterSet = defaultWhitespaces + ) throws { + + try self.init( + stream: stream, + codecType: UTF8.self, + hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter, + whitespaces: whitespaces) + } + + /// Create an instance with CSV string. + /// + /// - parameter string: An CSV string. + /// - parameter hasHeaderRow: `true` if the CSV has a header row, otherwise `false`. Default: `false`. + /// - parameter delimiter: Default: `","`. + public convenience init( + string: String, + hasHeaderRow: Bool = defaultHasHeaderRow, + trimFields: Bool = defaultTrimFields, + delimiter: UnicodeScalar = defaultDelimiter, + whitespaces: CharacterSet = defaultWhitespaces + ) throws { + + let iterator = string.unicodeScalars.makeIterator() + let config = Configuration(hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter, + whitespaces: whitespaces) + try self.init(iterator: iterator, configuration: config) + } + + private func errorHandler(error: Error) { + //configuration.fileInputErrorHandler?(error, currentRowIndex, currentFieldIndex) + self.error = error + } + +} + +// MARK: - Parse CSV + +extension CSVReader { + + fileprivate func readRow() -> [String]? { + currentFieldIndex = 0 + + var c = moveNext() + if c == nil { + return nil + } + + var row = [String]() + var field: String + var end: Bool + while true { + if configuration.trimFields { + // Trim the leading spaces + while c != nil && configuration.whitespaces.contains(c!) { + c = moveNext() + } + } + + if c == nil { + (field, end) = ("", true) + } else if c == DQUOTE { + (field, end) = readField(quoted: true) + } else { + back = c + (field, end) = readField(quoted: false) + + if configuration.trimFields { + // Trim the trailing spaces + field = field.trimmingCharacters(in: configuration.whitespaces) + } + } + row.append(field) + if end { + break + } + + currentFieldIndex += 1 + + c = moveNext() + } + + currentRowIndex += 1 + + currentRow = row + return row + } + + private func readField(quoted: Bool) -> (String, Bool) { + fieldBuffer.removeAll(keepingCapacity: true) + + while let c = moveNext() { + if quoted { + if c == DQUOTE { + var cNext = moveNext() + + if configuration.trimFields { + // Trim the trailing spaces + while cNext != nil && configuration.whitespaces.contains(cNext!) { + cNext = moveNext() + } + } + + if cNext == nil || cNext == CR || cNext == LF { + if cNext == CR { + let cNextNext = moveNext() + if cNextNext != LF { + back = cNextNext + } + } + // END ROW + return (String(fieldBuffer), true) + } else if cNext == configuration.delimiter { + // END FIELD + return (String(fieldBuffer), false) + } else if cNext == DQUOTE { + // ESC + fieldBuffer.append(DQUOTE) + } else { + // ERROR? + fieldBuffer.append(c) + } + } else { + fieldBuffer.append(c) + } + } else { + if c == CR || c == LF { + if c == CR { + let cNext = moveNext() + if cNext != LF { + back = cNext + } + } + // END ROW + return (String(fieldBuffer), true) + } else if c == configuration.delimiter { + // END FIELD + return (String(fieldBuffer), false) + } else { + fieldBuffer.append(c) + } + } + } + + // END FILE + return (String(fieldBuffer), true) + } + + private func moveNext() -> UnicodeScalar? { + if back != nil { + defer { + back = nil + } + return back + } + return iterator.next() + } + +} + +//extension CSVReader { +// +// public func enumerateRows(_ block: (([String], [String]?, inout Bool) throws -> Void)) throws { +// var stop = false +// while let row = readRow() { +// try block(row, headerRow, &stop) +// if stop { +// break +// } +// } +// if let error = error { +// throw error +// } +// } +// +//} + +extension CSVReader: IteratorProtocol { + + @discardableResult + public func next() -> [String]? { + return readRow() + } + +} + +extension CSVReader { + + public subscript(key: String) -> String? { + guard let header = headerRow else { + fatalError("CSVReader.headerRow must not be nil") + } + guard let index = header.index(of: key) else { + return nil + } + guard let row = currentRow else { + fatalError("CSVReader.currentRow must not be nil") + } + if index >= row.count { + return nil + } + return row[index] + } + +} diff --git a/Sources/CSVVersion.h b/Sources/CSVVersion.h old mode 100755 new mode 100644 diff --git a/Sources/CSVWriter.swift b/Sources/CSVWriter.swift new file mode 100644 index 0000000..d538e04 --- /dev/null +++ b/Sources/CSVWriter.swift @@ -0,0 +1,202 @@ +// +// CSVWriter.swift +// CSV +// +// Created by Yasuhiro Hatta on 2017/05/28. +// Copyright © 2017 yaslab. All rights reserved. +// + +import Foundation + +public class CSVWriter { + + public struct Configuration { + + public var delimiter: String + public var newline: String + + internal init(delimiter: String, newline: Newline) { + self.delimiter = delimiter + + switch newline { + case .lf: self.newline = String(LF) + case .crlf: self.newline = String(CR) + String(LF) + } + } + + } + + public enum Newline { + + /// "\n" + case lf + /// "\r\n" + case crlf + + } + + public let stream: OutputStream + public let configuration: Configuration + fileprivate let writeScalar: ((UnicodeScalar) throws -> Void) + + fileprivate var isFirstRow: Bool = true + fileprivate var isFirstField: Bool = true + + fileprivate init( + stream: OutputStream, + configuration: Configuration, + writeScalar: @escaping ((UnicodeScalar) throws -> Void)) throws { + + self.stream = stream + self.configuration = configuration + self.writeScalar = writeScalar + + if stream.streamStatus == .notOpen { + stream.open() + } + if stream.streamStatus != .open { + throw CSVError.cannotOpenFile + } + } + + deinit { + if stream.streamStatus == .open { + stream.close() + } + } + +} + +extension CSVWriter { + + public convenience init( + stream: OutputStream, + delimiter: String = String(defaultDelimiter), + newline: Newline = .lf + ) throws { + + try self.init(stream: stream, codecType: UTF8.self, delimiter: delimiter, newline: newline) + } + + public convenience init( + stream: OutputStream, + codecType: T.Type, + delimiter: String = String(defaultDelimiter), + newline: Newline = .lf + ) throws where T.CodeUnit == UInt8 { + + let config = Configuration(delimiter: delimiter, newline: newline) + try self.init(stream: stream, configuration: config) { (scalar: UnicodeScalar) throws in + var error: CSVError? = nil + codecType.encode(scalar) { (code: UInt8) in + var code = code + let count = stream.write(&code, maxLength: 1) + if count != 1 { + error = CSVError.cannotWriteStream + } + } + if let error = error { + throw error + } + } + } + + public convenience init( + stream: OutputStream, + codecType: T.Type, + endian: Endian = .big, + delimiter: String = String(defaultDelimiter), + newline: Newline = .lf + ) throws where T.CodeUnit == UInt16 { + + let config = Configuration(delimiter: delimiter, newline: newline) + try self.init(stream: stream, configuration: config) { (scalar: UnicodeScalar) throws in + var error: CSVError? = nil + codecType.encode(scalar) { (code: UInt16) in + var code = (endian == .big) ? code.bigEndian : code.littleEndian + withUnsafeBytes(of: &code) { (buffer) -> Void in + let count = stream.write(buffer.baseAddress!.assumingMemoryBound(to: UInt8.self), + maxLength: buffer.count) + if count != buffer.count { + error = CSVError.cannotWriteStream + } + } + } + if let error = error { + throw error + } + } + } + + public convenience init( + stream: OutputStream, + codecType: T.Type, + endian: Endian = .big, + delimiter: String = String(defaultDelimiter), + newline: Newline = .lf + ) throws where T.CodeUnit == UInt32 { + + let config = Configuration(delimiter: delimiter, newline: newline) + try self.init(stream: stream, configuration: config) { (scalar: UnicodeScalar) throws in + var error: CSVError? = nil + codecType.encode(scalar) { (code: UInt32) in + var code = (endian == .big) ? code.bigEndian : code.littleEndian + withUnsafeBytes(of: &code) { (buffer) -> Void in + let count = stream.write(buffer.baseAddress!.assumingMemoryBound(to: UInt8.self), + maxLength: buffer.count) + if count != buffer.count { + error = CSVError.cannotWriteStream + } + } + } + if let error = error { + throw error + } + } + } + +} + +extension CSVWriter { + + public func beginNewRow() { + isFirstField = true + } + + public func write(field value: String, quoted: Bool = false) throws { + if isFirstRow { + isFirstRow = false + } else { + if isFirstField { + try configuration.newline.unicodeScalars.forEach(writeScalar) + } + } + + if isFirstField { + isFirstField = false + } else { + try configuration.delimiter.unicodeScalars.forEach(writeScalar) + } + + var value = value + + if quoted { + value = value.replacingOccurrences(of: DQUOTE_STR, with: DQUOTE2_STR) + try writeScalar(DQUOTE) + } + + try value.unicodeScalars.forEach(writeScalar) + + if quoted { + try writeScalar(DQUOTE) + } + } + + public func write(row values: [String], quotedAtIndex: ((Int) -> Bool) = { _ in false }) throws { + beginNewRow() + for (i, value) in values.enumerated() { + try write(field: value, quoted: quotedAtIndex(i)) + } + } + +} diff --git a/Sources/Endian.swift b/Sources/Endian.swift index c5a207a..51c94e3 100644 --- a/Sources/Endian.swift +++ b/Sources/Endian.swift @@ -6,15 +6,14 @@ // Copyright © 2016 yaslab. All rights reserved. // -// TODO: Documentation -/// No overview available. +/// Represents byte order. public enum Endian { - - /// No overview available. + + /// Big endian. case big - /// No overview available. + /// Little endian. case little - /// No overview available. + /// Multibyte character sets. case unknown - + } diff --git a/Sources/Info.plist b/Sources/Info.plist index 3034b22..7e7479f 100644 --- a/Sources/Info.plist +++ b/Sources/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.1.2 + 2.0.0 CFBundleSignature ???? CFBundleVersion diff --git a/Sources/UnicodeIterator.swift b/Sources/UnicodeIterator.swift old mode 100755 new mode 100644 index 71c330d..9ac798a --- a/Sources/UnicodeIterator.swift +++ b/Sources/UnicodeIterator.swift @@ -6,26 +6,31 @@ // Copyright © 2016 yaslab. All rights reserved. // -internal struct UnicodeIterator< +internal class UnicodeIterator< Input: IteratorProtocol, InputEncoding: UnicodeCodec> : IteratorProtocol where InputEncoding.CodeUnit == Input.Element { - + private var input: Input private var inputEncoding: InputEncoding - + internal var errorHandler: ((Error) -> Void)? + internal init(input: Input, inputEncodingType: InputEncoding.Type) { self.input = input self.inputEncoding = inputEncodingType.init() } - - internal mutating func next() -> UnicodeScalar? { + + internal func next() -> UnicodeScalar? { switch inputEncoding.decode(&input) { - case .scalarValue(let c): return c - case .emptyInput: return nil - case .error: return nil + case .scalarValue(let c): + return c + case .emptyInput: + return nil + case .error: + errorHandler?(CSVError.unicodeDecoding) + return nil } } - + } diff --git a/Tests/CSVTests/CSVTests.swift b/Tests/CSVTests/CSVTests.swift old mode 100755 new mode 100644 index eb04e67..a19e97a --- a/Tests/CSVTests/CSVTests.swift +++ b/Tests/CSVTests/CSVTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import CSV class CSVTests: XCTestCase { - + static let allTests = [ ("testOneLine", testOneLine), ("testTwoLines", testTwoLines), @@ -22,44 +22,50 @@ class CSVTests: XCTestCase { ("testEscapedQuotationMark2", testEscapedQuotationMark2), ("testEmptyField", testEmptyField), ("testDoubleQuoteBeforeLineBreak", testDoubleQuoteBeforeLineBreak), - ("testSubscript", testSubscript), - ("testCSVState1", testCSVState1) + ("testCSVState1", testCSVState1), + ("testSubscriptInt", testSubscriptInt), + ("testSubscriptString1", testSubscriptString1), + ("testSubscriptString2", testSubscriptString2), + ("testToArray", testToArray) + //("testToDictionary1", testToDictionary1), + //("testToDictionary2", testToDictionary2) ] - + func testOneLine() { let csv = "\"abc\",1,2" var i = 0 - for row in try! CSV(string: csv) { + + for record in AnyIterator(try! CSVReader(string: csv)) { switch i { - case 0: XCTAssertEqual(row, ["abc", "1", "2"]) + case 0: XCTAssertEqual(record, ["abc", "1", "2"]) default: break } i += 1 } XCTAssertEqual(i, 1) } - + func testTwoLines() { let csv = "\"abc\",1,2\n\"cde\",3,4" var i = 0 - for row in try! CSV(string: csv) { + for record in AnyIterator(try! CSVReader(string: csv)) { switch i { - case 0: XCTAssertEqual(row, ["abc", "1", "2"]) - case 1: XCTAssertEqual(row, ["cde", "3", "4"]) + case 0: XCTAssertEqual(record, ["abc", "1", "2"]) + case 1: XCTAssertEqual(record, ["cde", "3", "4"]) default: break } i += 1 } XCTAssertEqual(i, 2) } - + func testLastLineIsEmpty() { let csv = "\"abc\",1,2\n\"cde\",3,4\n" var i = 0 - for row in try! CSV(string: csv) { + for record in AnyIterator(try! CSVReader(string: csv)) { switch i { - case 0: XCTAssertEqual(row, ["abc", "1", "2"]) - case 1: XCTAssertEqual(row, ["cde", "3", "4"]) + case 0: XCTAssertEqual(record, ["abc", "1", "2"]) + case 1: XCTAssertEqual(record, ["cde", "3", "4"]) default: break } i += 1 @@ -70,11 +76,11 @@ class CSVTests: XCTestCase { func testLastLineIsWhiteSpace() { let csv = "\"abc\",1,2\n\"cde\",3,4\n " var i = 0 - for row in try! CSV(string: csv) { + for record in AnyIterator(try! CSVReader(string: csv)) { switch i { - case 0: XCTAssertEqual(row, ["abc", "1", "2"]) - case 1: XCTAssertEqual(row, ["cde", "3", "4"]) - case 2: XCTAssertEqual(row, [" "]) + case 0: XCTAssertEqual(record, ["abc", "1", "2"]) + case 1: XCTAssertEqual(record, ["cde", "3", "4"]) + case 2: XCTAssertEqual(record, [" "]) default: break } i += 1 @@ -85,60 +91,60 @@ class CSVTests: XCTestCase { func testMiddleLineIsEmpty() { let csv = "\"abc\",1,2\n\n\"cde\",3,4" var i = 0 - for row in try! CSV(string: csv) { + for record in AnyIterator(try! CSVReader(string: csv)) { switch i { - case 0: XCTAssertEqual(row, ["abc", "1", "2"]) - case 1: XCTAssertEqual(row, [""]) - case 2: XCTAssertEqual(row, ["cde", "3", "4"]) + case 0: XCTAssertEqual(record, ["abc", "1", "2"]) + case 1: XCTAssertEqual(record, [""]) + case 2: XCTAssertEqual(record, ["cde", "3", "4"]) default: break } i += 1 } XCTAssertEqual(i, 3) } - + func testCommaInQuotationMarks() { let csvString = "abab,\"cd,cd\",efef" - var csv = try! CSV(string: csvString) - let row = csv.next()! - XCTAssertEqual(row, ["abab", "cd,cd", "efef"]) + let csv = try! CSVReader(string: csvString) + let record = csv.next()! + XCTAssertEqual(record, ["abab", "cd,cd", "efef"]) } - + func testEscapedQuotationMark1() { let csvString = "abab,\"\"\"cdcd\",efef\r\nzxcv,asdf,qwer" - var csv = try! CSV(string: csvString) - var row = csv.next()! - XCTAssertEqual(row, ["abab", "\"cdcd", "efef"]) - row = csv.next()! - XCTAssertEqual(row, ["zxcv", "asdf", "qwer"]) + let csv = try! CSVReader(string: csvString) + var record = csv.next()! + XCTAssertEqual(record, ["abab", "\"cdcd", "efef"]) + record = csv.next()! + XCTAssertEqual(record, ["zxcv", "asdf", "qwer"]) } - + func testEscapedQuotationMark2() { let csvString = "abab,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\"" - var csv = try! CSV(string: csvString) - var row = csv.next()! - XCTAssertEqual(row, ["abab", "cdcd", "efef"]) - row = csv.next()! - XCTAssertEqual(row, ["zxcv", "asdf", "qw\"er"]) + let csv = try! CSVReader(string: csvString) + var record = csv.next()! + XCTAssertEqual(record, ["abab", "cdcd", "efef"]) + record = csv.next()! + XCTAssertEqual(record, ["zxcv", "asdf", "qw\"er"]) } - + func testEmptyField() { let csvString = "abab,,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\"," - var csv = try! CSV(string: csvString) - var row = csv.next()! - XCTAssertEqual(row, ["abab", "", "cdcd", "efef"]) - row = csv.next()! - XCTAssertEqual(row, ["zxcv", "asdf", "qw\"er", ""]) + let csv = try! CSVReader(string: csvString) + var record = csv.next()! + XCTAssertEqual(record, ["abab", "", "cdcd", "efef"]) + record = csv.next()! + XCTAssertEqual(record, ["zxcv", "asdf", "qw\"er", ""]) } - + func testDoubleQuoteBeforeLineBreak() { let csv = "\"abc\",1,\"2\"\n\n\"cde\",3,\"4\"" var i = 0 - for row in try! CSV(string: csv) { + for record in AnyIterator(try! CSVReader(string: csv)) { switch i { - case 0: XCTAssertEqual(row, ["abc", "1", "2"]) - case 1: XCTAssertEqual(row, [""]) - case 2: XCTAssertEqual(row, ["cde", "3", "4"]) + case 0: XCTAssertEqual(record, ["abc", "1", "2"]) + case 1: XCTAssertEqual(record, [""]) + case 2: XCTAssertEqual(record, ["cde", "3", "4"]) default: break } i += 1 @@ -146,38 +152,84 @@ class CSVTests: XCTestCase { XCTAssertEqual(i, 3) } - func testSubscript() { - let csvString = "id,name\n001,hoge\n002,fuga" - var csv = try! CSV(string: csvString, hasHeaderRow: true) - var i = 0 - while csv.next() != nil { - switch i { - case 0: - XCTAssertEqual(csv["id"], "001") - XCTAssertEqual(csv["name"], "hoge") - case 1: - XCTAssertEqual(csv["id"], "002") - XCTAssertEqual(csv["name"], "fuga") - default: - break - } - i += 1 - } - XCTAssertEqual(i, 2) - } - func testCSVState1() { let it = "あ,い1,\"う\",えお\n,,x,".unicodeScalars.makeIterator() - var csv = try! CSV(iterator: it, hasHeaderRow: defaultHasHeaderRow, trimFields: defaultTrimFields, delimiter: defaultDelimiter) - - var rows = [[String]]() - - while let row = csv.next() { - rows.append(row) + let config = CSVReader.Configuration(hasHeaderRow: false, + trimFields: false, + delimiter: ",", + whitespaces: .whitespaces) + let csv = try! CSVReader(iterator: it, configuration: config) + + var records = [[String]]() + + while let record = csv.next() { + records.append(record) } - XCTAssertEqual(rows.count, 2) - XCTAssertEqual(rows[0], ["あ", "い1", "う", "えお"]) - XCTAssertEqual(rows[1], ["", "", "x", ""]) + XCTAssertEqual(records.count, 2) + XCTAssertEqual(records[0], ["あ", "い1", "う", "えお"]) + XCTAssertEqual(records[1], ["", "", "x", ""]) } + func testSubscriptInt() { + let csvString = "a,bb,ccc" + let csv = try! CSVReader(string: csvString) + for record in AnyIterator(csv) { + XCTAssertEqual(record[0], "a") + XCTAssertEqual(record[1], "bb") + XCTAssertEqual(record[2], "ccc") + } + } + + func testSubscriptString1() { + let csvString = "key1,key2\nvalue1,value2" + let csv = try! CSVReader(string: csvString, hasHeaderRow: true) + csv.next() + XCTAssertEqual(csv["key1"], "value1") + XCTAssertEqual(csv["key2"], "value2") + XCTAssertNil(csv["key9"]) + } + + func testSubscriptString2() { + let csvString = "key1,key2\nvalue1" + let csv = try! CSVReader(string: csvString, hasHeaderRow: true) + csv.next() + XCTAssertEqual(csv["key1"], "value1") + XCTAssertNil(csv["key2"]) + XCTAssertNil(csv["key9"]) + } + + func testToArray() { + let csvString = "1,2,3,4,5\n6,7,8,9,0" + let csv = try! CSVReader(string: csvString) + let records = AnyIterator(csv).map { $0 } + XCTAssertEqual(records[0], ["1", "2", "3", "4", "5"]) + XCTAssertEqual(records[1], ["6", "7", "8", "9", "0"]) + } + +// func testToDictionary1() { +// let csvString = "id,name\n1,name1\n2,name2" +// let config = CSVReader.Configuration(hasHeaderRow: true) +// let csv = try! CSVReader(string: csvString, configuration: config) +// let rows = AnyIterator(csv).map { $0.toDictionary() } +// XCTAssertEqual(rows[0]["id"], "1") +// XCTAssertEqual(rows[0]["name"], "name1") +// XCTAssertNil(rows[0]["xxx"]) +// XCTAssertEqual(rows[1]["id"], "2") +// XCTAssertEqual(rows[1]["name"], "name2") +// XCTAssertNil(rows[1]["yyy"]) +// } + +// func testToDictionary2() { +// let csvString = "id,name,id\n1,name1,11\n2,name2,22" +// let config = CSVReader.Configuration(hasHeaderRow: true) +// let csv = try! CSVReader(string: csvString, configuration: config) +// let rows = AnyIterator(csv).map { $0.toDictionary() } +// XCTAssertEqual(rows[0]["id"], "1") +// XCTAssertEqual(rows[0]["name"], "name1") +// XCTAssertNil(rows[0]["xxx"]) +// XCTAssertEqual(rows[1]["id"], "2") +// XCTAssertEqual(rows[1]["name"], "name2") +// XCTAssertNil(rows[1]["yyy"]) +// } + } diff --git a/Tests/CSVTests/CSVWriterTests.swift b/Tests/CSVTests/CSVWriterTests.swift new file mode 100644 index 0000000..f16e69b --- /dev/null +++ b/Tests/CSVTests/CSVWriterTests.swift @@ -0,0 +1,268 @@ +// +// CSVWriterTests.swift +// CSV +// +// Created by Yasuhiro Hatta on 2017/05/28. +// Copyright © 2017年 yaslab. All rights reserved. +// + +import Foundation +import XCTest + +import CSV + +extension OutputStream { + + var data: Data? { + guard let nsData = property(forKey: .dataWrittenToMemoryStreamKey) as? NSData else { + return nil + } + return Data(referencing: nsData) + } + +} + +class CSVWriterTests: XCTestCase { + + static let allTests = [ + ("testSingleFieldSingleRecord", testSingleFieldSingleRecord), + ("testSingleFieldMultipleRecord", testSingleFieldMultipleRecord), + ("testMultipleFieldSingleRecord", testMultipleFieldSingleRecord), + ("testMultipleFieldMultipleRecord", testMultipleFieldMultipleRecord), + ("testQuoted", testQuoted), + ("testQuotedNewline", testQuotedNewline), + ("testEscapeQuote", testEscapeQuote), + ("testDelimiter", testDelimiter), + ("testNewline", testNewline), + ("testUTF16BE", testUTF16BE), + ("testUTF16LE", testUTF16LE), + ("testUTF32BE", testUTF32BE), + ("testUTF32LE", testUTF32LE) + ] + + let str = "TEST-test-1234-😄😆👨‍👩‍👧‍👦" + + /// xxxx + func testSingleFieldSingleRecord() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = try! CSVWriter(stream: stream) + csv.beginNewRow() + try! csv.write(field: str) + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, str) + } + + /// xxxx + /// xxxx + func testSingleFieldMultipleRecord() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = try! CSVWriter(stream: stream) + csv.beginNewRow() + try! csv.write(field: str + "-1") + csv.beginNewRow() + try! csv.write(field: str + "-2") + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, "\(str)-1\n\(str)-2") + } + + /// xxxx,xxxx + func testMultipleFieldSingleRecord() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = try! CSVWriter(stream: stream) + csv.beginNewRow() + try! csv.write(field: str + "-1") + try! csv.write(field: str + "-2") + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, "\(str)-1,\(str)-2") + } + + /// xxxx,xxxx + /// xxxx,xxxx + func testMultipleFieldMultipleRecord() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = try! CSVWriter(stream: stream) + csv.beginNewRow() + try! csv.write(field: str + "-1-1") + try! csv.write(field: str + "-1-2") + csv.beginNewRow() + try! csv.write(field: str + "-2-1") + try! csv.write(field: str + "-2-2") + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, "\(str)-1-1,\(str)-1-2\n\(str)-2-1,\(str)-2-2") + } + + /// "xxxx",xxxx + func testQuoted() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = try! CSVWriter(stream: stream) + csv.beginNewRow() + try! csv.write(field: str + "-1", quoted: true) + try! csv.write(field: str + "-2") // quoted: false + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, "\"\(str)-1\",\(str)-2") + } + + /// xxxx,"xx\nxx" + func testQuotedNewline() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = try! CSVWriter(stream: stream) + csv.beginNewRow() + try! csv.write(field: str + "-1") // quoted: false + try! csv.write(field: str + "-\n-2", quoted: true) + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, "\(str)-1,\"\(str)-\n-2\"") + } + + /// xxxx,"xx""xx" + func testEscapeQuote() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = try! CSVWriter(stream: stream) + csv.beginNewRow() + try! csv.write(field: str + "-1") // quoted: false + try! csv.write(field: str + "-\"-2", quoted: true) + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, "\(str)-1,\"\(str)-\"\"-2\"") + } + + /// Test delimiter: "\t" + func testDelimiter() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = try! CSVWriter.init(stream: stream, delimiter: "\t") + csv.beginNewRow() + try! csv.write(field: str + "-1") + try! csv.write(field: str + "-2") + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, "\(str)-1\t\(str)-2") + } + + /// Test newline: "\r\n" + func testNewline() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = try! CSVWriter.init(stream: stream, newline: .crlf) + csv.beginNewRow() + try! csv.write(field: str + "-1") + csv.beginNewRow() + try! csv.write(field: str + "-2") + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, "\(str)-1\r\n\(str)-2") + } + + /// UTF16 Big Endian + func testUTF16BE() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = try! CSVWriter(stream: stream, codecType: UTF16.self, endian: .big) + csv.beginNewRow() + try! csv.write(field: str) + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf16BigEndian)! + + XCTAssertEqual(csvStr, str) + } + + /// UTF16 Little Endian + func testUTF16LE() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = try! CSVWriter(stream: stream, codecType: UTF16.self, endian: .little) + csv.beginNewRow() + try! csv.write(field: str) + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf16LittleEndian)! + + XCTAssertEqual(csvStr, str) + } + + /// UTF32 Big Endian + func testUTF32BE() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = try! CSVWriter(stream: stream, codecType: UTF32.self, endian: .big) + csv.beginNewRow() + try! csv.write(field: str) + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf32BigEndian)! + + XCTAssertEqual(csvStr, str) + } + + /// UTF32 Little Endian + func testUTF32LE() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = try! CSVWriter(stream: stream, codecType: UTF32.self, endian: .little) + csv.beginNewRow() + try! csv.write(field: str) + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf32LittleEndian)! + + XCTAssertEqual(csvStr, str) + } + +} diff --git a/Tests/CSVTests/LineBreakTests.swift b/Tests/CSVTests/LineBreakTests.swift index c68f8e6..8186137 100644 --- a/Tests/CSVTests/LineBreakTests.swift +++ b/Tests/CSVTests/LineBreakTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import CSV class LineBreakTests: XCTestCase { - + static let allTests = [ ("testLF", testLF), ("testCRLF", testCRLF), @@ -79,7 +79,7 @@ class LineBreakTests: XCTestCase { XCTAssertEqual(records[0], ["qwe", "asd"]) XCTAssertEqual(records[1], ["zxc", "rty"]) } - + func testLineBreakCR() { let csv = "qwe,asd\rzxc,rty" let records = parse(csv: csv) @@ -87,7 +87,7 @@ class LineBreakTests: XCTestCase { XCTAssertEqual(records[0], ["qwe", "asd"]) XCTAssertEqual(records[1], ["zxc", "rty"]) } - + func testLineBreakCRLF() { let csv = "qwe,asd\r\nzxc,rty" let records = parse(csv: csv) @@ -95,7 +95,7 @@ class LineBreakTests: XCTestCase { XCTAssertEqual(records[0], ["qwe", "asd"]) XCTAssertEqual(records[1], ["zxc", "rty"]) } - + func testLineBreakLFLF() { let csv = "qwe,asd\n\nzxc,rty" let records = parse(csv: csv) @@ -124,12 +124,13 @@ class LineBreakTests: XCTestCase { } private func parse(csv: String) -> [[String]] { - let reader = try! CSV(string: csv) - var records = [[String]]() - for row in reader { - records.append(row) - } - return records + let reader = try! CSVReader(string: csv) + return reader.map { $0 } +// var records = [[String]]() +// try! reader.enumerateRows { (row, _, _) in +// records.append(row) +// } +// return records } } diff --git a/Tests/CSVTests/ReadmeTests.swift b/Tests/CSVTests/ReadmeTests.swift old mode 100755 new mode 100644 index a78e44c..ce24aa7 --- a/Tests/CSVTests/ReadmeTests.swift +++ b/Tests/CSVTests/ReadmeTests.swift @@ -10,61 +10,105 @@ import XCTest @testable import CSV class ReadmeTests: XCTestCase { - + static let allTests = [ ("testFromCSVString", testFromCSVString), - ("testFromFilePath", testFromFilePath), + ("testFromFile", testFromFile), ("testGettingTheHeaderRow", testGettingTheHeaderRow), - ("testGetTheFieldValueUsingSubscript", testGetTheFieldValueUsingSubscript), - ("testProvideTheCharacterEncoding", testProvideTheCharacterEncoding) + ("testGetTheFieldValueUsingKey", testGetTheFieldValueUsingKey), + ("testProvideTheCharacterEncoding", testProvideTheCharacterEncoding), + ("testWriteToMemory", testWriteToMemory), + ("testWriteToFile", testWriteToFile) ] + // MARK: - Reading + func testFromCSVString() { - for row in try! CSV(string: "1,foo\n2,bar") { + let csvString = "1,foo\n2,bar" + let csv = try! CSVReader(string: csvString) + while let row = csv.next() { print("\(row)") - // => ["1", "foo"] - // => ["2", "bar"] } + // => ["1", "foo"] + // => ["2", "bar"] } - - func testFromFilePath() { + + func testFromFile() { // let stream = InputStream(fileAtPath: "/path/to/file.csv")! -// for row in try! CSV(stream: stream) { +// let csv = try! CSVReader(stream: stream) +// while let row = csv.next() { // print("\(row)") // } } - + func testGettingTheHeaderRow() { - let csv = try! CSV( - string: "id,name\n1,foo\n2,bar", - hasHeaderRow: true) // default: false - + let csvString = "id,name\n1,foo\n2,bar" + let csv = try! CSVReader(string: csvString, + hasHeaderRow: true) // It must be true. + let headerRow = csv.headerRow! print("\(headerRow)") // => ["id", "name"] - - for row in csv { + + while let row = csv.next() { print("\(row)") - // => ["1", "foo"] - // => ["2", "bar"] } + // => ["1", "foo"] + // => ["2", "bar"] } - - func testGetTheFieldValueUsingSubscript() { - var csv = try! CSV( - string: "id,name\n1,foo", - hasHeaderRow: true) // It must be true. + + func testGetTheFieldValueUsingKey() { + let csvString = "id,name\n1,foo" + let csv = try! CSVReader(string: csvString, + hasHeaderRow: true) // It must be true. - while let _ = csv.next() { + while csv.next() != nil { print("\(csv["id"]!)") // => "1" print("\(csv["name"]!)") // => "foo" } } - + func testProvideTheCharacterEncoding() { -// let csv = try! CSV( -// stream: InputStream(fileAtPath: "/path/to/file.csv")!, -// codecType: UTF16.self, -// endian: .big) +// let stream = InputStream(fileAtPath: "/path/to/file.csv")! +// let csv = try! CSV(stream: stream, +// codecType: UTF16.self, +// endian: .big) + } + + // MARK: - Writing + + func testWriteToMemory() { + let stream = OutputStream(toMemory: ()) + let csv = try! CSVWriter(stream: stream) + + // Write a row + try! csv.write(row: ["id", "name"]) + + // Write fields separately + csv.beginNewRow() + try! csv.write(field: "1") + try! csv.write(field: "foo") + csv.beginNewRow() + try! csv.write(field: "2") + try! csv.write(field: "bar") + + csv.stream.close() + + // Get a String + let csvData = stream.property(forKey: .dataWrittenToMemoryStreamKey) as! NSData + let csvString = String(data: Data(referencing: csvData), encoding: .utf8)! + print(csvString) + // => "id,name\n1,foo\n2,bar" + } + + func testWriteToFile() { +// let stream = OutputStream(toFileAtPath: "/path/to/file.csv", append: false)! +// let csv = try! CSVWriter(stream: stream) +// +// try! csv.write(row: ["id", "name"]) +// try! csv.write(row: ["1", "foo"]) +// try! csv.write(row: ["1", "bar"]) +// +// csv.stream.close() } } diff --git a/Tests/CSVTests/TrimFieldsTests.swift b/Tests/CSVTests/TrimFieldsTests.swift index c6ba2b3..9176f60 100644 --- a/Tests/CSVTests/TrimFieldsTests.swift +++ b/Tests/CSVTests/TrimFieldsTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import CSV class TrimFieldsTests: XCTestCase { - + static let allTests = [ ("testTrimFields1", testTrimFields1), ("testTrimFields2", testTrimFields2), @@ -24,115 +24,160 @@ class TrimFieldsTests: XCTestCase { ("testTrimFields10", testTrimFields10), ("testTrimFields11", testTrimFields11), ("testTrimFields12", testTrimFields12), - ("testTrimFields13", testTrimFields13), + ("testTrimFields13", testTrimFields13) ] func testTrimFields1() { let csvString = "abc,def,ghi" - let csv = try! CSV(string: csvString, trimFields: true) - for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + let csv = try! CSVReader(string: csvString, trimFields: true) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } - + func testTrimFields2() { let csvString = " abc, def, ghi" - let csv = try! CSV(string: csvString, trimFields: true) - for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + let csv = try! CSVReader(string: csvString, trimFields: true) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } - + func testTrimFields3() { let csvString = "abc ,def ,ghi " - let csv = try! CSV(string: csvString, trimFields: true) - for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + let csv = try! CSVReader(string: csvString, trimFields: true) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } - + func testTrimFields4() { let csvString = " abc , def , ghi " - let csv = try! CSV(string: csvString, trimFields: true) - for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + let csv = try! CSVReader(string: csvString, trimFields: true) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } - + func testTrimFields5() { let csvString = "\"abc\",\"def\",\"ghi\"" - let csv = try! CSV(string: csvString, trimFields: true) - for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + let csv = try! CSVReader(string: csvString, trimFields: true) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } - + func testTrimFields6() { let csvString = " \"abc\", \"def\", \"ghi\"" - let csv = try! CSV(string: csvString, trimFields: true) - for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + let csv = try! CSVReader(string: csvString, trimFields: true) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } - + func testTrimFields7() { let csvString = "\"abc\" ,\"def\" ,\"ghi\" " - let csv = try! CSV(string: csvString, trimFields: true) - for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + let csv = try! CSVReader(string: csvString, trimFields: true) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } - + func testTrimFields8() { let csvString = " \"abc\" , \"def\" , \"ghi\" " - let csv = try! CSV(string: csvString, trimFields: true) - for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + let csv = try! CSVReader(string: csvString, trimFields: true) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } - + func testTrimFields9() { let csvString = "\" abc \",\" def \",\" ghi \"" - let csv = try! CSV(string: csvString, trimFields: true) - for row in csv { - XCTAssertEqual(row, [" abc ", " def ", " ghi "]) + let csv = try! CSVReader(string: csvString, trimFields: true) + for record in AnyIterator(csv) { + XCTAssertEqual(record, [" abc ", " def ", " ghi "]) } } - + func testTrimFields10() { let csvString = "\tabc,\t\tdef\t,ghi\t" - let csv = try! CSV(string: csvString, trimFields: true) - for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + let csv = try! CSVReader(string: csvString, trimFields: true) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } - + func testTrimFields11() { let csvString = " abc \n def " - var csv = try! CSV(string: csvString, trimFields: true) - - let row1 = csv.next()! - XCTAssertEqual(row1, ["abc"]) - let row2 = csv.next()! - XCTAssertEqual(row2, ["def"]) + let csv = try! CSVReader(string: csvString, trimFields: true) + + let record1 = csv.next()! + XCTAssertEqual(record1, ["abc"]) + let record2 = csv.next()! + XCTAssertEqual(record2, ["def"]) } - + func testTrimFields12() { let csvString = " \"abc \" \n \" def\" " - var csv = try! CSV(string: csvString, trimFields: true) - - let row1 = csv.next()! - XCTAssertEqual(row1, ["abc "]) - let row2 = csv.next()! - XCTAssertEqual(row2, [" def"]) + let csv = try! CSVReader(string: csvString, trimFields: true) + + let record1 = csv.next()! + XCTAssertEqual(record1, ["abc "]) + let record2 = csv.next()! + XCTAssertEqual(record2, [" def"]) } - + func testTrimFields13() { let csvString = " abc \t\tdef\t ghi " - let csv = try! CSV(string: csvString, trimFields: true, delimiter: UnicodeScalar("\t")!) - for row in csv { - XCTAssertEqual(row, ["abc", "", "def", "ghi"]) + let csv = try! CSVReader(string: csvString, trimFields: true, delimiter: "\t") + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "", "def", "ghi"]) } } - + + func testTrimFields14() { + let csvString = "" + let csv = try! CSVReader(string: csvString, trimFields: true) + let records = AnyIterator(csv).map { $0 } + + XCTAssertEqual(records.count, 0) + } + + func testTrimFields15() { + let csvString = " " + let csv = try! CSVReader(string: csvString, trimFields: true) + let records = AnyIterator(csv).map { $0 } + + XCTAssertEqual(records.count, 1) + XCTAssertEqual(records[0], [""]) + } + + func testTrimFields16() { + let csvString = " , " + let csv = try! CSVReader(string: csvString, trimFields: true) + let records = AnyIterator(csv).map { $0 } + + XCTAssertEqual(records.count, 1) + XCTAssertEqual(records[0], ["", ""]) + } + + func testTrimFields17() { + let csvString = " , \n" + let csv = try! CSVReader(string: csvString, trimFields: true) + let records = AnyIterator(csv).map { $0 } + + XCTAssertEqual(records.count, 1) + XCTAssertEqual(records[0], ["", ""]) + } + + func testTrimFields18() { + let csvString = " , \n " + let csv = try! CSVReader(string: csvString, trimFields: true) + let records = AnyIterator(csv).map { $0 } + + XCTAssertEqual(records.count, 2) + XCTAssertEqual(records[0], ["", ""]) + XCTAssertEqual(records[1], [""]) + } + } diff --git a/Tests/CSVTests/UnicodeTests.swift b/Tests/CSVTests/UnicodeTests.swift index dd73ce8..0bba796 100644 --- a/Tests/CSVTests/UnicodeTests.swift +++ b/Tests/CSVTests/UnicodeTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import CSV class UnicodeTests: XCTestCase { - + static let allTests = [ ("testUTF8WithBOM", testUTF8WithBOM), ("testUTF16WithNativeEndianBOM", testUTF16WithNativeEndianBOM), @@ -29,24 +29,24 @@ class UnicodeTests: XCTestCase { mutableData.append(utf8BOM, count: utf8BOM.count) mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData) - let csv = try! CSV(stream: stream, codecType: UTF8.self) + let csv = try! CSVReader(stream: stream, codecType: UTF8.self) let records = getRecords(csv: csv) XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"]) XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""]) } - + func testUTF16WithNativeEndianBOM() { let csvString = "abab,,cdcd,efef\r\nzxcv,😆asdf,\"qw\"\"er\"," let encoding = String.Encoding.utf16 var mutableData = Data() mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData as Data) - let csv = try! CSV(stream: stream, codecType: UTF16.self, endian: .unknown) + let csv = try! CSVReader(stream: stream, codecType: UTF16.self, endian: .unknown) let records = getRecords(csv: csv) XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"]) XCTAssertEqual(records[1], ["zxcv", "😆asdf", "qw\"er", ""]) } - + func testUTF16WithBigEndianBOM() { let csvString = "abab,,cdcd,efef\r\n😆zxcv,asdf,\"qw\"\"er\"," let encoding = String.Encoding.utf16BigEndian @@ -54,12 +54,12 @@ class UnicodeTests: XCTestCase { mutableData.append(utf16BigEndianBOM, count: utf16BigEndianBOM.count) mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData as Data) - let csv = try! CSV(stream: stream, codecType: UTF16.self, endian: .big) + let csv = try! CSVReader(stream: stream, codecType: UTF16.self, endian: .big) let records = getRecords(csv: csv) XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"]) XCTAssertEqual(records[1], ["😆zxcv", "asdf", "qw\"er", ""]) } - + func testUTF16WithLittleEndianBOM() { let csvString = "abab,,cdcd,efef\r\nzxcv😆,asdf,\"qw\"\"er\"," let encoding = String.Encoding.utf16LittleEndian @@ -67,24 +67,24 @@ class UnicodeTests: XCTestCase { mutableData.append(utf16LittleEndianBOM, count: utf16LittleEndianBOM.count) mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData as Data) - let csv = try! CSV(stream: stream, codecType: UTF16.self, endian: .little) + let csv = try! CSVReader(stream: stream, codecType: UTF16.self, endian: .little) let records = getRecords(csv: csv) XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"]) XCTAssertEqual(records[1], ["zxcv😆", "asdf", "qw\"er", ""]) } - + func testUTF32WithNativeEndianBOM() { let csvString = "😆abab,,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\"," let encoding = String.Encoding.utf32 var mutableData = Data() mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData as Data) - let csv = try! CSV(stream: stream, codecType: UTF32.self, endian: .unknown) + let csv = try! CSVReader(stream: stream, codecType: UTF32.self, endian: .unknown) let records = getRecords(csv: csv) XCTAssertEqual(records[0], ["😆abab", "", "cdcd", "efef"]) XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""]) } - + func testUTF32WithBigEndianBOM() { let csvString = "abab,,cd😆cd,efef\r\nzxcv,asdf,\"qw\"\"er\"," let encoding = String.Encoding.utf32BigEndian @@ -92,12 +92,12 @@ class UnicodeTests: XCTestCase { mutableData.append(utf32BigEndianBOM, count: utf32BigEndianBOM.count) mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData as Data) - let csv = try! CSV(stream: stream, codecType: UTF32.self, endian: .big) + let csv = try! CSVReader(stream: stream, codecType: UTF32.self, endian: .big) let records = getRecords(csv: csv) XCTAssertEqual(records[0], ["abab", "", "cd😆cd", "efef"]) XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""]) } - + func testUTF32WithLittleEndianBOM() { let csvString = "abab,,cdcd,ef😆ef\r\nzxcv,asdf,\"qw\"\"er\"," let encoding = String.Encoding.utf32LittleEndian @@ -105,18 +105,19 @@ class UnicodeTests: XCTestCase { mutableData.append(utf32LittleEndianBOM, count: utf32LittleEndianBOM.count) mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData as Data) - let csv = try! CSV(stream: stream, codecType: UTF32.self, endian: .little) + let csv = try! CSVReader(stream: stream, codecType: UTF32.self, endian: .little) let records = getRecords(csv: csv) XCTAssertEqual(records[0], ["abab", "", "cdcd", "ef😆ef"]) XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""]) } - - private func getRecords(csv: CSV) -> [[String]] { - var records = [[String]]() - for row in csv { - records.append(row) - } - return records + + private func getRecords(csv: CSVReader) -> [[String]] { + return csv.map { $0 } +// var records = [[String]]() +// try! csv.enumerateRows { (record, _, _) in +// records.append(record) +// } +// return records } - + } diff --git a/Tests/CSVTests/Version1Tests.swift b/Tests/CSVTests/Version1Tests.swift new file mode 100644 index 0000000..ced4ea5 --- /dev/null +++ b/Tests/CSVTests/Version1Tests.swift @@ -0,0 +1,110 @@ +// +// Version1Tests.swift +// CSV +// +// Created by Yasuhiro Hatta on 2017/06/18. +// Copyright © 2017 yaslab. All rights reserved. +// + +import Foundation +import XCTest + +import CSV + +class Version1Tests: XCTestCase { + + static let allTests = [ + ("testV1", testV1) + ] + + func testV1() { + let str = "a,b,c\n1,2,3" + let data8 = str.data(using: .utf8)! + let data16 = str.data(using: .utf16BigEndian)! + let data32 = str.data(using: .utf32BigEndian)! + + let headerRow = ["a", "b", "c"] + let row = ["1", "2", "3"] + + do { + let stream = InputStream(data: data8) + var csv = try CSV(stream: stream, + codecType: UTF8.self, + hasHeaderRow: true, + trimFields: false, + delimiter: ",") + XCTAssertEqual(csv.headerRow!, headerRow) + XCTAssertEqual(csv.next()!, row) + XCTAssertEqual(csv["a"], row[0]) + } catch { + fatalError() + } + + do { + let stream = InputStream(data: data16) + var csv = try CSV(stream: stream, + codecType: UTF16.self, + endian: .big, + hasHeaderRow: true, + trimFields: false, + delimiter: ",") + XCTAssertEqual(csv.headerRow!, headerRow) + XCTAssertEqual(csv.next()!, row) + XCTAssertEqual(csv["a"], row[0]) + } catch { + fatalError() + } + + do { + let stream = InputStream(data: data32) + var csv = try CSV(stream: stream, + codecType: UTF32.self, + endian: .big, + hasHeaderRow: true, + trimFields: false, + delimiter: ",") + XCTAssertEqual(csv.headerRow!, headerRow) + XCTAssertEqual(csv.next()!, row) + XCTAssertEqual(csv["a"], row[0]) + } catch { + fatalError() + } + + do { + let stream = InputStream(data: data8) + var csv = try CSV(stream: stream, + hasHeaderRow: true, + trimFields: false, + delimiter: ",") + XCTAssertEqual(csv.headerRow!, headerRow) + XCTAssertEqual(csv.next()!, row) + XCTAssertEqual(csv["a"], row[0]) + } catch { + fatalError() + } + + do { + var csv = try CSV(string: str, + hasHeaderRow: true, + trimFields: false, + delimiter: ",") + XCTAssertEqual(csv.headerRow!, headerRow) + XCTAssertEqual(csv.next()!, row) + XCTAssertEqual(csv["a"], row[0]) + } catch { + fatalError() + } + + _ = CSVError.cannotOpenFile + _ = CSVError.cannotReadFile + _ = CSVError.streamErrorHasOccurred(error: NSError(domain: "", code: 0, userInfo: nil)) + _ = CSVError.cannotReadHeaderRow + _ = CSVError.stringEncodingMismatch + _ = CSVError.stringEndianMismatch + + _ = Endian.big + _ = Endian.little + _ = Endian.unknown + } + +} diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 9448d07..15d3947 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -3,7 +3,7 @@ // CSV // // Created by Yasuhiro Hatta on 2016/06/11. -// +// Copyright © 2016 yaslab. All rights reserved. // import XCTest @@ -11,8 +11,10 @@ import XCTest XCTMain([ testCase(CSVTests.allTests), + testCase(CSVWriterTests.allTests), testCase(LineBreakTests.allTests), testCase(ReadmeTests.allTests), testCase(TrimFieldsTests.allTests), - testCase(UnicodeTests.allTests) + testCase(UnicodeTests.allTests), + testCase(Version1Tests.allTests) ])