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