From b6cb8aa091d4db3426749d0b5397a28a0f56a7f2 Mon Sep 17 00:00:00 2001 From: Kevin Ratzel Date: Thu, 22 Oct 2026 11:24:21 -0400 Subject: [PATCH 1/2] Implement format warning button in PresetValueTextField Added a format warning button to indicate unexpected value formats and provide user guidance. --- src/POI/PresetValueTextField.swift | 44 +++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/POI/PresetValueTextField.swift b/src/POI/PresetValueTextField.swift index 9cb850773..1c251e85b 100644 --- a/src/POI/PresetValueTextField.swift +++ b/src/POI/PresetValueTextField.swift @@ -200,7 +200,8 @@ class PresetValueTextField: AutocompleteTextField, PanoramaxDelegate { private func updateAssociatedContent() { // Swift doesn't like too many ??'s so we break it into pieces 🤷‍♂️ - let associatedView1 = getSurveyDateButton() + let associatedView1 = getFormatWarningButton() + ?? getSurveyDateButton() ?? getAssociatedColor() ?? getOpeningHoursButton() ?? getWebsiteButton() @@ -219,6 +220,47 @@ class PresetValueTextField: AutocompleteTextField, PanoramaxDelegate { } } + // MARK: Value format warning + + private func getFormatWarningButton() -> UIView? { + guard key != "", + let value = text, + value != "", + let regex = WikiPage.shared.valueFormat(forKey: key, update: { [weak self] in + self?.updateAssociatedContent() + }), + !WikiPage.value(value, matches: regex) + else { return nil } + let button = UIButton(type: .system) + button.setImage(UIImage(systemName: "exclamationmark.triangle.fill"), for: .normal) + button.tintColor = .systemOrange + button.accessibilityLabel = NSLocalizedString("Unexpected format", comment: "") + button.addTarget(self, action: #selector(showFormatWarning), for: .touchUpInside) + return button + } + + @objc private func showFormatWarning() { + guard let viewController = owner.viewController, + let regex = WikiPage.shared.valueFormat(forKey: key, update: {}) + else { return } + let message = String.localizedStringWithFormat( + NSLocalizedString("The OpenStreetMap Wiki says values for %@ should match this pattern:\n\n%@", + comment: "tag key, regular expression"), + key, + String(regex.pattern.dropFirst(4).dropLast(2))) + let alert = UIAlertController(title: NSLocalizedString("Unexpected Format", comment: ""), + message: message, + preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .cancel)) + alert.addAction(UIAlertAction(title: NSLocalizedString("Read more on the Wiki", comment: ""), + style: .default) + { [key] _ in + let url = WikiPage.shared.urlFor(pageTitle: "Key:" + key) + viewController.present(SFSafariViewController(url: url), animated: true) + }) + viewController.present(alert, animated: true) + } + // MARK: Color preview private func getAssociatedColor() -> UIView? { From 461118aaffd399f3e67a5df80e829534db5cb43f Mon Sep 17 00:00:00 2001 From: Kevin Ratzel Date: Thu, 22 Oct 2026 11:26:49 -0400 Subject: [PATCH 2/2] Add valueFormatCache and implement valueFormat method Added valueFormatCache to store regex patterns and modified resetCache to clear it. Implemented valueFormat method to retrieve regex patterns from cache or fetch from API. --- src/Shared/Utility/WikiPage.swift | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/Shared/Utility/WikiPage.swift b/src/Shared/Utility/WikiPage.swift index f92df79aa..5fd28a445 100644 --- a/src/Shared/Utility/WikiPage.swift +++ b/src/Shared/Utility/WikiPage.swift @@ -163,10 +163,58 @@ class WikiPage { private let imageCache = PersistentWebCache(name: "wikiImageStore", memorySize: 10_000000, daysToKeep: 14.0) + private class ValueFormat { + let regex: NSRegularExpression? + init(regex: NSRegularExpression?) { + self.regex = regex + } + } + + private let valueFormatCache = PersistentWebCache(name: "wikiValueFormatStore", + memorySize: 1_000000, + daysToKeep: 14.0) + func resetCache() { descriptionCache.removeAllObjects() imageCache.removeAllObjects() + valueFormatCache.removeAllObjects() + } + + func valueFormat(forKey key: String, update: @escaping () -> Void) -> NSRegularExpression? { + return valueFormatCache.object( + withKey: key, + fallbackURL: { + var comps = URLComponents(string: "https://wiki.openstreetmap.org/w/api.php")! + comps.queryItems = [ + URLQueryItem(name: "action", value: "wbgetentities"), + URLQueryItem(name: "sites", value: "wiki"), + URLQueryItem(name: "titles", value: "Key:" + key.replacingOccurrences(of: "_", with: " ")), + URLQueryItem(name: "props", value: "claims|sitelinks"), + URLQueryItem(name: "format", value: "json") + ] + return comps.url + }, + objectForData: { data in + guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let entities = json["entities"] as? [String: Any] + else { return nil } + guard let entity = self.entity(in: entities, withTitlePrefix: "Key:"), + let pattern = self.valueFor(property: "P13", inEntity: entity, langQID: "") + else { return ValueFormat(regex: nil) } + return ValueFormat(regex: try? NSRegularExpression(pattern: "^(?:\(pattern))$")) + }, + completion: { result in + if (try? result.get())?.regex != nil { + update() + } + })?.regex + } + + static func value(_ value: String, matches regex: NSRegularExpression) -> Bool { + func matches(_ text: String) -> Bool { + return regex.firstMatch(in: text, range: NSRange(text.startIndex..., in: text)) != nil + } } private func propertyListFor(property: String, inEntity entity: [String: Any]) -> [[String: Any]]? {