Title-case station names: fix Sainsbury's apostrophe, lowercase/mixed names, hyphen/paren chunks, acronyms; re-sanitize cache on load
This commit is contained in:
+56
-10
@@ -119,17 +119,55 @@ struct FuelStation: Identifiable, Codable, Equatable {
|
||||
}
|
||||
|
||||
extension String {
|
||||
/// Word-capitalises ALL-CAPS titles ("SHELL SALTERHEBBLE" → "Shell Salterhebble"),
|
||||
/// preserving known acronyms (BP, MFG, ASDA). Mixed-case names pass through untouched.
|
||||
/// Word-capitalises station titles to title case:
|
||||
/// "SHELL SALTERHEBBLE" → "Shell Salterhebble"
|
||||
/// "SAINSBURYS HALIFAX" → "Sainsbury's Halifax"
|
||||
/// "birmingham road service station" → "Birmingham Road Service Station"
|
||||
/// Known acronyms (BP, MFG, ASDA, MOTO, SPAR, UK, NI…) stay uppercase; short
|
||||
/// all-caps tokens in mixed names are treated as initials (TJ, WR, SJS…);
|
||||
/// connectors (and/of/the/on/ta/t-a) stay lowercase; "LTD" → "Ltd";
|
||||
/// apostrophes keep their chunk together; hyphens/parens/& reset a chunk.
|
||||
var sanitizedStationTitle: String {
|
||||
guard self == self.uppercased(), self.rangeOfCharacter(from: .letters) != nil else { return self }
|
||||
let keepUppercase: Set<String> = ["BP", "MFG", "ASDA", "MOTO"]
|
||||
return self.split(separator: " ").map { word in
|
||||
let w = String(word)
|
||||
if keepUppercase.contains(w.uppercased()) { return w.uppercased() }
|
||||
return w.prefix(1).uppercased() + w.dropFirst().lowercased()
|
||||
guard rangeOfCharacter(from: .letters) != nil else { return self }
|
||||
let keepUppercase: Set<String> = ["BP", "MFG", "ASDA", "MOTO", "SPAR", "UK", "NI", "SS"]
|
||||
let keepLowercase: Set<String> = ["of", "and", "the", "on", "ta", "t/a"]
|
||||
let isAllCaps = self == self.uppercased()
|
||||
return self.split(separator: " ").map { rawWord in
|
||||
let w = String(rawWord)
|
||||
if w.uppercased() == "SAINSBURYS" { return "Sainsbury's" }
|
||||
if keepLowercase.contains(w.lowercased()) { return w.lowercased() }
|
||||
let core = w.filter { $0.isLetter }
|
||||
if keepUppercase.contains(core.uppercased()) { return w.uppercased() }
|
||||
if core.uppercased() == "LTD" { return "Ltd" }
|
||||
if !isAllCaps, w == w.uppercased(), (1...3).contains(core.count) { return w }
|
||||
return w.capitalizedChunks
|
||||
}.joined(separator: " ")
|
||||
}
|
||||
|
||||
/// Capitalises the first letter of each alpha-chunk and lowercases the rest.
|
||||
/// Apostrophes do NOT reset the chunk ("Sainsbury's", "Adam's");
|
||||
/// hyphens, &, parens and dots do ("NEWCASTLE-UNDER-LYME" → "Newcastle-Under-Lyme",
|
||||
/// "(MEADOWHALL" → "(Meadowhall").
|
||||
private var capitalizedChunks: String {
|
||||
var out = ""
|
||||
var newChunk = true
|
||||
for ch in self {
|
||||
if ch.isLetter {
|
||||
if newChunk {
|
||||
out.append(ch.uppercased())
|
||||
newChunk = false
|
||||
} else {
|
||||
out.append(ch.lowercased())
|
||||
}
|
||||
} else if ch == "'" {
|
||||
out.append(ch)
|
||||
} else {
|
||||
out.append(ch)
|
||||
newChunk = true
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Shared store
|
||||
@@ -157,16 +195,24 @@ struct FuelStore {
|
||||
let data = defaults.data(forKey: stationsKey),
|
||||
let stations = try? JSONDecoder().decode([FuelStation].self, from: data),
|
||||
!stations.isEmpty {
|
||||
return stations
|
||||
return stations.map(sanitized)
|
||||
}
|
||||
if let data = keychainData(service: stationsKey),
|
||||
let stations = try? JSONDecoder().decode([FuelStation].self, from: data),
|
||||
!stations.isEmpty {
|
||||
return stations
|
||||
return stations.map(sanitized)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/// Re-runs the title-case sanitizer on cached stations so names fixed by
|
||||
/// newer sanitizer logic appear without waiting for the next fetch.
|
||||
private static func sanitized(_ station: FuelStation) -> FuelStation {
|
||||
var s = station
|
||||
s.name = station.name.sanitizedStationTitle
|
||||
return s
|
||||
}
|
||||
|
||||
static func saveStations(_ stations: [FuelStation]) {
|
||||
if let data = try? JSONEncoder().encode(stations) {
|
||||
UserDefaults(suiteName: appGroupSuite)?.set(data, forKey: stationsKey)
|
||||
|
||||
Reference in New Issue
Block a user