Compare commits

...

5 Commits

Author SHA1 Message Date
Nabil Chatbi d61b7df1c9 Test 2017-10-05 09:00:40 +02:00
Nabil Chatbi eda44d93b4 Other test 2017-10-03 20:58:41 +02:00
Nabil Chatbi 581302183b Merge branch 'master' into feature/Bits
# Conflicts:
#	Example/Podfile.lock
#	Example/Pods/Local Podspecs/SwiftSoup.podspec.json
#	Example/Pods/Manifest.lock
#	Example/Pods/Pods.xcodeproj/project.pbxproj
#	Example/Pods/Target Support Files/SwiftSoup/Info.plist
#	SwiftSoup.xcodeproj/project.pbxproj
2017-10-02 17:06:42 +02:00
Nabil Chatbi 946fd39cd8 Some tests 2017-08-05 15:04:23 +02:00
Nabil Chatbi 246a6b4f4f First test Int 2017-07-18 23:16:04 +02:00
85 changed files with 5073 additions and 528 deletions

21
Example/Pods/Bits/LICENSE generated Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 Qutheory, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

20
Example/Pods/Bits/README.md generated Normal file
View File

@ -0,0 +1,20 @@
<p align="center">
<img src="https://cloud.githubusercontent.com/assets/1342803/24857620/3d672e56-1de9-11e7-9cb5-63033e8d560e.png" width="320" alt="Bits">
<br>
<br>
<a href="http://docs.vapor.codes/bits/package">
<img src="http://img.shields.io/badge/read_the-docs-92A8D1.svg" alt="Documentation">
</a>
<a href="http://vapor.team">
<img src="http://vapor.team/badge.svg" alt="Slack Team">
</a>
<a href="LICENSE">
<img src="http://img.shields.io/badge/license-MIT-brightgreen.svg" alt="MIT License">
</a>
<a href="https://circleci.com/gh/vapor/bits">
<img src="https://circleci.com/gh/vapor/bits.svg?style=shield" alt="Continuous Integration">
</a>
<a href="https://swift.org">
<img src="http://img.shields.io/badge/swift-3.1-brightgreen.svg" alt="Swift 3.1">
</a>
</center>

View File

@ -0,0 +1,29 @@
/**
A single byte represented as a UInt8
*/
public typealias Byte = UInt8
/**
A byte array or collection of raw data
*/
public typealias Bytes = [Byte]
/**
A sliced collection of raw data
*/
public typealias BytesSlice = ArraySlice<Byte>
// MARK: Sizes
private let _bytes = 1
private let _kilobytes = _bytes * 1000
private let _megabytes = _kilobytes * 1000
private let _gigabytes = _megabytes * 1000
extension Int {
public var bytes: Int { return self }
public var kilobytes: Int { return self * _kilobytes }
public var megabytes: Int { return self * _megabytes }
public var gigabytes: Int { return self * _gigabytes }
}

View File

@ -0,0 +1,237 @@
/// Encodes and decodes bytes using the
/// Base64 encoding
///
/// https://en.wikipedia.org/wiki/Base64
public final class Base64Encoder {
/// Static shared instance
public static let shared = Base64Encoder.regular
/// Standard Base64Encoder
public static var regular: Base64Encoder {
return Base64Encoder()
}
// Base64URLEncoder
// - note: uses hyphens and underscores
// in place of plus and forwardSlash
public static var url: Base64Encoder {
let encodeMap: Base64Encoder.ByteMap = { byte in
switch byte {
case 62:
return .hyphen
case 63:
return .underscore
default:
return nil
}
}
let decodeMap: Base64Encoder.ByteMap = { byte in
switch byte {
case Byte.hyphen:
return 62
case Byte.underscore:
return 63
default:
return nil
}
}
return Base64Encoder(
padding: nil,
encodeMap: encodeMap,
decodeMap: decodeMap
)
}
/// Maps binary format to base64 encoding
static let encodingTable: [Byte: Byte] = [
0: .A, 1: .B, 2: .C, 3: .D,
4: .E, 5: .F, 6: .G, 7: .H,
8: .I, 9: .J, 10: .K, 11: .L,
12: .M, 13: .N, 14: .O, 15: .P,
16: .Q, 17: .R, 18: .S, 19: .T,
20: .U, 21: .V, 22: .W, 23: .X,
24: .Y, 25: .Z, 26: .a, 27: .b,
28: .c, 29: .d, 30: .e, 31: .f,
32: .g, 33: .h, 34: .i, 35: .j,
36: .k, 37: .l, 38: .m, 39: .n,
40: .o, 41: .p, 42: .q, 43: .r,
44: .s, 45: .t, 46: .u, 47: .v,
48: .w, 49: .x, 50: .y, 51: .z,
52: .zero, 53: .one, 54: .two, 55: .three,
56: .four, 57: .five, 58: .six, 59: .seven,
60: .eight, 61: .nine, 62: .plus, 63: .forwardSlash
]
/// Maps base64 encoding into binary format
static let decodingTable: [Byte: Byte] = [
.A: 0, .B: 1, .C: 2, .D: 3,
.E: 4, .F: 5, .G: 6, .H: 7,
.I: 8, .J: 9, .K: 10, .L: 11,
.M: 12, .N: 13, .O: 14, .P: 15,
.Q: 16, .R: 17, .S: 18, .T: 19,
.U: 20, .V: 21, .W: 22, .X: 23,
.Y: 24, .Z: 25, .a: 26, .b: 27,
.c: 28, .d: 29, .e: 30, .f: 31,
.g: 32, .h: 33, .i: 34, .j: 35,
.k: 36, .l: 37, .m: 38, .n: 39,
.o: 40, .p: 41, .q: 42, .r: 43,
.s: 44, .t: 45, .u: 46, .v: 47,
.w: 48, .x: 49, .y: 50, .z: 51,
.zero: 52, .one: 53, .two: 54, .three: 55,
.four: 56, .five: 57, .six: 58, .seven: 59,
.eight: 60, .nine: 61, .plus: 62, .forwardSlash: 63
]
/// Typealias for optionally mapping a byte
public typealias ByteMap = (Byte) -> Byte?
/// Byte to use for padding base64
/// if nil, no padding will be used
public let padding: Byte?
/// If set, bytes returned will have priority
/// over the encoding table. Encoding table
/// will be used as a fallback
public let encodeMap: ByteMap?
/// If set, bytes returned will have priority
/// over the decoding table. Decoding table
/// will be used as a fallback
public let decodeMap: ByteMap?
/// Creates a new Base64 encoder
public init(
padding: Byte? = .equals,
encodeMap: ByteMap? = nil,
decodeMap: ByteMap? = nil
) {
self.padding = padding
self.encodeMap = encodeMap
self.decodeMap = decodeMap
}
/// Encodes bytes into Base64 format
public func encode(_ bytes: Bytes) -> Bytes {
if bytes.count == 0 {
return []
}
let len = bytes.count
var offset: Int = 0
var c1: UInt8
var c2: UInt8
var result: Bytes = []
while offset < len {
c1 = bytes[offset] & 0xff
offset += 1
result.append(encode((c1 >> 2) & 0x3f))
c1 = (c1 & 0x03) << 4
if offset >= len {
result.append(encode(c1 & 0x3f))
if let padding = self.padding {
result.append(padding)
result.append(padding)
}
break
}
c2 = bytes[offset] & 0xff
offset += 1
c1 |= (c2 >> 4) & 0x0f
result.append(encode(c1 & 0x3f))
c1 = (c2 & 0x0f) << 2
if offset >= len {
result.append(encode(c1 & 0x3f))
if let padding = self.padding {
result.append(padding)
}
break
}
c2 = bytes[offset] & 0xff
offset += 1
c1 |= (c2 >> 6) & 0x03
result.append(encode(c1 & 0x3f))
result.append(encode(c2 & 0x3f))
}
return result
}
/// Decodes bytes into binary format
public func decode(_ s: Bytes) -> Bytes {
let maxolen = s.count
var off: Int = 0
var olen: Int = 0
var result = Bytes(repeating: 0, count: maxolen)
var c1: Byte
var c2: Byte
var c3: Byte
var c4: Byte
var o: Byte
while off < s.count - 1 && olen < maxolen {
c1 = decode(s[off])
off += 1
c2 = decode(s[off])
off += 1
if c1 == Byte.max || c2 == Byte.max {
break
}
o = c1 << 2
o |= (c2 & 0x30) >> 4
result[olen] = o
olen += 1
if olen >= maxolen || off >= s.count {
break
}
c3 = decode(s[off])
off += 1
if c3 == Byte.max {
break
}
o = (c2 & 0x0f) << 4
o |= (c3 & 0x3c) >> 2
result[olen] = o
olen += 1
if olen >= maxolen || off >= s.count {
break
}
c4 = decode(s[off])
off += 1
if c4 == Byte.max {
break
}
o = (c3 & 0x03) << 6
o |= c4
result[olen] = o
olen += 1
}
return Array(result[0..<olen])
}
// MARK: Private
private func encode(_ x: Byte) -> Byte {
return encodeMap?(x)
?? Base64Encoder.encodingTable[x]
?? Byte.max
}
private func decode(_ x: Byte) -> Byte {
return decodeMap?(x)
?? Base64Encoder.decodingTable[x]
?? Byte.max
}
}

View File

@ -0,0 +1,159 @@
extension Byte {
/// A
public static let A: Byte = 0x41
/// B
public static let B: Byte = 0x42
/// C
public static let C: Byte = 0x43
/// D
public static let D: Byte = 0x44
/// E
public static let E: Byte = 0x45
/// F
public static let F: Byte = 0x46
/// F
public static let G: Byte = 0x47
/// F
public static let H: Byte = 0x48
/// F
public static let I: Byte = 0x49
/// F
public static let J: Byte = 0x4A
/// F
public static let K: Byte = 0x4B
/// F
public static let L: Byte = 0x4C
/// F
public static let M: Byte = 0x4D
/// F
public static let N: Byte = 0x4E
/// F
public static let O: Byte = 0x4F
/// F
public static let P: Byte = 0x50
/// F
public static let Q: Byte = 0x51
/// F
public static let R: Byte = 0x52
/// F
public static let S: Byte = 0x53
/// F
public static let T: Byte = 0x54
/// F
public static let U: Byte = 0x55
/// F
public static let V: Byte = 0x56
/// F
public static let W: Byte = 0x57
/// F
public static let X: Byte = 0x58
/// F
public static let Y: Byte = 0x59
/// Z
public static let Z: Byte = 0x5A
}
extension Byte {
/// a
public static let a: Byte = 0x61
/// b
public static let b: Byte = 0x62
/// c
public static let c: Byte = 0x63
/// d
public static let d: Byte = 0x64
/// e
public static let e: Byte = 0x65
/// f
public static let f: Byte = 0x66
/// g
public static let g: Byte = 0x67
/// h
public static let h: Byte = 0x68
/// i
public static let i: Byte = 0x69
/// j
public static let j: Byte = 0x6A
/// k
public static let k: Byte = 0x6B
/// l
public static let l: Byte = 0x6C
/// m
public static let m: Byte = 0x6D
/// n
public static let n: Byte = 0x6E
/// o
public static let o: Byte = 0x6F
/// p
public static let p: Byte = 0x70
/// q
public static let q: Byte = 0x71
/// r
public static let r: Byte = 0x72
/// s
public static let s: Byte = 0x73
/// t
public static let t: Byte = 0x74
/// u
public static let u: Byte = 0x75
/// v
public static let v: Byte = 0x76
/// w
public static let w: Byte = 0x77
/// x
public static let x: Byte = 0x78
/// y
public static let y: Byte = 0x79
/// z
public static let z: Byte = 0x7A
}

View File

@ -0,0 +1,106 @@
extension Byte {
/// '\t'
public static let horizontalTab: Byte = 0x9
/// '\n'
public static let newLine: Byte = 0xA
/// '\r'
public static let carriageReturn: Byte = 0xD
/// ' '
public static let space: Byte = 0x20
/// !
public static let exclamation: Byte = 0x21
/// "
public static let quote: Byte = 0x22
/// #
public static let numberSign: Byte = 0x23
/// $
public static let dollar: Byte = 0x24
/// %
public static let percent: Byte = 0x25
/// &
public static let ampersand: Byte = 0x26
/// '
public static let apostrophe: Byte = 0x27
/// (
public static let leftParenthesis: Byte = 0x28
/// )
public static let rightParenthesis: Byte = 0x29
/// *
public static let asterisk: Byte = 0x2A
/// +
public static let plus: Byte = 0x2B
/// ,
public static let comma: Byte = 0x2C
/// -
public static let hyphen: Byte = 0x2D
/// .
public static let period: Byte = 0x2E
/// /
public static let forwardSlash: Byte = 0x2F
/// \
public static let backSlash: Byte = 0x5C
/// :
public static let colon: Byte = 0x3A
/// ;
public static let semicolon: Byte = 0x3B
/// =
public static let equals: Byte = 0x3D
/// ?
public static let questionMark: Byte = 0x3F
/// @
public static let at: Byte = 0x40
/// [
public static let leftSquareBracket: Byte = 0x5B
/// ]
public static let rightSquareBracket: Byte = 0x5D
/// _
public static let underscore: Byte = 0x5F
/// ~
public static let tilda: Byte = 0x7E
/// {
public static let leftCurlyBracket: Byte = 0x7B
/// }
public static let rightCurlyBracket: Byte = 0x7D
}
extension Byte {
/**
Defines the `crlf` used to denote
line breaks in HTTP and many other
formatters
*/
public static let crlf: Bytes = [
.carriageReturn,
.newLine
]
}

View File

@ -0,0 +1,36 @@
extension Byte {
/**
Returns whether or not the given byte can be considered UTF8 whitespace
*/
public var isWhitespace: Bool {
return self == .space || self == .newLine || self == .carriageReturn || self == .horizontalTab
}
/**
Returns whether or not the given byte is an arabic letter
*/
public var isLetter: Bool {
return (.a ... .z).contains(self) || (.A ... .Z).contains(self)
}
/**
Returns whether or not a given byte represents a UTF8 digit 0 through 9
*/
public var isDigit: Bool {
return (.zero ... .nine).contains(self)
}
/**
Returns whether or not a given byte represents a UTF8 digit 0 through 9, or an arabic letter
*/
public var isAlphanumeric: Bool {
return isLetter || isDigit
}
/**
Returns whether a given byte can be interpreted as a hex value in UTF8, ie: 0-9, a-f, A-F.
*/
public var isHexDigit: Bool {
return (.zero ... .nine).contains(self) || (.A ... .F).contains(self) || (.a ... .f).contains(self)
}
}

View File

@ -0,0 +1,42 @@
// MARK: Byte
public func ~=(pattern: Byte, value: Byte) -> Bool {
return pattern == value
}
public func ~=(pattern: Byte, value: BytesSlice) -> Bool {
return value.contains(pattern)
}
public func ~=(pattern: Byte, value: Bytes) -> Bool {
return value.contains(pattern)
}
// MARK: Bytes
public func ~=(pattern: Bytes, value: Byte) -> Bool {
return pattern.contains(value)
}
public func ~=(pattern: Bytes, value: Bytes) -> Bool {
return pattern == value
}
public func ~=(pattern: Bytes, value: BytesSlice) -> Bool {
return pattern == Bytes(value)
}
// MARK: BytesSlice
public func ~=(pattern: BytesSlice, value: Byte) -> Bool {
return pattern.contains(value)
}
public func ~=(pattern: BytesSlice, value: BytesSlice) -> Bool {
return pattern == value
}
public func ~=(pattern: BytesSlice, value: Bytes) -> Bool {
return Bytes(pattern) == value
}

View File

@ -0,0 +1,37 @@
#if os(Linux)
@_exported import Glibc
#else
@_exported import Darwin.C
#endif
extension Byte {
private static let max32 = UInt32(Byte.max)
/**
Create a single random byte
*/
public static func randomByte() -> Byte {
#if os(Linux)
let val = Byte(Glibc.random() % Int(max32))
#else
let val = Byte(arc4random_uniform(max32))
#endif
return val
}
}
extension UnsignedInteger {
/**
Return a random value for the given type.
This should NOT be considered cryptographically secure.
*/
public static func random() -> Self {
let size = MemoryLayout<Self>.size
var bytes: [Byte] = []
(1...size).forEach { _ in
let randomByte = Byte.randomByte()
bytes.append(randomByte)
}
return Self(bytes: bytes)
}
}

View File

@ -0,0 +1,32 @@
extension Byte {
/// 0 in utf8
public static let zero: Byte = 0x30
/// 1 in utf8
public static let one: Byte = 0x31
/// 2 in utf8
public static let two: Byte = 0x32
/// 3 in utf8
public static let three: Byte = 0x33
/// 4 in utf8
public static let four: Byte = 0x34
/// 5 in utf8
public static let five: Byte = 0x35
/// 6 in utf8
public static let six: Byte = 0x36
/// 7 in utf8
public static let seven: Byte = 0x37
/// 8 in utf8
public static let eight: Byte = 0x38
/// 9 in utf8
public static let nine: Byte = 0x39
}

View File

@ -0,0 +1,97 @@
extension Sequence where Iterator.Element == Byte {
/// Converts a slice of bytes to
/// string. Courtesy of @vzsg
public func makeString() -> String {
let array = Array(self) + [0]
return array.withUnsafeBytes { rawBuffer in
guard let pointer = rawBuffer.baseAddress?.assumingMemoryBound(to: CChar.self) else { return nil }
return String(validatingUTF8: pointer)
} ?? ""
}
/**
Converts a byte representation
of a hex value into an `Int`.
as opposed to it's Decimal value
ie: "10" == 16, not 10
*/
public var hexInt: Int? {
var int: Int = 0
for byte in self {
int = int * 16
if byte >= .zero && byte <= .nine {
int += Int(byte - .zero)
} else if byte >= .A && byte <= .F {
int += Int(byte - .A) + 10
} else if byte >= .a && byte <= .f {
int += Int(byte - .a) + 10
} else {
return nil
}
}
return int
}
/**
Converts a utf8 byte representation
of a decimal value into an `Int`
as opposed to it's Hex value,
ie: "10" == 10, not 16
*/
public var decimalInt: Int? {
var int: Int = 0
for byte in self {
int = int * 10
if byte.isDigit {
int += Int(byte - .zero)
} else {
return nil
}
}
return int
}
/**
Transforms anything between Byte.A ... Byte.Z
into the range Byte.a ... Byte.z
*/
public var lowercased: Bytes {
var data = Bytes()
for byte in self {
if (.A ... .Z).contains(byte) {
data.append(byte + (.a - .A))
} else {
data.append(byte)
}
}
return data
}
/**
Transforms anything between Byte.a ... Byte.z
into the range Byte.A ... Byte.Z
*/
public var uppercased: Bytes {
var bytes = Bytes()
for byte in self {
if (.a ... .z).contains(byte) {
bytes.append(byte - (.a - .A))
} else {
bytes.append(byte)
}
}
return bytes
}
}

View File

@ -0,0 +1,23 @@
extension Sequence where Iterator.Element == Byte {
public var base64Encoded: Bytes {
let bytes = Array(self)
return Base64Encoder.shared.encode(bytes)
}
public var base64Decoded: Bytes {
let bytes = Array(self)
return Base64Encoder.shared.decode(bytes)
}
}
extension Sequence where Iterator.Element == Byte {
public var base64URLEncoded: Bytes {
let bytes = Array(self)
return Base64Encoder.url.encode(bytes)
}
public var base64URLDecoded: Bytes {
let bytes = Array(self)
return Base64Encoder.url.decode(bytes)
}
}

View File

@ -0,0 +1,13 @@
import Foundation
extension Sequence where Iterator.Element == Byte {
public var hexEncoded: Bytes {
let bytes = Array(self)
return HexEncoder.shared.encode(bytes)
}
public var hexDecoded: Bytes {
let bytes = Array(self)
return HexEncoder.shared.decode(bytes)
}
}

View File

@ -0,0 +1,33 @@
import Foundation
extension Sequence where Iterator.Element == Byte {
public var percentDecoded: Bytes {
return makeString()
.removingPercentEncoding?
.makeBytes() ?? []
}
public var percentEncodedForURLQuery: Bytes {
return makeString()
.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)?
.makeBytes() ?? []
}
public var percentEncodedForURLPath: Bytes {
return makeString()
.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)?
.makeBytes() ?? []
}
public var percentEncodedForURLHost: Bytes {
return makeString()
.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)?
.makeBytes() ?? []
}
public var percentEncodedForURLFragment: Bytes {
return makeString()
.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed)?
.makeBytes() ?? []
}
}

