Skip to content

Commit 088ac49

Browse files
committed
Update for Swift 5
1 parent ca37277 commit 088ac49

18 files changed

+431
-243
lines changed

.travis.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
language: swift
2-
osx_image: xcode8.3
3-
xcode_sdk: iphonesimulator10.3
2+
osx_image: xcode10.3
3+
xcode_sdk: iphonesimulator12.4
44
script:
5-
- xcodebuild clean build test -project Ocarina.xcodeproj -scheme Ocarina -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 7,OS=10.3.1'
5+
- xcodebuild clean build test -project Ocarina.xcodeproj -scheme Ocarina -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 8,OS=12.4'

Kanna/CSS.swift

+58-46
Original file line numberDiff line numberDiff line change
@@ -24,64 +24,78 @@ SOFTWARE.
2424
*/
2525
import Foundation
2626

27-
import SwiftLibXML2
27+
import libxmlKanna
2828

2929
typealias AKRegularExpression = NSRegularExpression
30+
#if os(Linux) && swift(>=4)
3031
typealias AKTextCheckingResult = NSTextCheckingResult
32+
#elseif os(Linux) && swift(>=3)
33+
typealias AKTextCheckingResult = TextCheckingResult
34+
#else
35+
typealias AKTextCheckingResult = NSTextCheckingResult
36+
#endif
37+
38+
public enum CSSError: Error {
39+
case UnsupportSyntax(String)
40+
}
3141

