在 Swift 中解析 CSV 文件并将其加载到 Core Data 中

Posted

技术标签:

【中文标题】在 Swift 中解析 CSV 文件并将其加载到 Core Data 中【英文标题】:Parsing a CSV file and loading it into Core Data, in Swift 【发布时间】:2019-04-17 17:50:31 【问题描述】:

我正在处理之前关于 AppCode 的一篇名为“核心数据基础:预加载数据和使用现有 SQLite 数据库”的帖子,位于此处:https://www.appcoda.com/core-data-preload-sqlite-database/

在 Simon Ng 的帖子中,有一个名为 parseCSV 的函数,它完成了扫描 .csv 并将其分解为相应的行的所有繁重工作,这样每一行的元素就可以保存到核心数据中各自的 managedObjectContext 中。

不幸的是,所有代码似乎都是用 Swift 1.0 或 Swift 2.0 编写的,我无法理解将其转换为 Swift 4 时遇到的错误。

我已将 Xcode 建议的关于“this”的所有更改都替换为“that”,最后一个错误告诉我“Argument labels '(contentsOfURL:, encoding:, error:)' do不匹配任何可用的重载”,我无法理解或纠正。

//https://www.appcoda.com/core-data-preload-sqlite-database/

    func parseCSV (contentsOfURL: NSURL, encoding: String.Encoding, error: NSErrorPointer) -> [(name:String, detail:String, price: String)]? 
        // Load the CSV file and parse it
        let delimiter = ","
        var items:[(name:String, detail:String, price: String)]?

        if let content = String(contentsOfURL: contentsOfURL, encoding: encoding, error: error) 
            items = []
            let lines:[String] = content.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()) as [String]

            for line in lines 
                var values:[String] = []
                if line != "" 
                    // For a line with double quotes
                    // we use NSScanner to perform the parsing
                    if line.range(of: "\"") != nil 
                        var textToScan:String = line
                        var value:NSString?
                        var textScanner:Scanner = Scanner(string: textToScan)
                        while textScanner.string != "" 

                            if (textScanner.string as NSString).substring(to: 1) == "\"" 
                                textScanner.scanLocation += 1
                                textScanner.scanUpTo("\"", into: &value)
                                textScanner.scanLocation += 1
                             else 
                                textScanner.scanUpTo(delimiter, into: &value)
                            

                            // Store the value into the values array
                            values.append(value! as String)

                            // Retrieve the unscanned remainder of the string
                            if textScanner.scanLocation < textScanner.string.count 
                                textToScan = (textScanner.string as NSString).substring(from: textScanner.scanLocation + 1)
                             else 
                                textToScan = ""
                            
                            textScanner = Scanner(string: textToScan)
                        

                        // For a line without double quotes, we can simply separate the string
                        // by using the delimiter (e.g. comma)
                     else  
                        values = line.components(separatedBy: delimiter)
                    

                    // Put the values into the tuple and add it to the items array
                    let item = (name: values[0], detail: values[1], price: values[2])
                    items?.append(item)
                
            
        

        return items
    

第 5 行:

if let content = String(contentsOfURL: contentsOfURL, encoding: encoding, error: error)

抛出以下错误:

参数标签 '(contentsOfURL:, encoding:, error:)' 不匹配任何可用的重载

这超出了我的理解和技能水平。我真的只是想找到将逗号分隔的 .csv 文件导入核心数据对象的最佳方法。

我们将不胜感激。 Simon Ng 的原始示例似乎非常适合我想要实现的目标。好久没更新了。

【问题讨论】:

