朧の.Netの足跡
問合せ先:support@oborodukiyo.info サイト内検索はこちら
Swift CSVファイルの読み書きのサンプル





CSVファイルの読み書きにはいくつか方法があると思いますが、ここのサンプルは正規表現を使ってしています。
他のサンプルを見ていると、列データを特に"や'で囲んでないサンプルが多いようです。
コメントアウトしてないコードでは、"で囲まれたCSVデータに対応しています。
コメントアウトしているところに、'で囲まれたCSVデータと何にも囲まれていないCSVデータに対応できるようにしています。
皆さんの助力になれば幸いです。


ViewController.swift

class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
    //ファイルより一行を取得する正規表現
    let CONST_REG_LINE: String = "^.*$"
    //項目データを取得する正規表現
    //2段構えとする
    let CONST_REG_COLUMN_USE_DOUBLE_QUOTATION: String = "(?<=(^|,))\\s*\"((\"\")|[^\"])*\"\\s*(?=(,|$))"
    let CONST_REG_NET_COLUMN_USE_DOUBLE_QUOTATION: String = "(?<=\")((\"\")|[^\"])*(?=\")"
    let CONST_REG_COLUMN_USE_SINGLE_QUOTATION: String = "(?<=(^|,))\\s*'(('')|[^'])*'\\s*(?=(,|$))"
    let CONST_REG_NET_COLUMN_USE_SINGLE_QUOTATION: String = "(?<=')(('')|[^'])*(?=')"
    let CONST_REG_NET_COLUMN_NO_USE_QUOTATION: String = "(?<=(^|,))[^,]*(?=(,|$))"
    //読み込んだCSVのデータ
    var result: [[String]] = []
    //CSVファイルにカラム名を含むかどうか
    var hasTitle: Bool = true
    @IBOutlet weak var tv: NSTableView!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        self.tv.delegate = self
        self.tv.dataSource = self
    }
    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }
    
    @IBAction func readCSV(_ sender: Any) {
        //列番号の変数
        var index: Int = 0
        //列名を処理したかどうかのフラグ
        var blDidTitle: Bool = false
                
        let op = NSOpenPanel()
        op.canChooseFiles = true
        op.canChooseDirectories = false
        op.allowsMultipleSelection = false
        op.allowedFileTypes = ["", "csv", "txt"]
        
        if op.runModal() == NSApplication.ModalResponse.OK {
            let u = op.url
            if u != nil {
                
                do {
                    //ファイルデータを取得
                    let s = try String(contentsOf: u!, encoding: String.Encoding.utf8)
                    //データのクリア
                    self.result = []
                    
                    let match_item: [String] = getMatchStrings(targetString: s, pattern: CONST_REG_LINE)
                    
                    for item in match_item {
                        let column = getColumnData(item)
                        
                        if blDidTitle == false {
                            blDidTitle = true
                            if self.hasTitle {
                                //タイトル行を絡むヘッダーに
                                for r in column {
                                    let col: NSTableColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: index.description))
                                    col.title = r
                                    col.isEditable = true
                                    index += 1
                                    tv.addTableColumn(col)
                                }
                            } else {
                                //ダミーのカラムヘッダーを
                                //タイトル行を絡むヘッダーに
                                result.append(column)
                                for _ in result[0] {
                                    let col: NSTableColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: index.description))
                                    col.title = "Column" + index.description
                                    col.isEditable = true
                                    index += 1
                                    tv.addTableColumn(col)
                                }
                                
                            }
                            
                            
                        } else {
                            result.append(column)
                        }
                        
                        
                    }
                } catch {
                    return
                }
                
                
            }
        } else {
            //何もしない
        }
        
        self.tv.reloadData()
    }
    
    @IBAction func writeCSV(_ sender: Any) {
        var saveData: String = ""
        
        if self.hasTitle {
            for i in 0 ..< self.tv.tableColumns.count {
                if i == 0 {
                    saveData = saveData + "\"" + tv.tableColumns[i].title + "\""
                } else {
                    saveData = saveData + ",\"" + tv.tableColumns[i].title + "\""
                }
            }
            saveData = saveData + "\n"
        }
        
        for line in self.result {
            for i in 0 ..< line.count {
                //"で列データが囲まれている場合
                if i == 0 {
                    saveData = saveData + "\"" + line[i] + "\""
                } else {
                    saveData = saveData + ",\"" + line[i] + "\""
                }
                //'で列データが囲まれている場合
                //if i == 0 {
                //    saveData = saveData + "'" + line[i] + "'"
                //} else {
                //    saveData = saveData + ",'" + line[i] + "'"
                //}
                //列データを囲んでいない場合
                //if i == 0 {
                //    saveData = saveData + line[i]
                //} else {
                //    saveData = saveData + "," + line[i]
                //}
            }
            saveData = saveData + "\n"
        }
        
        //ファイルに保存
        let sp = NSSavePanel()
        sp.title = "保存"
        sp.prompt = "保存"
        
        if sp.runModal() == NSApplication.ModalResponse.OK {
            let path = sp.url!
            
            do {
                try saveData.write(to: path, atomically: false, encoding: String.Encoding.utf8)
            } catch {
                print("error: saveCSV")
                print("\(error.localizedDescription)")
            }
        }
    }
    
    func numberOfRows(in tableView: NSTableView) -> Int {
        return result.count
    }
    
    func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
        let i: Int = Int(tableColumn!.identifier.rawValue)!
        return result[row][i]
    }

    func getMatchStrings(targetString: String, pattern: String) -> [String] {
        var matchStrings: [String] = []
        do {
            let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive,.anchorsMatchLines])
            let targetStringRange = NSRange(location: 0, length: (targetString as NSString).length)
            let matches = regex.matches(in: targetString, options: [], range: targetStringRange)
            print(matches.count)
            
            for match in matches {
                let range = match.range(at: 0)
                let result = (targetString as NSString).substring(with: range)
                //print(match)
                print(result)
                matchStrings.append(result)
            }
            return matchStrings
        } catch {
            print("error: getMatchStrings")
        }
        
        return []
    }
    
    func getColumnData(_ data: String) -> [String] {
        //'でカラムが囲まれている時
        //return getColumnDataByQuotation(data, regColumn: self.CONST_REG_COLUMN_USE_SINGLE_QUOTATION, regNetColumn: self.CONST_REG_NET_COLUMN_USE_SINGLE_QUOTATION)
        //"でカラムが囲まれているとき
        return getColumnDataByQuotation(data, regColumn: self.CONST_REG_COLUMN_USE_DOUBLE_QUOTATION, regNetColumn: self.CONST_REG_NET_COLUMN_USE_DOUBLE_QUOTATION)
        //特にカラムが囲まれていない時
        //return getColumnDataByComma(data)
    }
    
    func getColumnDataByQuotation(_ data: String, regColumn: String, regNetColumn: String) -> [String] {
        var columnData: [String] = []
        
        do {
            //let regex = try NSRegularExpression(pattern: self.CONST_REG_COLUMN_USE_DOUBLE_QUOTATION, options: [.caseInsensitive, .anchorsMatchLines])
            let regex = try NSRegularExpression(pattern: regColumn, options: [.caseInsensitive, .anchorsMatchLines])
            let targetStringRange = NSRange(location: 0, length: (data as NSString).length)
            let matches = regex.matches(in: data, options: [], range: targetStringRange)
            print(matches.count)
            
            for match in matches {
                //let regex2 = try NSRegularExpression(pattern: self.CONST_REG_NET_COLUMN_USE_DOUBLE_QUOTATION, options: [.caseInsensitive])
                let regex2 = try NSRegularExpression(pattern: regNetColumn, options: [.caseInsensitive])
                //let targetStringRange2 = NSRange(location: 0, length: (data as NSString).length)
                //一度マッチした項目の前後を含んだデータを取得するためのRange
                let range2 = match.range(at: 0)
                //ここで正味のほかも含む文字列を取得する
                let result2 = (data as NSString).substring(with: range2)
                let targetStringRange2 = NSRange(location: 0, length: (result2 as NSString).length)
                let matches2 = regex2.matches(in: result2, options: [], range: targetStringRange2)
                let match2 = matches2[0]
                let range = match2.range(at: 0)
                let result = (result2 as NSString).substring(with: range)
                print(result)
                columnData.append(result)
            }
        } catch {
            print(error.localizedDescription)
            print("error: getColumnDataByQuotation")
        }
        return columnData
    }

    func getColumnDataByComma(_ data: String) -> [String] {
        var columnData: [String] = []
        
        do {
            let regex = try NSRegularExpression(pattern: self.CONST_REG_NET_COLUMN_NO_USE_QUOTATION, options: [.caseInsensitive, .anchorsMatchLines])
            let targetStringRange = NSRange(location: 0, length: (data as NSString).length)
            let matches = regex.matches(in: data, options: [], range: targetStringRange)
            print(matches.count)
            
            for match in matches {
                let range = match.range(at: 0)
                let result = (data as NSString).substring(with: range)
                print(result)
                columnData.append(result)
            }
        } catch {
            print(error.localizedDescription)
            print("error: getColumnDataByComma")
        }
        return columnData
    }
}

    








良いやや良い普通やや悪い悪い

投稿日時評価コメント