114 lines
4.1 KiB
Swift
114 lines
4.1 KiB
Swift
//
|
|
// AttributedString+Trim.swift
|
|
// Tusker
|
|
//
|
|
// Created by Shadowfacts on 8/29/18.
|
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
private let ASCII_NEWLINE: unichar = 10
|
|
private let ASCII_SPACE: unichar = 32
|
|
|
|
extension NSAttributedString {
|
|
|
|
var fullRange: NSRange {
|
|
return NSRange(location: 0, length: self.length)
|
|
}
|
|
|
|
/// Creates a new string with the whitespace collapsed according to the CSS Text Module Level 3 rules.
|
|
/// See https://www.w3.org/TR/css-text-3/#white-space-phase-1
|
|
func collapsingWhitespace() -> NSAttributedString {
|
|
let mut = NSMutableAttributedString(attributedString: self)
|
|
mut.collapseWhitespace()
|
|
return mut
|
|
}
|
|
|
|
}
|
|
|
|
extension NSMutableAttributedString {
|
|
|
|
func trimLeadingCharactersInSet(_ charSet: CharacterSet) {
|
|
var end = string.startIndex
|
|
while end < string.endIndex && charSet.contains(string.unicodeScalars[end]) {
|
|
end = string.unicodeScalars.index(after: end)
|
|
}
|
|
if end > string.startIndex {
|
|
let length = string.utf16.distance(from: string.startIndex, to: end)
|
|
replaceCharacters(in: NSRange(location: 0, length: length), with: "")
|
|
}
|
|
}
|
|
|
|
func trimTrailingCharactersInSet(_ charSet: CharacterSet) {
|
|
if string.isEmpty {
|
|
return
|
|
}
|
|
var start = string.index(before: string.endIndex)
|
|
while start > string.startIndex && charSet.contains(string.unicodeScalars[start]) {
|
|
start = string.unicodeScalars.index(before: start)
|
|
}
|
|
if start < string.endIndex {
|
|
if start != string.startIndex || !charSet.contains(string.unicodeScalars[start]) {
|
|
start = string.unicodeScalars.index(after: start)
|
|
}
|
|
let location = string.utf16.distance(from: string.startIndex, to: start)
|
|
let length = string.utf16.distance(from: start, to: string.endIndex)
|
|
replaceCharacters(in: NSRange(location: location, length: length), with: "")
|
|
}
|
|
}
|
|
|
|
/// Collapses whitespace in this string according to the CSS Text Module Level 3 rules.
|
|
/// See https://www.w3.org/TR/css-text-3/#white-space-phase-1
|
|
func collapseWhitespace() {
|
|
let str = self.mutableString
|
|
|
|
var i = 0
|
|
while i < str.length {
|
|
if str.character(at: i) == ASCII_NEWLINE {
|
|
var j: Int
|
|
if i > 0 {
|
|
// scan backwards to find beginning of space characters preceeding newline
|
|
j = i - 1
|
|
while j >= 0 {
|
|
if str.character(at: j) != ASCII_SPACE {
|
|
break
|
|
}
|
|
j -= 1
|
|
}
|
|
// add one after loop completes because start of range is _inclusive_
|
|
j += 1
|
|
} else {
|
|
j = 0
|
|
}
|
|
|
|
var k: Int
|
|
if i < str.length - 1 {
|
|
// scan forwards to find end of space characters following newline
|
|
k = i + 1
|
|
while k < str.length {
|
|
if str.character(at: k) != ASCII_SPACE {
|
|
break
|
|
}
|
|
k += 1
|
|
}
|
|
// don't need to subtract one before breaking out of loop, because end of range is _exclusive_
|
|
} else {
|
|
// range end is _exclusive_, so use whole string length that way last character is included
|
|
k = str.length
|
|
}
|
|
|
|
// if there's only one character to be replaced, that means we'd be replacing the newline with a newline, so don't bother
|
|
if k - j > 1 {
|
|
str.replaceCharacters(in: NSRange(location: j, length: k - j), with: "\n")
|
|
|
|
// continue scanning through the string starting after the newline we just inserted
|
|
i = j
|
|
}
|
|
}
|
|
i += 1
|
|
}
|
|
}
|
|
|
|
}
|