SoulverCore 为您提供从 Swift 字符串中进行人性化、类型安全且高性能的数据解析。
指定您想从字符串中解析的类型。如果它们存在,您将获得可以直接使用的数据基本类型(而不是字符串!)。
这种数据解析方法允许您忽略
让我们看几个例子
let (testCount, failureCount, timeTaken) = "Executed 4 tests, with 1 failure in 0.009 seconds".find(.number, .number, .time)!
testCount // 4
failureCount // 1
timeTaken // 0.009 seconds
let (date, temperature, humidity) = "On August 23, 2022 the temperature in Chicago was 68.3 ºF (with a humidity of 74%)".find(.date, .temperature, .percentage)!
date // August 23, 2022
temperature // 68.3 ºF
humidity // 74%
let (earnings, fileSize, url) = "Total Earnings From PDF: $12.2k (3.25 MB, at https://lifeadvice.co.uk/pdfs/download?id=guide)".find(.currency, .fileSize, .url)!
earnings // 12,200 USD
fileSize // 3.25 MB
url // https://lifeadvice.co.uk/pdfs/download?id=guide
注意:返回的数据点不是字符串。它们是原生 Swift 数据类型(作为元组中的元素提供),您可以立即对其执行操作
let numbers = "100 + 20".find(.number, .number)!
let sum = numbers.0 + numbers.1 // 120
单次调用最多可以请求 6 个数据点。Variadic 泛型计划在 Swift 6 中推出,因此我们将来会支持更多。
观察此处使用的高阶概念的魅力:数字有多种格式(1,000、30k、.456),但简单的“.number”查询“匹配”所有这些格式。.date 也“匹配”常用日期格式的日期。
对于区域设置在数据格式中起作用的情况,您可以在 find 方法中指定区域设置(否则将使用当前系统区域设置)
let europeanNumber = "€1.333,24".find(.currency, locale: Locale(identifier: "en_DE"))
let americanDate = "05/30/21".find(.date, locale: Locale(identifier: "en_US")) // month/day/year
在可能的情况下,会返回标准 Swift 基本类型(URL、Date、Decimal 等)。如果 Swift 基本类型无法完全捕获字符串中存在的数据,则会返回 SoulverCore 值类型,其中包含相关数据的属性。
符号 | 匹配示例 | 返回类型 |
---|---|---|
.number | 123.45, 10k, -.3, 3,000, 50_000 | Decimal |
.binaryNumber | 0b1011010 | UInt |
.hexNumber | 0x31FE28 | UInt |
.boolean | 'true' 或 'false' | Bool |
.percentage | 10%, 230.99% | Decimal |
.date | March 12, 2004, 21/04/77, July the 4th, 等等 | Date |
.unixTimestamp | 1661259854 | TimeInterval |
.place | Paris, Tokyo, Bali, Israel | SoulverCore.Place |
.airport | SFO, LAX, SYD | SoulverCore.Place |
.timezone | AEST, GMT, EST | SoulverCore.Place |
.currencyCode | USD, EUR, DOGE | String |
.currency | $10.00, AU$30k, 350 JPY | SoulverCore.UnitExpression |
.time | 10 s, 3 min, 4 weeks | SoulverCore.UnitExpression |
.distance | 10 km, 3 miles, 4 cm | SoulverCore.UnitExpression |
.temperature | 25 °C, 77 °F, 10C, 5 F | SoulverCore.UnitExpression |
.weight | 10kg, 45 lb | SoulverCore.UnitExpression |
.area | 30 m2, 40 in2 | SoulverCore.UnitExpression |
.speed | 30 mph | SoulverCore.UnitExpression |
.volume | 3 litres, 4 cups, 10 fl oz | SoulverCore.UnitExpression |
.timespan | 3 hours 12 minutes | SoulverCore.Timespan |
.laptime | 01:30:22.490 (hh:mm:ss.ms) | SoulverCore.Laptime |
.timecode | 03:10:21:16 (hh:mm:ss:frames) | SoulverCore.Frametime |
.pitch | A4, Bb7, C#9 | SoulverCore.Pitch |
.url | https://soulver.app | URL |
.emailAddress | bob@hotmail.com | String |
.hashTag | #this_is_a_tag | String |
.whitespace | 所有空白字符(包括制表符)都将折叠为单个空白标记 | String |
正如我们在上面看到的,在字符串中查找数据点就像请求它一样简单
let percent = "Results of likeness test: 83% match".find(.percentage)
// percent is the decimal 0.83
提取多个数据点也同样容易。将返回一个元组,其中包含正确数量的参数和数据类型
let payrollEntry = "CREDIT 03/02/2022 Payroll from employer $200.23" // this string has inconsistent whitespace between entities, but this isn't a problem for us
let (date, currency) = payrollEntry.find(.date, .currency)!
date // Either February 3, or March 2, depending on your system locale
currency // UnitExpression object (use .value to get the decimalValue, and .unit.identifier to get the currency code - USD)
我们还可以对字符串数组调用带有单个数据类型的 find,并返回与匹配项对应的数组数据类型
let amounts = ["Zac spent $50", "Molly spent US$81.9 (with her 10% discount)", "Jude spent $43.90 USD"].find(.currency)
let totalAmount = amounts.reduce(0.0) {
$0 + $1.value
}
// totalAmount is $175.80
假设我们想标准化上一个示例中字符串中的空格
let standardized = "CREDIT 03/02/2022 Payroll from employer $200.23".replacingAll(.whitespace) { whitespace in
return " "
}
// standardized is "CREDIT 03/02/2022 Payroll from employer $200.23"
或者,也许您想将欧洲格式化的数字转换为 Swift “标准”数字
let standardized = "10.330,99 8.330,22 330,99".replacingAll(.number, locale: Locale(identifier: "en_DE")) { number in
return NumberFormatter.localizedString(from: number as NSNumber, number: .decimal)
}
// standardized is "10,330.99 8,330.22 330.99")
或者,也许您想将摄氏温度转换为华氏温度
let convertedTemperatures = ["25 °C", "12.5 degrees celsius", "-22.6 C"].replacingAll(.temperature) { celsius in
let measurementC: Measurement<UnitTemperature> = Measurement(value: celsius.value.doubleValue, unit: .celsius)
let measurementF = measurementC.converted(to: .fahrenheit)
let formatter = MeasurementFormatter()
formatter.unitOptions = .providedUnit
return formatter.string(from: measurementF)
}
// convertedTemperatures is ["77°F", "54.5°F", "-8.68°F"]
假设我们有以下格式的字符串,描述一些容器
我们想将这些数据提取到表示 Container 的自定义 Swift 类型中。
enum Color: String, RawRepresentable {
case blue
case red
case yellow
}
enum Size: String, RawRepresentable {
case small
case medium
case large
}
struct Container {
let color: Color
let size: Size
let volume: Decimal
init(_ data: (Color, Size, UnitExpression)) {
self.color = data.0
self.size = data.1
self.volume = data.2.value
}
}
struct ColorParser: DataFromTokenParser {
typealias DataType = Color
func parseDataFrom(token: SoulverCore.Token) -> Color? {
return Color(rawValue: token.stringValue.lowercased())
}
}
struct SizeParser: DataFromTokenParser {
typealias DataType = Size
func parseDataFrom(token: SoulverCore.Token) -> Size? {
return Size(rawValue: token.stringValue.lowercased())
}
}
extension DataPoint {
static var color: DataPoint<ColorParser> {
return DataPoint<ColorParser>(parser: ColorParser())
}
static var size: DataPoint<SizeParser> {
return DataPoint<SizeParser>(parser: SizeParser())
}
}
let container1 = Container("Color: blue, size: medium, volume: 12.5 cm3".find(.color, .size, .volume)!)
let container2 = Container("Color: red, size: small, volume: 6.2 cm3".find(.color, .size, .volume)!)
let container3 = Container("Color: yellow, size: large, volume: 17.82 cm3".find(.color, .size, .volume)!)
SoulverCore 将能够用于解析 Swift regex builder DSL (5.7 版本中推出) 内的数据。这通常比弄清楚如何使用正则表达式匹配您的数据格式更容易。
if #available(macOS 13.0, iOS 16.0, *) {
let input = "Cost: 365.45, Date: March 12, 2022"
let regex = Regex {
"Cost: "
Capture {
DataPoint<NumberFromTokenParser>.number
}
", Date: "
Capture {
DataPoint<DateFromTokenParser>.date
}
}
let match = input.wholeMatch(of: regex).1 // 365.45
}
注意:Swift 编译器似乎无法从 DataPoint 上的静态变量推断 DataPoint 泛型参数,这既令人困惑又令人遗憾(有人知道为什么吗?)。
在修复此问题之前,您必须显式指定与您要匹配的数据类型对应的 DataFromTokenParser。
SoulverCore 不太可能成为您应用程序的瓶颈。
在我们的测试中,SoulverCore 在 Intel 上执行约 6k 次操作/秒,在 Silicon 上执行 10k+ 次操作/秒。
虽然这确实不如正则表达式快,但公平地说,SoulverCore 做了更多的工作。在检查您的查询是否匹配之前,SoulverCore 会将整个字符串解析为表示各种数据类型的标记,它可以识别 20 多种数据类型(包括各种格式的日期、数字和单位、地点、时区等等……)。
如果正则表达式也这样做,那将是不可能构建的,即使这样的正则表达式是可能的,它的运行速度也会比 SoulverCore 慢得多。
Apple 的字符串解析工具包包括 Regex、NSScanner 和 NSDataDetector。让我们将这些工具包中的每一个与 SoulverCore 进行比较和对比。
正则表达式 将永远伴随我们,但请自问,您真的想使用它们进行数据处理吗?
它们乍一看不易理解,并且构造正确的正则表达式来匹配数据至少是乏味的(如果不是有时在精神上相当具有挑战性的话)。
正则表达式只“看到”字符/数字/空格集,因此它迫使您考虑要解析的数据的字符串格式,并且通常还要考虑如何跳过通向它的其他字符串。
因此,即使在 Swift 5.7 中对正则表达式进行了重大增强(类型安全的元组匹配和正则表达式构建器语法),正则表达式也使您在错误的抽象级别(即字符,而不是数据类型)考虑数据解析。
如果 Swift 要实现其成为 世界上最伟大的字符串和数据处理语言 的目标,它需要一些在数据抽象级别上更人性化的东西,而不是字符集。
扫描器是一种命令式(而不是声明式)方法,用于从字符串中解析数据。您逐步移动扫描器通过字符串,扫描出您想要的组件。
NSScanner 的一个好处是它能够忽略您不关心的字符串部分。但是,扫描器仍然只知道数字和字符串 - 而不知道更高级别的数据类型。
这是一个 StackOverflow 帖子,说明了如何使用 NSScanner 从字符串 “user logged (3 attempts)” 中扫描整数。
NSString *logString = @"user logged (3 attempts)";
NSString *numberString;
NSScanner *scanner = [NSScanner scannerWithString:logString];
[scanner scanUpToCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet] intoString:nil];
[scanner scanCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet] intoString:&numberString];
NSLog(@"Attempts: %i", [numberString intValue]); // 3
正则表达式(在 Swift 5.7+ 中)在某种程度上更简洁
if #available(macOS 13.0, iOS 16.0, *) {
let match = "user logged (3 attempts)".firstMatch(of: /([+\\-]?[0-9]+)/)
let numberSubstring = match!.0
let number = Int(numberSubstring)
}
现在是 SoulverCore
let number = "user logged (3 attempts)".find(.number)
NSDataDetector 是 NSRegularExpression 的子类,能够扫描字符串中的日期、URL、电话号码、地址和航班详细信息。这是一个很棒的类,支持多种不同的格式。此外,它从字符串返回正确的数据类型,例如 URL 和 Date(非常像 SoulverCore)。
比较
let input = "Learn more at https://fascinatingcaptian.com today."
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let url = detector.firstMatch(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count))!.url!
let url = "Learn more at https://fascinatingcaptian.com today".find(.url)
NSDataDetector 的缺点是 API 不是特别 “Swifty”,支持的数据类型有限,并且它不是 Foundation 的平台独立实现的一部分(因此您无法在 Linux、Windows 等上使用它)
SoulverCore 是一个商业许可的、闭源 Swift 框架。SoulverCore 的标准许可条款适用于其在字符串处理中的使用(请参阅 SoulverCore 许可证)。
对于个人(非商业)项目,您不需要许可证。因此,请继续在您的个人项目中使用这个很棒的库吧!
对于一些商业用例,也提供仅需署名的许可证。