Merge pull request #21 from yaslab/develop

Version 2.0
This commit is contained in:
Yasuhiro Hatta 2017-06-19 02:45:58 +09:00 committed by GitHub
commit 1ca5d1a558
25 changed files with 1591 additions and 680 deletions

View File

@ -1 +1 @@
3.0
3.1

6
.swiftlint.yml Normal file
View File

@ -0,0 +1,6 @@
disabled_rules:
- force_cast
- force_try
- variable_name
included:
- Sources

View File

@ -1,5 +1,5 @@
language: objective-c
osx_image: xcode8
osx_image: xcode8.3
env:
- LC_CTYPE=en_US.UTF-8
git:

View File

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

View File

@ -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 = "<group>"; };
0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVWriterTests.swift; sourceTree = "<group>"; };
0E54021E1EDA81E80019C3ED /* CSVWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVWriter.swift; sourceTree = "<group>"; };
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 = "<group>"; };
0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVReader.swift; sourceTree = "<group>"; };
0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVError.swift; sourceTree = "<group>"; };
0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSVVersion.h; sourceTree = "<group>"; };
0E7E8CAC1D0BC8610057A1C1 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -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 = "<group>"; };
0E9317D81D0DB30800AC20A0 /* CSV+subscript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSV+subscript.swift"; sourceTree = "<group>"; };
0E7F657A1EF6437E00E1E1A0 /* Version1Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Version1Tests.swift; sourceTree = "<group>"; };
0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BinaryReader.swift; sourceTree = "<group>"; };
0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeIterator.swift; sourceTree = "<group>"; };
0EA37AC91DD8C0B900F5B274 /* CSVTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVTests.swift; sourceTree = "<group>"; };
0EA37ACA1DD8C0B900F5B274 /* LineBreakTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineBreakTests.swift; sourceTree = "<group>"; };
0EA37ACB1DD8C0B900F5B274 /* ReadmeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadmeTests.swift; sourceTree = "<group>"; };
0EA37ACC1DD8C0B900F5B274 /* TrimFieldsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrimFieldsTests.swift; sourceTree = "<group>"; };
0EA37ACD1DD8C0B900F5B274 /* UnicodeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeTests.swift; sourceTree = "<group>"; };
0EC628FD1EF675B300289554 /* CSV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSV.swift; sourceTree = "<group>"; };
0EDF8ECD1DDB73370068056A /* CSVTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVTests.swift; sourceTree = "<group>"; };
0EDF8ECE1DDB73370068056A /* LineBreakTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineBreakTests.swift; sourceTree = "<group>"; };
0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadmeTests.swift; sourceTree = "<group>"; };
0EDF8ED01DDB73370068056A /* TrimFieldsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrimFieldsTests.swift; sourceTree = "<group>"; };
0EDF8ED11DDB73370068056A /* UnicodeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeTests.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
@ -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;
};

100
README.md
View File

@ -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)
]
)
```

89
Sources/BinaryReader.swift Executable file → Normal file
View File

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

View File

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

View File

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

251
Sources/CSV.swift Executable file → Normal file
View File

@ -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<UnicodeScalar>
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<T: IteratorProtocol>(
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<T: UnicodeCodec>(
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<T: UnicodeCodec>(
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<T: UnicodeCodec>(
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()
}
}

View File

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

399
Sources/CSVReader.swift Normal file
View File

@ -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<UnicodeScalar>
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<T: IteratorProtocol>(
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<T: UnicodeCodec>(
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<T: UnicodeCodec>(
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<T: UnicodeCodec>(
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]
}
}

0
Sources/CSVVersion.h Executable file → Normal file
View File

202
Sources/CSVWriter.swift Normal file
View File

@ -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<T: UnicodeCodec>(
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<T: UnicodeCodec>(
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<T: UnicodeCodec>(
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))
}
}
}

View File

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

View File

@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.1.2</string>
<string>2.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>

23
Sources/UnicodeIterator.swift Executable file → Normal file
View File

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

208
Tests/CSVTests/CSVTests.swift Executable file → Normal file
View File

@ -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"])
// }
}

View File

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

View File

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

104
Tests/CSVTests/ReadmeTests.swift Executable file → Normal file
View File

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

View File

@ -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], [""])
}
}

View File

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

View File

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

View File

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