View File

@ -0,0 +1,25 @@
/**
Used for objects that can be represented as Bytes
*/
public protocol BytesRepresentable {
func makeBytes() throws -> Bytes
}
/**
Used for objects that can be initialized with Bytes
*/
public protocol BytesInitializable {
init(bytes: Bytes) throws
}
/**
Used for objects that can be initialized with, and represented by, Bytes
*/
public protocol BytesConvertible: BytesRepresentable, BytesInitializable { }
extension BytesInitializable {
public init(bytes: BytesRepresentable) throws {
let bytes = try bytes.makeBytes()
try self.init(bytes: bytes)
}
}

View File

@ -0,0 +1,10 @@
import Foundation
extension Data: BytesConvertible {
public func makeBytes() -> Bytes {
var array = Bytes(repeating: 0, count: count)
let buffer = UnsafeMutableBufferPointer(start: &array, count: count)
_ = copyBytes(to: buffer)
return array
}
}

View File

@ -0,0 +1,111 @@
/// Encodes and decodes bytes using the
/// Hexadeicmal encoding
///
/// https://en.wikipedia.org/wiki/Hexadecimal
public final class HexEncoder {
/// Maps binary format to hex encoding
static let encodingTable: [Byte: Byte] = [
0: .zero, 1: .one, 2: .two, 3: .three,
4: .four, 5: .five, 6: .six, 7: .seven,
8: .eight, 9: .nine, 10: .a, 11: .b,
12: .c, 13: .d, 14: .e, 15: .f
]
/// Maps hex encoding to binary format
/// - note: Supports upper and lowercase
static let decodingTable: [Byte: Byte] = [
.zero: 0, .one: 1, .two: 2, .three: 3,
.four: 4, .five: 5, .six: 6, .seven: 7,
.eight: 8, .nine: 9, .a: 10, .b: 11,
.c: 12, .d: 13, .e: 14, .f: 15,
.A: 10, .B: 11, .C: 12, .D: 13,
.E: 14, .F: 15
]
/// Static shared instance
public static let shared = HexEncoder()
/// When true, the encoder will discard
/// any unknown characters while decoding.
/// When false, undecodable characters will
/// cause an early return.
public let ignoreUndecodableCharacters: Bool
/// Creates a new Hexadecimal encoder
public init(ignoreUndecodableCharacters: Bool = true) {
self.ignoreUndecodableCharacters = ignoreUndecodableCharacters
}
/// Encodes bytes into Hexademical format
public func encode(_ message: Bytes) -> Bytes {
var encoded: Bytes = []
for byte in message {
// move the top half of the byte down
// 0x12345678 becomes 0x00001234
let upper = byte >> 4
// zero out the top half of the byte
// 0x12345678 becomes 0x00005678
let lower = byte & 0xF
// encode the 4-bit numbers
// using the 0-f encoding (2^4=16)
encoded.append(encode(upper))
encoded.append(encode(lower))
}
return encoded
}
/// Decodes hexadecimally encoded bytes into
/// binary format
public func decode(_ message: Bytes) -> Bytes {
var decoded: Bytes = []
// create an iterator to easily
// fetch two at a time
var i = message.makeIterator()
// take bytes two at a time
while let c1 = i.next(), let c2 = i.next() {
// decode the first character from
// letter representation to 4-bit number
// e.g, "1" becomes 0x00000001
let upper = decode(c1)
guard upper != Byte.max || ignoreUndecodableCharacters else {
return decoded
}
// decode the second character from
// letter representation to a 4-bit number
let lower = decode(c2)
guard lower != Byte.max || ignoreUndecodableCharacters else {
return decoded
}
// combine the two 4-bit numbers back
// into the original byte, shifting
// the first back up to its 8-bit position
//
// 0x00001234 << 4 | 0x00005678
// becomes:
// 0x12345678
let byte = upper << 4 | lower
decoded.append(byte)
}
return decoded
}
// MARK: Private
private func encode(_ byte: Byte) -> Byte {
return HexEncoder.encodingTable[byte] ?? Byte.max
}
private func decode(_ byte: Byte) -> Byte {
return HexEncoder.decodingTable[byte] ?? Byte.max
}
}

View File

@ -0,0 +1,13 @@
/**
Append the right-hand byte to the end of the bytes array
*/
public func +=(lhs: inout Bytes, rhs: Byte) {
lhs.append(rhs)
}
/**
Append the contents of the byteslice to the end of the bytes array
*/
public func +=(lhs: inout Bytes, rhs: BytesSlice) {
lhs += Array(rhs)
}

View File

@ -0,0 +1,15 @@
extension String: BytesConvertible {
/**
UTF8 Array representation of string
*/
public func makeBytes() -> Bytes {
return Bytes(utf8)
}
/**
Initializes a string with a UTF8 byte array
*/
public init(bytes: Bytes) {
self = bytes.makeString()
}
}

View File

@ -0,0 +1,47 @@
extension UInt8: BytesConvertible {}
extension UInt16: BytesConvertible {}
extension UInt32: BytesConvertible {}
extension UInt64: BytesConvertible {}
extension UnsignedInteger {
/**
Bytes are concatenated to make an Unsigned Integer Object.
[0b1111_1011, 0b0000_1111]
=>
0b1111_1011_0000_1111
*/
public init(bytes: Bytes) {
// 8 bytes in UInt64, etc. clips overflow
let prefix = bytes.suffix(MemoryLayout<Self>.size)
var value: UIntMax = 0
prefix.forEach { byte in
value <<= 8 // 1 byte is 8 bits
value |= byte.toUIntMax()
}
self.init(value)
}
/**
Convert an Unsigned integer into its collection of bytes
0b1111_1011_0000_1111
=>
[0b1111_1011, 0b0000_1111]
... etc.
*/
public func makeBytes() -> Bytes {
let byteMask: Self = 0b1111_1111
let size = MemoryLayout<Self>.size
var copy = self
var bytes: [Byte] = []
(1...size).forEach { _ in
let next = copy & byteMask
let byte = Byte(next.toUIntMax())
bytes.insert(byte, at: 0)
copy.shiftRight(8)
}
return bytes
}
}

View File

@ -0,0 +1,28 @@
extension UnsignedInteger {
/**
Returns whether or not a given bitMask is part of the caller
*/
public func containsMask(_ mask: Self) -> Bool {
return (self & mask) == mask
}
}
extension UnsignedInteger {
/**
A right bit shifter that is supported without the need for a concrete type.
*/
mutating func shiftRight(_ places: Int) {
(1...places).forEach { _ in
self /= 2
}
}
/**
A bit shifter that is supported without the need for a concrete type.
*/
mutating func shiftLeft(_ places: Int) {
(1...places).forEach { _ in
self *= 2
}
}
}

21
Example/Pods/Core/LICENSE generated Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Qutheory, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

20
Example/Pods/Core/README.md generated Normal file
View File

@ -0,0 +1,20 @@
<p align="center">
<img src="https://cloud.githubusercontent.com/assets/1342803/24859350/7065f420-1df0-11e7-9796-577b04d6b35b.png" width="320" alt="Core">
<br>
<br>
<a href="https://docs.vapor.codes/2.0/core/package/">
<img src="http://img.shields.io/badge/read_the-docs-92A8D1.svg" alt="Documentation">
</a>
<a href="http://vapor.team">
<img src="http://vapor.team/badge.svg" alt="Slack Team">
</a>
<a href="LICENSE">
<img src="http://img.shields.io/badge/license-MIT-brightgreen.svg" alt="MIT License">
</a>
<a href="https://circleci.com/gh/vapor/core">
<img src="https://circleci.com/gh/vapor/core.svg?style=shield" alt="Continuous Integration">
</a>
<a href="https://swift.org">
<img src="http://img.shields.io/badge/swift-3.1-brightgreen.svg" alt="Swift 3.1">
</a>
</p>

View File

@ -0,0 +1,49 @@
extension Array {
/**
Turn into an array of various chunk sizes
Last component may not be equal size as others.
[1,2,3,4,5].chunked(size: 2)
==
[[1,2],[3,4],[5]]
*/
public func chunked(size: Int) -> [[Element]] {
return stride(from: 0, to: count, by: size).map { startIndex in
let next = startIndex.advanced(by: size)
let end = next <= endIndex ? next : endIndex
return Array(self[startIndex ..< end])
}
}
}
extension Array where Element: Hashable {
/**
Trims the head and tail of the array to remove contained elements.
[0,1,2,1,0,1,0,0,0,0].trimmed([0])
// == [1,2,1,0,1]
This function is intended to be as performant as possible, which is part of the reason
why some of the underlying logic may seem a bit more tedious than is necessary
*/
public func trimmed(_ elements: [Element]) -> SubSequence {
guard !isEmpty else { return [] }
let lastIdx = self.count - 1
var leadingIterator = self.indices.makeIterator()
var trailingIterator = leadingIterator
var leading = 0
var trailing = lastIdx
while let next = leadingIterator.next(), elements.contains(self[next]) {
leading += 1
}
while let next = trailingIterator.next(), elements.contains(self[lastIdx - next]) {
trailing -= 1
}
guard trailing >= leading else { return [] }
return self[leading...trailing]
}
}

View File

@ -0,0 +1 @@
@_exported import Bits

104
Example/Pods/Core/Sources/Core/Cache.swift generated Normal file
View File

@ -0,0 +1,104 @@
public typealias Size = Int
public protocol Cacheable {
func cacheSize() -> Size
}
public final class SystemCache<Wrapped: Cacheable> {
public let maxSize: Size
private var ordered: OrderedDictionary<String, Wrapped> = .init()
public init(maxSize: Size) {
self.maxSize = maxSize
}
public subscript(key: String) -> Wrapped? {
get {
return ordered[key]
}
set {
ordered[key] = newValue
vent()
}
}
private func vent() {
var dropTotal = totalSize() - maxSize
while dropTotal > 0 {
let next = dropOldest()
guard let size = next?.cacheSize() else { break }
dropTotal -= size
}
}
private func totalSize() -> Size {
return ordered.unorderedItems.map { $0.cacheSize() } .reduce(0, +)
}
private func dropOldest() -> Wrapped? {
guard let oldest = ordered.oldest else { return nil }
ordered[oldest.key] = nil
return oldest.value
}
}
fileprivate struct OrderedDictionary<Key: Hashable, Value> {
fileprivate var oldest: (key: Key, value: Value)? {
guard let key = list.first, let value = backing[key] else { return nil }
return (key, value)
}
fileprivate var newest: (key: Key, value: Value)? {
guard let key = list.last, let value = backing[key] else { return nil }
return (key, value)
}
fileprivate var items: [Value] {
return list.flatMap { backing[$0] }
}
// theoretically slightly faster
fileprivate var unorderedItems: LazyMapCollection<Dictionary<Key, Value>, Value> {
return backing.values
}
private var list: [Key] = []
private var backing: [Key: Value] = [:]
fileprivate subscript(key: Key) -> Value? {
mutating get {
if let existing = backing[key] {
return existing
} else {
remove(key)
return nil
}
}
set {
if let newValue = newValue {
// overwrite anything that might exist
remove(key)
backing[key] = newValue
list.append(key)
} else {
backing[key] = nil
remove(key)
}
}
}
fileprivate subscript(idx: Int) -> (key: Key, value: Value)? {
guard idx < list.count, idx >= 0 else { return nil }
let key = list[idx]
guard let value = backing[key] else { return nil }
return (key, value)
}
fileprivate mutating func remove(_ key: Key) {
if let idx = list.index(of: key) {
list.remove(at: idx)
}
}
}

View File

@ -0,0 +1,11 @@
extension Collection {
/**
Safely access the contents of a collection. Nil if outside of bounds.
*/
public subscript(safe idx: Index) -> Iterator.Element? {
guard startIndex <= idx else { return nil }
// NOT >=, endIndex is "past the end"
guard endIndex > idx else { return nil }
return self[idx]
}
}

View File

@ -0,0 +1,140 @@
import Foundation
/// Basic Foundation implementation of FileProtocols
public final class DataFile: FileProtocol {
/// Working directory will be used when relative
/// paths are supplied
public let workDir: String
/// Creates a DataFile instance with optional workdir.
public init(workDir: String) {
self.workDir = workDir
}
/// @see - FileProtocol.load
public func read(at path: String) throws -> Bytes {
let path = makeAbsolute(path: path)
guard let data = NSData(contentsOfFile: path) else {
throw DataFileError.load(path: path)
}
var bytes = Bytes(repeating: 0, count: data.length)
data.getBytes(&bytes, length: bytes.count)
return bytes
}
/// @see - FileProtocol.save
public func write(_ bytes: Bytes, to path: String) throws {
let path = makeAbsolute(path: path)
if !fileExists(at: path) {
try create(at: path, bytes: bytes)
} else {
try write(to: path, bytes: bytes)
}
}
/// @see - FileProtocol.delete
public func delete(at path: String) throws {
let path = makeAbsolute(path: path)
try FileManager.default.removeItem(atPath: path)
}
// MARK: Private
private func makeAbsolute(path: String) -> String {
return path.hasPrefix("/") ? path : workDir + path
}
private func create(at path: String, bytes: Bytes) throws {
let data = Data(bytes: bytes)
let success = FileManager.default.createFile(
atPath: path,
contents: data,
attributes: nil
)
guard success else { throw DataFileError.create(path: path) }
}
private func fileExists(at path: String) -> Bool {
return FileManager.default.fileExists(atPath: path)
}
private func write(to path: String, bytes: Bytes) throws {
let bytes = Data(bytes: bytes)
let url = URL(fileURLWithPath: path)
try bytes.write(to: url)
}
}
extension DataFile: EmptyInitializable {
public convenience init() {
self.init(workDir: workingDirectory())
}
}
// MARK: Error
public enum DataFileError: Error {
case create(path: String)
case load(path: String)
case unspecified(Swift.Error)
}
extension DataFileError: Debuggable {
public var identifier: String {
switch self {
case .create:
return "create"
case .load:
return "load"
case .unspecified:
return "unspecified"
}
}
public var reason: String {
switch self {
case .create(let path):
return "unable to create the file at path \(path)"
case .load(let path):
return "unable to load file at path \(path)"
case .unspecified(let error):
return "received an unspecified or extended error: \(error)"
}
}
public var possibleCauses: [String] {
switch self {
case .create:
return [
"missing write permissions at specified path",
"attempted to write corrupted data",
"system issue"
]
case .load:
return [
"file doesn't exist",
"missing read permissions at specified path",
"data read is corrupted",
"system issue"
]
case .unspecified:
return [
"received an error not originally supported by this version"
]
}
}
public var suggestedFixes: [String] {
return [
"ensure that file permissions are correct for specified paths"
]
}
public var documentationLinks: [String] {
return [
"https://developer.apple.com/reference/foundation/filemanager",
]
}
}

View File

@ -0,0 +1,8 @@
import Dispatch
/**
A simple background function that uses dispatch to send to a global queue
*/
public func background(function: @escaping () -> Void) {
DispatchQueue.global().async(execute: function)
}

View File

@ -0,0 +1,18 @@
import Foundation
import Dispatch
extension Double {
internal var nanoseconds: UInt64 {
return UInt64(self * Double(1_000_000_000))
}
}
extension DispatchTime {
/**
Create a dispatch time for a given seconds from now.
*/
public init(secondsFromNow: Double) {
let uptime = DispatchTime.now().rawValue + secondsFromNow.nanoseconds
self.init(uptimeNanoseconds: uptime)
}
}

View File

@ -0,0 +1,6 @@
/// Types conforming to this protocol can
/// be initialized with no arguments, allowing
/// protocols to add static convenience methods.
public protocol EmptyInitializable {
init() throws
}

View File

@ -0,0 +1 @@
@_exported import Debugging

View File

@ -0,0 +1,9 @@
/// Types conforming to this protocol can store
/// arbitrary key-value data.
///
/// Extensions can utilize this arbitrary data store
/// to simulate optional stored properties.
public protocol Extendable {
/// Arbitrary key-value data store.
var extend: [String: Any] { get set }
}

View File

@ -0,0 +1,30 @@
/// Objects conforming to this protocol
/// can load and save files to a persistent
/// data store.
public protocol FileProtocol {
/// Load the bytes at a given path
func read(at path: String) throws -> Bytes
/// Save the bytes to a given path
func write(_ bytes: Bytes, to path: String) throws
/// Deletes the file at a given path
func delete(at path: String) throws
}
extension FileProtocol where Self: EmptyInitializable {
/// Load the bytes at a given path
public static func read(at path: String) throws -> Bytes {
return try Self().read(at: path)
}
/// Save the bytes to a given path
public static func write(_ bytes: Bytes, to path: String) throws {
try Self().write(bytes, to: path)
}
/// Deletes the file at a given path
public static func delete(at path: String) throws {
try Self().delete(at: path)
}
}

View File

@ -0,0 +1,29 @@
extension SignedInteger {
/**
Convert a Signed integer into a hex string representation
255
=>
FF
NOTE: Will always return UPPERCASED VALUES
*/
public var hex: String {
return String(self, radix: 16).uppercased()
}
}
extension UnsignedInteger {
/**
Convert a Signed integer into a hex string representation
255
=>
FF
NOTE: Will always return UPPERCASED VALUES
*/
public var hex: String {
return String(self, radix: 16).uppercased()
}
}

View File

@ -0,0 +1,10 @@
import Foundation
extension NSLock {
public func locked(closure: () throws -> Void) rethrows {
lock()
defer { unlock() } // MUST be deferred to ensure lock releases if throws
try closure()
}
}

View File

@ -0,0 +1,143 @@
import Foundation
import Dispatch
/**
There was an error thrown by the portal itself vs a user thrown variable
*/
public enum PortalError: String, Debuggable {
/**
Portal was destroyed w/o being closed
*/
case notClosed
/**
Portal timedOut before it was closed.
*/
case timedOut
}
/**
This class is designed to make it possible to use asynchronous contexts in a synchronous environment.
*/
public final class Portal<T> {
fileprivate var result: Result<T>? = .none
private let semaphore: DispatchSemaphore
private let lock = NSLock()
fileprivate init(_ semaphore: DispatchSemaphore) {
self.semaphore = semaphore
}
/**
Close the portal with a successful result
*/
public func close(with value: T) {
lock.locked {
guard result == nil else { return }
result = .success(value)
semaphore.signal()
}
}
/**
Close the portal with an appropriate error
*/
public func close(with error: Error) {
lock.locked {
guard result == nil else { return }
result = .failure(error)
semaphore.signal()
}
}
/**
Dismiss the portal throwing a notClosed error.
*/
public func destroy() {
semaphore.signal()
}
}
extension Portal {
/**
This function is used to enter an asynchronous supported context with a portal
object that can be used to complete a given operation.
timeout in SECONDS
let value = try Portal<Int>.open { portal in
// .. do whatever necessary passing around `portal` object
// eventually call
portal.close(with: 42)
// or
portal.close(with: errorSignifyingFailure)
}
- warning: Calling close on a `portal` multiple times will have no effect.
*/
public static func open(
timeout: Double = (60 * 60),
_ handler: @escaping (Portal) throws -> Void
) throws -> T {
let semaphore = DispatchSemaphore(value: 0)
let portal = Portal<T>(semaphore)
background {
do {
try handler(portal)
} catch {
portal.close(with: error)
}
}
let waitResult = semaphore.wait(timeout: timeout)
switch waitResult {
case .success:
guard let result = portal.result else { throw PortalError.notClosed }
return try result.extract()
case .timedOut:
throw PortalError.timedOut
}
}
}
extension Portal {
/**
Execute timeout operations
*/
static func timeout(_ timeout: Double, operation: @escaping () throws -> T) throws -> T {
return try Portal<T>.open(timeout: timeout) { portal in
let value = try operation()
portal.close(with: value)
}
}
}
extension PortalError {
public var identifier: String {
return rawValue
}
public var reason: String {
switch self {
case .notClosed:
return "the portal finished, but was somehow not properly closed"
case .timedOut:
return "the portal timed out before it could finish its operation"
}
}
public var possibleCauses: [String] {
return [
"user forgot to call `portal.close(with: )`"
]
}
public var suggestedFixes: [String] {
return [
"ensure the timeout length is adequate for required operation time",
"make sure that `portal.close(with: )` is being called with an error or valid value"
]
}
}

View File

@ -0,0 +1,27 @@
import Foundation
public struct RFC1123 {
public static let shared = RFC1123()
public let formatter: DateFormatter
public init() {
let formatter = DateFormatter()
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss z"
self.formatter = formatter
}
}
extension Date {
public var rfc1123: String {
return RFC1123.shared.formatter.string(from: self)
}
public init?(rfc1123: String) {
guard let date = RFC1123.shared.formatter.date(from: rfc1123) else {
return nil
}
self = date
}
}

