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
3struct DocumentWordCounts {
4 let words: Int
5 let characters: Int
6 let charactersNoSpaces: Int
7 let paragraphs: Int
8 let selectedWords: Int?
9
10 init(projection: CRDTProjection, content: NSAttributedString, selection: NSRange) {
11 var whole = ""
12 func visit(_ node: CRDTNode) {
13 if node.type == "text" { whole += node.plainText }
14 else {
15 if !["image", "hard_break"].contains(node.type), !whole.isEmpty { whole += " " }
16 for child in node.children ?? [] { visit(child) }
17 }
18 }
19 for node in projection.children { visit(node) }
20 words = Self.countWords(whole)
21 characters = whole.utf16.count
22 charactersNoSpaces = whole.unicodeScalars.filter { !CharacterSet.whitespacesAndNewlines.contains($0) }.reduce(0) { $0 + $1.utf16.count }
23 paragraphs = projection.children.filter {
24 ["paragraph", "heading", "code_block", "bullet_list", "ordered_list", "blockquote"].contains($0.type)
25 }.count
26 let valid = NSIntersectionRange(selection, NSRange(location: 0, length: content.length))
27 if valid.length == 0 { selectedWords = nil }
28 else {
29 var selected = ""
30 content.enumerateAttributes(in: valid) { attributes, range, _ in
31 if attributes[.docsMarks] is Data {
32 selected += (content.string as NSString).substring(with: range)
33 } else if attributes[.docsBlock] != nil {
34 // Only the paragraph terminator separates words. Inline
35 // images and hard breaks contribute no text in web counts.
36 for scalar in (content.string as NSString).substring(with: range).unicodeScalars where scalar == "\n" { selected += " " }
37 }
38 }
39 selectedWords = Self.countWords(selected)
40 }
41 }
42
43 static func countWords(_ text: String) -> Int {
44 var count = 0
45 text.enumerateSubstrings(in: text.startIndex..<text.endIndex, options: [.byWords, .substringNotRequired]) { _, _, _, _ in count += 1 }
46 return count
47 }
48}
49