oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1import Foundation
2
3/// Linear scanning avoids regex backtracking on malformed clipboard HTML such
4/// as a long run of '<' without a closing '>'. No rendering or I/O occurs.
5struct DocsHTMLScanner {
6 enum Piece { case text(String), tag(String) }
7 let source: NSString
8 var offset = 0
9 init(_ value: String) { source = value as NSString }
10 mutating func next() -> Piece? {
11 guard offset < source.length else { return nil }
12 let start = offset
13 if source.character(at: offset) != 60 {
14 let next = source.range(of: "<", range: NSRange(location: offset, length: source.length - offset))
15 offset = next.location == NSNotFound ? source.length : next.location
16 return .text(source.substring(with: NSRange(location: start, length: offset - start)))
17 }
18 if source.length - offset >= 4, source.substring(with: NSRange(location: offset, length: 4)) == "<!--" {
19 let end = source.range(of: "-->", range: NSRange(location: offset + 4, length: source.length - offset - 4))
20 offset = end.location == NSNotFound ? source.length : NSMaxRange(end)
21 return .tag("<!-- -->")
22 }
23 var quote: unichar = 0
24 offset += 1
25 while offset < source.length {
26 let value = source.character(at: offset)
27 offset += 1
28 if quote != 0 {
29 if value == quote { quote = 0 }
30 } else if value == 34 || value == 39 { quote = value }
31 else if value == 62 { return .tag(source.substring(with: NSRange(location: start, length: offset - start))) }
32 }
33 return .text(source.substring(from: start))
34 }
35 static func attributes(_ value: String) -> [String: String] {
36 let source = value as NSString
37 var index = 0
38 var result: [String: String] = [:]
39 let accepted: Set<String> = ["href", "title", "style", "class", "src", "alt", "start", "align", "face"]
40 func space(_ value: unichar) -> Bool { [9, 10, 12, 13, 32].contains(value) }
41 while index < source.length {
42 while index < source.length && space(source.character(at: index)) { index += 1 }
43 let start = index
44 while index < source.length && !space(source.character(at: index)) && source.character(at: index) != 61 { index += 1 }
45 let key = index - start <= 128 ? source.substring(with: NSRange(location: start, length: index - start)).lowercased() : ""
46 while index < source.length && space(source.character(at: index)) { index += 1 }
47 guard index < source.length else { break }
48 guard source.character(at: index) == 61 else { continue }
49 index += 1
50 while index < source.length && space(source.character(at: index)) { index += 1 }
51 guard index < source.length else { break }
52 let first = source.character(at: index)
53 let quoted = first == 34 || first == 39
54 if quoted { index += 1 }
55 let content = index
56 while index < source.length && (quoted ? source.character(at: index) != first : !space(source.character(at: index))) { index += 1 }
57 if accepted.contains(key) { result[key] = source.substring(with: NSRange(location: content, length: index - content)) }
58 if quoted && index < source.length { index += 1 }
59 }
60 return result
61 }
62}
63