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? { 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]]? {