2
votes

Analyser un fichier CSV et le charger dans Core Data, dans Swift

Je travaille à partir d'une publication précédente sur AppCode intitulée "Principes de base des données de base: précharger les données et utiliser la base de données SQLite existante" située ici: https://www.appcoda.com/core-data-preload-sqlite-database/

Dans la publication de Simon Ng se trouve une fonction appelée parseCSV qui effectue tout le travail de balayage d'un .csv et le divise en ses lignes respectives afin que les éléments de chaque ligne puissent ensuite être enregistrés dans leur managedObjectContext respectif dans les données de base.

Malheureusement, tout le code semble être écrit en Swift 1.0 ou Swift 2.0 et je n'ai pas pu comprendre les erreurs que j'obtiens en le convertissant en Swift 4.

J'ai apporté toutes les modifications suggérées par Xcode en ce qui concerne "this" a été remplacé par "that", avec l'erreur finale me disant "Les étiquettes d'argument '(contentsOfURL :, encoding :, error :)' ne correspondent à aucune surcharge disponible" que j'ai été incapable à comprendre ni à corriger.

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

La 5ème ligne:

si laisser le contenu = String (contentsOfURL: contentsOfURL, encoding: encoding, error: error) {

renvoie l'erreur suivante:

Les étiquettes d'argument '(contentsOfURL :, encoding :, error :)' ne correspond à aucune surcharge disponible

Ce qui dépasse ma compréhension et mon niveau de compétence. J'essaie vraiment de trouver la meilleure façon d'importer un fichier .csv séparé par des virgules dans un objet de données de base.

Toute aide serait appréciée. L'exemple original de Simon Ng semble parfait pour ce que j'essaie d'accomplir. Il n'a tout simplement pas été mis à jour depuis très longtemps.


4 commentaires

Laissez Xcode vous aider en utilisant la complétion de code. Tapez if let content = String.init ( et il vous montrera les initialiseurs disponibles. Une fois que vous avez obtenu celui que vous voulez, vous pouvez supprimer le .init .


Voir stackoverflow.com/questions/24010569/ … mais il existe de nombreux autres problèmes. Dans Swift 3, la syntaxe a considérablement changé.


J'ai appris quelque chose de nouveau de rmaddy - jeter dans l'ol '.init fournit vraiment une aide supplémentaire lors du formatage des complétions de code. Merci rmaddy!


Je dois te dire - je suis étonné de toute la grande aide que j'ai obtenue si vite. Toutes les bonnes lectures. Merci à tous.


4 Réponses :


0
votes

Depuis Swift 3, cette fonction a été changée en String (contentsOf :, encoding :) donc il vous suffit de modifier les étiquettes d'argument dans le code.

Il est également intéressant de mentionner que cette fonction va maintenant être lancée, vous devrez donc gérer cela. Cela ne vous ferait aucun mal de jeter un œil à ceci page sur la gestion des exceptions dans Swift.


0 commentaires

1
votes

Tout d'abord, vous êtes tous des contributeurs brillants et très rapides à votre intel. Je tiens à vous remercier tous d'avoir répondu si rapidement. Voici où je me suis retrouvé avec cette fonction particulière dans la dernière syntaxe 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
}


2 commentaires

N'utilisez pas NSCharacterSet , NSString , NSURL dans Swift 3+. Il existe des équivalents natifs. Et NSErrorPointer est devenu obsolète. Et n'essayez pas ? , détectez l'erreur.


Tu m'as sauvé la vie.



0
votes

Parce que Scanner a été modifié dans iOS 13 d'une manière qui semble mal expliquée, j'ai réécrit cela pour fonctionner sans lui. Pour mon application, la ligne d'en-tête est intéressante, elle est donc capturée séparément; si ce n'est pas significatif, alors cette partie peut être omise.

Le code commence par workingText qui a été lu à partir de n'importe quel fichier ou URL est la source des données.

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

}

Je reconnais volontiers que j'utilise probablement plus de conversions en String que celles qui sont plus intelligentes que moi à la manière des chaînes Apple, des sous-chaînes, des scanners, etc. En analysant un fichier de quelques centaines de lignes x environ une douzaine de colonnes, cette approche semble fonctionner correctement; pour quelque chose de beaucoup plus grand, les frais généraux supplémentaires peuvent commencer à avoir de l'importance.


0 commentaires

0
votes

Une alternative est d'utiliser une bibliothèque pour ce faire. https://github.com/dehesa/CodableCSV prend en charge cela et a une liste d'autres bibliothèques csv swift aussi


0 commentaires