3242
/**
3343
CSS
3444
*/
35-
public struct CSS {
45+
public enum CSS {
3646
/**
3747
CSS3 selector to XPath
3848

3949
@param selector CSS3 selector
4050

4151
@return XPath
4252
*/
43-
public static func toXPath(_ selector: String) -> String? {
53+
public static func toXPath(_ css: String) throws -> String {
54+
let selectorGroups = css.components(separatedBy: ",")
55+
return try selectorGroups
56+
.map { try toXPath(selector: $0) }
57+
.joined(separator: " | ")
58+
}
59+
60+
private static func toXPath(selector: String) throws -> String {
4461
var xpath = "//"
4562
var str = selector
4663
var prev = str
4764

48-
while str.utf16.count > 0 {
65+
while !str.isEmpty {
4966
var attributes: [String] = []
5067
var combinator: String = ""
51-
52-
if let result = matchBlank(str) {
53-
str = str.substring(from: str.index(str.startIndex, offsetBy: result.range.length))
54-
}
55-
68+
69+
str = str.trimmingCharacters(in: .whitespaces)
70+
5671
// element
5772
let element = getElement(&str)
58-
73+
5974
// class / id
6075
while let attr = getClassId(&str) {
6176
attributes.append(attr)
6277
}
63-
78+
6479
// attribute
6580
while let attr = getAttribute(&str) {
6681
attributes.append(attr)
6782
}
68-
83+
6984
// matchCombinator
7085
if let combi = genCombinator(&str) {
7186
combinator = combi
7287
}
73-
88+
7489
// generate xpath phrase
75-
let attr = attributes.reduce("") { $0.isEmpty ? $1 : $0 + " and " + $1 }
90+
let attr = attributes.joined(separator: " and ")
7691
if attr.isEmpty {
7792
xpath += "\(element)\(combinator)"
7893
} else {
7994
xpath += "\(element)[\(attr)]\(combinator)"
8095
}
8196

8297
if str == prev {
83-
print("CSS Syntax Error: Unsupport syntax '\(selector)'")
84-
return nil
98+
throw CSSError.UnsupportSyntax(selector)
8599
}
86100
prev = str
87101
}
@@ -127,43 +141,47 @@ private func nth_last_child(a: Int, b: Int) -> String {
127141
return nth(prefix: "following", a: a, b: b)
128142
}
129143

130-
private let matchBlank = firstMatch("^\\s*|\\s$")
131-
private let matchElement = firstMatch("^([a-z0-9\\*_-]+)((\\|)([a-z0-9\\*_-]+))?")
132-
private let matchClassId = firstMatch("^([#.])([a-z0-9\\*_-]+)")
144+
private let escapePattern = "(?:\\\\([!\"#\\$%&\'\\(\\)\\*\\+,\\./:;<=>\\?@\\[\\\\\\]\\^`\\{\\|\\}~]))"
145+
private let escapeRepeatPattern = "\(escapePattern)*"
146+
private let matchElement = firstMatch("^((?:[a-z0-9\\*_-]+\(escapeRepeatPattern))+)((\\|)((?:[a-z0-9\\*_-]+\(escapeRepeatPattern))+))?")
147+
private let matchClassId = firstMatch("^([#.])((?:[a-z0-9\\*_-]+\(escapeRepeatPattern))+)")
133148
private let matchAttr1 = firstMatch("^\\[([^\\]]*)\\]")
134-
private let matchAttr2 = firstMatch("^\\[\\s*([^~\\|\\^\\$\\*=\\s]+)\\s*([~\\|\\^\\$\\*]?=)\\s*([^\"]*)\\s*\\]")
149+
private let matchAttr2 = firstMatch("^\\[\\s*([^~\\|\\^\\$\\*=\\s]+)\\s*([~\\|\\^\\$\\*]?=)\\s*(.*)\\s*\\]")
135150
private let matchAttrN = firstMatch("^:not\\((.*?\\)?)\\)")
136-
private let matchPseudo = firstMatch("^:([\'()a-z0-9_+-]+)")
151+
private let matchPseudo = firstMatch("^:([\'\"()a-z0-9_+-]+)")
137152
private let matchCombinator = firstMatch("^\\s*([\\s>+~,])\\s*")
138153
private let matchSubNthChild = firstMatch("^(nth-child|nth-last-child)\\(\\s*(odd|even|\\d+)\\s*\\)")
139154
private let matchSubNthChildN = firstMatch("^(nth-child|nth-last-child)\\(\\s*(-?\\d*)n(\\+\\d+)?\\s*\\)")
140155
private let matchSubNthOfType = firstMatch("nth-of-type\\((odd|even|\\d+)\\)")
141156
private let matchSubContains = firstMatch("contains\\([\"\'](.*?)[\"\']\\)")
142-
private let matchSubBlank = firstMatch("^\\s*$")
143157

144158
private func substringWithRangeAtIndex(_ result: AKTextCheckingResult, str: String, at: Int) -> String {
145159
if result.numberOfRanges > at {
146-
#if os(Linux)
160+
#if swift(>=4.0) || os(Linux)
147161
let range = result.range(at: at)
148162
#else
149163
let range = result.rangeAt(at)
150164
#endif
151165
if range.length > 0 {
152166
let startIndex = str.index(str.startIndex, offsetBy: range.location)
153167
let endIndex = str.index(startIndex, offsetBy: range.length)
154-
return str.substring(with: startIndex..<endIndex)
168+
return String(str[startIndex..<endIndex])
155169
}
156170
}
157171
return ""
158172
}
159173

174+
private func escapeCSS(_ text: String) -> String {
175+
return text.replacingOccurrences(of: escapePattern, with: "$1", options: .regularExpression, range: nil)
176+
}
177+
160178
private func getElement(_ str: inout String, skip: Bool = true) -> String {
161179
if let result = matchElement(str) {
162-
let (text, text2) = (substringWithRangeAtIndex(result, str: str, at: 1),
163-
substringWithRangeAtIndex(result, str: str, at: 4))
180+
let (text, text2) = (escapeCSS(substringWithRangeAtIndex(result, str: str, at: 1)),
181+
escapeCSS(substringWithRangeAtIndex(result, str: str, at: 5)))
164182

165183
if skip {
166-
str = str.substring(from: str.characters.index(str.startIndex, offsetBy: result.range.length))
184+
str = String(str[str.index(str.startIndex, offsetBy: result.range.length)..<str.endIndex])
167185
}
168186

169187
// tag with namespace
@@ -181,10 +199,10 @@ private func getElement(_ str: inout String, skip: Bool = true) -> String {
181199

182200
private func getClassId(_ str: inout String, skip: Bool = true) -> String? {
183201
if let result = matchClassId(str) {
184-
let (attr, text) = (substringWithRangeAtIndex(result, str: str, at: 1),
185-
substringWithRangeAtIndex(result, str: str, at: 2))
202+
let (attr, text) = (escapeCSS(substringWithRangeAtIndex(result, str: str, at: 1)),
203+
escapeCSS(substringWithRangeAtIndex(result, str: str, at: 2)))
186204
if skip {
187-
str = str.substring(from: str.characters.index(str.startIndex, offsetBy: result.range.length))
205+
str = String(str[str.index(str.startIndex, offsetBy: result.range.length)..<str.endIndex])
188206
}
189207

190208
if attr.hasPrefix("#") {
@@ -198,12 +216,12 @@ private func getClassId(_ str: inout String, skip: Bool = true) -> String? {
198216

199217
private func getAttribute(_ str: inout String, skip: Bool = true) -> String? {
200218
if let result = matchAttr2(str) {
201-
let (attr, expr, text) = (substringWithRangeAtIndex(result, str: str, at: 1),
219+
let (attr, expr, text) = (escapeCSS(substringWithRangeAtIndex(result, str: str, at: 1)),
202220
substringWithRangeAtIndex(result, str: str, at: 2),
203-
substringWithRangeAtIndex(result, str: str, at: 3).replacingOccurrences(of: "[\'\"](.*)[\'\"]", with: "$1", options: .regularExpression, range: nil))
221+
escapeCSS(substringWithRangeAtIndex(result, str: str, at: 3).replacingOccurrences(of: "[\'\"](.*)[\'\"]", with: "$1", options: .regularExpression, range: nil)))
204222

205223
if skip {
206-
str = str.substring(from: str.characters.index(str.startIndex, offsetBy: result.range.length))
224+
str = String(str[str.index(str.startIndex, offsetBy: result.range.length)..<str.endIndex])
207225
}
208226

209227
switch expr {
@@ -225,7 +243,7 @@ private func getAttribute(_ str: inout String, skip: Bool = true) -> String? {
225243
} else if let result = matchAttr1(str) {
226244
let atr = substringWithRangeAtIndex(result, str: str, at: 1)
227245
if skip {
228-
str = str.substring(from: str.characters.index(str.startIndex, offsetBy: result.range.length))
246+
str = String(str[str.index(str.startIndex, offsetBy: result.range.length)..<str.endIndex])
229247
}
230248

231249
return "@\(atr)"
@@ -237,7 +255,7 @@ private func getAttribute(_ str: inout String, skip: Bool = true) -> String? {
237255
} else if let result = matchPseudo(str) {
238256
let one = substringWithRangeAtIndex(result, str: str, at: 1)
239257
if skip {
240-
str = str.substring(from: str.characters.index(str.startIndex, offsetBy: result.range.length))
258+
str = String(str[str.index(str.startIndex, offsetBy: result.range.length)..<str.endIndex])
241259
}
242260

243261
switch one {
@@ -257,8 +275,6 @@ private func getAttribute(_ str: inout String, skip: Bool = true) -> String? {
257275
return "not(node())"
258276
case "root":
259277
return "not(parent::*)"
260-
case "last-child":
261-
return "count(following-sibling::*) = 0"
262278
default:
263279
if let sub = matchSubNthChild(one) {
264280
let (nth, arg1) = (substringWithRangeAtIndex(sub, str: one, at: 1),
@@ -305,21 +321,21 @@ private func getAttrNot(_ str: inout String, skip: Bool = true) -> String? {
305321
if let result = matchAttrN(str) {
306322
var one = substringWithRangeAtIndex(result, str: str, at: 1)
307323
if skip {
308-
str = str.substring(from: str.characters.index(str.startIndex, offsetBy: result.range.length))
324+
str = String(str[str.index(str.startIndex, offsetBy: result.range.length)..<str.endIndex])
309325
}
310326

311327
if let attr = getAttribute(&one, skip: false) {
312328
return attr
313329
} else if let sub = matchElement(one) {
314-
#if os(Linux)
330+
#if swift(>=4.0) || os(Linux)
315331
let range = sub.range(at: 1)
316332
#else
317333
let range = sub.rangeAt(1)
318334
#endif
319335
let startIndex = one.index(one.startIndex, offsetBy: range.location)
320336
let endIndex = one.index(startIndex, offsetBy: range.length)
321337

322-
let elem = one.substring(with: startIndex ..< endIndex)
338+
let elem = one[startIndex ..< endIndex]
323339
return "self::\(elem)"
324340
} else if let attr = getClassId(&one) {
325341
return attr
@@ -332,7 +348,7 @@ private func genCombinator(_ str: inout String, skip: Bool = true) -> String? {
332348
if let result = matchCombinator(str) {
333349
let one = substringWithRangeAtIndex(result, str: str, at: 1)
334350
if skip {
335-
str = str.substring(from: str.characters.index(str.startIndex, offsetBy: result.range.length))
351+
str = String(str[str.index(str.startIndex, offsetBy: result.range.length)..<str.endIndex])
336352
}
337353

338354
switch one {
@@ -343,11 +359,7 @@ private func genCombinator(_ str: inout String, skip: Bool = true) -> String? {
343359
case "~":
344360
return "/following-sibling::"
345361
default:
346-
if let _ = matchSubBlank(one) {
347-
return "//"
348-
} else {
349-
return " | //"
350-
}
362+
return "//"
351363
}
352364
}
353365
return nil

Kanna/Deprecated.swift

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//
2+
// Deprecated.swift
3+
// Kanna
4+
//
5+
// Created by Atsushi Kiwaki on 2017/10/27.
6+
// Copyright © 2017 Atsushi Kiwaki. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
//-------------------------------------------------------------
12+
// XML
13+
//-------------------------------------------------------------
14+
@available(*, unavailable, message: "Use XML(xml: String, url: String?, encoding: String.Encoding, option: ParseOption). The type of the second argument has been changed to String.Encoding from UInt.")
15+
public func XML(xml: String, url: String? = nil, encoding: UInt, option: ParseOption = kDefaultXmlParseOption) -> XMLDocument? {
16+
return nil
17+
}
18+
19+
@available(*, unavailable, message: "Use XML(xml: Data, url: String?, encoding: String.Encoding, option: ParseOption). The type of the first argument has been changed to Data and the type of the second argument has been changed to String.Encoding from UInt.")
20+
public func XML(xml: NSData, url: String? = nil, encoding: UInt, option: ParseOption = kDefaultXmlParseOption) -> XMLDocument? {
21+
return nil
22+
}
23+
24+
@available(*, unavailable, message: "Use XML(url: URL, encoding: String.Encoding, option: ParseOption). The type of the second argument has been changed to String.Encoding from UInt.")
25+
public func XML(url: URL, encoding: UInt, option: ParseOption = kDefaultXmlParseOption) -> XMLDocument? {
26+
return nil
27+
}
28+
29+
//-------------------------------------------------------------
30+
// HTML
31+
//-------------------------------------------------------------
32+
@available(*, unavailable, message: "Use HTML(html: String, url: String?, encoding: String.Encoding, option: ParseOption). The type of the second argument has been changed to String.Encoding from UInt.")
33+
public func HTML(html: String, url: String? = nil, encoding: UInt, option: ParseOption = kDefaultXmlParseOption) -> XMLDocument? {
34+
return nil
35+
}
36+
37+
@available(*, unavailable, message: "Use HTML(html: Data, url: String?, encoding: String.Encoding, option: ParseOption). The type of the first argument has been changed to Data and the type of the second argument has been changed to String.Encoding from UInt.")
38+
public func HTML(html: NSData, url: String? = nil, encoding: UInt, option: ParseOption = kDefaultXmlParseOption) -> XMLDocument? {
39+
return nil
40+
}
41+
42+
@available(*, unavailable, message: "Use HTML(url: URL, encoding: String.Encoding, option: ParseOption). The type of the second argument has been changed to String.Encoding from UInt.")
43+
public func HTML(url: URL, encoding: UInt, option: ParseOption = kDefaultXmlParseOption) -> XMLDocument? {
44+
return nil
45+
}

0 commit comments

Comments
 (0)