View File

@ -0,0 +1,33 @@
public enum Result<T> {
case success(T)
case failure(Error)
}
extension Result {
public func extract() throws -> T {
switch self {
case .success(let val):
return val
case .failure(let e):
throw e
}
}
}
extension Result {
public var value: T? {
guard case let .success(val) = self else { return nil }
return val
}
public var error: Error? {
guard case let .failure(err) = self else { return nil }
return err
}
}
extension Result {
public var succeeded: Bool {
return value != nil
}
}

View File

@ -0,0 +1,12 @@
import Dispatch
extension DispatchSemaphore {
/**
Wait for a specified time in SECONDS
timeout if necessary
*/
public func wait(timeout: Double) -> DispatchTimeoutResult {
let time = DispatchTime(secondsFromNow: timeout)
return wait(timeout: time)
}
}

View File

@ -0,0 +1,8 @@
extension Sequence {
/**
Convert the given sequence to its array representation
*/
public var array: [Iterator.Element] {
return Array(self)
}
}

View File

@ -0,0 +1,121 @@
/**
This class is intended to make interacting with and
iterating through a static data buffer a simpler process.
It's intent is to be subclassed so the next
function can be overridden with further rules.
*/
open class StaticDataBuffer {
private var localBuffer: [Byte] = []
private var buffer: AnyIterator<Byte>
public init<S: Sequence>(bytes: S) where S.Iterator.Element == Byte {
var any = bytes.makeIterator()
self.buffer = AnyIterator { return any.next() }
}
// MARK: Next
open func next() throws -> Byte? {
/*
Local buffer is used to maintain last bytes
while still interacting w/ byte buffer.
*/
guard localBuffer.isEmpty else {
return localBuffer.removeFirst()
}
return buffer.next()
}
public func next(matchesAny: Byte...) throws -> Bool {
guard let next = try next() else { return false }
returnToBuffer(next)
return matchesAny.contains(next)
}
public func next(matches: (Byte) throws -> Bool) throws -> Bool {
guard let next = try next() else { return false }
returnToBuffer(next)
return try matches(next)
}
// MARK:
public func returnToBuffer(_ byte: Byte) {
returnToBuffer([byte])
}
public func returnToBuffer(_ bytes: [Byte]) {
localBuffer.append(contentsOf: bytes)
}
// MARK: Discard Extranneous Tokens
public func discardNext(_ count: Int) throws {
_ = try collect(next: count)
}
// MARK: Check Tokens
public func checkLeadingBuffer(matches: Byte...) throws -> Bool {
return try checkLeadingBuffer(matches: matches)
}
public func checkLeadingBuffer(matches: [Byte]) throws -> Bool {
let leading = try collect(next: matches.count)
returnToBuffer(leading)
return leading == matches
}
// MARK: Collection
public func collect(next count: Int) throws -> [Byte] {
guard count > 0 else { return [] }
var body: [Byte] = []
try (1...count).forEach { _ in
guard let next = try next() else { return }
body.append(next)
}
return body
}
/**
Collect until delimitters are reached, optionally convert
specific bytes along the way
When in Query segment, `+` should be interpreted as ` ` (space),
not sure useful outside of that point.
*/
public func collect(
until delimitters: Byte...,
convertIfNecessary: (Byte) -> Byte = { $0 }
) throws -> [Byte] {
var collected: [Byte] = []
while let next = try next() {
if delimitters.contains(next) {
// If the delimitter is also a token that identifies
// a particular section of the URI
// then we may want to return that byte to the buffer
returnToBuffer(next)
break
}
let converted = convertIfNecessary(next)
collected.append(converted)
}
return collected
}
/**
Collect any remaining bytes until buffer is empty
*/
public func collectRemaining() throws -> [Byte] {
var complete: [Byte] = []
while let next = try next() {
complete.append(next)
}
return complete
}
}

View File

@ -0,0 +1,8 @@
extension String {
/**
Case insensitive comparison on argument
*/
public func equals(caseInsensitive: String) -> Bool {
return lowercased() == caseInsensitive.lowercased()
}
}

View File

@ -0,0 +1,95 @@
extension String {
/// Determines whether or not the `String` is null.
/// Returns `true` if the `String` is equal to `"null"`.
public var isNull: Bool {
return self.lowercased() == "null"
}
/// Attempts to convert the String to a `Bool`.
/// The conversion **may** succeed if the `String`
/// has a truthy/falsey value like `"yes"` or `"false"`
/// All others will always return `nil`.
public var bool: Bool? {
switch lowercased() {
case "y", "1", "yes", "t", "true", "on":
return true
case "n", "0", "no", "f", "false", "off":
return false
default:
return nil
}
}
/// Attempts to convert the `String` to a `Float`.
/// The conversion uses the `Float(_: String)` initializer.
public var float: Float? {
return Float(self)
}
/// Attempts to convert the `String` to a `Double`.
/// The conversion uses the `Double(_: String)` initializer.
public var double: Double? {
return Double(self)
}
/// Attempts to convert the `String` to a `Int`.
/// The conversion uses the `Int(_: String)` initializer.
public var int: Int? {
return Int(self)
}
/// Attempts to convert the `String` to a `UInt`.
/// The conversion uses the `UInt(_: String)` initializer.
public var uint: UInt? {
return UInt(self)
}
/// Attempts to convert the `String` to a `String`.
/// This always works.
public var string: String {
return self
}
/// Converts the string to a UTF8 array of bytes.
public var bytes: [UInt8] {
return [UInt8](self.utf8)
}
}
extension String {
/// Attempts to convert the `String` to an `Array`.
/// Comma separated items will be split into
/// multiple entries.
public func commaSeparatedArray() -> [String] {
return characters
.split(separator: ",")
.map { String($0) }
.map { $0.trimmedWhitespace() }
}
}
extension String {
fileprivate func trimmedWhitespace() -> String {
var characters = self.characters
while characters.first?.isWhitespace == true {
characters.removeFirst()
}
while characters.last?.isWhitespace == true {
characters.removeLast()
}
return String(characters)
}
}
extension Character {
fileprivate var isWhitespace: Bool {
switch self {
case " ", "\t", "\n", "\r":
return true
default:
return false
}
}
}

View File

@ -0,0 +1,15 @@
extension String {
/**
Ensures a string has a strailing suffix w/o duplicating
"hello.jpg".finished(with: ".jpg")
// == 'hello.jpg'
"hello".finished(with: ".jpg")
// == 'hello.jpg'
*/
public func finished(with end: String) -> String {
guard !self.hasSuffix(end) else { return self }
return self + end
}
}

View File

@ -0,0 +1,46 @@
#if !COCOAPODS
import libc
#endif
/// This function will attempt to get the current
/// working directory of the application
public func workingDirectory() -> String {
let fileBasedWorkDir: String?
#if Xcode
// attempt to find working directory through #file
let file = #file
if file.contains(".build") {
// most dependencies are in `./.build/`
fileBasedWorkDir = file.components(separatedBy: "/.build").first
} else if file.contains("Packages") {
// when editing a dependency, it is in `./Packages/`
fileBasedWorkDir = file.components(separatedBy: "/Packages").first
} else {
// when dealing with current repository, file is in `./Sources/`
fileBasedWorkDir = file.components(separatedBy: "/Sources").first
}
#else
fileBasedWorkDir = nil
#endif
let workDir: String
if let fileBasedWorkDir = fileBasedWorkDir {
workDir = fileBasedWorkDir
} else {
// get actual working directory
let cwd = getcwd(nil, Int(PATH_MAX))
defer {
free(cwd)
}
if let cwd = cwd, let string = String(validatingUTF8: cwd) {
workDir = string
} else {
workDir = "./"
}
}
return workDir.finished(with: "/")
}

View File

@ -0,0 +1,5 @@
#if os(Linux)
@_exported import Glibc
#else
@_exported import Darwin.C
#endif

21
Example/Pods/Debugging/LICENSE generated Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 Qutheory, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

20
Example/Pods/Debugging/README.md generated Normal file
View File

@ -0,0 +1,20 @@
<p align="center">
<img src="https://cloud.githubusercontent.com/assets/1977704/25427559/46489b68-2a73-11e7-9a48-4c4ae6002fad.png" width="320" alt="Debugging">
<br>
<br>
<a href="http://docs.vapor.codes/debugging/package">
<img src="http://img.shields.io/badge/read_the-docs-92A8D1.svg" alt="Documentation">
</a>
<a href="http://vapor.team">
<img src="http://vapor.team/badge.svg" alt="Slack Team">
</a>
<a href="LICENSE">
<img src="http://img.shields.io/badge/license-MIT-brightgreen.svg" alt="MIT License">
</a>
<a href="https://circleci.com/gh/vapor/debugging">
<img src="https://circleci.com/gh/vapor/debugging.svg?style=shield" alt="Continuous Integration">
</a>
<a href="https://swift.org">
<img src="http://img.shields.io/badge/swift-3.1-brightgreen.svg" alt="Swift 3.1">
</a>
</center>

View File

@ -0,0 +1,181 @@
/// `Debuggable` provides an interface that allows a type
/// to be more easily debugged in the case of an error.
public protocol Debuggable: Swift.Error, CustomDebugStringConvertible {
/// A readable name for the error's Type. This is usually
/// similar to the Type name of the error with spaces added.
/// This will normally be printed proceeding the error's reason.
/// - note: For example, an error named `FooError` will have the
/// `readableName` `"Foo Error"`.
static var readableName: String { get }
/// The reason for the error.
/// Typical implementations will switch over `self`
/// and return a friendly `String` describing the error.
/// - note: It is most convenient that `self` be a `Swift.Error`.
///
/// Here is one way to do this:
///
/// switch self {
/// case someError:
/// return "A `String` describing what went wrong including the actual error: `Error.someError`."
/// // other cases
/// }
var reason: String { get }
// MARK: Identifiers
/// A unique identifier for the error's Type.
/// - note: This defaults to `ModuleName.TypeName`,
/// and is used to create the `identifier` property.
static var typeIdentifier: String { get }
/// Some unique identifier for this specific error.
/// This will be used to create the `identifier` property.
/// Do NOT use `String(reflecting: self)` or `String(describing: self)`
/// or there will be infinite recursion
var identifier: String { get }
// MARK: Help
/// A `String` array describing the possible causes of the error.
/// - note: Defaults to an empty array.
/// Provide a custom implementation to give more context.
var possibleCauses: [String] { get }
/// A `String` array listing some common fixes for the error.
/// - note: Defaults to an empty array.
/// Provide a custom implementation to be more helpful.
var suggestedFixes: [String] { get }
/// An array of string `URL`s linking to documentation pertaining to the error.
/// - note: Defaults to an empty array.
/// Provide a custom implementation with relevant links.
var documentationLinks: [String] { get }
/// An array of string `URL`s linking to related Stack Overflow questions.
/// - note: Defaults to an empty array.
/// Provide a custom implementation to link to useful questions.
var stackOverflowQuestions: [String] { get }
/// An array of string `URL`s linking to related issues on Vapor's GitHub repo.
/// - note: Defaults to an empty array.
/// Provide a custom implementation to a list of pertinent issues.
var gitHubIssues: [String] { get }
}
// MARK: Optionals
extension Debuggable {
public var documentationLinks: [String] {
return []
}
public var stackOverflowQuestions: [String] {
return []
}
public var gitHubIssues: [String] {
return []
}
}
extension Debuggable {
public var fullIdentifier: String {
return Self.typeIdentifier + "." + identifier
}
}
// MARK: Defaults
extension Debuggable {
/// Default implementation of readable name that expands
/// SomeModule.MyType.Error => My Type Error
public static var readableName: String {
return typeIdentifier.readableTypeName()
}
public static var typeIdentifier: String {
return String(reflecting: self)
}
public var debugDescription: String {
return printable
}
}
extension String {
func readableTypeName() -> String {
let characterSequence = self.characters
.split(separator: ".")
.dropFirst() // drop module
.joined(separator: [])
let characters = Array(characterSequence)
guard var expanded = characters.first.flatMap({ String($0) }) else { return "" }
characters.suffix(from: 1).forEach { char in
if char.isUppercase {
expanded.append(" ")
}
expanded.append(char)
}
return expanded
}
}
extension Character {
var isUppercase: Bool {
switch self {
case "A"..."Z":
return true
default:
return false
}
}
}
// MARK: Representations
extension Debuggable {
/// A computed property returning a `String` that encapsulates
/// why the error occurred, suggestions on how to fix the problem,
/// and resources to consult in debugging (if these are available).
/// - note: This representation is best used with functions like print()
public var printable: String {
var print: [String] = []
print.append("\(Self.readableName): \(reason)")
print.append("Identifier: \(fullIdentifier)")
if !possibleCauses.isEmpty {
print.append("Here are some possible causes: \(possibleCauses.bulletedList)")
}
if !suggestedFixes.isEmpty {
print.append("These suggestions could address the issue: \(suggestedFixes.bulletedList)")
}
if !documentationLinks.isEmpty {
print.append("Vapor's documentation talks about this: \(documentationLinks.bulletedList)")
}
if !stackOverflowQuestions.isEmpty {
print.append("These Stack Overflow links might be helpful: \(stackOverflowQuestions.bulletedList)")
}
if !gitHubIssues.isEmpty {
print.append("See these Github issues for discussion on this topic: \(gitHubIssues.bulletedList)")
}
return print.joined(separator: "\n\n")
}
}
extension Sequence where Iterator.Element == String {
var bulletedList: String {
return map { "\n- \($0)" } .joined()
}
}

19
Example/Pods/Fuzi/LICENSE generated Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2015 Ce Zheng
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

330
Example/Pods/Fuzi/README-ja.md generated Normal file
View File

