Dactyloidae/mobile/ios/Shared/Extensions/StringExtensions.swift
2026-06-26 21:04:09 -07:00

107 lines
4.2 KiB
Swift

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Foundation
public extension String {
public func startsWith(_ other: String) -> Bool {
// rangeOfString returns nil if other is empty, destroying the analogy with (ordered) sets.
if other.isEmpty {
return true
}
if let range = self.range(of: other,
options: NSString.CompareOptions.anchored) {
return range.lowerBound == self.startIndex
}
return false
}
public func endsWith(_ other: String) -> Bool {
// rangeOfString returns nil if other is empty, destroying the analogy with (ordered) sets.
if other.isEmpty {
return true
}
if let range = self.range(of: other,
options: [NSString.CompareOptions.anchored, NSString.CompareOptions.backwards]) {
return range.upperBound == self.endIndex
}
return false
}
func escape() -> String? {
// We can't guaruntee that strings have a valid string encoding, as this is an entry point for tainted data,
// we should be very careful about forcefully dereferencing optional types.
// https://stackoverflow.com/questions/33558933/why-is-the-return-value-of-string-addingpercentencoding-optional#33558934
let queryItemDividers = CharacterSet(charactersIn: "?=&")
let allowedEscapes = CharacterSet.urlQueryAllowed.symmetricDifference(queryItemDividers)
return self.addingPercentEncoding(withAllowedCharacters: allowedEscapes)
}
func unescape() -> String? {
return self.removingPercentEncoding
}
/**
Ellipsizes a String only if it's longer than `maxLength`
"ABCDEF".ellipsize(4)
// "ABEF"
:param: maxLength The maximum length of the String.
:returns: A String with `maxLength` characters or less
*/
func ellipsize(maxLength: Int) -> String {
if (maxLength >= 2) && (self.characters.count > maxLength) {
let index1 = self.characters.index(self.startIndex, offsetBy: (maxLength + 1) / 2) // `+ 1` has the same effect as an int ceil
let index2 = self.characters.index(self.endIndex, offsetBy: maxLength / -2)
return self.substring(to: index1) + "\u{2060}" + self.substring(from: index2)
}
return self
}
private var stringWithAdditionalEscaping: String {
return self.replacingOccurrences(of: "|", with: "%7C", options: NSString.CompareOptions(), range: nil)
}
public var asURL: URL? {
// Firefox and NSURL disagree about the valid contents of a URL.
// Let's escape | for them.
// We'd love to use one of the more sophisticated CFURL* or NSString.* functions, but
// none seem to be quite suitable.
return URL(string: self) ??
URL(string: self.stringWithAdditionalEscaping)
}
/// Returns a new string made by removing the leading String characters contained
/// in a given character set.
public func stringByTrimmingLeadingCharactersInSet(_ set: CharacterSet) -> String {
var trimmed = self
while trimmed.rangeOfCharacter(from: set)?.lowerBound == trimmed.startIndex {
trimmed.remove(at: trimmed.startIndex)
}
return trimmed
}
/// Adds a newline at the closest space from the middle of a string.
/// Example turning "Mark as Read" into "Mark as\n Read"
public func stringSplitWithNewline() -> String {
let mid = self.characters.count/2
let arr: [Int] = self.characters.indices.flatMap {
if self.characters[$0] == " " {
return self.distance(from: startIndex, to: $0)
}
return nil
}
guard let closest = arr.enumerated().min(by: { abs($0.1 - mid) < abs($1.1 - mid) }) else {
return self
}
var newString = self
newString.insert("\n", at: newString.characters.index(newString.characters.startIndex, offsetBy: closest.element))
return newString
}
}