Files
divkit/client/ios/DivKit/Expressions/Functions/StringFunctions.swift
T
pkurchatov 976bc60b76 Expression errors refactoring
0f64602cade0949128541074d27144a4f680418a
2024-05-13 19:41:36 +03:00

170 lines
4.5 KiB
Swift

import Foundation
extension [String: Function] {
mutating func addStringFunctions() {
addFunction("contains", _contains)
addFunction("decodeUri", _decodeUri)
addFunction("encodeUri", _encodeUri)
addFunction("index", _index)
addFunction("len", _len)
addFunction("lastIndex", _lastIndex)
addFunction("padEnd", _padEnd)
addFunction("padStart", _padStart)
addFunction("replaceAll", _replaceAll)
addFunction("substring", _substring)
addFunction("testRegex", _testRegex)
addFunction("trim", _trim)
addFunction("trimLeft", _trimLeft)
addFunction("trimRight", _trimRight)
addFunction("toUpperCase", _toUpperCase)
addFunction("toLowerCase", _toLowerCase)
}
}
private let dontNeedEncoding = CharacterSet(charactersIn: "a"..."z")
.union(CharacterSet(charactersIn: "A"..."Z"))
.union(CharacterSet(charactersIn: "0"..."9"))
.union(CharacterSet(charactersIn: "-_.*!~'()"))
private var _len = FunctionUnary<String, Int> {
$0.count
}
private var _contains = FunctionBinary<String, String, Bool> {
$1.isEmpty || $0.range(of: $1) != nil
}
private var _substring = FunctionTernary<String, Int, Int, String> {
guard $1 <= $2 else {
throw ExpressionError("Indexes should be in ascending order.")
}
guard $1 >= 0, $2 <= $0.count else {
throw ExpressionError("Indexes are out of bounds.")
}
return String($0[$0.rangeOfCharsIn($1..<$2)])
}
private var _replaceAll = FunctionTernary<String, String, String, String> {
$0.replacingOccurrences(of: $1, with: $2)
}
private var _index = FunctionBinary<String, String, Int> {
if $1.isEmpty {
return 0
}
guard let range = $0.range(of: $1) else {
return -1
}
return $0.distance(to: range.lowerBound)
}
private var _lastIndex = FunctionBinary<String, String, Int> {
guard let range = $0.range(of: $1, options: .backwards) else {
return -1
}
return $0.distance(to: range.lowerBound)
}
private var _trim = FunctionUnary<String, String> {
$0.trimmed
}
private var _trimLeft = FunctionUnary<String, String> { value in
for index in value.indices {
if !value[index].isWhitespace {
return String(value[index...])
}
}
return ""
}
private var _trimRight = FunctionUnary<String, String> { value in
for index in value.indices.reversed() {
if !value[index].isWhitespace {
return String(value[...index])
}
}
return ""
}
private var _toUpperCase = FunctionUnary<String, String> {
$0.uppercased()
}
private var _toLowerCase = FunctionUnary<String, String> {
$0.lowercased()
}
private var _encodeUri = FunctionUnary<String, String> {
guard let value = $0.addingPercentEncoding(withAllowedCharacters: dontNeedEncoding) else {
throw ExpressionError("String is empty after encoding.")
}
return value
}
private var _decodeUri = FunctionUnary<String, String> {
guard let value = $0.removingPercentEncoding else {
throw ExpressionError("String is empty after decoding.")
}
return value
}
private var _padStart = OverloadedFunction(functions: [
FunctionTernary<String, Int, String, String> {
padStart(value: $0, len: $1, pad: $2)
},
FunctionTernary<Int, Int, String, String> {
padStart(value: String($0), len: $1, pad: $2)
},
])
private var _padEnd = OverloadedFunction(functions: [
FunctionTernary<String, Int, String, String> {
padEnd(value: $0, len: $1, pad: $2)
},
FunctionTernary<Int, Int, String, String> {
padEnd(value: String($0), len: $1, pad: $2)
},
])
private func padStart(value: String, len: Int, pad: String) -> String {
let prefix = calcPad(value: value, len: len, pad: pad)
return prefix + value
}
private func padEnd(value: String, len: Int, pad: String) -> String {
let suffix = calcPad(value: value, len: len, pad: pad)
return value + suffix
}
private func calcPad(value: String, len: Int, pad: String) -> String {
var part = ""
guard pad.count > 0 else {
DivKitLogger.warning("String for padding is empty.")
return part
}
while part.count + value.count < len {
part += pad
}
if part.count > 0, part.count + value.count > len {
part = String(part.prefix(len - value.count))
}
return part
}
private var _testRegex = FunctionBinary<String, String, Bool> { text, regex in
do {
let regex = try NSRegularExpression(pattern: regex)
let range = NSRange(text.startIndex..., in: text)
return regex.firstMatch(in: text, range: range) != nil
} catch {
throw ExpressionError("Invalid regular expression.")
}
}
extension String {
fileprivate func distance(to index: Index) -> Int {
distance(from: startIndex, to: index)
}
}