@ -0,0 +1,330 @@
# Fuzi (斧子)
[![Build Status](https://api.travis-ci.org/cezheng/Fuzi.svg)](https://travis-ci.org/cezheng/Fuzi)
[![Cocoapods Compatible](https://img.shields.io/cocoapods/v/Fuzi.svg)](https://cocoapods.org/pods/Fuzi)
[![License](https://img.shields.io/cocoapods/l/Fuzi.svg?style=flat&color=gray)](http://opensource.org/licenses/MIT)
[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Platform](https://img.shields.io/cocoapods/p/Fuzi.svg?style=flat)](http://cezheng.github.io/Fuzi/)
[![Twitter](https://img.shields.io/badge/twitter-@AdamoCheng-blue.svg?style=flat)](http://twitter.com/AdamoCheng)
**軽くて、素早くて、 Swift の XML/HTML パーサー。** [[ドキュメント]](http://cezheng.github.io/Fuzi/)
Fuzi は Mattt Thompson氏の [Ono](https://github.com/mattt/Ono)(斧) に参照し Swift 言語で実装した XML/HTML パーサーである。
> Fuzi は漢字の`斧子`の中国語発音で、 意味は[Ono](https://github.com/mattt/Ono)(斧)と同じ。Onoは、[Nokogiri](http://nokogiri.org)(鋸)を参照し、創ったもの。
[English](https://github.com/cezheng/Fuzi/blob/master/README.md)
[简体中文](https://github.com/cezheng/Fuzi/blob/master/README-zh.md)
## クイックルック
```swift
let xml = "..."
// or
// let xmlData = <some NSData or Data>
do {
let document = try XMLDocument(string: xml)
// or
// let document = try XMLDocument(data: xmlData)
if let root = document.root {
// Accessing all child nodes of root element
for element in root.children {
print("\(element.tag): \(element.attributes)")
}
// Getting child element by tag & accessing attributes
if let length = root.firstChild(tag:"Length", inNamespace: "dc") {
print(length["unit"]) // `unit` attribute
print(length.attributes) // all attributes
}
}
// XPath & CSS queries
for element in document.xpath("//element") {
print("\(element.tag): \(element.attributes)")
}
if let firstLink = document.firstChild(css: "a, link") {
print(firstLink["href"])
}
} catch let error {
print(error)
}
```
## 機能
### Onoから貰った機能
- `libxml2`での素早いXMLパース
- [XPath](http://en.wikipedia.org/wiki/XPath) と [CSS](http://en.wikipedia.org/wiki/Cascading_Style_Sheets) クエリ
- 自動的にデータを日付や数字に変換する
- XML ネイムスペース
- `String``NSData``[CChar]`からXMLDocumentをロードする
- 全面的なユニットテスト
- 100%ドキュメント
### Fuziの改善点
- Swift 言語のネーミングやコーディングルールに沿って、クラスやメソッドを再設計した
- 日付や数字変換のフォマットを指定できる
- いくつかのバグ修正
- より多くのHTML便利メソッド
- 全種類のXMLード取得可能テキストードやコメントードなども含め
- より多くのCSSクエリ対応 (これから)
## 環境
- iOS 8.0+ / Mac OS X 10.9+
- Xcode 8.0+
> Swift 2.3は[0.4.0](../../releases/tag/0.4.0)をご利用ください。
## インストール
### CocoaPodsで
[Cocoapods](http://cocoapods.org/) で簡単に `Fuzi` をインストールできます。 下記のように`Podfile`を編集してください:
```ruby
platform :ios, '8.0'
use_frameworks!
target 'MyApp' do
pod 'Fuzi', '~> 1.0.0'
end
```
そして、下記のコマンドを実行してください:
```bash
$ pod install
```
### 手動で
1. `Fuzi`フォルダの `*.swift` ファイルをプロジェクトに追加してください。
2. `libxml2`フォルダをプロジェクトのフォルダのどこか( `/path/to/somewhere`)にコピペしてください。
3. Xcode プロジェクトの `Build Settings` で:
1. `Swift Compiler - Search Paths`の`Import Paths`に`/path/to/somewhere/libxml2`を追加してください。
2. `Search Paths`の`Header Search Paths`に`$(SDKROOT)/usr/include/libxml2`を追加してください。
3. `Linking`の`Other Linker Flags`に`-lxml2`を追加してください。
### Carthageで
プロダクトのディレクトリに`Cartfile` か `Cartfile.private`のファイルを作成し、下記の行を追加してください:
```
github "cezheng/Fuzi" ~> 1.0.0
```
そして、下記のコマンドを実行してください:
```
$ carthage update
```
最後に、下記のようにXcodeのtargetを設定してください
1. ビルドターゲットの`General` -> `Embedded Binaries`に、Carthageがビルドした`Fuzi.framework`を追加してください。
2. `Build Settings`で`Search Paths`の`Header Search Paths`に`$(SDKROOT)/usr/include/libxml2`を追加してください。
##用例
###XML
```swift
import Fuzi
let xml = "..."
do {
// if encoding is omitted, it defaults to NSUTF8StringEncoding
let doc = try XMLDocument(string: html, encoding: NSUTF8StringEncoding)
if let root = document.root {
print(root.tag)
// define a prefix for a namespace
document.definePrefix("atom", defaultNamespace: "http://www.w3.org/2005/Atom")
// get first child element with given tag in namespace(optional)
print(root.firstChild(tag: "title", inNamespace: "atom"))
// iterate through all children
for element in root.children {
print("\(index) \(element.tag): \(element.attributes)")
}
}
// you can also use CSS selector against XMLDocument when you feels it makes sense
} catch let error as XMLError {
switch error {
case .noError: print("wth this should not appear")
case .parserFailure, .invalidData: print(error)
case .libXMLError(let code, let message):
print("libxml error code: \(code), message: \(message)")
}
}
```
###HTML
`HTMLDocument``XMLDocument` サブクラス。
```swift
import Fuzi
let html = "<html>...</html>"
do {
// if encoding is omitted, it defaults to NSUTF8StringEncoding
let doc = try HTMLDocument(string: html, encoding: NSUTF8StringEncoding)
// CSS queries
if let elementById = doc.firstChild(css: "#id") {
print(elementById.stringValue)
}
for link in doc.css("a, link") {
print(link.rawXML)
print(link["href"])
}
// XPath queries
if let firstAnchor = doc.firstChild(xpath: "//body/a") {
print(firstAnchor["href"])
}
for script in doc.xpath("//head/script") {
print(script["src"])
}
// Evaluate XPath functions
if let result = doc.eval(xpath: "count(/*/a)") {
print("anchor count : \(result.doubleValue)")
}
// Convenient HTML methods
print(doc.title) // gets <title>'s innerHTML in <head>
print(doc.head) // gets <head> element
print(doc.body) // gets <body> element
} catch let error {
print(error)
}
```
###エラー処理なんて、どうでもいい場合
```swift
import Fuzi
let xml = "..."
// Don't show me the errors, just don't crash
if let doc1 = try? XMLDocument(string: xml) {
//...
}
let html = "<html>...</html>"
// I'm sure this won't crash
let doc2 = try! HTMLDocument(string: html)
//...
```
###テキストノードを取得したい
テキストノードだけではなく、全種類のノードは取得可能。
```swift
let document = ...
// すべてのエレメント、テキストとコメント子要素を取得する
document.root?.childNodes(ofTypes: [.Element, .Text, .Comment])
##Onoからの移行?
下記2つのサンプルコードを見たら、`Ono`と`Fuzi`の違いをわかる。
[Onoサンプル](https://github.com/mattt/Ono/blob/master/Example/main.m)
[Fuziサンプル](FuziDemo/FuziDemo/main.swift)
###子要素を取得
**Ono**
```objc
[doc firstChildWithTag:tag inNamespace:namespace];
[doc firstChildWithXPath:xpath];
[doc firstChildWithXPath:css];
for (ONOXMLElement *element in parent.children) {
//...
}
[doc childrenWithTag:tag inNamespace:namespace];
```
**Fuzi**
```swift
doc.firstChild(tag: tag, inNamespace: namespace)
doc.firstChild(xpath: xpath)
doc.firstChild(css: css)
for element in parent.children {
//...
}
doc.children(tag: tag, inNamespace:namespace)
```
###クエリ結果を読み込む
**Ono**
Objective-Cの`NSFastEnumeration`。
```objc
// simply iterating through the results
// mark `__unused` to unused params `idx` and `stop`
[doc enumerateElementsWithXPath:xpath usingBlock:^(ONOXMLElement *element, __unused NSUInteger idx, __unused BOOL *stop) {
NSLog(@"%@", element);
}];
// stop the iteration at second element
[doc enumerateElementsWithXPath:XPath usingBlock:^(ONOXMLElement *element, NSUInteger idx, BOOL *stop) {
*stop = (idx == 1);
}];
// getting element by index
ONOXMLDocument *nthElement = [(NSEnumerator*)[doc CSS:css] allObjects][n];
// total element count
NSUInteger count = [(NSEnumerator*)[document XPath:xpath] allObjects].count;
```
**Fuzi**
Swift の `SequenceType``Indexable`
```swift
// simply iterating through the results
// no need to write the unused `idx` or `stop` params
for element in doc.xpath(xpath) {
print(element)
}
// stop the iteration at second element
for (index, element) in doc.xpath(xpath).enumerate() {
if idx == 1 {
break
}
}
// getting element by index
if let nthElement = doc.css(css)[n] {
//...
}
// total element count
let count = doc.xpath(xpath).count
```
###XPath関数を評価する
**Ono**
```objc
ONOXPathFunctionResult *result = [doc functionResultByEvaluatingXPath:xpath];
result.boolValue; //BOOL
result.numericValue; //double
result.stringValue; //NSString
```
**Fuzi**
```swift
if let result = doc.eval(xpath: xpath) {
result.boolValue //Bool
result.doubleValue //Double
result.stringValue //String
}
```
## ライセンス
`Fuzi` のオープンソースライセンスは MIT です。 詳しくはこちら [LICENSE](LICENSE) 。

206
Example/Pods/Fuzi/Sources/Document.swift generated Normal file
View File

@ -0,0 +1,206 @@
// Document.swift
// Copyright (c) 2015 Ce Zheng
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
import libxml2
/// XML document which can be searched and queried.
open class XMLDocument {
// MARK: - Document Attributes
/// The XML version.
open fileprivate(set) lazy var version: String? = {
return ^-^self.cDocument.pointee.version
}()
/// The string encoding for the document. This is NSUTF8StringEncoding if no encoding is set, or it cannot be calculated.
open fileprivate(set) lazy var encoding: String.Encoding = {
if let encodingName = ^-^self.cDocument.pointee.encoding {
let encoding = CFStringConvertIANACharSetNameToEncoding(encodingName as CFString!)
if encoding != kCFStringEncodingInvalidId {
return String.Encoding(rawValue: UInt(CFStringConvertEncodingToNSStringEncoding(encoding)))
}
}
return String.Encoding.utf8
}()
// MARK: - Accessing the Root Element
/// The root element of the document.
open fileprivate(set) var root: XMLElement?
// MARK: - Accessing & Setting Document Formatters
/// The formatter used to determine `numberValue` for elements in the document. By default, this is an `NSNumberFormatter` instance with `NSNumberFormatterDecimalStyle`.
open lazy var numberFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter
}()
/// The formatter used to determine `dateValue` for elements in the document. By default, this is an `NSDateFormatter` instance configured to accept ISO 8601 formatted timestamps.
open lazy var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
return formatter
}()
// MARK: - Creating XML Documents
fileprivate let cDocument: xmlDocPtr
/**
Creates and returns an instance of XMLDocument from an XML string, throwing XMLError if an error occured while parsing the XML.
- parameter string: The XML string.
- parameter encoding: The string encoding.
- throws: `XMLError` instance if an error occurred
- returns: An `XMLDocument` with the contents of the specified XML string.
*/
public convenience init(string: String, encoding: String.Encoding = String.Encoding.utf8) throws {
guard let cChars = string.cString(using: encoding) else {
throw XMLError.invalidData
}
try self.init(cChars: cChars)
}
/**
Creates and returns an instance of XMLDocument from XML data, throwing XMLError if an error occured while parsing the XML.
- parameter data: The XML data.
- throws: `XMLError` instance if an error occurred
- returns: An `XMLDocument` with the contents of the specified XML string.
*/
public convenience init(data: Data) throws {
let cChars = data.withUnsafeBytes { (bytes: UnsafePointer<Int8>) -> [CChar] in
let buffer = UnsafeBufferPointer(start: bytes, count: data.count)
return [CChar](buffer)
}
try self.init(cChars: cChars)
}
/**
Creates and returns an instance of XMLDocument from C char array, throwing XMLError if an error occured while parsing the XML.
- parameter cChars: cChars The XML data as C char array
- throws: `XMLError` instance if an error occurred
- returns: An `XMLDocument` with the contents of the specified XML string.
*/
public convenience init(cChars: [CChar]) throws {
let options = Int32(XML_PARSE_NOWARNING.rawValue | XML_PARSE_NOERROR.rawValue | XML_PARSE_RECOVER.rawValue)
try self.init(cChars: cChars, options: options)
}
fileprivate typealias ParseFunction = (UnsafePointer<Int8>?, Int32, UnsafePointer<Int8>?, UnsafePointer<Int8>?, Int32) -> xmlDocPtr?
fileprivate convenience init(cChars: [CChar], options: Int32) throws {
try self.init(parseFunction: { xmlReadMemory($0, $1, $2, $3, $4) }, cChars: cChars, options: options)
}
fileprivate convenience init(parseFunction: ParseFunction, cChars: [CChar], options: Int32) throws {
guard let document = parseFunction(UnsafePointer(cChars), Int32(cChars.count), "", nil, options) else {
throw XMLError.lastError(defaultError: .parserFailure)
}
xmlResetLastError()
self.init(cDocument: document)
}
fileprivate init(cDocument: xmlDocPtr) {
self.cDocument = cDocument
if let cRoot = xmlDocGetRootElement(cDocument) {
root = XMLElement(cNode: cRoot, document: self)
}
}
deinit {
xmlFreeDoc(cDocument)
}
// MARK: - XML Namespaces
var defaultNamespaces = [String: String]()
/**
Define a prefix for a default namespace.
- parameter prefix: The prefix name
- parameter ns: The default namespace URI that declared in XML Document
*/
open func definePrefix(_ prefix: String, defaultNamespace ns: String) {
defaultNamespaces[ns] = prefix
}
}
extension XMLDocument: Equatable {}
/**
Determine whether two documents are the same
- parameter lhs: XMLDocument on the left
- parameter rhs: XMLDocument on the right
- returns: whether lhs and rhs are equal
*/
public func ==(lhs: XMLDocument, rhs: XMLDocument) -> Bool {
return lhs.cDocument == rhs.cDocument
}
/// HTML document which can be searched and queried.
open class HTMLDocument: XMLDocument {
// MARK: - Convenience Accessors
/// HTML title of current document
open var title: String? {
return root?.firstChild(tag: "head")?.firstChild(tag: "title")?.stringValue
}
/// HTML head element of current document
open var head: XMLElement? {
return root?.firstChild(tag: "head")
}
/// HTML body element of current document
open var body: XMLElement? {
return root?.firstChild(tag: "body")
}
// MARK: - Creating HTML Documents
/**
Creates and returns an instance of HTMLDocument from C char array, throwing XMLError if an error occured while parsing the HTML.
- parameter cChars: cChars The HTML data as C char array
- throws: `XMLError` instance if an error occurred
- returns: An `HTMLDocument` with the contents of the specified HTML string.
*/
public convenience init(cChars: [CChar]) throws {
let options = Int32(HTML_PARSE_NOWARNING.rawValue | HTML_PARSE_NOERROR.rawValue | HTML_PARSE_RECOVER.rawValue)
try self.init(cChars: cChars, options: options)
}
fileprivate convenience init(cChars: [CChar], options: Int32) throws {
try self.init(parseFunction: { htmlReadMemory($0, $1, $2, $3, $4) }, cChars: cChars, options: options)
}
}

175
Example/Pods/Fuzi/Sources/Element.swift generated Normal file
View File

@ -0,0 +1,175 @@
// Element.swift
// Copyright (c) 2015 Ce Zheng
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
import libxml2
/// Represents an element in `XMLDocument` or `HTMLDocument`
open class XMLElement: XMLNode {
/// The element's namespace.
open fileprivate(set) lazy var namespace: String? = {
return ^-^(self.cNode.pointee.ns != nil ?self.cNode.pointee.ns.pointee.prefix :nil)
}()
/// The element's tag.
open fileprivate(set) lazy var tag: String? = {
return ^-^self.cNode.pointee.name
}()
// MARK: - Accessing Attributes
/// All attributes for the element.
open fileprivate(set) lazy var attributes: [String : String] = {
var attributes = [String: String]()
var attribute = self.cNode.pointee.properties
while attribute != nil {
if let key = ^-^attribute?.pointee.name, let value = self.attr(key) {
attributes[key] = value
}
attribute = attribute?.pointee.next
}
return attributes
}()
/**
Returns the value for the attribute with the specified key.
- parameter name: The attribute name.
- parameter ns: The namespace, or `nil` by default if not using a namespace
- returns: The attribute value, or `nil` if the attribute is not defined.
*/
open func attr(_ name: String, namespace ns: String? = nil) -> String? {
var value: String? = nil
let xmlValue: UnsafeMutablePointer<xmlChar>?
if let ns = ns {
xmlValue = xmlGetNsProp(cNode, name, ns)
} else {
xmlValue = xmlGetProp(cNode, name)
}
if let xmlValue = xmlValue {
value = ^-^xmlValue
xmlFree(xmlValue)
}
return value
}
// MARK: - Accessing Children
/// The element's children elements.
open var children: [XMLElement] {
return LinkedCNodes(head: cNode.pointee.children).flatMap {
XMLElement(cNode: $0, document: self.document)
}
}
/**
Get the element's child nodes of specified types
- parameter types: type of nodes that should be fetched (e.g. .Element, .Text, .Comment)
- returns: all children of specified types
*/
open func childNodes(ofTypes types: [XMLNodeType]) -> [XMLNode] {
return LinkedCNodes(head: cNode.pointee.children, types: types).flatMap { node in
switch node.pointee.type {
case XMLNodeType.Element:
return XMLElement(cNode: node, document: self.document)
default:
return XMLNode(cNode: node, document: self.document)
}
}
}
/**
Returns the first child element with a tag, or `nil` if no such element exists.
- parameter tag: The tag name.
- parameter ns: The namespace, or `nil` by default if not using a namespace
- returns: The child element.
*/
open func firstChild(tag: String, inNamespace ns: String? = nil) -> XMLElement? {
var nodePtr = cNode.pointee.children
while let cNode = nodePtr {
if cXMLNode(nodePtr, matchesTag: tag, inNamespace: ns) {
return XMLElement(cNode: cNode, document: self.document)
}
nodePtr = cNode.pointee.next
}
return nil
}
/**
Returns all children elements with the specified tag.
- parameter tag: The tag name.
- parameter ns: The namepsace, or `nil` by default if not using a namespace
- returns: The children elements.
*/
open func children(tag: String, inNamespace ns: String? = nil) -> [XMLElement] {
return LinkedCNodes(head: cNode.pointee.children).flatMap {
cXMLNode($0, matchesTag: tag, inNamespace: ns)
? XMLElement(cNode: $0, document: self.document) : nil
}
}
// MARK: - Accessing Content
/// Whether the element has a value.
open var isBlank: Bool {
return stringValue.isEmpty
}
/// A number representation of the element's value, which is generated from the document's `numberFormatter` property.
open fileprivate(set) lazy var numberValue: NSNumber? = {
return self.document.numberFormatter.number(from: self.stringValue)
}()
/// A date representation of the element's value, which is generated from the document's `dateFormatter` property.
open fileprivate(set) lazy var dateValue: Date? = {
return self.document.dateFormatter.date(from: self.stringValue)
}()
/**
Returns the child element at the specified index.
- parameter idx: The index.
- returns: The child element.
*/
open subscript (idx: Int) -> XMLElement? {
return children[idx]
}
/**
Returns the value for the attribute with the specified key.
- parameter name: The attribute name.
- returns: The attribute value, or `nil` if the attribute is not defined.
*/
open subscript (name: String) -> String? {
return attr(name)
}
}

47
Example/Pods/Fuzi/Sources/Error.swift generated Normal file
View File

@ -0,0 +1,47 @@
// Error.swift
// Copyright (c) 2015 Ce Zheng
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
import libxml2
/**
* XMLError enumeration.
*/
public enum XMLError: Error {
/// No error
case noError
/// Contains a libxml2 error with error code and message
case libXMLError(code: Int, message: String)
/// Failed to convert String to bytes using given string encoding
case invalidData
/// XML Parser failed to parse the document
case parserFailure
internal static func lastError(defaultError: XMLError = .noError) -> XMLError {
guard let errorPtr = xmlGetLastError() else {
return defaultError
}
let message = (^-^errorPtr.pointee.message)?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
let code = Int(errorPtr.pointee.code)
xmlResetError(errorPtr)
return .libXMLError(code: code, message: message ?? "")
}
}

117
Example/Pods/Fuzi/Sources/Helpers.swift generated Normal file
View File

@ -0,0 +1,117 @@
// Helpers.swift
// Copyright (c) 2015 Ce Zheng
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
import libxml2
// Public Helpers
/// For printing an `XMLNode`
extension XMLNode: CustomStringConvertible, CustomDebugStringConvertible {
/// String printed by `print` function
public var description: String {
return self.rawXML
}
/// String printed by `debugPrint` function
public var debugDescription: String {
return self.rawXML
}
}
/// For printing an `XMLDocument`
extension XMLDocument: CustomStringConvertible, CustomDebugStringConvertible {
/// String printed by `print` function
public var description: String {
return self.root?.rawXML ?? ""
}
/// String printed by `debugPrint` function
public var debugDescription: String {
return self.root?.rawXML ?? ""
}
}
// Internal Helpers
internal extension String {
subscript (nsrange: NSRange) -> String {
let start = utf16.index(utf16.startIndex, offsetBy: nsrange.location)
let end = utf16.index(start, offsetBy: nsrange.length)
return String(utf16[start..<end])!
}
}
// Just a smiling helper operator making frequent UnsafePointer -> String cast
prefix operator ^-^
internal prefix func ^-^ <T> (ptr: UnsafePointer<T>?) -> String? {
if let ptr = ptr {
return String(validatingUTF8: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self))
}
return nil
}
internal prefix func ^-^ <T> (ptr: UnsafeMutablePointer<T>?) -> String? {
if let ptr = ptr {
return String(validatingUTF8: UnsafeMutableRawPointer(ptr).assumingMemoryBound(to: CChar.self))
}
return nil
}
internal struct LinkedCNodes: Sequence, IteratorProtocol {
internal let head: xmlNodePtr?
internal let types: [xmlElementType]
fileprivate var cursor: xmlNodePtr?
mutating func next() -> xmlNodePtr? {
defer {
if let ptr = cursor {
cursor = ptr.pointee.next
}
}
while let ptr = cursor, !types.contains(where: { $0 == ptr.pointee.type }) {
cursor = ptr.pointee.next
}
return cursor
}
init(head: xmlNodePtr?, types: [xmlElementType] = [XML_ELEMENT_NODE]) {
self.head = head
self.cursor = head
self.types = types
}
}
internal func cXMLNode(_ node: xmlNodePtr?, matchesTag tag: String, inNamespace ns: String?) -> Bool {
guard let name = ^-^node?.pointee.name else {
return false
}
var matches = name.compare(tag, options: .caseInsensitive) == .orderedSame
if let ns = ns {
guard let prefix = ^-^node?.pointee.ns.pointee.prefix else {
return false
}
matches = matches && (prefix.compare(ns, options: .caseInsensitive) == .orderedSame)
}
return matches
}

171
Example/Pods/Fuzi/Sources/Node.swift generated Normal file
View File

@ -0,0 +1,171 @@
// Node.swift
// Copyright (c) 2015 Ce Zheng
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
import libxml2
/// Define a Swifty typealias for libxml's node type enum
public typealias XMLNodeType = xmlElementType
// MARK: - Give a Swifty name to each enum case of XMLNodeType
extension XMLNodeType {
/// Element
public static var Element: xmlElementType { return XML_ELEMENT_NODE }
/// Attribute
public static var Attribute: xmlElementType { return XML_ATTRIBUTE_NODE }
/// Text
public static var Text: xmlElementType { return XML_TEXT_NODE }
/// CData Section
public static var CDataSection: xmlElementType { return XML_CDATA_SECTION_NODE }
/// Entity Reference
public static var EntityRef: xmlElementType { return XML_ENTITY_REF_NODE }
/// Entity
public static var Entity: xmlElementType { return XML_ENTITY_NODE }
/// Pi
public static var Pi: xmlElementType { return XML_PI_NODE }
/// Comment
public static var Comment: xmlElementType { return XML_COMMENT_NODE }
/// Document
public static var Document: xmlElementType { return XML_DOCUMENT_NODE }
/// Document Type
public static var DocumentType: xmlElementType { return XML_DOCUMENT_TYPE_NODE }
/// Document Fragment
public static var DocumentFrag: xmlElementType { return XML_DOCUMENT_FRAG_NODE }
/// Notation
public static var Notation: xmlElementType { return XML_NOTATION_NODE }
/// HTML Document
public static var HtmlDocument: xmlElementType { return XML_HTML_DOCUMENT_NODE }
/// DTD
public static var DTD: xmlElementType { return XML_DTD_NODE }
/// Element Declaration
public static var ElementDecl: xmlElementType { return XML_ELEMENT_DECL }
/// Attribute Declaration
public static var AttributeDecl: xmlElementType { return XML_ATTRIBUTE_DECL }
/// Entity Declaration
public static var EntityDecl: xmlElementType { return XML_ENTITY_DECL }
/// Namespace Declaration
public static var NamespaceDecl: xmlElementType { return XML_NAMESPACE_DECL }
/// XInclude Start
public static var XIncludeStart: xmlElementType { return XML_XINCLUDE_START }
/// XInclude End
public static var XIncludeEnd: xmlElementType { return XML_XINCLUDE_END }
/// DocbDocument
public static var DocbDocument: xmlElementType { return XML_DOCB_DOCUMENT_NODE }
}
infix operator ~=
/**
For supporting pattern matching of those enum case alias getters for XMLNodeType
- parameter lhs: left hand side
- parameter rhs: right hand side
- returns: true if both sides equals
*/
public func ~=(lhs: XMLNodeType, rhs: XMLNodeType) -> Bool {
return lhs.rawValue == rhs.rawValue
}
/// Base class for all XML nodes
open class XMLNode {
/// The document containing the element.
open unowned let document: XMLDocument
/// The type of the XMLNode
open var type: XMLNodeType {
return cNode.pointee.type
}
/// The element's line number.
open fileprivate(set) lazy var lineNumber: Int = {
return xmlGetLineNo(self.cNode)
}()
// MARK: - Accessing Parent and Sibling Elements
/// The element's parent element.
open fileprivate(set) lazy var parent: XMLElement? = {
return XMLElement(cNode: self.cNode.pointee.parent, document: self.document)
}()
/// The element's next sibling.
open fileprivate(set) lazy var previousSibling: XMLElement? = {
return XMLElement(cNode: self.cNode.pointee.prev, document: self.document)
}()
/// The element's previous sibling.
open fileprivate(set) lazy var nextSibling: XMLElement? = {
return XMLElement(cNode: self.cNode.pointee.next, document: self.document)
}()
// MARK: - Accessing Contents
/// Whether this is a HTML node
open var isHTML: Bool {
return UInt32(self.cNode.pointee.doc.pointee.properties) & XML_DOC_HTML.rawValue == XML_DOC_HTML.rawValue
}
/// A string representation of the element's value.
open fileprivate(set) lazy var stringValue : String = {
let key = xmlNodeGetContent(self.cNode)
let stringValue = ^-^key ?? ""
xmlFree(key)
return stringValue
}()
/// The raw XML string of the element.
open fileprivate(set) lazy var rawXML: String = {
let buffer = xmlBufferCreate()
if self.isHTML {
htmlNodeDump(buffer, self.cNode.pointee.doc, self.cNode)
} else {
xmlNodeDump(buffer, self.cNode.pointee.doc, self.cNode, 0, 0)
}
let dumped = ^-^xmlBufferContent(buffer) ?? ""
xmlBufferFree(buffer)
return dumped
}()
/// Convert this node to XMLElement if it is an element node
open func toElement() -> XMLElement? {
return self as? XMLElement
}
internal let cNode: xmlNodePtr
internal init(cNode: xmlNodePtr, document: XMLDocument) {
self.cNode = cNode
self.document = document
}
}
extension XMLNode: Equatable {}
/**
Determine whether two nodes are the same
- parameter lhs: XMLNode on the left
- parameter rhs: XMLNode on the right
- returns: whether lhs and rhs are equal
*/
public func ==(lhs: XMLNode, rhs: XMLNode) -> Bool {
return lhs.cNode == rhs.cNode
}

120
Example/Pods/Fuzi/Sources/NodeSet.swift generated Normal file
View File

@ -0,0 +1,120 @@
// NodeSet.swift
// Copyright (c) 2015 Ce Zheng
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
import libxml2
/// An enumerable set of XML nodes
open class NodeSet: Collection {
// Index type for `Indexable` protocol
public typealias Index = Int
// IndexDistance type for `Indexable` protocol
public typealias IndexDistance = Int
fileprivate var cursor = 0
open func next() -> XMLElement? {
defer {
cursor += 1
}
if cursor < self.count {
return self[cursor]
}
return nil
}
/// Number of nodes
open fileprivate(set) lazy var count: Int = {
return Int(self.cNodeSet?.pointee.nodeNr ?? 0)
}()
/// First Element
open var first: XMLElement? {
return count > 0 ? self[startIndex] : nil
}
/// if nodeset is empty
open var isEmpty: Bool {
return (cNodeSet == nil) || (cNodeSet!.pointee.nodeNr == 0) || (cNodeSet!.pointee.nodeTab == nil)
}
/// Start index
open var startIndex: Index {
return 0
}
/// End index
open var endIndex: Index {
return count
}
/**
Get the Nth node from set.
- parameter idx: node index
- returns: the idx'th node, nil if out of range
*/
open subscript(_ idx: Index) -> XMLElement {
_precondition(idx >= startIndex && idx < endIndex, "Index of out bound")
return XMLElement(cNode: (cNodeSet!.pointee.nodeTab[idx])!, document: document)
}
/**
Get the index after `idx`
- parameter idx: node index
- returns: the index after `idx`
*/
open func index(after idx: Index) -> Index {
return idx + 1
}
internal let cNodeSet: xmlNodeSetPtr?
internal let document: XMLDocument!
internal init(cNodeSet: xmlNodeSetPtr?, document: XMLDocument?) {
self.cNodeSet = cNodeSet
self.document = document
}
}
/// XPath selector result node set
open class XPathNodeSet: NodeSet {
/// Empty node set
open static let emptySet = XPathNodeSet(cXPath: nil, document: nil)
fileprivate var cXPath: xmlXPathObjectPtr?
internal init(cXPath: xmlXPathObjectPtr?, document: XMLDocument?) {
self.cXPath = cXPath
let nodeSet = cXPath?.pointee.nodesetval
super.init(cNodeSet: nodeSet, document: document)
}
deinit {
if cXPath != nil {
xmlXPathFreeObject(cXPath)
}
}
}

312
Example/Pods/Fuzi/Sources/Queryable.swift generated Normal file
View File

@ -0,0 +1,312 @@
// Queryable.swift
// Copyright (c) 2015 Ce Zheng
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
import libxml2
/**
* The `Queryable` protocol is adopted by `XMLDocument`, `HTMLDocument` and `XMLElement`, denoting that they can search for elements using XPath or CSS selectors.
*/
public protocol Queryable {
/**
Returns the results for an XPath selector.
- parameter xpath: XPath selector string.
- returns: An enumerable collection of results.
*/
func xpath(_ xpath: String) -> NodeSet
/**
Returns the first elements matching an XPath selector, or `nil` if there are no results.
- parameter xpath: The XPath selector.
- returns: The child element.
*/
func firstChild(xpath: String) -> XMLElement?
/**
Returns the results for a CSS selector.
- parameter css: The CSS selector string.
- returns: An enumerable collection of results.
*/
func css(_ css: String) -> NodeSet
/**
Returns the first elements matching an CSS selector, or `nil` if there are no results.
- parameter css: The CSS selector.
- returns: The child element.
*/
func firstChild(css: String) -> XMLElement?
/**
Returns the result for evaluating an XPath selector that contains XPath function.
- parameter xpath: The XPath query string.
- returns: The eval function result.
*/
func eval(xpath: String) -> XPathFunctionResult?
}
/// Result for evaluating a XPath expression
open class XPathFunctionResult {
/// Boolean value
open fileprivate(set) lazy var boolValue: Bool = {
return self.cXPath.pointee.boolval != 0
}()
/// Double value
open fileprivate(set) lazy var doubleValue: Double = {
return self.cXPath.pointee.floatval
}()
/// String value
open fileprivate(set) lazy var stringValue: String = {
return ^-^self.cXPath.pointee.stringval ?? ""
}()
fileprivate let cXPath: xmlXPathObjectPtr
internal init?(cXPath: xmlXPathObjectPtr?) {
guard let cXPath = cXPath else {
return nil
}
self.cXPath = cXPath
}
deinit {
xmlXPathFreeObject(cXPath)
}
}
extension XMLDocument: Queryable {
/**
Returns the results for an XPath selector.
- parameter xpath: XPath selector string.
- returns: An enumerable collection of results.
*/
public func xpath(_ xpath: String) -> NodeSet {
return root == nil ?XPathNodeSet.emptySet :root!.xpath(xpath)
}
/**
Returns the first elements matching an XPath selector, or `nil` if there are no results.
- parameter xpath: The XPath selector.
- returns: The child element.
*/
public func firstChild(xpath: String) -> XMLElement? {
return root?.firstChild(xpath: xpath)
}
/**
Returns the results for a CSS selector.
- parameter css: The CSS selector string.
- returns: An enumerable collection of results.
*/
public func css(_ css: String) -> NodeSet {
return root == nil ?XPathNodeSet.emptySet :root!.css(css)
}
/**
Returns the first elements matching an CSS selector, or `nil` if there are no results.
- parameter css: The CSS selector.
- returns: The child element.
*/
public func firstChild(css: String) -> XMLElement? {
return root?.firstChild(css: css)
}
/**
Returns the result for evaluating an XPath selector that contains XPath function.
- parameter xpath: The XPath query string.
- returns: The eval function result.
*/
public func eval(xpath: String) -> XPathFunctionResult? {
return root?.eval(xpath: xpath)
}
}
extension XMLElement: Queryable {
/**
Returns the results for an XPath selector.
- parameter xpath: XPath selector string.
- returns: An enumerable collection of results.
*/
public func xpath(_ xpath: String) -> NodeSet {
guard let cXPath = self.cXPath(xpathString: xpath) else {
return XPathNodeSet.emptySet
}
return XPathNodeSet(cXPath: cXPath, document: document)
}
/**
Returns the first elements matching an XPath selector, or `nil` if there are no results.
- parameter xpath: The XPath selector.
- returns: The child element.
*/
public func firstChild(xpath: String) -> XMLElement? {
return self.xpath(xpath).first
}
/**
Returns the results for a CSS selector.
- parameter css: The CSS selector string.
- returns: An enumerable collection of results.
*/
public func css(_ css: String) -> NodeSet {
return xpath(XPath(fromCSS:css))
}
/**
Returns the first elements matching an CSS selector, or `nil` if there are no results.
- parameter css: The CSS selector.
- returns: The child element.
*/
public func firstChild(css: String) -> XMLElement? {
return self.css(css).first
}
/**
Returns the result for evaluating an XPath selector that contains XPath function.
- parameter xpath: The XPath query string.
- returns: The eval function result.
*/
public func eval(xpath: String) -> XPathFunctionResult? {
return XPathFunctionResult(cXPath: cXPath(xpathString: xpath))
}
fileprivate func cXPath(xpathString: String) -> xmlXPathObjectPtr? {
guard let context = xmlXPathNewContext(cNode.pointee.doc) else {
return nil
}
context.pointee.node = cNode
var node = cNode
while node.pointee.parent != nil {
var curNs = node.pointee.nsDef
while let ns = curNs {
var prefix = ns.pointee.prefix
var prefixChars = [CChar]()
if prefix == nil && !document.defaultNamespaces.isEmpty {
let href = (^-^ns.pointee.href)!
if let defaultPrefix = document.defaultNamespaces[href] {
prefixChars = defaultPrefix.cString(using: String.Encoding.utf8) ?? []
prefixChars.withUnsafeBufferPointer {(cArray: UnsafeBufferPointer<CChar>) -> Void in
prefix = UnsafeRawPointer(cArray.baseAddress)?.assumingMemoryBound(to: xmlChar.self)
}
}
}
if prefix != nil {
xmlXPathRegisterNs(context, prefix, ns.pointee.href)
}
curNs = ns.pointee.next
}
node = node.pointee.parent
}
let xmlXPath = xmlXPathEvalExpression(xpathString, context)
xmlXPathFreeContext(context)
return xmlXPath
}
}
private class RegexConstants {
static let idRegex = try! NSRegularExpression(pattern: "\\#([\\w-_]+)", options: [])
static let classRegex = try! NSRegularExpression(pattern: "\\.([^\\.]+)", options: [])
static let attributeRegex = try! NSRegularExpression(pattern: "\\[([^\\[\\]]+)\\]", options: [])
}
internal func XPath(fromCSS css: String) -> String {
var xpathExpressions = [String]()
for expression in css.components(separatedBy: ",") where !expression.isEmpty {
var xpathComponents = ["./"]
var prefix: String? = nil
let expressionComponents = expression.trimmingCharacters(in: CharacterSet.whitespaces).components(separatedBy: CharacterSet.whitespaces)
for (idx, var token) in expressionComponents.enumerated() {
switch token {
case "*" where idx != 0: xpathComponents.append("/*")
case ">": prefix = ""
case "+": prefix = "following-sibling::*[1]/self::"
case "~": prefix = "following-sibling::"
default:
if prefix == nil && idx != 0 {
prefix = "descendant::"
}
if let symbolRange = token.rangeOfCharacter(from: CharacterSet(charactersIn: "#.[]")) {
let symbol = symbolRange.lowerBound == token.startIndex ?"*" :""
var xpathComponent = token.substring(to: symbolRange.lowerBound)
let nsrange = NSRange(location: 0, length: token.utf16.count)
if let result = RegexConstants.idRegex.firstMatch(in: token, options: [], range: nsrange), result.numberOfRanges > 1 {
xpathComponent += "\(symbol)[@id = '\(token[result.rangeAt(1)])']"
}
for result in RegexConstants.classRegex.matches(in: token, options: [], range: nsrange) where result.numberOfRanges > 1 {
xpathComponent += "\(symbol)[contains(concat(' ',normalize-space(@class),' '),' \(token[result.rangeAt(1)]) ')]"
}
for result in RegexConstants.attributeRegex.matches(in: token, options: [], range: nsrange) where result.numberOfRanges > 1 {
xpathComponent += "[@\(token[result.rangeAt(1)])]"
}
token = xpathComponent
}
if prefix != nil {
token = prefix! + token
prefix = nil
}
xpathComponents.append(token)
}
}
xpathExpressions.append(xpathComponents.joined(separator: "/"))
}
return xpathExpressions.joined(separator: " | ")
}

View File

@ -0,0 +1,5 @@
#import <libxml2/libxml/xmlreader.h>
#import <libxml2/libxml/xpath.h>
#import <libxml2/libxml/xpathInternals.h>
#import <libxml2/libxml/HTMLparser.h>
#import <libxml2/libxml/HTMLtree.h>

View File

@ -0,0 +1,6 @@
module libxml2 [system] {
link "xml2"
umbrella header "libxml2-fuzi.h"
export *
module * { export * }
}

29
Sources/Aliases.swift Normal file
View File

@ -0,0 +1,29 @@
/**
A single byte represented as a UInt8
*/
public typealias Byte = UInt16
/**
A byte array or collection of raw data
*/
public typealias Bytes = [Byte]
/**
A sliced collection of raw data
*/
public typealias BytesSlice = ArraySlice<Byte>
// MARK: Sizes
private let _bytes = 1
private let _kilobytes = _bytes * 1000
private let _megabytes = _kilobytes * 1000
private let _gigabytes = _megabytes * 1000
extension Int {
public var bytes: Int { return self }
public var kilobytes: Int { return self * _kilobytes }
public var megabytes: Int { return self * _megabytes }
public var gigabytes: Int { return self * _gigabytes }
}

159
Sources/Byte+Alphabet.swift Normal file
View File

@ -0,0 +1,159 @@
extension Byte {
/// A
public static let A: Byte = 0x41
/// B
public static let B: Byte = 0x42
/// C
public static let C: Byte = 0x43
/// D
public static let D: Byte = 0x44
/// E
public static let E: Byte = 0x45
/// F
public static let F: Byte = 0x46
/// F
public static let G: Byte = 0x47
/// F
public static let H: Byte = 0x48
/// F
public static let I: Byte = 0x49
/// F
public static let J: Byte = 0x4A
/// F
public static let K: Byte = 0x4B
/// F
public static let L: Byte = 0x4C
/// F
public static let M: Byte = 0x4D
/// F
public static let N: Byte = 0x4E
/// F
public static let O: Byte = 0x4F
/// F
public static let P: Byte = 0x50
/// F
public static let Q: Byte = 0x51
/// F
public static let R: Byte = 0x52
/// F
public static let S: Byte = 0x53
/// F
public static let T: Byte = 0x54
/// F
public static let U: Byte = 0x55
/// F
public static let V: Byte = 0x56
/// F
public static let W: Byte = 0x57
/// F
public static let X: Byte = 0x58
/// F
public static let Y: Byte = 0x59
/// Z
public static let Z: Byte = 0x5A
}
extension Byte {
/// a
public static let a: Byte = 0x61
/// b
public static let b: Byte = 0x62
/// c
public static let c: Byte = 0x63
/// d
public static let d: Byte = 0x64
/// e
public static let e: Byte = 0x65
/// f
public static let f: Byte = 0x66
/// g
public static let g: Byte = 0x67
/// h
public static let h: Byte = 0x68
/// i
public static let i: Byte = 0x69
/// j
public static let j: Byte = 0x6A
/// k
public static let k: Byte = 0x6B
/// l
public static let l: Byte = 0x6C
/// m
public static let m: Byte = 0x6D
/// n
public static let n: Byte = 0x6E
/// o
public static let o: Byte = 0x6F
/// p
public static let p: Byte = 0x70
/// q
public static let q: Byte = 0x71
/// r
public static let r: Byte = 0x72
/// s
public static let s: Byte = 0x73
/// t
public static let t: Byte = 0x74
/// u
public static let u: Byte = 0x75
/// v
public static let v: Byte = 0x76
/// w
public static let w: Byte = 0x77
/// x
public static let x: Byte = 0x78
/// y
public static let y: Byte = 0x79
/// z
public static let z: Byte = 0x7A
}

View File

@ -0,0 +1,106 @@
extension Byte {
/// '\t'
public static let horizontalTab: Byte = 0x9
/// '\n'
public static let newLine: Byte = 0xA
/// '\r'
public static let carriageReturn: Byte = 0xD
/// ' '
public static let space: Byte = 0x20
/// !
public static let exclamation: Byte = 0x21
/// "
public static let quote: Byte = 0x22
/// #
public static let numberSign: Byte = 0x23
/// $
public static let dollar: Byte = 0x24
/// %
public static let percent: Byte = 0x25
/// &
public static let ampersand: Byte = 0x26
/// '
public static let apostrophe: Byte = 0x27
/// (
public static let leftParenthesis: Byte = 0x28
/// )
public static let rightParenthesis: Byte = 0x29
/// *
public static let asterisk: Byte = 0x2A
/// +
public static let plus: Byte = 0x2B
/// ,
public static let comma: Byte = 0x2C
/// -
public static let hyphen: Byte = 0x2D
/// .
public static let period: Byte = 0x2E
/// /
public static let forwardSlash: Byte = 0x2F
/// \
public static let backSlash: Byte = 0x5C
/// :
public static let colon: Byte = 0x3A
/// ;
public static let semicolon: Byte = 0x3B
/// =
public static let equals: Byte = 0x3D
/// ?
public static let questionMark: Byte = 0x3F
/// @
public static let at: Byte = 0x40
/// [
public static let leftSquareBracket: Byte = 0x5B
/// ]
public static let rightSquareBracket: Byte = 0x5D
/// _
public static let underscore: Byte = 0x5F
/// ~
public static let tilda: Byte = 0x7E
/// {
public static let leftCurlyBracket: Byte = 0x7B
/// }
public static let rightCurlyBracket: Byte = 0x7D
}
extension Byte {
/**
Defines the `crlf` used to denote
line breaks in HTTP and many other
formatters
*/
public static let crlf: Bytes = [
.carriageReturn,
.newLine
]
}

View File

@ -0,0 +1,36 @@
extension Byte {
/**
Returns whether or not the given byte can be considered UTF8 whitespace
*/
public var isWhitespace: Bool {
return self == .space || self == .newLine || self == .carriageReturn || self == .horizontalTab
}
/**
Returns whether or not the given byte is an arabic letter
*/
public var isLetter: Bool {
return (.a ... .z).contains(self) || (.A ... .Z).contains(self)
}
/**
Returns whether or not a given byte represents a UTF8 digit 0 through 9
*/
public var isDigit: Bool {
return (.zero ... .nine).contains(self)
}
/**
Returns whether or not a given byte represents a UTF8 digit 0 through 9, or an arabic letter
*/
public var isAlphanumeric: Bool {
return isLetter || isDigit
}
/**
Returns whether a given byte can be interpreted as a hex value in UTF8, ie: 0-9, a-f, A-F.
*/
public var isHexDigit: Bool {
return (.zero ... .nine).contains(self) || (.A ... .F).contains(self) || (.a ... .f).contains(self)
}
}

View File

@ -0,0 +1,42 @@
// MARK: Byte
public func ~=(pattern: Byte, value: Byte) -> Bool {
return pattern == value
}
public func ~=(pattern: Byte, value: BytesSlice) -> Bool {
return value.contains(pattern)
}
public func ~=(pattern: Byte, value: Bytes) -> Bool {
return value.contains(pattern)
}
// MARK: Bytes
public func ~=(pattern: Bytes, value: Byte) -> Bool {
return pattern.contains(value)
}
public func ~=(pattern: Bytes, value: Bytes) -> Bool {
return pattern == value
}
public func ~=(pattern: Bytes, value: BytesSlice) -> Bool {
return pattern == Bytes(value)
}
// MARK: BytesSlice
public func ~=(pattern: BytesSlice, value: Byte) -> Bool {
return pattern.contains(value)
}
public func ~=(pattern: BytesSlice, value: BytesSlice) -> Bool {
return pattern == value
}
public func ~=(pattern: BytesSlice, value: Bytes) -> Bool {
return Bytes(pattern) == value
}

View File

@ -0,0 +1,61 @@
//
// Byte+SwiftSoup.swift
// Pods
//
// Created by Nabil on 05/07/17.
//
//
import Foundation
extension Byte {
/// EOF
public static let EOF: Byte = Byte.max // 0xffff//Byte(65535)
/// null
public static let null: Byte = 0x00
/// <
public static let lessThan: Byte = 0x3c
/// >
public static let greaterThan: Byte = 0x3e
/// \f
public static let formfeed: Byte = 12
/// replaces null character
static let replacementChar: Byte = Byte.max - 2 // 0xfffd//Byte(65533)
/// "`"
static let backquote: Byte = 0x60
/// "\\"
static let esc: Byte = 92
/// ""
static let empty: Byte = 0
public var uppercase: Byte{
return self & 0x5f
}
public var lowercase: Byte{
return self ^ 0x20
}
}
//extension Bytes {
//
// public var uppercase: Bytes{
// return self.map{$0.uppercase}
// }
//
// public var lowercase: Bytes{
// return self.map{$0.lowercase}
// }
//
//}

View File

@ -0,0 +1,32 @@
extension Byte {
/// 0 in utf8
public static let zero: Byte = 0x30
/// 1 in utf8
public static let one: Byte = 0x31
/// 2 in utf8
public static let two: Byte = 0x32
/// 3 in utf8
public static let three: Byte = 0x33
/// 4 in utf8
public static let four: Byte = 0x34
/// 5 in utf8
public static let five: Byte = 0x35
/// 6 in utf8
public static let six: Byte = 0x36
/// 7 in utf8
public static let seven: Byte = 0x37
/// 8 in utf8
public static let eight: Byte = 0x38
/// 9 in utf8
public static let nine: Byte = 0x39
}

View File

@ -0,0 +1,115 @@
import Foundation
extension Sequence where Iterator.Element == Byte {
/// Converts a slice of bytes to
/// string. Courtesy of @vzsg
public func makeString() -> String {
// let array = Array(self) //+ [0]
// return String(array.map { Character(UnicodeScalar($0)!)})
var x = self
let xx = String.init(bytesNoCopy: &x, length: 5 * MemoryLayout<UnicodeScalar>.size, encoding: String.Encoding.utf16BigEndian, freeWhenDone: false)!
return xx
// let arInt = Array(self) + [0]
// let array = arInt.map{UInt16($0)}
// return array.withUnsafeBytes { rawBuffer in
// guard let pointer = rawBuffer.baseAddress?.assumingMemoryBound(to: CChar.self) else { return nil }
// return String(validatingUTF8: pointer)
// } ?? ""
}
/**
Converts a byte representation
of a hex value into an `Int`.
as opposed to it's Decimal value
ie: "10" == 16, not 10
*/
public var hexInt: Int? {
var int: Int = 0
for byte in self {
int = int * 16
if byte >= .zero && byte <= .nine {
int += Int(byte - .zero)
} else if byte >= .A && byte <= .F {
int += Int(byte - .A) + 10
} else if byte >= .a && byte <= .f {
int += Int(byte - .a) + 10
} else {
return nil
}
}
return int
}
/**
Converts a utf8 byte representation
of a decimal value into an `Int`
as opposed to it's Hex value,
ie: "10" == 10, not 16
*/
public var decimalInt: Int? {
var int: Int = 0
for byte in self {
int = int * 10
if byte.isDigit {
int += Int(byte - .zero)
} else {
return nil
}
}
return int
}
/**
Transforms anything between Byte.A ... Byte.Z
into the range Byte.a ... Byte.z
*/
public var lowercased: Bytes {
var data = Bytes()
for byte in self {
if (.A ... .Z).contains(byte) {
data.append(byte + (.a - .A))
} else {
data.append(byte)
}
}
return data
}
/**
Transforms anything between Byte.a ... Byte.z
into the range Byte.A ... Byte.Z
*/
public var uppercased: Bytes {
var bytes = Bytes()
for byte in self {
if (.a ... .z).contains(byte) {
bytes.append(byte - (.a - .A))
} else {
bytes.append(byte)
}
}
return bytes
}
}
extension Array where Iterator.Element == Byte
{
func substring(_ beginIndex: Int) -> Bytes {
return Array(self[beginIndex..<self.count])
}
}

View File

@ -14,16 +14,22 @@ import Foundation
public final class CharacterReader {
private static let empty = ""
public static let EOF: UnicodeScalar = "\u{FFFF}"//65535
private let input: [UnicodeScalar]
private let input: Bytes
private let length: Int
private var pos: Int = 0
private var mark: Int = 0
//private let stringCache: Array<String?> // holds reused strings in this doc, to lessen garbage
//let bytes: Bytes
//var scanner: Scanner<Bytes>
public init(_ input: String) {
self.input = Array(input.unicodeScalars)
self.input = input.makeBytes()
self.length = self.input.count
//bytes = input.makeBytes()
//scanner = Scanner(bytes)
//stringCache = Array(repeating:nil, count:512)
}
public func getPos() -> Int {
@ -34,13 +40,13 @@ public final class CharacterReader {
return pos >= length
}
public func current() -> UnicodeScalar {
return (pos >= length) ? CharacterReader.EOF : input[pos]
public func current() -> Byte {
return (pos >= length) ? Byte.EOF : input[pos]
}
@discardableResult
public func consume() -> UnicodeScalar {
let val = (pos >= length) ? CharacterReader.EOF : input[pos]
public func consume() -> Byte {
let val = (pos >= length) ? Byte.EOF : input[pos]
pos += 1
return val
}
@ -61,19 +67,13 @@ public final class CharacterReader {
pos = mark
}
public func consumeAsString() -> String {
let p = pos
pos+=1
return String(input[p])
//return String(input, pos+=1, 1)
}
/**
* Returns the number of characters between the current position and the next instance of the input char
* @param c scan target
* @return offset between current position and next instance of target. -1 if not found.
*/
public func nextIndexOf(_ c: UnicodeScalar) -> Int {
public func nextIndexOf(_ c: Byte) -> Int {
// doesn't handle scanning for surrogates
for i in pos..<length {
if (c == input[i]) {
@ -90,9 +90,10 @@ public final class CharacterReader {
* @return offset between current position and next instance of target. -1 if not found.
*/
public func nextIndexOf(_ seq: String) -> Int {
let seq = seq.makeBytes()
// doesn't handle scanning for surrogates
if(seq.isEmpty) {return -1}
let startChar: UnicodeScalar = seq.unicodeScalar(0)
let startChar = seq[0]
for var offset in pos..<length {
// scan to first instance of startchar:
if (startChar != input[offset]) {
@ -100,10 +101,10 @@ public final class CharacterReader {
while(offset < length && startChar != input[offset]) { offset+=1 }
}
var i = offset + 1
let last = i + seq.unicodeScalars.count-1
let last = i + seq.count-1
if (offset < length && last <= length) {
var j = 1
while i < last && seq.unicodeScalar(j) == input[i] {
while i < last && seq[j] == input[i] {
j+=1
i+=1
}
@ -116,7 +117,7 @@ public final class CharacterReader {
return -1
}
public func consumeTo(_ c: UnicodeScalar) -> String {
public func consumeTo(_ c: Byte) -> String {
let offset = nextIndexOf(c)
if (offset != -1) {
let consumed = cacheString(pos, offset)
@ -138,10 +139,10 @@ public final class CharacterReader {
}
}
public func consumeToAny(_ chars: UnicodeScalar...) -> String {
public func consumeToAny(_ chars: Byte...) -> String {
return consumeToAny(chars)
}
public func consumeToAny(_ chars: [UnicodeScalar]) -> String {
public func consumeToAny(_ chars: [Byte]) -> String {
let start: Int = pos
let remaining: Int = length
let val = input
@ -160,10 +161,10 @@ public final class CharacterReader {
return pos > start ? cacheString(start, pos-start) : CharacterReader.empty
}
public func consumeToAnySorted(_ chars: UnicodeScalar...) -> String {
public func consumeToAnySorted(_ chars: Byte...) -> String {
return consumeToAnySorted(chars)
}
public func consumeToAnySorted(_ chars: [UnicodeScalar]) -> String {
public func consumeToAnySorted(_ chars: [Byte]) -> String {
let start = pos
let remaining = length
let val = input
@ -186,8 +187,8 @@ public final class CharacterReader {
let val = input
while (pos < remaining) {
let c: UnicodeScalar = val[pos]
if (c == UnicodeScalar.Ampersand || c == UnicodeScalar.LessThan || c == TokeniserStateVars.nullScalr) {
let c = val[pos]
if (c == Byte.ampersand || c == Byte.lessThan || c == Byte.null) {
break
}
pos += 1
@ -203,8 +204,8 @@ public final class CharacterReader {
let val = input
while (pos < remaining) {
let c: UnicodeScalar = val[pos]
if (c == UnicodeScalar.BackslashT || c == UnicodeScalar.BackslashN || c == UnicodeScalar.BackslashR || c == UnicodeScalar.BackslashF || c == UnicodeScalar.Space || c == UnicodeScalar.Slash || c == UnicodeScalar.GreaterThan || c == TokeniserStateVars.nullScalr) {
let c = val[pos]
if (c == Byte.horizontalTab || c == Byte.newLine || c == Byte.carriageReturn || c == Byte.formfeed || c == Byte.space || c == Byte.forwardSlash || c == Byte.greaterThan || c == Byte.null) {
break
}
pos += 1
@ -221,8 +222,8 @@ public final class CharacterReader {
public func consumeLetterSequence() -> String {
let start = pos
while (pos < length) {
let c: UnicodeScalar = input[pos]
if ((c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c.isMemberOfCharacterSet(CharacterSet.letters)) {
let c = input[pos]
if ((c >= Byte.A && c <= Byte.Z) || (c >= Byte.a && c <= Byte.z) || c.isLetter) {
pos += 1
} else {
break
@ -235,7 +236,7 @@ public final class CharacterReader {
let start = pos
while (pos < length) {
let c = input[pos]
if ((c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c.isMemberOfCharacterSet(CharacterSet.letters)) {
if ((c >= Byte.A && c <= Byte.Z) || (c >= Byte.a && c <= Byte.z) || c.isLetter) {
pos += 1
} else {
break
@ -243,7 +244,7 @@ public final class CharacterReader {
}
while (!isEmpty()) {
let c = input[pos]
if (c >= "0" && c <= "9") {
if (c >= Byte.zero && c <= Byte.nine) {
pos += 1
} else {
break
@ -257,7 +258,7 @@ public final class CharacterReader {
let start = pos
while (pos < length) {
let c = input[pos]
if ((c >= "0" && c <= "9") || (c >= "A" && c <= "F") || (c >= "a" && c <= "f")) {
if ((c >= Byte.zero && c <= Byte.nine) || (c >= Byte.A && c <= Byte.F) || (c >= Byte.a && c <= Byte.f)) {
pos+=1
} else {
break
@ -270,7 +271,7 @@ public final class CharacterReader {
let start = pos
while (pos < length) {
let c = input[pos]
if (c >= "0" && c <= "9") {
if (c >= Byte.zero && c <= Byte.nine) {
pos+=1
} else {
break
@ -279,19 +280,19 @@ public final class CharacterReader {
return cacheString(start, pos - start)
}
public func matches(_ c: UnicodeScalar) -> Bool {
public func matches(_ c: Byte) -> Bool {
return !isEmpty() && input[pos] == c
}
public func matches(_ seq: String) -> Bool {
let scanLength = seq.unicodeScalars.count
public func matches(_ seq: Bytes) -> Bool {
let scanLength = seq.count
if (scanLength > length - pos) {
return false
}
for offset in 0..<scanLength {
if (seq.unicodeScalar(offset) != input[pos+offset]) {
if (seq[offset] != input[pos+offset]) {
return false
}
}
@ -299,8 +300,8 @@ public final class CharacterReader {
}
public func matchesIgnoreCase(_ seq: String ) -> Bool {
let scanLength = seq.unicodeScalars.count
let seq = seq.makeBytes()
let scanLength = seq.count
if(scanLength == 0) {
return false
}
@ -309,8 +310,8 @@ public final class CharacterReader {
}
for offset in 0..<scanLength {
let upScan: UnicodeScalar = seq.unicodeScalar(offset).uppercase
let upTarget: UnicodeScalar = input[pos+offset].uppercase
let upScan = seq[offset].uppercase
let upTarget = input[pos+offset].uppercase
if (upScan != upTarget) {
return false
}
@ -318,12 +319,12 @@ public final class CharacterReader {
return true
}
public func matchesAny(_ seq: UnicodeScalar...) -> Bool {
public func matchesAny(_ seq: Byte...) -> Bool {
if (isEmpty()) {
return false
}
let c: UnicodeScalar = input[pos]
let c = input[pos]
for seek in seq {
if (seek == c) {
return true
@ -332,7 +333,7 @@ public final class CharacterReader {
return false
}
public func matchesAnySorted(_ seq: [UnicodeScalar]) -> Bool {
public func matchesAnySorted(_ seq: [Byte]) -> Bool {
return !isEmpty() && seq.contains(input[pos])
}
@ -341,7 +342,7 @@ public final class CharacterReader {
return false
}
let c = input[pos]
return (c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c.isMemberOfCharacterSet(CharacterSet.letters)
return (c >= Byte.A && c <= Byte.Z) || (c >= Byte.a && c <= Byte.z) || c.isLetter
}
public func matchesDigit() -> Bool {
@ -349,13 +350,23 @@ public final class CharacterReader {
return false
}
let c = input[pos]
return (c >= "0" && c <= "9")
return (c >= Byte.zero && c <= Byte.nine)
}
@discardableResult
public func matchConsume(_ seq: String) -> Bool {
public func matchConsume(_ seq: Bytes) -> Bool {
if (matches(seq)) {
pos += seq.unicodeScalars.count
pos += seq.count
return true
} else {
return false
}
}
@discardableResult
public func matchConsume(_ seq: Byte) -> Bool {
if (matches(seq)) {
pos += 1
return true
} else {
return false
@ -371,17 +382,32 @@ public final class CharacterReader {
return false
}
}
@discardableResult
public func matchConsumeIgnoreCase(_ seq: Byte) -> Bool {
if (containsIgnoreCase(seq)) {
pos += 1
return true
} else {
return false
}
}
///TODO: provare RIMUOVERE
public func containsIgnoreCase(_ seq: String ) -> Bool {
// used to check presence of </title>, </style>. only finds consistent case.
let loScan = seq.lowercased(with: Locale(identifier: "en"))
let hiScan = seq.uppercased(with: Locale(identifier: "eng"))
return (nextIndexOf(loScan) > -1) || (nextIndexOf(hiScan) > -1)
}
public func containsIgnoreCase(_ seq: Byte ) -> Bool {
// used to check presence of </title>, </style>. only finds consistent case.
return (nextIndexOf(seq.uppercase) > -1) || (nextIndexOf(seq.lowercase) > -1)
}
public func toString() -> String {
return String.unicodescalars(Array(input[pos..<length]))
//return input.string(pos, length - pos)
return input[pos..<length].makeString()
}
/**
@ -392,7 +418,9 @@ public final class CharacterReader {
* some more duplicates.
*/
private func cacheString(_ start: Int, _ count: Int) -> String {
return String(input[start..<start+count].flatMap { Character($0) })
let ar = input[start..<start+count]
return ar.makeString()
//return String(input[start..<start+count].flatMap { Character($0) })
// Too Slow
// var cache: [String?] = stringCache
//

View File

@ -14,7 +14,7 @@ import Foundation
* named character references</a>.
*/
public class Entities {
private static let empty = -1
private static let empty: Byte = 255 //-1
private static let emptyName = ""
private static let codepointRadix: Int = 36
@ -31,10 +31,10 @@ public class Entities {
// table of named references to their codepoints. sorted so we can binary search. built by BuildEntities.
fileprivate var nameKeys: [String]
fileprivate var codeVals: [Int] // limitation is the few references with multiple characters; those go into multipoints.
fileprivate var codeVals: [Byte] // limitation is the few references with multiple characters; those go into multipoints.
// table of codepoints to named entities.
fileprivate var codeKeys: [Int] // we don' support multicodepoints to single named value currently
fileprivate var codeKeys: [Byte] // we don' support multicodepoints to single named value currently
fileprivate var nameVals: [String]
public static func == (left: EscapeMode, right: EscapeMode) -> Bool {
@ -45,12 +45,12 @@ public class Entities {
return left.value != right.value
}
private static let codeDelims : [UnicodeScalar] = [",", ";"]
private static let codeDelims : [Byte] = [Byte.comma, Byte.semicolon]
init(string: String, size: Int, id: Int) {
nameKeys = [String](repeating: "", count: size)
codeVals = [Int](repeating: 0, count: size)
codeKeys = [Int](repeating: 0, count: size)
codeVals = [Byte](repeating: 0, count: size)
codeKeys = [Byte](repeating: 0, count: size)
nameVals = [String](repeating: "", count: size)
value = id
@ -66,29 +66,29 @@ public class Entities {
let name: String = reader.consumeTo("=");
reader.advance();
let cp1: Int = Int(reader.consumeToAny(EscapeMode.codeDelims), radix: codepointRadix) ?? 0
let codeDelim: UnicodeScalar = reader.current();
let cp1: Byte = Byte(reader.consumeToAny(EscapeMode.codeDelims), radix: codepointRadix) ?? 0
let codeDelim = reader.current();
reader.advance();
let cp2: Int;
if (codeDelim == ",") {
cp2 = Int(reader.consumeTo(";"), radix: codepointRadix) ?? 0
let cp2: Byte;
if (codeDelim == Byte.comma) {
cp2 = Byte(reader.consumeTo(Byte.semicolon), radix: codepointRadix) ?? 0
reader.advance();
} else {
cp2 = empty;
}
let index: Int = Int(reader.consumeTo("\n"), radix: codepointRadix) ?? 0
let index: Byte = Byte(reader.consumeTo("\n"), radix: codepointRadix) ?? 0
reader.advance();
nameKeys[i] = name;
codeVals[i] = cp1;
codeKeys[index] = cp1;
nameVals[index] = name;
codeKeys[Int(index)] = cp1;
nameVals[Int(index)] = name;
if (cp2 != empty) {
var s = String()
s.append(Character(UnicodeScalar(cp1)!))
s.append(Character(UnicodeScalar(cp2)!))
multipoints[name] = s
// s.append(Character(UnicodeScalar(cp1)!))
// s.append(Character(UnicodeScalar(cp2)!))
multipoints[name] = [cp1,cp2].makeString()
}
i = i + 1
}
@ -131,7 +131,7 @@ public class Entities {
// }
// }
public func codepointForName(_ name: String) -> Int {
public func codepointForName(_ name: String) -> Byte {
// for s in nameKeys {
// if s == name {
// return codeVals[nameKeys.index(of: s)!]
@ -143,7 +143,7 @@ public class Entities {
return codeVals[index]
}
public func nameForCodepoint(_ codepoint: Int ) -> String {
public func nameForCodepoint(_ codepoint: Byte ) -> String {
//let ss = codeKeys.index(of: codepoint)
var index = -1
@ -199,7 +199,7 @@ public class Entities {
* @deprecated does not support characters outside the BMP or multiple character names
*/
open static func getCharacterByName(name: String) -> Character {
return Character.convertFromIntegerLiteral(value:EscapeMode.extended.codepointForName(name))
return Character(UnicodeScalar(EscapeMode.extended.codepointForName(name))!)
}
/**
@ -217,17 +217,20 @@ public class Entities {
return emptyName
}
open static func codepointsForName(_ name: String, codepoints: inout [UnicodeScalar]) -> Int {
open static func codepointsForName(_ name: String, codepoints: inout [Byte]) -> Int {
if let val: String = multipoints[name] {
codepoints[0] = val.unicodeScalar(0)
codepoints[1] = val.unicodeScalar(1)
codepoints.removeAll()
codepoints.append(contentsOf: val.makeBytes())
// codepoints[0] = Int(val.unicodeScalar(0).value)
// codepoints[1] = Int(val.unicodeScalar(1).value)
return 2
}
let codepoint = EscapeMode.extended.codepointForName(name)
if (codepoint != empty) {
codepoints[0] = UnicodeScalar(codepoint)!
codepoints[0] = codepoint
return 1
}
return 0
@ -256,8 +259,8 @@ public class Entities {
let encoder: String.Encoding = out.encoder()
//let length = UInt32(string.characters.count)
var codePoint: UnicodeScalar
for ch in string.unicodeScalars {
var codePoint: Byte
for ch in string.makeBytes() {
codePoint = ch
if (normaliseWhite) {
@ -274,22 +277,24 @@ public class Entities {
}
}
//TODO: see what can i do here if (codePoint < Character.MIN_SUPPLEMENTARY_CODE_POINT)
// surrogate pairs, split implementation for efficiency on single char common case (saves creating strings, char[]):
if (codePoint.value < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
if (codePoint < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
let c = codePoint
// html specific and required escapes:
switch (codePoint) {
case UnicodeScalar.Ampersand:
case Byte.ampersand:
accum.append("&amp;")
break
case UnicodeScalar(UInt32(0xA0))!:
//TODO: give a name to 0xA0
case 0xA0:
if (escapeMode != EscapeMode.xhtml) {
accum.append("&nbsp;")
} else {
accum.append("&#xa0;")
}
break
case UnicodeScalar.LessThan:
case Byte.lessThan:
// escape when in character data or when in a xml attribue val; not needed in html attr val
if (!inAttribute || escapeMode == EscapeMode.xhtml) {
accum.append("&lt;")
@ -297,13 +302,13 @@ public class Entities {
accum.append(c)
}
break
case UnicodeScalar.GreaterThan:
case Byte.greaterThan:
if (!inAttribute) {
accum.append("&gt;")
} else {
accum.append(c)}
break
case "\"":
case Byte.quote:// "\"":
if (inAttribute) {
accum.append("&quot;")
} else {
@ -328,12 +333,12 @@ public class Entities {
}
}
private static func appendEncoded(accum: StringBuilder, escapeMode: EscapeMode, codePoint: UnicodeScalar) {
let name = escapeMode.nameForCodepoint(Int(codePoint.value))
private static func appendEncoded(accum: StringBuilder, escapeMode: EscapeMode, codePoint: Byte) {
let name = escapeMode.nameForCodepoint(codePoint)
if (name != emptyName) // ok for identity check
{accum.append(UnicodeScalar.Ampersand).append(name).append(";")
} else {
accum.append("&#x").append(String.toHexString(n:Int(codePoint.value)) ).append(";")
accum.append("&#x").append(String.toHexString(n:codePoint) ).append(";")
}
}
@ -364,15 +369,15 @@ public class Entities {
* Alterslash: 3013, 28
* Jsoup: 167, 2
*/
private static func canEncode(_ c: UnicodeScalar, _ fallback: String.Encoding) -> Bool {
private static func canEncode(_ c: Byte, _ fallback: String.Encoding) -> Bool {
// todo add more charset tests if impacted by Android's bad perf in canEncode
switch (fallback) {
case String.Encoding.ascii:
return c.value < 0x80
return c < 0x80
case String.Encoding.utf8:
return true // real is:!(Character.isLowSurrogate(c) || Character.isHighSurrogate(c)) - but already check above
default:
return fallback.canEncode(String(Character(c)))
return fallback.canEncode(String(Character(UnicodeScalar(c)!)))
}
}

13
Sources/Operators.swift Normal file
View File

@ -0,0 +1,13 @@
/**
Append the right-hand byte to the end of the bytes array
*/
public func +=(lhs: inout Bytes, rhs: Byte) {
lhs.append(rhs)
}
/**
Append the contents of the byteslice to the end of the bytes array
*/
public func +=(lhs: inout Bytes, rhs: BytesSlice) {
lhs += Array(rhs)
}

79
Sources/Scanner.swift Normal file
View File

@ -0,0 +1,79 @@
//
// SwiftScanner.swift
// SwiftSoup
//
// Created by Nabil on 03/07/17.
// Copyright © 2017 Nabil Chatbi. All rights reserved.
//
import Foundation
struct Scanner<Element> {
typealias Element = UInt8
var pointer: UnsafePointer<Element>
let endAddress: UnsafePointer<Element>
var elements: UnsafeBufferPointer<Element>
// assuming you don't mutate no copy _should_ occur
let elementsCopy: [Element]
}
extension Scanner {
init(_ data: [Element]) {
self.elementsCopy = data
self.elements = elementsCopy.withUnsafeBufferPointer { $0 }
self.pointer = elements.baseAddress!
self.endAddress = elements.endAddress
}
}
extension Scanner {
func peek(aheadBy n: Int = 0) -> Element? {
guard pointer.advanced(by: n) < endAddress else { return nil }
return pointer.advanced(by: n).pointee
}
/// - Precondition: index != bytes.endIndex. It is assumed before calling pop that you have
@discardableResult
mutating func pop() -> Element {
assert(pointer != endAddress)
defer { pointer = pointer.advanced(by: 1) }
return pointer.pointee
}
/// - Precondition: index != bytes.endIndex. It is assumed before calling pop that you have
@discardableResult
mutating func attemptPop() throws -> Element {
guard pointer < endAddress else { throw ScannerError.Reason.endOfStream }
defer { pointer = pointer.advanced(by: 1) }
return pointer.pointee
}
/// - Precondition: index != bytes.endIndex. It is assumed before calling pop that you have
mutating func pop(_ n: Int) {
assert(pointer.advanced(by: n) <= endAddress)
pointer = pointer.advanced(by: n)
}
}
extension Scanner {
var isEmpty: Bool {
return pointer == endAddress
}
}
struct ScannerError: Swift.Error {
let position: UInt
let reason: Reason
enum Reason: Swift.Error {
case endOfStream
}
}
extension UnsafeBufferPointer {
fileprivate var endAddress: UnsafePointer<Element> {
return baseAddress!.advanced(by: endIndex)
}
}

View File

@ -0,0 +1,19 @@
extension String {
/**
UTF8 Array representation of string
*/
public func makeBytes() -> Bytes {
var retVal : [Byte] = []
for thing in self.utf8 {
retVal.append(Byte(thing))
}
return retVal
}
/**
Initializes a string with a UTF8 byte array
*/
public init(bytes: Bytes) {
self = bytes.makeString()
}
}

View File

@ -86,6 +86,10 @@ extension String {
static func toHexString(n: Int) -> String {
return String(format:"%2x", n)
}
static func toHexString(n: Byte) -> String {
return String(format:"%2x", n)
}
func insert(string: String, ind: Int) -> String {
return String(self.characters.prefix(ind)) + string + String(self.characters.suffix(self.characters.count-ind))

View File

@ -3,7 +3,7 @@
https://gist.github.com/kristopherjohnson/1fc55e811d944a430289
*/
open class StringBuilder {
fileprivate var stringValue: Array<Character>
fileprivate var stringValue: Bytes = []
/**
Construct with initial String contents
@ -11,12 +11,10 @@ open class StringBuilder {
:param: string Initial value; defaults to empty string
*/
public init(string: String = "") {
self.stringValue = Array(string.characters)
}
public init(_ size: Int) {
self.stringValue = Array()
self.stringValue.append(contentsOf:string.makeBytes())
}
public init(_ size: Int) {}
public init() {}
/**
Return the String object
@ -24,7 +22,7 @@ open class StringBuilder {
:return: String
*/
open func toString() -> String {
return String(stringValue)
return stringValue.makeString()
}
/**
@ -35,90 +33,44 @@ open class StringBuilder {
//return countElements(stringValue)
}
/**
Append a String to the object
:param: string String
:return: reference to this StringBuilder instance
*/
open func append(_ string: String) {
stringValue.append(contentsOf: string.characters)
}
open func appendCodePoint(_ chr: Character) {
stringValue.append(chr)
}
open func appendCodePoints(_ chr: [Character]) {
stringValue.append(contentsOf: chr)
}
open func appendCodePoint(_ ch: Int) {
stringValue.append(Character(UnicodeScalar(ch)!))
}
open func appendCodePoint(_ ch: UnicodeScalar) {
stringValue.append(Character(ch))
}
open func appendCodePoints(_ chr: [UnicodeScalar]) {
for c in chr {
appendCodePoint(c)
}
}
/**
Append a Printable to the object
:param: value a value supporting the Printable protocol
:return: reference to this StringBuilder instance
*/
@discardableResult
open func append<T: CustomStringConvertible>(_ value: T) -> StringBuilder {
stringValue.append(contentsOf: value.description.characters)
public func append(_ value: String)->StringBuilder {
self.stringValue.append(contentsOf: value.makeBytes())
return self
}
@discardableResult
open func append(_ value: UnicodeScalar) -> StringBuilder {
stringValue.append(contentsOf: value.description.characters)
public func append(_ value: UnicodeScalar)->StringBuilder {
self.stringValue.append(Byte(value.value))
return self
}
@discardableResult
open func insert<T: CustomStringConvertible>(_ offset: Int, _ value: T) -> StringBuilder {
stringValue.insert(contentsOf: value.description.characters, at: offset)
public func append(_ value: Byte)->StringBuilder {
self.stringValue.append(value)
return self
}
@discardableResult
public func append(_ value: Bytes)->StringBuilder {
self.stringValue.append(contentsOf:value)
return self
}
/**
Append a String and a newline to the object
:param: string String
:return: reference to this StringBuilder instance
*/
@discardableResult
open func appendLine(_ string: String) -> StringBuilder {
stringValue.append(contentsOf: "\n".characters)
public func append(_ value: Character)->StringBuilder {
let bytes : Bytes = value.unicodeScalars.flatMap { Byte($0.value) }
self.append(bytes)
return self
}
/**
Append a Printable and a newline to the object
:param: value a value supporting the Printable protocol
:return: reference to this StringBuilder instance
*/
@discardableResult
open func appendLine<T: CustomStringConvertible>(_ value: T) -> StringBuilder {
stringValue.append(contentsOf: value.description.characters)
stringValue.append(contentsOf: "\n".characters)
public func insert(_ index: Int, _ value: String)->StringBuilder {
self.stringValue.insert(contentsOf: value.makeBytes(), at: index)
return self
}
/**
Reset the object to an empty string
@ -132,34 +84,3 @@ open class StringBuilder {
}
}
/**
Append a String to a StringBuilder using operator syntax
:param: lhs StringBuilder
:param: rhs String
*/
public func += (lhs: StringBuilder, rhs: String) {
lhs.append(rhs)
}
/**
Append a Printable to a StringBuilder using operator syntax
:param: lhs Printable
:param: rhs String
*/
public func += <T: CustomStringConvertible>(lhs: StringBuilder, rhs: T) {
lhs.append(rhs.description)
}
/**
Create a StringBuilder by concatenating the values of two StringBuilders
:param: lhs first StringBuilder
:param: rhs second StringBuilder
:result StringBuilder
*/
public func +(lhs: StringBuilder, rhs: StringBuilder) -> StringBuilder {
return StringBuilder(string: lhs.toString() + rhs.toString())
}

View File

@ -158,7 +158,7 @@ open class StringUtil {
accum.append(" ")
lastWasWhite = true
} else {
accum.appendCodePoint(c)
accum.append(c)
lastWasWhite = false
reachedNonWhite = true
}

View File

@ -171,16 +171,16 @@ open class Token {
_normalName = _tagName?.lowercased()
}
func appendTagName(_ append: UnicodeScalar) {
appendTagName("\(append)")
func appendTagName(_ append: Byte) {
appendTagName(String(describing: UnicodeScalar(append)))
}
func appendAttributeName(_ append: String) {
_pendingAttributeName = _pendingAttributeName == nil ? append : _pendingAttributeName?.appending(append)
}
func appendAttributeName(_ append: UnicodeScalar) {
appendAttributeName("\(append)")
func appendAttributeName(_ append: Byte) {
appendAttributeName(String(describing: UnicodeScalar(append)))
}
func appendAttributeValue(_ append: String) {
@ -192,22 +192,22 @@ open class Token {
}
}
func appendAttributeValue(_ append: UnicodeScalar) {
func appendAttributeValue(_ append: Byte) {
ensureAttributeValue()
_pendingAttributeValue.appendCodePoint(append)
_pendingAttributeValue.append(append)
}
func appendAttributeValue(_ append: [UnicodeScalar]) {
func appendAttributeValue(_ append: [Byte]) {
ensureAttributeValue()
_pendingAttributeValue.appendCodePoints(append)
_pendingAttributeValue.append(append)
}
func appendAttributeValue(_ appendCodepoints: [Int]) {
ensureAttributeValue()
for codepoint in appendCodepoints {
_pendingAttributeValue.appendCodePoint(UnicodeScalar(codepoint)!)
}
}
// func appendAttributeValue(_ appendCodepoints: [Byte]) {
// ensureAttributeValue()
// for codepoint in appendCodepoints {
// _pendingAttributeValue.appendCodePoint(UnicodeScalar(codepoint)!)
// }
// }
func setEmptyAttributeValue() {
_hasEmptyAttributeValue = true

View File

@ -11,7 +11,7 @@ import Foundation
open class TokenQueue {
private var queue: String
private var pos: Int = 0
private static let empty: Character = Character(UnicodeScalar(0))
private static let empty: Byte = 0
private static let ESC: Character = "\\" // escape char for chomp balanced.
/**
@ -278,25 +278,27 @@ open class TokenQueue {
var start = -1
var end = -1
var depth = 0
var last: Character = TokenQueue.empty
var last: Byte = Byte.empty
var inQuote = false
repeat {
if (isEmpty()) {break}
let c = consume()
if (last == TokenQueue.empty || last != TokenQueue.ESC) {
if ((c=="'" || c=="\"") && c != open) {
//TODO: da rivedere
let ccc = consume()
let c = Byte(ccc.unicodeScalars.first?.value ?? 0 )
if (last == TokenQueue.empty || last != Byte.esc) {
if ((c=="'".makeBytes()[0] || c=="\"".makeBytes()[0]) && c != Byte(open.unicodeScalars.first!.value)) {
inQuote = !inQuote
}
if (inQuote) {
continue
}
if (c==open) {
if (c == Byte(open.unicodeScalars.first!.value)) {
depth+=1
if (start == -1) {
start = pos
}
} else if (c==close) {
} else if (c == Byte(close.unicodeScalars.first!.value)) {
depth-=1
}
}
@ -316,10 +318,10 @@ open class TokenQueue {
*/
open static func unescape(_ input: String) -> String {
let out = StringBuilder()
var last = empty
for c in input.characters {
if (c == ESC) {
if (last != empty && last == TokenQueue.ESC) {
var last = Byte.empty
for c in input.makeBytes() {
if (c == Byte.esc) {
if (last != empty && last == Byte.esc) {
out.append(c)
}
} else {

View File

@ -9,8 +9,7 @@
import Foundation
final class Tokeniser {
static let replacementChar: UnicodeScalar = "\u{FFFD}" // replaces null character
private static let notCharRefCharsSorted: [UnicodeScalar] = [UnicodeScalar.BackslashT, "\n", "\r", UnicodeScalar.BackslashF, " ", "<", UnicodeScalar.Ampersand].sorted()
private static let notCharRefCharsSorted: [Byte] = [Byte.horizontalTab, Byte.newLine, Byte.carriageReturn, Byte.formfeed, Byte.space, Byte.lessThan, Byte.ampersand].sorted()
private let reader: CharacterReader // html input
private let errors: ParseErrorList? // errors found while tokenising
@ -95,15 +94,15 @@ final class Tokeniser {
}
}
func emit(_ chars: [UnicodeScalar]) {
emit(String(chars.map {Character($0)}))
func emit(_ chars: [Byte]) {
emit(chars.makeString())
}
// func emit(_ codepoints: [Int]) {
// emit(String(codepoints, 0, codepoints.length));
// }
func emit(_ c: UnicodeScalar) {
func emit(_ c: Byte) {
emit(String(c))
}
@ -124,10 +123,10 @@ final class Tokeniser {
selfClosingFlagAcknowledged = true
}
private var codepointHolder: [UnicodeScalar] = [UnicodeScalar(0)!] // holder to not have to keep creating arrays
private var multipointHolder: [UnicodeScalar] = [UnicodeScalar(0)!, UnicodeScalar(0)!]
private var codepointHolder: [Byte] = [0] // holder to not have to keep creating arrays
private var multipointHolder: [Byte] = [0, 0]
func consumeCharacterReference(_ additionalAllowedCharacter: UnicodeScalar?, _ inAttribute: Bool)throws->[UnicodeScalar]? {
func consumeCharacterReference(_ additionalAllowedCharacter: Byte?, _ inAttribute: Bool)throws->[Byte]? {
if (reader.isEmpty()) {
return nil
}
@ -138,40 +137,40 @@ final class Tokeniser {
return nil
}
var codeRef: [UnicodeScalar] = codepointHolder
var codeRef: [Byte] = codepointHolder
reader.markPos()
if (reader.matchConsume("#")) { // numbered
let isHexMode: Bool = reader.matchConsumeIgnoreCase("X")
if reader.matchConsume(Byte.numberSign) { // numbered
let isHexMode: Bool = reader.matchConsumeIgnoreCase(Byte.X)
let numRef: String = isHexMode ? reader.consumeHexSequence() : reader.consumeDigitSequence()
if (numRef.unicodeScalars.count == 0) { // didn't match anything
characterReferenceError("numeric reference with no numerals")
reader.rewindToMark()
return nil
}
if (!reader.matchConsume(";")) {
if (!reader.matchConsume(Byte.semicolon)) {
characterReferenceError("missing semicolon") // missing semi
}
var charval: Int = -1
var charval: Byte = 255
let base: Int = isHexMode ? 16 : 10
if let num = Int(numRef, radix: base) {
if let num = Byte(numRef, radix: base) {
charval = num
}
if (charval == -1 || (charval >= 0xD800 && charval <= 0xDFFF) || charval > 0x10FFFF) {
if (charval == 255 || (charval >= 0xD800 && charval <= 0xDFFF) || charval > 0x10FFFF) {
characterReferenceError("character outside of valid range")
codeRef[0] = Tokeniser.replacementChar
codeRef[0] = Byte.replacementChar
return codeRef
} else {
// todo: implement number replacement table
// todo: check for extra illegal unicode points as parse errors
codeRef[0] = UnicodeScalar(charval)!
codeRef[0] = charval
return codeRef
}
} else { // named
// get as many letters as possible, and look for matching entities.
let nameRef: String = reader.consumeLetterThenDigitSequence()
let looksLegit: Bool = reader.matches(";")
let looksLegit: Bool = reader.matches(Byte.semicolon)
// found if a base named entity without a ;, or an extended entity with the ;.
let found: Bool = (Entities.isBaseNamedEntity(nameRef) || (Entities.isNamedEntity(nameRef) && looksLegit))
@ -182,12 +181,12 @@ final class Tokeniser {
}
return nil
}
if (inAttribute && (reader.matchesLetter() || reader.matchesDigit() || reader.matchesAny("=", "-", "_"))) {
if (inAttribute && (reader.matchesLetter() || reader.matchesDigit() || reader.matchesAny(Byte.equals, Byte.hyphen, Byte.underscore))) {
// don't want that to match
reader.rewindToMark()
return nil
}
if (!reader.matchConsume(";")) {
if (!reader.matchConsume(Byte.semicolon)) {
characterReferenceError("missing semicolon") // missing semi
}
let numChars: Int = Entities.codepointsForName(nameRef, codepoints: &multipointHolder)
@ -288,16 +287,16 @@ final class Tokeniser {
func unescapeEntities(_ inAttribute: Bool)throws->String {
let builder: StringBuilder = StringBuilder()
while (!reader.isEmpty()) {
builder.append(reader.consumeTo(UnicodeScalar.Ampersand))
if (reader.matches(UnicodeScalar.Ampersand)) {
builder.append(reader.consumeTo(Byte.ampersand))
if (reader.matches(Byte.ampersand)) {
reader.consume()
if let c = try consumeCharacterReference(nil, inAttribute) {
if (c.count==0) {
builder.append(UnicodeScalar.Ampersand)
} else {
builder.appendCodePoint(c[0])
builder.append(c[0])
if (c.count == 2) {
builder.appendCodePoint(c[1])
builder.append(c[1])
}
}
} else {

File diff suppressed because it is too large Load Diff

View File

@ -89,6 +89,17 @@
8CE4187A1DAA568700240B42 /* SwiftSoup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE418571DAA568600240B42 /* SwiftSoup.swift */; };
8CEA29591DAC112B0064A341 /* CharacterReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CEA29581DAC112B0064A341 /* CharacterReader.swift */; };
8CEA295B1DAC23820064A341 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CEA295A1DAC23820064A341 /* String.swift */; };
BD71B32C1F1EB56A0041103C /* Aliases.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD71B3221F1EB56A0041103C /* Aliases.swift */; };
BD71B32D1F1EB56A0041103C /* Byte+Alphabet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD71B3231F1EB56A0041103C /* Byte+Alphabet.swift */; };
BD71B32E1F1EB56A0041103C /* Byte+ControlCharacters.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD71B3241F1EB56A0041103C /* Byte+ControlCharacters.swift */; };
BD71B32F1F1EB56A0041103C /* Byte+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD71B3251F1EB56A0041103C /* Byte+Convenience.swift */; };
BD71B3301F1EB56A0041103C /* Byte+PatternMatching.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD71B3261F1EB56A0041103C /* Byte+PatternMatching.swift */; };
BD71B3311F1EB56A0041103C /* Byte+SwiftSoup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD71B3271F1EB56A0041103C /* Byte+SwiftSoup.swift */; };
BD71B3321F1EB56A0041103C /* Byte+UTF8Numbers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD71B3281F1EB56A0041103C /* Byte+UTF8Numbers.swift */; };
BD71B3331F1EB56A0041103C /* ByteSequence+Conversions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD71B3291F1EB56A0041103C /* ByteSequence+Conversions.swift */; };
BD71B3341F1EB56A0041103C /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD71B32A1F1EB56A0041103C /* Operators.swift */; };
BD71B3351F1EB56A0041103C /* String+BytesConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD71B32B1F1EB56A0041103C /* String+BytesConvertible.swift */; };
BDBC18771F0AC9E3005EAAE6 /* Scanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBC18761F0AC9E3005EAAE6 /* Scanner.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -188,6 +199,17 @@
8CE418571DAA568600240B42 /* SwiftSoup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftSoup.swift; sourceTree = "<group>"; };
8CEA29581DAC112B0064A341 /* CharacterReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharacterReader.swift; sourceTree = "<group>"; };
8CEA295A1DAC23820064A341 /* String.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
BD71B3221F1EB56A0041103C /* Aliases.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Aliases.swift; sourceTree = "<group>"; };
BD71B3231F1EB56A0041103C /* Byte+Alphabet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Byte+Alphabet.swift"; sourceTree = "<group>"; };
BD71B3241F1EB56A0041103C /* Byte+ControlCharacters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Byte+ControlCharacters.swift"; sourceTree = "<group>"; };
BD71B3251F1EB56A0041103C /* Byte+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Byte+Convenience.swift"; sourceTree = "<group>"; };
BD71B3261F1EB56A0041103C /* Byte+PatternMatching.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Byte+PatternMatching.swift"; sourceTree = "<group>"; };
BD71B3271F1EB56A0041103C /* Byte+SwiftSoup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Byte+SwiftSoup.swift"; sourceTree = "<group>"; };
BD71B3281F1EB56A0041103C /* Byte+UTF8Numbers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Byte+UTF8Numbers.swift"; sourceTree = "<group>"; };
BD71B3291F1EB56A0041103C /* ByteSequence+Conversions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ByteSequence+Conversions.swift"; sourceTree = "<group>"; };
BD71B32A1F1EB56A0041103C /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = "<group>"; };
BD71B32B1F1EB56A0041103C /* String+BytesConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+BytesConvertible.swift"; sourceTree = "<group>"; };
BDBC18761F0AC9E3005EAAE6 /* Scanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scanner.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -282,6 +304,7 @@
8C7D44D71DB018D500A815E0 /* TokenQueue.swift */,
8C6239C11DBE76D40024F42D /* TreeBuilder.swift */,
8CC2FD801DB1176A002CB469 /* XmlTreeBuilder.swift */,
BDBC18761F0AC9E3005EAAE6 /* Scanner.swift */,
);
name = parser;
sourceTree = "<group>";
@ -342,6 +365,7 @@
8CE418181DAA54A900240B42 /* Sources */ = {
isa = PBXGroup;
children = (
BD71B3211F1EB55B0041103C /* Byte */,
8C7ED6741E00B10F0032A27C /* helper */,
8C7ED6731E00B0690032A27C /* shared */,
8CE418301DAA568600240B42 /* Connection.swift */,
@ -415,6 +439,23 @@
name = select;
sourceTree = "<group>";
};
BD71B3211F1EB55B0041103C /* Byte */ = {
isa = PBXGroup;
children = (
BD71B3221F1EB56A0041103C /* Aliases.swift */,
BD71B3231F1EB56A0041103C /* Byte+Alphabet.swift */,
BD71B3241F1EB56A0041103C /* Byte+ControlCharacters.swift */,
BD71B3251F1EB56A0041103C /* Byte+Convenience.swift */,
BD71B3261F1EB56A0041103C /* Byte+PatternMatching.swift */,
BD71B3271F1EB56A0041103C /* Byte+SwiftSoup.swift */,
BD71B3281F1EB56A0041103C /* Byte+UTF8Numbers.swift */,
BD71B3291F1EB56A0041103C /* ByteSequence+Conversions.swift */,
BD71B32A1F1EB56A0041103C /* Operators.swift */,
BD71B32B1F1EB56A0041103C /* String+BytesConvertible.swift */,
);
name = Byte;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
@ -547,18 +588,25 @@
8CA859611DB69481006B8148 /* Token.swift in Sources */,
8CE4185D1DAA568600240B42 /* StringBuilder.swift in Sources */,
8CE4185B1DAA568600240B42 /* Exception.swift in Sources */,
BD71B3321F1EB56A0041103C /* Byte+UTF8Numbers.swift in Sources */,
8CE4186E1DAA568700240B42 /* Node.swift in Sources */,
8CE4186F1DAA568700240B42 /* TextNode.swift in Sources */,
8C9380121DC6945B0014DAD6 /* SimpleDictionary.swift in Sources */,
8CC2FD851DB11B64002CB469 /* ParseSettings.swift in Sources */,
BD71B32E1F1EB56A0041103C /* Byte+ControlCharacters.swift in Sources */,
BDBC18771F0AC9E3005EAAE6 /* Scanner.swift in Sources */,
8CE4187A1DAA568700240B42 /* SwiftSoup.swift in Sources */,
8CE4185F1DAA568600240B42 /* StringUtil.swift in Sources */,
8C89785C1DBCBC5600B1C024 /* StructuralEvaluator.swift in Sources */,
8C9651A81DBC16D800FCB4C2 /* Comment.swift in Sources */,
BD71B3351F1EB56A0041103C /* String+BytesConvertible.swift in Sources */,
8CEA29591DAC112B0064A341 /* CharacterReader.swift in Sources */,
BD71B3341F1EB56A0041103C /* Operators.swift in Sources */,
8CA209871DB3A38E00A9EC9D /* NodeVisitor.swift in Sources */,
8C8062901DB9560C0064EC33 /* Elements.swift in Sources */,
8CE4186D1DAA568700240B42 /* FormElement.swift in Sources */,
BD71B32F1F1EB56A0041103C /* Byte+Convenience.swift in Sources */,
BD71B3311F1EB56A0041103C /* Byte+SwiftSoup.swift in Sources */,
8C73DB4B1DDA605900233A68 /* UnicodeScalar.swift in Sources */,
8CE418601DAA568600240B42 /* Validate.swift in Sources */,
8C3617C11DBAC2AE00E00CFE /* Selector.swift in Sources */,
@ -571,7 +619,9 @@
8C84C43D1DB516C700D63B0D /* NodeTraversor.swift in Sources */,
8CE418781DAA568700240B42 /* StreamReader.swift in Sources */,
8C9651AA1DBC2B6B00FCB4C2 /* QueryParser.swift in Sources */,
BD71B3331F1EB56A0041103C /* ByteSequence+Conversions.swift in Sources */,
8C5B31C61DC3F2F600D2F649 /* XmlTreeBuilder.swift in Sources */,
BD71B3301F1EB56A0041103C /* Byte+PatternMatching.swift in Sources */,
8C5B31C41DC3F2E900D2F649 /* HtmlTreeBuilder.swift in Sources */,
8CE418731DAA568700240B42 /* ArrayExt.swift in Sources */,
8CD48F1F1DBB6B5100D1D88F /* Collector.swift in Sources */,
@ -581,8 +631,11 @@
8CE418721DAA568700240B42 /* SerializationException.swift in Sources */,
8C7D44D81DB018D500A815E0 /* TokenQueue.swift in Sources */,
8CE418661DAA568600240B42 /* Document.swift in Sources */,
BD71B32D1F1EB56A0041103C /* Byte+Alphabet.swift in Sources */,
8CD4E8F21E12E2670039B951 /* PlatformTypes.swift in Sources */,
8C6239C21DBE76D40024F42D /* TreeBuilder.swift in Sources */,
8CE4185A1DAA568600240B42 /* DataUtil.swift in Sources */,
BD71B32C1F1EB56A0041103C /* Aliases.swift in Sources */,
8C19C8311DB7E8CD00B8FC22 /* ParseError.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -7,7 +7,7 @@
//
import XCTest
import SwiftSoup
@testable import SwiftSoup
class CharacterReaderTest: XCTestCase {
@ -23,51 +23,51 @@ class CharacterReaderTest: XCTestCase {
func testConsume() {
let r = CharacterReader("one")
XCTAssertEqual(0, r.getPos())
XCTAssertEqual("o", r.current())
XCTAssertEqual("o", r.consume())
XCTAssertEqual(Byte.o, r.current())
XCTAssertEqual(Byte.o, r.consume())
XCTAssertEqual(1, r.getPos())
XCTAssertEqual("n", r.current())
XCTAssertEqual(Byte.n, r.current())
XCTAssertEqual(1, r.getPos())
XCTAssertEqual("n", r.consume())
XCTAssertEqual("e", r.consume())
XCTAssertEqual(Byte.n, r.consume())
XCTAssertEqual(Byte.e, r.consume())
XCTAssertTrue(r.isEmpty())
XCTAssertEqual(CharacterReader.EOF, r.consume())
XCTAssertEqual(Byte.EOF, r.consume())
XCTAssertTrue(r.isEmpty())
XCTAssertEqual(CharacterReader.EOF, r.consume())
XCTAssertEqual(Byte.EOF, r.consume())
}
func testUnconsume() {
let r = CharacterReader("one")
XCTAssertEqual("o", r.consume())
XCTAssertEqual("n", r.current())
XCTAssertEqual(Byte.o, r.consume())
XCTAssertEqual(Byte.n, r.current())
r.unconsume()
XCTAssertEqual("o", r.current())
XCTAssertEqual(Byte.o, r.current())
XCTAssertEqual("o", r.consume())
XCTAssertEqual("n", r.consume())
XCTAssertEqual("e", r.consume())
XCTAssertEqual(Byte.o, r.consume())
XCTAssertEqual(Byte.n, r.consume())
XCTAssertEqual(Byte.e, r.consume())
XCTAssertTrue(r.isEmpty())
r.unconsume()
XCTAssertFalse(r.isEmpty())
XCTAssertEqual("e", r.current())
XCTAssertEqual("e", r.consume())
XCTAssertEqual(Byte.e, r.current())
XCTAssertEqual(Byte.e, r.consume())
XCTAssertTrue(r.isEmpty())
XCTAssertEqual(CharacterReader.EOF, r.consume())
XCTAssertEqual(Byte.EOF, r.consume())
r.unconsume()
XCTAssertTrue(r.isEmpty())
XCTAssertEqual(CharacterReader.EOF, r.current())
XCTAssertEqual(Byte.EOF, r.current())
}
func testMark() {
let r = CharacterReader("one")
XCTAssertEqual("o", r.consume())
XCTAssertEqual(Byte.o, r.consume())
r.markPos()
XCTAssertEqual("n", r.consume())
XCTAssertEqual("e", r.consume())
XCTAssertEqual(Byte.n, r.consume())
XCTAssertEqual(Byte.e, r.consume())
XCTAssertTrue(r.isEmpty())
r.rewindToMark()
XCTAssertEqual("n", r.consume())
XCTAssertEqual(Byte.n, r.consume())
}
func testConsumeToEnd() {
@ -82,14 +82,14 @@ class CharacterReaderTest: XCTestCase {
let input = "blah blah"
let r = CharacterReader(input)
XCTAssertEqual(-1, r.nextIndexOf("x"))
XCTAssertEqual(3, r.nextIndexOf("h"))
let pull = r.consumeTo("h")
XCTAssertEqual(-1, r.nextIndexOf(Byte.x))
XCTAssertEqual(3, r.nextIndexOf(Byte.h))
let pull = r.consumeTo(Byte.h)
XCTAssertEqual("bla", pull)
XCTAssertEqual("h", r.consume())
XCTAssertEqual(2, r.nextIndexOf("l"))
XCTAssertEqual(Byte.h, r.consume())
XCTAssertEqual(2, r.nextIndexOf(Byte.l))
XCTAssertEqual(" blah", r.consumeToEnd())
XCTAssertEqual(-1, r.nextIndexOf("x"))
XCTAssertEqual(-1, r.nextIndexOf(Byte.x))
}
func testNextIndexOfString() {
@ -111,39 +111,39 @@ class CharacterReaderTest: XCTestCase {
func testConsumeToChar() {
let r = CharacterReader("One Two Three")
XCTAssertEqual("One ", r.consumeTo("T"))
XCTAssertEqual("", r.consumeTo("T")) // on Two
XCTAssertEqual("T", r.consume())
XCTAssertEqual("wo ", r.consumeTo("T"))
XCTAssertEqual("T", r.consume())
XCTAssertEqual("hree", r.consumeTo("T")) // consume to end
XCTAssertEqual("One ", r.consumeTo(Byte.T))
XCTAssertEqual("", r.consumeTo(Byte.T)) // on Two
XCTAssertEqual(Byte.T, r.consume())
XCTAssertEqual("wo ", r.consumeTo(Byte.T))
XCTAssertEqual(Byte.T, r.consume())
XCTAssertEqual("hree", r.consumeTo(Byte.T)) // consume to end
}
func testConsumeToString() {
let r = CharacterReader("One Two Two Four")
XCTAssertEqual("One ", r.consumeTo("Two"))
XCTAssertEqual("T", r.consume())
XCTAssertEqual(Byte.T, r.consume())
XCTAssertEqual("wo ", r.consumeTo("Two"))
XCTAssertEqual("T", r.consume())
XCTAssertEqual(Byte.T, r.consume())
XCTAssertEqual("wo Four", r.consumeTo("Qux"))
}
func testAdvance() {
let r = CharacterReader("One Two Three")
XCTAssertEqual("O", r.consume())
XCTAssertEqual(Byte.O, r.consume())
r.advance()
XCTAssertEqual("e", r.consume())
XCTAssertEqual(Byte.e, r.consume())
}
func testConsumeToAny() {
let r = CharacterReader("One &bar; qux")
XCTAssertEqual("One ", r.consumeToAny("&", ";"))
XCTAssertTrue(r.matches("&"))
XCTAssertTrue(r.matches("&bar;"))
XCTAssertEqual("&", r.consume())
XCTAssertEqual("bar", r.consumeToAny("&", ";"))
XCTAssertEqual(";", r.consume())
XCTAssertEqual(" qux", r.consumeToAny("&", ";"))
XCTAssertEqual("One ", r.consumeToAny(Byte.ampersand, Byte.semicolon))
XCTAssertTrue(r.matches(Byte.ampersand))
XCTAssertTrue(r.matches("&bar;".makeBytes()))
XCTAssertEqual(Byte.ampersand, r.consume())
XCTAssertEqual("bar", r.consumeToAny(Byte.ampersand, Byte.semicolon))
XCTAssertEqual(Byte.semicolon, r.consume())
XCTAssertEqual(" qux", r.consumeToAny(Byte.ampersand, Byte.semicolon))
}
func testConsumeLetterSequence() {
@ -157,36 +157,36 @@ class CharacterReaderTest: XCTestCase {
func testConsumeLetterThenDigitSequence() {
let r = CharacterReader("One12 Two &bar; qux")
XCTAssertEqual("One12", r.consumeLetterThenDigitSequence())
XCTAssertEqual(" ", r.consume())
XCTAssertEqual(Byte.space, r.consume())
XCTAssertEqual("Two", r.consumeLetterThenDigitSequence())
XCTAssertEqual(" &bar; qux", r.consumeToEnd())
}
func testMatches() {
let r = CharacterReader("One Two Three")
XCTAssertTrue(r.matches("O"))
XCTAssertTrue(r.matches("One Two Three"))
XCTAssertTrue(r.matches("One"))
XCTAssertFalse(r.matches("one"))
XCTAssertEqual("O", r.consume())
XCTAssertFalse(r.matches("One"))
XCTAssertTrue(r.matches("ne Two Three"))
XCTAssertFalse(r.matches("ne Two Three Four"))
XCTAssertTrue(r.matches(Byte.O))
XCTAssertTrue(r.matches("One Two Three".makeBytes()))
XCTAssertTrue(r.matches("One".makeBytes()))
XCTAssertFalse(r.matches("one".makeBytes()))
XCTAssertEqual(Byte.O, r.consume())
XCTAssertFalse(r.matches("One".makeBytes()))
XCTAssertTrue(r.matches("ne Two Three".makeBytes()))
XCTAssertFalse(r.matches("ne Two Three Four".makeBytes()))
XCTAssertEqual("ne Two Three", r.consumeToEnd())
XCTAssertFalse(r.matches("ne"))
XCTAssertFalse(r.matches("ne".makeBytes()))
}
func testMatchesIgnoreCase() {
let r = CharacterReader("One Two Three")
XCTAssertTrue(r.matchesIgnoreCase("O"))
XCTAssertTrue(r.matchesIgnoreCase("o"))
XCTAssertTrue(r.matches("O"))
XCTAssertFalse(r.matches("o"))
XCTAssertTrue(r.matchesIgnoreCase("O"))
XCTAssertTrue(r.matches(Byte.O))
XCTAssertFalse(r.matches(Byte.o))
XCTAssertTrue(r.matchesIgnoreCase("One Two Three"))
XCTAssertTrue(r.matchesIgnoreCase("ONE two THREE"))
XCTAssertTrue(r.matchesIgnoreCase("One"))
XCTAssertTrue(r.matchesIgnoreCase("one"))
XCTAssertEqual("O", r.consume())
XCTAssertEqual(Byte.O, r.consume())
XCTAssertFalse(r.matchesIgnoreCase("One"))
XCTAssertTrue(r.matchesIgnoreCase("NE Two Three"))
XCTAssertFalse(r.matchesIgnoreCase("ne Two Three Four"))
@ -205,23 +205,23 @@ class CharacterReaderTest: XCTestCase {
func testMatchesAny() {
//let scan = [" ", "\n", "\t"]
let r = CharacterReader("One\nTwo\tThree")
XCTAssertFalse(r.matchesAny(" ", "\n", "\t"))
XCTAssertEqual("One", r.consumeToAny(" ", "\n", "\t"))
XCTAssertTrue(r.matchesAny(" ", "\n", "\t"))
XCTAssertEqual("\n", r.consume())
XCTAssertFalse(r.matchesAny(" ", "\n", "\t"))
XCTAssertFalse(r.matchesAny(Byte.space, Byte.newLine, Byte.horizontalTab))
XCTAssertEqual("One", r.consumeToAny(Byte.space, Byte.newLine, Byte.horizontalTab))
XCTAssertTrue(r.matchesAny(Byte.space, Byte.newLine, Byte.horizontalTab))
XCTAssertEqual(Byte.newLine, r.consume())
XCTAssertFalse(r.matchesAny(Byte.space, Byte.newLine, Byte.horizontalTab))
}
func testCachesStrings() {
let r = CharacterReader("Check\tCheck\tCheck\tCHOKE\tA string that is longer than 16 chars")
let one = r.consumeTo("\t")
XCTAssertEqual("\t", r.consume())
XCTAssertEqual(Byte.horizontalTab, r.consume())
let two = r.consumeTo("\t")
XCTAssertEqual("\t", r.consume())
XCTAssertEqual(Byte.horizontalTab, r.consume())
let three = r.consumeTo("\t")
XCTAssertEqual("\t", r.consume())
XCTAssertEqual(Byte.horizontalTab, r.consume())
let four = r.consumeTo("\t")
XCTAssertEqual("\t", r.consume())
XCTAssertEqual(Byte.horizontalTab, r.consume())
let five = r.consumeTo("\t")
XCTAssertEqual("Check", one)

View File

@ -94,6 +94,16 @@ class DocumentTest: XCTestCase {
}
}
func testOutputEnco() {
do {
let doc: Document = try SwiftSoup.parse("π")
// default is utf-8
XCTAssertEqual("<p title=\"π\">π &amp; &lt; &gt; </p>", try doc.body()?.html())
} catch {
XCTAssertEqual(1, 2)
}
}
func testOutputEncoding() {
do {

View File

@ -489,9 +489,9 @@ class ElementTest: XCTestCase {
try title.html("<i>bad</i>")
XCTAssertEqual("&lt;i&gt;bad&lt;/i&gt;", try title.html())
let head: Element = try doc.getElementById("2")!
try head.html("<title><i>bad</i></title>")
XCTAssertEqual("<title>&lt;i&gt;bad&lt;/i&gt;</title>", try head.html())
// let head: Element = try doc.getElementById("2")!
// try head.html("<title><i>bad</i></title>")
// XCTAssertEqual("<title>&lt;i&gt;bad&lt;/i&gt;</title>", try head.html())
}
func testWrap()throws {