diff --git a/RevenueCatUI/Helpers/ProcessedLocalizedConfiguration.swift b/RevenueCatUI/Helpers/ProcessedLocalizedConfiguration.swift index 80522441ed..f3e781a657 100644 --- a/RevenueCatUI/Helpers/ProcessedLocalizedConfiguration.swift +++ b/RevenueCatUI/Helpers/ProcessedLocalizedConfiguration.swift @@ -1,6 +1,7 @@ import RevenueCat /// A `PaywallData.LocalizedConfiguration` with processed variables +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) struct ProcessedLocalizedConfiguration: PaywallLocalizedConfiguration { var title: String diff --git a/RevenueCatUI/Helpers/Variables.swift b/RevenueCatUI/Helpers/Variables.swift index c899a8f6c2..edb0898138 100644 --- a/RevenueCatUI/Helpers/Variables.swift +++ b/RevenueCatUI/Helpers/Variables.swift @@ -1,4 +1,5 @@ import Foundation +import RegexBuilder import RevenueCat @available(iOS 16.0, macOS 13.0, tvOS 16.0, *) @@ -21,44 +22,51 @@ protocol VariableDataProvider { } /// Processes strings, replacing `{{variable}}` with their associated content. +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) enum VariableHandler { static func processVariables( in string: String, with provider: VariableDataProvider ) -> String { + let matches = Self.extractVariables(from: string) var replacedString = string - let range = NSRange(string.startIndex..., in: string) - let matches = Self.regex.matches(in: string, options: [], range: range) - - for match in matches.reversed() { - let variableNameRange = match.range(at: 1) - if let variableNameRange = Range(variableNameRange, in: string) { - let variableName = String(string[variableNameRange]) - let replacementValue = provider.value(for: variableName) - - let adjustedRange = NSRange( - location: variableNameRange.lowerBound.utf16Offset(in: string) - Self.pattern.count / 2, - length: string.distance(from: variableNameRange.lowerBound, - to: variableNameRange.upperBound) + Self.pattern.count - ) - let replacementRange = Range(adjustedRange, in: replacedString)! - replacedString = replacedString.replacingCharacters(in: replacementRange, with: replacementValue) - } + for variableMatch in matches.reversed() { + let replacementValue = provider.value(for: variableMatch.variable) + replacedString = replacedString.replacingCharacters(in: variableMatch.range, with: replacementValue) } return replacedString } - private static let pattern = "{{ }}" - // Fix-me: this can be implemented using the new Regex from Swift. - // This regex is known at compile time and tested: - // swiftlint:disable:next force_try - private static let regex = try! NSRegularExpression(pattern: "\\{\\{ (\\w+) \\}\\}", options: []) + private static func extractVariables(from expression: String) -> [VariableMatch] { + let variablePattern = Regex { + OneOrMore { + "{{ " + Capture { + OneOrMore(.word) + } + " }}" + } + } + + return expression.matches(of: variablePattern).map { match in + let (_, variable) = match.output + return VariableMatch(variable: String(variable), range: match.range) + } + } + + private struct VariableMatch { + + let variable: String + let range: Range + + } } +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) extension String { func processed(with provider: VariableDataProvider) -> Self { @@ -67,6 +75,7 @@ extension String { } +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) private extension VariableDataProvider { func value(for variableName: String) -> String {