Sources/BFKit/Linux/Foundation/String+Extensions.swift
//
// String+Extensions.swift
// BFKit-Swift
//
// The MIT License (MIT)
//
// Copyright (c) 2015 - 2019 Fabrizio Brancati.
//
// 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
// MARK: - String extension
/// This extesion adds some useful functions to String.
public extension String {
// MARK: - Variables
/// Gets the individual characters and puts them in an array as Strings.
var array: [String] {
description.map { String($0) }
}
/// Returns the Float value
var floatValue: Float {
NSString(string: self).floatValue
}
/// Returns the Int value
var intValue: Int {
Int(NSString(string: self).intValue)
}
/// Convert self to a Data.
var dataValue: Data? {
data(using: .utf8)
}
/// Encoded string to Base64.
var base64encoded: String {
guard let data: Data = data(using: .utf8) else {
return ""
}
return data.base64EncodedString()
}
/// Decoded Base64 to string.
var base64decoded: String {
guard let data = Data(base64Encoded: String(self), options: .ignoreUnknownCharacters), let dataString = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else {
return ""
}
return String(describing: dataString)
}
/// Encode self to an encoded url string.
var urlEncoded: String? {
addingPercentEncoding(withAllowedCharacters: CharacterSet.urlHostAllowed)
}
/// Returns the localized string from self.
var localized: String {
NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
}
/// Convert the String to a NSNumber.
var numberValue: NSNumber? {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter.number(from: self)
}
// MARK: - Functions
/// Get the character at a given index.
///
/// - Parameter index: The index.
/// - Returns: Returns the character at a given index, starts from 0.
func character(at index: Int) -> Character {
self[self.index(startIndex, offsetBy: index)]
}
/// Returns a new string containing the characters of the String from the one at a given index to the end.
///
/// - Parameter index: The index.
/// - Returns: Returns the substring from index.
func substring(from index: Int) -> String {
String(self[self.index(startIndex, offsetBy: index)...])
}
/// Creates a substring from the given character.
///
/// - Parameter character: The character.
/// - Returns: Returns the substring from character.
func substring(from character: Character) -> String {
let index = self.index(of: character)
guard index > -1 else {
return ""
}
return substring(from: index + 1)
}
/// Returns a new string containing the characters of the String up to, but not including, the one at a given index.
///
/// - Parameter index: The index.
/// - Returns: Returns the substring to index.
func substring(to index: Int) -> String {
guard index <= count else {
return ""
}
return String(self[..<self.index(startIndex, offsetBy: index)])
}
/// Creates a substring to the given character.
///
/// - Parameter character: The character.
/// - Returns: Returns the substring to character.
func substring(to character: Character) -> String {
let index: Int = self.index(of: character)
guard index > -1 else {
return ""
}
return substring(to: index)
}
/// Creates a substring with a given range.
///
/// - Parameter range: The range.
/// - Returns: Returns the string between the range.
func substring(with range: Range<Int>) -> String {
let start = index(startIndex, offsetBy: range.lowerBound)
let end = index(startIndex, offsetBy: range.upperBound)
return String(self[start..<end])
}
/// Creates a substring with a given range.
///
/// - Parameter range: The range.
/// - Returns: Returns the string between the range.
func substring(with range: CountableClosedRange<Int>) -> String {
substring(with: Range(uncheckedBounds: (lower: range.lowerBound, upper: range.upperBound + 1)))
}
/// Returns the index of the given character.
///
/// - Parameter character: The character to search.
/// - Returns: Returns the index of the given character, -1 if not found.
func index(of character: Character) -> Int {
guard let index: Index = firstIndex(of: character) else {
return -1
}
return distance(from: startIndex, to: index)
}
/// Check if self has the given substring in case-sensitiv or case-insensitive.
///
/// - Parameters:
/// - string: The substring to be searched.
/// - caseSensitive: If the search has to be case-sensitive or not.
/// - Returns: Returns true if founded, otherwise false.
func range(of string: String, caseSensitive: Bool = true) -> Bool {
caseSensitive ? (range(of: string) != nil) : (lowercased().range(of: string.lowercased()) != nil)
}
/// Check if self has the given substring in case-sensitiv or case-insensitive.
///
/// - Parameters:
/// - string: The substring to be searched.
/// - caseSensitive: If the search has to be case-sensitive or not.
/// - Returns: Returns true if founded, otherwise false.
func has(_ string: String, caseSensitive: Bool = true) -> Bool {
range(of: string, caseSensitive: caseSensitive)
}
/// Returns the number of occurrences of a String into
///
/// - Parameter string: String of occurrences.
/// - Returns: Returns the number of occurrences of a String into
func occurrences(of string: String, caseSensitive: Bool = true) -> Int {
var string = string
if !caseSensitive {
string = string.lowercased()
}
return lowercased().components(separatedBy: string).count - 1
}
/// Conver self to a capitalized string.
/// Example: "This is a Test" will return "This is a test" and "this is a test" will return "This is a test".
///
/// - Returns: Returns the capitalized sentence string.
func sentenceCapitalizedString() -> String {
guard !isEmpty else {
return ""
}
let uppercase: String = substring(to: 1).uppercased()
let lowercase: String = substring(from: 1).lowercased()
return uppercase + lowercase
}
/// Returns the query parameter string.
///
/// - Parameter parameter: Parameter to be searched.
/// - Returns: Returns the query parameter string.
func queryStringParameter(parameter: String) -> String? {
guard let url = URLComponents(string: self) else {
return nil
}
return url.queryItems?.first { $0.name == parameter }?.value
}
/// Converts the query to a dictionary of parameters.
///
/// - Returns: Returns the dictionary of parameters.
func queryDictionary() -> [String: String] {
var queryStrings: [String: String] = [:]
for pair in self.components(separatedBy: "&") {
let key = pair.components(separatedBy: "=")[0]
let value = pair.components(separatedBy: "=")[1].replacingOccurrences(of: "+", with: " ").removingPercentEncoding ?? ""
queryStrings[key] = value
}
return queryStrings
}
#if !os(Linux)
/// Check if the URL is a valid HTTP URL.
///
/// - Returns: Returns if the URL is a valid HTTP URL
func isURLValid() -> Bool {
let regEx = "((https|http)://)((\\w|-)+)(([.]|[/])((\\w|-)+))?.+"
let predicate = NSPredicate(format: "SELF MATCHES %@", argumentArray: [regEx])
return predicate.evaluate(with: self)
}
#endif
/// Convert a String to a NSAttributedString.
/// With that variable you can customize a String with a style.
///
/// Example:
///
/// string.attributedString.font(UIFont(fontName: .helveticaNeue, size: 20))
///
/// You can even concatenate two or more styles:
///
/// string.attributedString.font(UIFont(fontName: .helveticaNeue, size: 20)).backgroundColor(UIColor.red)
var attributedString: NSAttributedString {
NSAttributedString(string: self)
}
/// Returns the last path component.
var lastPathComponent: String {
NSString(string: self).lastPathComponent
}
/// Returns the path extension.
var pathExtension: String {
NSString(string: self).pathExtension
}
/// Delete the last path component.
var deletingLastPathComponent: String {
NSString(string: self).deletingLastPathComponent
}
/// Delete the path extension.
var deletingPathExtension: String {
NSString(string: self).deletingPathExtension
}
/// Returns an array of path components.
var pathComponents: [String] {
NSString(string: self).pathComponents
}
/// Appends a path component to the string.
///
/// - Parameter path: Path component to append.
/// - Returns: Returns all the string.
func appendingPathComponent(_ path: String) -> String {
let string = NSString(string: self)
return string.appendingPathComponent(path)
}
/// Appends a path extension to the string.
///
/// - Parameter ext: Extension to append.
/// - Returns: Returns all the string.
func appendingPathExtension(_ ext: String) -> String? {
let nsSt = NSString(string: self)
return nsSt.appendingPathExtension(ext)
}
/// Converts self to an UUID APNS valid (No "<>" or "-" or spaces).
///
/// - Returns: Converts self to an UUID APNS valid (No "<>" or "-" or spaces).
func readableUUID() -> String {
trimmingCharacters(in: CharacterSet(charactersIn: "<>")).replacingOccurrences(of: " ", with: "").replacingOccurrences(of: "-", with: "")
}
/// Returns string with the first character uppercased.
///
/// - returns: Returns string with the first character uppercased.
func uppercasedFirst() -> String {
String(prefix(1)).uppercased() + String(dropFirst())
}
/// Returns string with the first character lowercased.
///
/// - returns: Returns string with the first character lowercased.
func lowercasedFirst() -> String {
String(prefix(1)).lowercased() + String(dropFirst())
}
/// Returns the reversed String.
///
/// - parameter preserveFormat: If set to true preserve the String format.
/// The default value is false.
/// **Example:**
/// "Let's try this function?" ->
/// "?noitcnuf siht yrt S'tel"
///
/// - returns: Returns the reversed String.
func reversed(preserveFormat: Bool) -> String {
guard !isEmpty else {
return ""
}
var reversed = String(removeExtraSpaces().reversed())
guard preserveFormat else {
return reversed
}
let words = reversed.components(separatedBy: " ").filter { !$0.isEmpty }
reversed.removeAll()
for word in words {
if let char = word.unicodeScalars.last {
if CharacterSet.uppercaseLetters.contains(char) {
reversed += word.lowercased().uppercasedFirst()
} else {
reversed += word.lowercased()
}
} else {
reversed += word.lowercased()
}
if word != words[words.count - 1] {
reversed += " "
}
}
return reversed
}
/// Returns true if the String has at least one uppercase chatacter, otherwise false.
///
/// - returns: Returns true if the String has at least one uppercase chatacter, otherwise false.
func hasUppercasedCharacters() -> Bool {
var found = false
for character in unicodeScalars where CharacterSet.uppercaseLetters.contains(character) {
found = true
}
return found
}
/// Returns true if the String has at least one lowercase chatacter, otherwise false.
///
/// - returns: Returns true if the String has at least one lowercase chatacter, otherwise false.
func hasLowercasedCharacters() -> Bool {
var found = false
for character in unicodeScalars where CharacterSet.lowercaseLetters.contains(character) {
found = true
}
return found
}
/// Remove double or more duplicated spaces.
///
/// - returns: Remove double or more duplicated spaces.
func removeExtraSpaces() -> String {
let squashed = replacingOccurrences(of: "[ ]+", with: " ", options: .regularExpression, range: nil)
return squashed.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
}
/// Returns a new string in which all occurrences of a target strings in a specified range of the String are replaced by another given string.
///
/// - Parameters:
/// - target: Target strings array.
/// - replacement: Replacement string.
/// - Returns: Returns a new string in which all occurrences of a target strings in a specified range of the String are replaced by another given string.
func replacingOccurrences(of target: [String], with replacement: String) -> String {
var string = self
for occurrence in target {
string = string.replacingOccurrences(of: occurrence, with: replacement)
}
return string
}
/// Count the number of lowercase characters.
///
/// - Returns: Number of lowercase characters.
func countLowercasedCharacters() -> Int {
var countLowercased = 0
for char in self where char.isLowercase {
countLowercased += 1
}
return countLowercased
}
/// Count the number of uppercase characters.
///
/// - Returns: Number of uppercase characters.
func countUppercasedCharacters() -> Int {
var countUppercased = 0
for char in self where char.isUppercase {
countUppercased += 1
}
return countUppercased
}
/// Count the number of numbers.
///
/// - Returns: Number of numbers.
func countNumbers() -> Int {
var countNumbers = 0
for char in self where char.isNumber {
countNumbers += 1
}
return countNumbers
}
/// Count the number of punctuations.
///
/// - Returns: Number of punctuations.
func countPunctuations() -> Int {
var countPuntuactions = 0
for char in self where char.isPunctuation {
countPuntuactions += 1
}
return countPuntuactions
}
/// Convert HEX string (separated by space) to "usual" characters string.
/// Example: "68 65 6c 6c 6f" -> "hello".
///
/// - Returns: Readable string.
func stringFromHEX() -> String {
var hex = self
var string: String = ""
hex = hex.replacingOccurrences(of: " ", with: "")
while !hex.isEmpty {
let character = String(hex[..<hex.index(hex.startIndex, offsetBy: 2)])
var characterInt: UInt32 = 0
hex = String(hex[hex.index(hex.startIndex, offsetBy: 2)...])
_ = Scanner(string: character).scanHexInt32(&characterInt)
string += String(format: "%c", characterInt)
}
return string
}
/// Return if self is anagram of another String.
///
/// - Parameter string: Other String.
/// - Returns: Return true if self is anagram of another String, otherwise false.
func isAnagram(of string: String) -> Bool {
let lowerSelf = lowercased().replacingOccurrences(of: " ", with: "")
let lowerOther = string.lowercased().replacingOccurrences(of: " ", with: "")
return lowerSelf.sorted() == lowerOther.sorted()
}
/// Returns if self is palindrome.
///
/// - Returns: Returns true if self is palindrome, otherwise false.
func isPalindrome() -> Bool {
let selfString = lowercased().replacingOccurrences(of: " ", with: "")
let otherString = String(selfString.reversed())
return selfString == otherString
}
/// Returns the character at the given index.
///
/// - Parameter index: Returns the character at the given index.
subscript(index: Int) -> Character {
self[self.index(startIndex, offsetBy: index)]
}
/// Returns the index of the given character, -1 if not found.
///
/// - Parameter character: Returns the index of the given character, -1 if not found.
subscript(character: Character) -> Int {
index(of: character)
}
/// Returns the character at the given index as String.
///
/// - Parameter index: Returns the character at the given index as String.
subscript(index: Int) -> String {
String(self[index])
}
/// Returns the string from a given range.
/// Example: print("BFKit"[1...3]) the result is "FKi".
///
/// - Parameter range: Returns the string from a given range.
subscript(range: Range<Int>) -> String {
substring(with: range)
}
/// Returns if self is a valid UUID or not.
///
/// - Returns: Returns if self is a valid UUID or not.
func isUUID() -> Bool {
do {
let regex: NSRegularExpression = try NSRegularExpression(pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", options: .caseInsensitive)
let matches: Int = regex.numberOfMatches(in: self, options: .reportCompletion, range: NSRange(location: 0, length: count))
return matches == 1
} catch {
return false
}
}
/// Returns if self is a valid UUID for APNS (Apple Push Notification System) or not.
///
/// - Returns: Returns if self is a valid UUID for APNS (Apple Push Notification System) or not.
func isUUIDForAPNS() -> Bool {
do {
let regex: NSRegularExpression = try NSRegularExpression(pattern: "^[0-9a-f]{32}$", options: .caseInsensitive)
let matches: Int = regex.numberOfMatches(in: self, options: .reportCompletion, range: NSRange(location: 0, length: count))
return matches == 1
} catch {
return false
}
}
/// Returns a new string containing matching regular expressions replaced with the template string.
///
/// - Parameters:
/// - regexString: The regex string.
/// - replacement: The replacement string.
/// - Returns: Returns a new string containing matching regular expressions replaced with the template string.
/// - Throws: Throws NSRegularExpression(pattern:, options:) errors.
func replacingMatches(regex regexString: String, with replacement: String) throws -> String {
let regex: NSRegularExpression = try NSRegularExpression(pattern: regexString, options: .caseInsensitive)
return regex.stringByReplacingMatches(in: self, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSRange(location: 0, length: count), withTemplate: "")
}
/// Localize current String using self as key.
///
/// - Returns: Returns localized String using self as key.
func localize() -> String {
NSLocalizedString(self, comment: "")
}
// MARK: - Functions not available on Linux
#if !os(Linux)
/// Check if self is an email.
///
/// - Returns: Returns true if it is an email, otherwise false.
func isEmail() -> Bool {
let emailRegEx: String = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
let regExPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegEx)
return regExPredicate.evaluate(with: lowercased())
}
/// Returns an array of String with all the links in
///
/// - Returns: Returns an array of String with all the links in
/// - Throws: Throws NSDataDetector errors.
func links() throws -> [String] {
let detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let links = Array(detector.matches(in: self, options: NSRegularExpression.MatchingOptions.reportCompletion, range: NSRange(location: 0, length: count)))
return links.filter { $0.url != nil }.map { $0.url?.absoluteString ?? "" }
}
/// Returns an array of Date with all the dates in
///
/// - Returns: Returns an array of Date with all the date in
/// - Throws: Throws NSDataDetector errors.
func dates() throws -> [Date] {
let detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.date.rawValue)
let dates = Array(detector.matches(in: self, options: NSRegularExpression.MatchingOptions.withTransparentBounds, range: NSRange(location: 0, length: count)))
return dates.filter { $0.date != nil }.map { $0.date ?? Date() }
}
/// Returns an array of String with all the hashtags in
///
/// - Returns: Returns an array of String with all the hashtags in
/// - Throws: Throws NSRegularExpression errors.
func hashtags() throws -> [String] {
let detector = try NSRegularExpression(pattern: "#(\\w+)", options: NSRegularExpression.Options.caseInsensitive)
let hashtags = Array(detector.matches(in: self, options: NSRegularExpression.MatchingOptions.withoutAnchoringBounds, range: NSRange(location: 0, length: count)))
return hashtags.map { (self as NSString).substring(with: $0.range(at: 1)) }
}
/// Returns an array of String with all the mentions in
///
/// - Returns: Returns an array of String with all the mentions in
/// - Throws: Throws NSRegularExpression errors.
func mentions() throws -> [String] {
let detector = try NSRegularExpression(pattern: "@(\\w+)", options: NSRegularExpression.Options.caseInsensitive)
let mentions = Array(detector.matches(in: self, options: NSRegularExpression.MatchingOptions.withoutAnchoringBounds, range: NSRange(location: 0, length: count)))
return mentions.map { (self as NSString).substring(with: $0.range(at: 1)) }
}
#endif
}
/// Infix operator `???` with NilCoalescingPrecedence.
infix operator ???: NilCoalescingPrecedence
/// Returns defaultValue if optional is nil, otherwise returns optional.
///
/// - Parameters:
/// - optional: The optional variable.
/// - defaultValue: The default value.
/// - Returns: Returns defaultValue if optional is nil, otherwise returns optional.
public func ??? <T>(optional: T?, defaultValue: @autoclosure () -> String) -> String {
optional.map { String(describing: $0) } ?? defaultValue()
}