在 Apple Swift 中解析 Excel 数据
Parsing Excel Data in Apple Swift
我当前的工作流程涉及使用 Applescript 来本质上分隔 Excel 数据并将其格式化为纯文本文件。我们正在推动全 Swift 环境,但我还没有找到任何类型的工具包来将我的 Excel 数据解析为 Swift.
我唯一能想到的就是用C什么的然后把它包起来,但这并不理想。关于解析此数据以用于 Swift 的任何更好建议?
目标是消除 Applescript,但我不确定在与 Excel 文件交互的同时是否可行。通过 Applescript 编写脚本 Excel 似乎是唯一的方法。
编辑: 我没有从该工作流中删除 Excel 的选项。这就是数据将如何进入应用程序,因此我必须包含它。
能够简化解析此数据然后对其进行处理的过程将是最重要的。我知道 Applescript 过去在帮助我处理它方面做得很好;但是,它对我来说有点太封闭了。
我一直在考虑用 Swift/Cocoa 编写一些东西,但这可能仍然需要使用 Applescript 提取数据,对吗?
推送 Swift 的一大优点是可读性。我不太了解 Objective-C,我觉得 swift 会更容易过渡。
我在 PC 上的工作流程一直在使用 COM 对象,如前所述,它在 Mac Excel 应用程序中不可用。我现在只是在寻找数据提取。一些以前的应用程序在应用程序内进行处理,但我希望使其非常独立,因此所有处理都在我正在开发的应用程序内进行。从 .XLS 或 .XLSX 文件中提取数据后,我将通过 RegEx 进行一些文本编辑,也许还有一些 c运行ching。没什么太疯狂的。截至目前,它将 运行 在客户端,但我希望将其扩展到服务器进程。
您可以使用 ScriptingBridge or NSAppleScript 与 Apple Scriptable 内容进行交互
ScriptingBridge 可以从 Apple Script 字典生成头文件。
NSAppleScript 可以通过 String
为你执行任何 AppleScript
在 Mac OS X 10.6 Snow Leopard Apple 中引入了 AppleScriptObjC 框架,这使得 Cocoa 和 AppleScript 之间的交互变得非常容易。可以在同一源文件中使用 AppleScript 代码和类似 Objective-C 的语法。比Scripting Bridge
和NSAppleScript
方便多了。
AppleScriptObjC 不能在Swift 中直接使用,因为NSBundle 的命令loadAppleScriptObjectiveCScripts
没有桥接到Swift.
但是您可以使用 Objective-C 网桥 class 例如
ASObjC.h
@import Foundation;
@import AppleScriptObjC;
@interface NSObject (Excel)
- (void)openExcelDocument:(NSString *)filePath;
- (NSArray *)valueOfUsedRange;
@end
@interface ASObjC : NSObject
+ (ASObjC *)sharedASObjC;
@property id Excel;
@end
ASObjC.m
#import "ASObjC.h"
@implementation ASObjC
+ (void)initialize
{
if (self == [ASObjC class]) {
[[NSBundle mainBundle] loadAppleScriptObjectiveCScripts];
}
}
+ (ASObjC *)sharedASObjC
{
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[ASObjC alloc] init];
});
return sharedInstance;
}
- (instancetype)init
{
self = [super init];
if (self) {
_Excel = NSClassFromString(@"ASExcel");
}
return self;
}
@end
从 AppleScriptObjC 模板创建 AppleScript 源文件
ASExcel.applescript
script ASExcel
property parent: class "NSObject"
on openExcelDocument:filePath
set asFilePath to filePath as text
tell application "Microsoft Excel"
set sourceBook to open workbook workbook file name asFilePath
repeat
try
get workbooks
return
end try
delay 0.5
end repeat
end tell
end openDocument
on valueOfUsedRange()
tell application "Microsoft Excel"
tell active sheet
set activeRange to used range
return value of activeRange
end tell
end tell
end valueOfUsedRange
end script
如有必要,Link 到 AppleScriptObjC 框架。
创建桥接头并导入 ASObjC.h
然后你可以用
从Swift调用AppleScriptObjC
ASObjC.sharedASObjC().Excel.openExcelDocument("Macintosh HD:Users:MyUser:Path:To:ExcelFile.xlsx")
或
let excelData = ASObjC.sharedASObjC().Excel.valueOfUsedRange() as! Array<[String]>
有点不清楚你是想消除 Excel 作为一种依赖(这不是不合理的:它要花钱而且不是每个人都有)或者 AppleScript 作为一种语言(完全可以理解,但不好Apple 的应用程序自动化替代方案都很糟糕)。
有第三方 Excel-解析库可用于其他语言,例如我在自己的项目中成功使用了 Python 的 openpyxl
(对于 .xlsx 文件)和 xlrd
(对于 .xsl)库。我通过谷歌的魔法看到有人写了一个 ObjC 框架,DHlibxls,[假设没有动态欺骗] 应该可以直接从 Swift 中使用,但我自己没有用过,所以可以'不多说了。
1。导出为纯文本 CSV
如果您要做的只是从 Excel 中提取 数据 以在其他地方使用,而不是捕获 Excel 公式和格式,那么您可能不应该尝试阅读 .xls 文件。 XLS 是一种复杂的格式。它适用于 Excel,不适用于一般数据交换。
同样,如果您只想将数据保存为纯文本,您可能不需要使用 AppleScript 或其他任何东西来与 Excel 集成。 Excel 已经知道如何将数据保存为明文。只需使用 Excel 的 "Save As" 命令。 (这就是它在 Mac 上的名称。我不知道 PC。)
接下来的问题是使用什么明文格式。一个明显的选择是 纯文本逗号分隔值文件 (CSV),因为它是一个简单的事实标准(与 XML 等复杂的官方标准相反)。这将使使用 Swift 或任何其他语言变得容易。
2。如果可能,以 UTF-8 编码导出,否则以 UTF-16
那么你究竟是怎么做到的呢?明文非常简单,但您需要注意的一个微妙之处是 文本编码 。文本编码是一种表示纯文本文件中字符的方法。不幸的是,您无法仅通过检查文件来可靠地判断文件的编码,因此您需要在保存文件时选择一种编码,并记住在读取文件时使用该编码。如果你把它搞砸了,重音字符、印刷者的引号、破折号和其他非 ASCII 字符将被破坏。那么你应该使用什么文本编码呢?简短的回答是,如果可能,您应该始终使用 UTF-8。
但是如果您使用的是旧版本的 Excel,那么您可能无法使用 UTF-8。在这种情况下,您应该使用 UTF-16。特别是,我认为,UTF-16 是 Excel 2011 中 Mac 的唯一导出选项,它会产生可预测的结果,不会以令人惊讶的方式依赖于晦涩的区域设置或 Microsoft 特定编码。
因此,如果您在 Excel 2011 年 Mac,例如,从 Excel 的“另存为”命令中选择 "UTF-16 Unicode Text"。
这将导致 Excel 保存文件,以便每一行都是一行文本,每一列都由制表符分隔。 (所以从技术上讲,这是一个制表符分隔值文件,而不是逗号分隔值文件。)
3。使用 Swift
导入
现在你有了一个纯文本文件,你知道它是以 UTF-8(或 UTF-16)编码保存的。所以现在你可以阅读它并在 Swift.
中解析它
如果您的 Excel 数据很复杂,您可能需要一个功能齐全的 CSV 解析器。 最好的选择可能是CHCSVParser。
使用CHCSV,您可以使用以下代码解析文件:
NSURL * const inputFileURL = [NSURL fileURLWithPath:@"/path/to/exported/file.txt"];
unichar tabCharacter = '\t';
NSArray *rows = [NSArray arrayWithContentsOfCSVFile:inputFilePath options:CHCSVParserOptionsSanitizesFields
delimiter:tabCharacter];
(当然,您也可以从 Swift 调用它。)
另一方面,如果您的数据相对简单(例如,它没有转义字符),那么您可能根本不需要使用外部库。您可以编写一些 Swift 代码来解析制表符分隔值,只需将文件作为字符串读入,按换行符拆分,然后按制表符拆分 .
此函数将采用 String
表示 TSV 数据和 return 字典数组:
/**
Reads a multiline, tab-separated String and returns an Array<NSictionary>, taking column names from the first line or an explicit parameter
*/
func JSONObjectFromTSV(tsvInputString:String, columnNames optionalColumnNames:[String]? = nil) -> Array<NSDictionary>
{
let lines = tsvInputString.componentsSeparatedByString("\n")
guard lines.isEmpty == false else { return [] }
let columnNames = optionalColumnNames ?? lines[0].componentsSeparatedByString("\t")
var lineIndex = (optionalColumnNames != nil) ? 0 : 1
let columnCount = columnNames.count
var result = Array<NSDictionary>()
for line in lines[lineIndex ..< lines.count] {
let fieldValues = line.componentsSeparatedByString("\t")
if fieldValues.count != columnCount {
// NSLog("WARNING: header has %u columns but line %u has %u columns. Ignoring this line", columnCount, lineIndex,fieldValues.count)
}
else
{
result.append(NSDictionary(objects: fieldValues, forKeys: columnNames))
}
lineIndex = lineIndex + 1
}
return result
}
所以只需要将文件读入字符串,传给这个函数即可。该片段来自 this gist for a tsv-to-json converter. And if you need to know more about which text encodings Microsoft products produce, and which ones Cocoa can auto-detect, then this repo on text encoding 包含对导出样本的研究,该研究得出的结论是 UTF-16 是 Mac.
上旧 Microsoft 产品的使用方式
(我意识到我正在链接到我自己的回购协议。抱歉?)
Swift 无需将 Excel 个文件导出为 CSV,因为您可以使用 existing open-source library for parsing XLSX files. If you use CocoaPods 或 Swift
Package Manager 用于集成 3rd 方库,CoreXLSX
支持这些。库集成后,可以这样使用:
import CoreXLSX
guard let file = XLSXFile(filepath: "./file.xlsx") else {
fatalError("XLSX file corrupted or does not exist")
}
for path in try file.parseWorksheetPaths() {
let ws = try file.parseWorksheet(at: path)
for row in ws.sheetData.rows {
for c in row.cells {
print(c)
}
}
}
这将打开 file.xlsx
并打印该文件中的所有单元格。您还可以按引用过滤单元格,并仅访问自动化所需的单元格数据。
我当前的工作流程涉及使用 Applescript 来本质上分隔 Excel 数据并将其格式化为纯文本文件。我们正在推动全 Swift 环境,但我还没有找到任何类型的工具包来将我的 Excel 数据解析为 Swift.
我唯一能想到的就是用C什么的然后把它包起来,但这并不理想。关于解析此数据以用于 Swift 的任何更好建议?
目标是消除 Applescript,但我不确定在与 Excel 文件交互的同时是否可行。通过 Applescript 编写脚本 Excel 似乎是唯一的方法。
编辑: 我没有从该工作流中删除 Excel 的选项。这就是数据将如何进入应用程序,因此我必须包含它。
能够简化解析此数据然后对其进行处理的过程将是最重要的。我知道 Applescript 过去在帮助我处理它方面做得很好;但是,它对我来说有点太封闭了。
我一直在考虑用 Swift/Cocoa 编写一些东西,但这可能仍然需要使用 Applescript 提取数据,对吗?
推送 Swift 的一大优点是可读性。我不太了解 Objective-C,我觉得 swift 会更容易过渡。
我在 PC 上的工作流程一直在使用 COM 对象,如前所述,它在 Mac Excel 应用程序中不可用。我现在只是在寻找数据提取。一些以前的应用程序在应用程序内进行处理,但我希望使其非常独立,因此所有处理都在我正在开发的应用程序内进行。从 .XLS 或 .XLSX 文件中提取数据后,我将通过 RegEx 进行一些文本编辑,也许还有一些 c运行ching。没什么太疯狂的。截至目前,它将 运行 在客户端,但我希望将其扩展到服务器进程。
您可以使用 ScriptingBridge or NSAppleScript 与 Apple Scriptable 内容进行交互
ScriptingBridge 可以从 Apple Script 字典生成头文件。
NSAppleScript 可以通过 String
在 Mac OS X 10.6 Snow Leopard Apple 中引入了 AppleScriptObjC 框架,这使得 Cocoa 和 AppleScript 之间的交互变得非常容易。可以在同一源文件中使用 AppleScript 代码和类似 Objective-C 的语法。比Scripting Bridge
和NSAppleScript
方便多了。
AppleScriptObjC 不能在Swift 中直接使用,因为NSBundle 的命令loadAppleScriptObjectiveCScripts
没有桥接到Swift.
但是您可以使用 Objective-C 网桥 class 例如
ASObjC.h
@import Foundation;
@import AppleScriptObjC;
@interface NSObject (Excel)
- (void)openExcelDocument:(NSString *)filePath;
- (NSArray *)valueOfUsedRange;
@end
@interface ASObjC : NSObject
+ (ASObjC *)sharedASObjC;
@property id Excel;
@end
ASObjC.m
#import "ASObjC.h"
@implementation ASObjC
+ (void)initialize
{
if (self == [ASObjC class]) {
[[NSBundle mainBundle] loadAppleScriptObjectiveCScripts];
}
}
+ (ASObjC *)sharedASObjC
{
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[ASObjC alloc] init];
});
return sharedInstance;
}
- (instancetype)init
{
self = [super init];
if (self) {
_Excel = NSClassFromString(@"ASExcel");
}
return self;
}
@end
从 AppleScriptObjC 模板创建 AppleScript 源文件
ASExcel.applescript
script ASExcel
property parent: class "NSObject"
on openExcelDocument:filePath
set asFilePath to filePath as text
tell application "Microsoft Excel"
set sourceBook to open workbook workbook file name asFilePath
repeat
try
get workbooks
return
end try
delay 0.5
end repeat
end tell
end openDocument
on valueOfUsedRange()
tell application "Microsoft Excel"
tell active sheet
set activeRange to used range
return value of activeRange
end tell
end tell
end valueOfUsedRange
end script
如有必要,Link 到 AppleScriptObjC 框架。
创建桥接头并导入 ASObjC.h
然后你可以用
从Swift调用AppleScriptObjC ASObjC.sharedASObjC().Excel.openExcelDocument("Macintosh HD:Users:MyUser:Path:To:ExcelFile.xlsx")
或
let excelData = ASObjC.sharedASObjC().Excel.valueOfUsedRange() as! Array<[String]>
有点不清楚你是想消除 Excel 作为一种依赖(这不是不合理的:它要花钱而且不是每个人都有)或者 AppleScript 作为一种语言(完全可以理解,但不好Apple 的应用程序自动化替代方案都很糟糕)。
有第三方 Excel-解析库可用于其他语言,例如我在自己的项目中成功使用了 Python 的 openpyxl
(对于 .xlsx 文件)和 xlrd
(对于 .xsl)库。我通过谷歌的魔法看到有人写了一个 ObjC 框架,DHlibxls,[假设没有动态欺骗] 应该可以直接从 Swift 中使用,但我自己没有用过,所以可以'不多说了。
1。导出为纯文本 CSV
如果您要做的只是从 Excel 中提取 数据 以在其他地方使用,而不是捕获 Excel 公式和格式,那么您可能不应该尝试阅读 .xls 文件。 XLS 是一种复杂的格式。它适用于 Excel,不适用于一般数据交换。
同样,如果您只想将数据保存为纯文本,您可能不需要使用 AppleScript 或其他任何东西来与 Excel 集成。 Excel 已经知道如何将数据保存为明文。只需使用 Excel 的 "Save As" 命令。 (这就是它在 Mac 上的名称。我不知道 PC。)
接下来的问题是使用什么明文格式。一个明显的选择是 纯文本逗号分隔值文件 (CSV),因为它是一个简单的事实标准(与 XML 等复杂的官方标准相反)。这将使使用 Swift 或任何其他语言变得容易。
2。如果可能,以 UTF-8 编码导出,否则以 UTF-16
那么你究竟是怎么做到的呢?明文非常简单,但您需要注意的一个微妙之处是 文本编码 。文本编码是一种表示纯文本文件中字符的方法。不幸的是,您无法仅通过检查文件来可靠地判断文件的编码,因此您需要在保存文件时选择一种编码,并记住在读取文件时使用该编码。如果你把它搞砸了,重音字符、印刷者的引号、破折号和其他非 ASCII 字符将被破坏。那么你应该使用什么文本编码呢?简短的回答是,如果可能,您应该始终使用 UTF-8。
但是如果您使用的是旧版本的 Excel,那么您可能无法使用 UTF-8。在这种情况下,您应该使用 UTF-16。特别是,我认为,UTF-16 是 Excel 2011 中 Mac 的唯一导出选项,它会产生可预测的结果,不会以令人惊讶的方式依赖于晦涩的区域设置或 Microsoft 特定编码。
因此,如果您在 Excel 2011 年 Mac,例如,从 Excel 的“另存为”命令中选择 "UTF-16 Unicode Text"。
这将导致 Excel 保存文件,以便每一行都是一行文本,每一列都由制表符分隔。 (所以从技术上讲,这是一个制表符分隔值文件,而不是逗号分隔值文件。)
3。使用 Swift
导入现在你有了一个纯文本文件,你知道它是以 UTF-8(或 UTF-16)编码保存的。所以现在你可以阅读它并在 Swift.
中解析它如果您的 Excel 数据很复杂,您可能需要一个功能齐全的 CSV 解析器。 最好的选择可能是CHCSVParser。
使用CHCSV,您可以使用以下代码解析文件:
NSURL * const inputFileURL = [NSURL fileURLWithPath:@"/path/to/exported/file.txt"];
unichar tabCharacter = '\t';
NSArray *rows = [NSArray arrayWithContentsOfCSVFile:inputFilePath options:CHCSVParserOptionsSanitizesFields
delimiter:tabCharacter];
(当然,您也可以从 Swift 调用它。)
另一方面,如果您的数据相对简单(例如,它没有转义字符),那么您可能根本不需要使用外部库。您可以编写一些 Swift 代码来解析制表符分隔值,只需将文件作为字符串读入,按换行符拆分,然后按制表符拆分 .
此函数将采用 String
表示 TSV 数据和 return 字典数组:
/**
Reads a multiline, tab-separated String and returns an Array<NSictionary>, taking column names from the first line or an explicit parameter
*/
func JSONObjectFromTSV(tsvInputString:String, columnNames optionalColumnNames:[String]? = nil) -> Array<NSDictionary>
{
let lines = tsvInputString.componentsSeparatedByString("\n")
guard lines.isEmpty == false else { return [] }
let columnNames = optionalColumnNames ?? lines[0].componentsSeparatedByString("\t")
var lineIndex = (optionalColumnNames != nil) ? 0 : 1
let columnCount = columnNames.count
var result = Array<NSDictionary>()
for line in lines[lineIndex ..< lines.count] {
let fieldValues = line.componentsSeparatedByString("\t")
if fieldValues.count != columnCount {
// NSLog("WARNING: header has %u columns but line %u has %u columns. Ignoring this line", columnCount, lineIndex,fieldValues.count)
}
else
{
result.append(NSDictionary(objects: fieldValues, forKeys: columnNames))
}
lineIndex = lineIndex + 1
}
return result
}
所以只需要将文件读入字符串,传给这个函数即可。该片段来自 this gist for a tsv-to-json converter. And if you need to know more about which text encodings Microsoft products produce, and which ones Cocoa can auto-detect, then this repo on text encoding 包含对导出样本的研究,该研究得出的结论是 UTF-16 是 Mac.
上旧 Microsoft 产品的使用方式(我意识到我正在链接到我自己的回购协议。抱歉?)
Swift 无需将 Excel 个文件导出为 CSV,因为您可以使用 existing open-source library for parsing XLSX files. If you use CocoaPods 或 Swift
Package Manager 用于集成 3rd 方库,CoreXLSX
支持这些。库集成后,可以这样使用:
import CoreXLSX
guard let file = XLSXFile(filepath: "./file.xlsx") else {
fatalError("XLSX file corrupted or does not exist")
}
for path in try file.parseWorksheetPaths() {
let ws = try file.parseWorksheet(at: path)
for row in ws.sheetData.rows {
for c in row.cells {
print(c)
}
}
}
这将打开 file.xlsx
并打印该文件中的所有单元格。您还可以按引用过滤单元格,并仅访问自动化所需的单元格数据。