让 Xcode 通过使用代码完成来提供帮助。键入if let content = String.init(,它将显示可用的初始化程序。一旦你得到你想要的,你可以删除.init 请参阅***.com/questions/24010569/…,但还有许多其他问题。在 Swift 3 中,语法发生了很大变化。 从 rmaddy 学到了一些新东西——在格式化代码完成时,在 ol' .init 中折腾确实提供了额外的帮助。谢谢rmaddy! 我得告诉你——我很惊讶我这么快就得到了很大的帮助。所有伟大的阅读。谢谢大家。 【参考方案1】:

首先,你们都是出色的贡献者,而且对你的情报了解得非常快。我要感谢大家这么快回答。这是我在最新的 Swift 5 语法中最终使用该特定函数的地方。

func parseCSV (contentsOfURL: NSURL, encoding: String.Encoding, error: NSErrorPointer) -> [(name:String, detail:String, price: String)]? 
   // Load the CSV file and parse it
    let delimiter = ","
    var items:[(name:String, detail:String, price: String)]?

    //if let content = String(contentsOfURL: contentsOfURL, encoding: encoding, error: error) 
    if let content = try? String(contentsOf: contentsOfURL as URL, encoding: encoding) 
        items = []
        let lines:[String] = content.components(separatedBy: NSCharacterSet.newlines) as [String]

        for line in lines 
            var values:[String] = []
            if line != "" 
                // For a line with double quotes
                // we use NSScanner to perform the parsing
                if line.range(of: "\"") != nil 
                    var textToScan:String = line
                    var value:NSString?
                    var textScanner:Scanner = Scanner(string: textToScan)
                    while textScanner.string != "" 

                        if (textScanner.string as NSString).substring(to: 1) == "\"" 
                            textScanner.scanLocation += 1
                            textScanner.scanUpTo("\"", into: &value)
                            textScanner.scanLocation += 1
                         else 
                            textScanner.scanUpTo(delimiter, into: &value)
                        

                        // Store the value into the values array
                        values.append(value! as String)

                        // Retrieve the unscanned remainder of the string
                        if textScanner.scanLocation < textScanner.string.count 
                            textToScan = (textScanner.string as NSString).substring(from: textScanner.scanLocation + 1)
                         else 
                            textToScan = ""
                        
                        textScanner = Scanner(string: textToScan)
                    

                    // For a line without double quotes, we can simply separate the string
                    // by using the delimiter (e.g. comma)
                 else  
                    values = line.components(separatedBy: delimiter)
                

                // Put the values into the tuple and add it to the items array
                let item = (name: values[0], detail: values[1], price: values[2])
                items?.append(item)
            
        
    

    return items

【讨论】:

不要在 Swift 3+ 中使用 NSCharacterSetNSStringNSURL。有本地等价物。而NSErrorPointer 已经过时了。并且不要try?捕获错误。【参考方案2】:

从 Swift 3 开始,该函数已更改为 String(contentsOf:, encoding:),因此您只需修改代码中的参数标签。

还值得一提的是,这个函数现在会抛出,所以你必须处理它。看看this Swift 中的异常处理页面不会对您造成任何伤害。

【讨论】:

【参考方案3】:

由于扫描仪在 ios 13 中的更改方式似乎难以解释,因此我重写了它以使其在没有它的情况下工作。对于我的应用程序,标题行很重要,因此单独捕获;如果没有意义,那部分可以省略。

代码以workingText 开头,它是从作为数据源的任何文件或 URL 中读取的。

var headers : [String] = []
var data : [[String]]  = []

let workingLines = workingText.split$0.isNewline
if let headerLine = workingLines.first 
    headers = parseCsvLine(ln: String(headerLine))
    for ln in workingLines 
        if ln != headerLine 
            let fields = parseCsvLine(ln: String(ln))
            data.append(fields)
        
    


print("-----------------------------")
print("Headers: \(headers)")
print("Data:")
for d in data 
    print(d)    // gives each data row its own printed row; print(data) has no line breaks anywhere + is hard to read

print("-----------------------------")

func parseCsvLine(ln: String) -> [String] 
// takes a line of a CSV file and returns the separated values
// so input of 'a,b,2' should return ["a","b","2"]
// or input of '"Houston, TX","Hello",5,"6,7"' should return ["Houston, TX","Hello","5","6,7"]

let delimiter = ","
let quote = "\""
var nextTerminator = delimiter
var andDiscardDelimiter = false

var currentValue = ""
var allValues : [String] = []

for char in ln 
    let chr = String(char)
    if chr == nextTerminator 
        if andDiscardDelimiter 
            // we've found the comma after a closing quote. No action required beyond clearing this flag.
            andDiscardDelimiter = false
        
        else 
            // we've found the comma or closing quote terminating one value
            allValues.append(currentValue)
            currentValue = ""
        
        nextTerminator = delimiter  // either way, next thing we look for is the comma
     else if chr == quote 
        // this is an OPENING quote, so clear currentValue (which should be nothing but maybe a single space):
        currentValue = ""
        nextTerminator = quote
        andDiscardDelimiter = true
     else 
        currentValue += chr
    

return allValues

我坦率地承认,在 Apple 字符串、子字符串、扫描仪等方面,我可能使用了比我更聪明的转换到 String 的次数。解析几百行 x 大约十几列的文件,这种方法似乎工作正常;对于更大的东西,额外的开销可能开始变得重要。

【讨论】:

【参考方案4】:

另一种方法是使用库来执行此操作。 https://github.com/dehesa/CodableCSV 支持这一点,并且还有其他 swift csv 库的列表

【讨论】:

以上是关于在 Swift 中解析 CSV 文件并将其加载到 Core Data 中的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Swift(CreateML) 中读取文本文件的内容并将其解析为字典

发送我想要制作的文件并将其作为IHttpActionResult存储在内存中

html 此javascript文件将解析csv文件并将其添加到SharePoint列表。这是javascript开关的一个很好的例子,也是一个例子

将数据从 Web 托管的 CSV 加载到 Oracle 中?

如何将 CSV 文件批量加载到 Snowflake 中,并将文件名添加为列?

将大型 csv 加载到 mysql 等 RDB 的推荐方法