@@ -4,14 +4,20 @@ import MapboxDirections
4
4
// Will automatically read localized Instructions.plist
5
5
let OSRMTextInstructionsStrings = NSDictionary ( contentsOfFile: Bundle ( for: OSRMInstructionFormatter . self) . path ( forResource: " Instructions " , ofType: " plist " ) !) !
6
6
7
- extension String {
8
- public var sentenceCased : String {
9
- return String ( characters. prefix ( 1 ) ) . uppercased ( ) + String( characters. dropFirst ( ) )
10
- }
7
+ protocol Tokenized {
8
+ associatedtype T
11
9
12
10
/**
13
11
Replaces `{tokens}` in the receiver using the given closure.
14
12
*/
13
+ func replacingTokens( using interpolator: ( ( TokenType ) -> T ) ) -> T
14
+ }
15
+
16
+ extension String : Tokenized {
17
+ public var sentenceCased : String {
18
+ return String ( characters. prefix ( 1 ) ) . uppercased ( ) + String( characters. dropFirst ( ) )
19
+ }
20
+
15
21
public func replacingTokens( using interpolator: ( ( TokenType ) -> String ) ) -> String {
16
22
let scanner = Scanner ( string: self )
17
23
scanner. charactersToBeSkipped = nil
@@ -52,6 +58,48 @@ extension String {
52
58
}
53
59
}
54
60
61
+ extension NSAttributedString : Tokenized {
62
+ func replacingTokens( using interpolator: ( ( TokenType ) -> NSAttributedString ) ) -> NSAttributedString {
63
+ let scanner = Scanner ( string: string)
64
+ scanner. charactersToBeSkipped = nil
65
+ let result = NSMutableAttributedString ( )
66
+ while !scanner. isAtEnd {
67
+ var buffer : NSString ?
68
+
69
+ if scanner. scanUpTo ( " { " , into: & buffer) {
70
+ result. append ( NSAttributedString ( string: buffer! as String ) )
71
+ }
72
+ guard scanner. scanString ( " { " , into: nil ) else {
73
+ continue
74
+ }
75
+
76
+ var token : NSString ?
77
+ guard scanner. scanUpTo ( " } " , into: & token) else {
78
+ continue
79
+ }
80
+
81
+ if scanner. scanString ( " } " , into: nil ) {
82
+ if let tokenType = TokenType ( description: token! as String ) {
83
+ result. append ( interpolator ( tokenType) )
84
+ }
85
+ } else {
86
+ result. append ( NSAttributedString ( string: token! as String ) )
87
+ }
88
+ }
89
+
90
+ // remove excess spaces
91
+ let wholeRange = NSRange ( location: 0 , length: result. mutableString. length)
92
+ result. mutableString. replaceOccurrences ( of: " \\ s \\ s " , with: " " , options: . regularExpression, range: wholeRange)
93
+
94
+ // capitalize
95
+ let meta = OSRMTextInstructionsStrings [ " meta " ] as! [ String : Any ]
96
+ if meta [ " capitalizeFirstLetter " ] as? Bool ?? false {
97
+ result. replaceCharacters ( in: NSRange ( location: 0 , length: 1 ) , with: String ( result. string. characters. first!) . uppercased ( ) )
98
+ }
99
+ return result as NSAttributedString
100
+ }
101
+ }
102
+
55
103
public class OSRMInstructionFormatter : Formatter {
56
104
let version : String
57
105
let instructions : [ String : Any ]
@@ -169,14 +217,34 @@ public class OSRMInstructionFormatter: Formatter {
169
217
/**
170
218
Creates an instruction given a step and options.
171
219
172
- - parameter step:
220
+ - parameter step: The step to format.
173
221
- parameter legIndex: Current leg index the user is currently on.
174
222
- parameter numberOfLegs: Total number of `RouteLeg` for the given `Route`.
175
223
- parameter roadClasses: Option set representing the classes of road for the `RouteStep`.
176
224
- parameter modifyValueByKey: Allows for mutating the instruction at given parts of the instruction.
177
225
- returns: An instruction as a `String`.
178
226
*/
179
227
public func string( for obj: Any ? , legIndex: Int ? , numberOfLegs: Int ? , roadClasses: RoadClasses ? = RoadClasses ( [ ] ) , modifyValueByKey: ( ( TokenType , String ) -> String ) ? ) -> String ? {
228
+ var modifyAttributedValueByKey : ( ( TokenType , NSAttributedString ) -> NSAttributedString ) ?
229
+ if let modifyValueByKey = modifyValueByKey {
230
+ modifyAttributedValueByKey = { ( key: TokenType , value: NSAttributedString ) -> NSAttributedString in
231
+ return NSAttributedString ( string: modifyValueByKey ( key, value. string) )
232
+ }
233
+ }
234
+ return attributedString ( for: obj, legIndex: legIndex, numberOfLegs: numberOfLegs, roadClasses: roadClasses, modifyValueByKey: modifyAttributedValueByKey) ? . string
235
+ }
236
+
237
+ /**
238
+ Creates an instruction as an attributed string given a step and options.
239
+
240
+ - parameter step: The step to format.
241
+ - parameter legIndex: Current leg index the user is currently on.
242
+ - parameter numberOfLegs: Total number of `RouteLeg` for the given `Route`.
243
+ - parameter roadClasses: Option set representing the classes of road for the `RouteStep`.
244
+ - parameter modifyValueByKey: Allows for mutating the instruction at given parts of the instruction.
245
+ - returns: An instruction as an `NSAttributedString`.
246
+ */
247
+ public func attributedString( for obj: Any ? , legIndex: Int ? , numberOfLegs: Int ? , roadClasses: RoadClasses ? = RoadClasses ( [ ] ) , modifyValueByKey: ( ( TokenType , NSAttributedString ) -> NSAttributedString ) ? ) -> NSAttributedString ? {
180
248
guard let step = obj as? RouteStep else {
181
249
return nil
182
250
}
@@ -198,14 +266,14 @@ public class OSRMInstructionFormatter: Formatter {
198
266
199
267
var instructionObject : InstructionsByToken
200
268
var rotaryName = " "
201
- var wayName : String
269
+ var wayName : NSAttributedString
202
270
switch type {
203
271
case . takeRotary, . takeRoundabout:
204
272
// Special instruction types have an intermediate level keyed to “default”.
205
273
let instructionsByModifier = instructions [ type. description] as! [ String : InstructionsByModifier ]
206
274
let defaultInstructions = instructionsByModifier [ " default " ] !
207
275
208
- wayName = step. exitNames? . first ?? " "
276
+ wayName = NSAttributedString ( string : step. exitNames? . first ?? " " )
209
277
if let _rotaryName = step. names? . first, let _ = step. exitIndex, let obj = defaultInstructions [ " name_exit " ] {
210
278
instructionObject = obj
211
279
rotaryName = _rotaryName
@@ -234,24 +302,43 @@ public class OSRMInstructionFormatter: Formatter {
234
302
let isMotorway = roadClasses? . contains ( . motorway) ?? false
235
303
236
304
if let name = name, let ref = ref, name != ref, !isMotorway {
305
+ let attributedName = NSAttributedString ( string: name)
306
+ let attributedRef = NSAttributedString ( string: ref)
237
307
let phrases = instructions [ " phrase " ] as! [ String : String ]
238
- let phrase = phrases [ " name and ref " ] !
239
- wayName = phrase. replacingTokens ( using: { ( tokenType) -> String in
308
+ let phrase = NSAttributedString ( string : phrases [ " name and ref " ] !)
309
+ wayName = phrase. replacingTokens ( using: { ( tokenType) -> NSAttributedString in
240
310
switch tokenType {
241
311
case . wayName:
242
- return modifyValueByKey ? ( . wayName, name ) ?? name
312
+ return modifyValueByKey ? ( . wayName, attributedName ) ?? attributedName
243
313
case . code:
244
- return modifyValueByKey ? ( . code, ref ) ?? ref
314
+ return modifyValueByKey ? ( . code, attributedRef ) ?? attributedRef
245
315
default :
246
316
fatalError ( " Unexpected token type \( tokenType) in name-and-ref phrase " )
247
317
}
248
318
} )
249
319
} else if let ref = ref, isMotorway, let decimalRange = ref. rangeOfCharacter ( from: . decimalDigits) , !decimalRange. isEmpty {
250
- wayName = modifyValueByKey != nil ? " \( modifyValueByKey!( . code, ref) ) " : ref
320
+ let attributedRef = NSAttributedString ( string: ref)
321
+ if let modifyValueByKey = modifyValueByKey {
322
+ wayName = modifyValueByKey ( . code, attributedRef)
323
+ } else {
324
+ wayName = attributedRef
325
+ }
251
326
} else if name == nil , let ref = ref {
252
- wayName = modifyValueByKey != nil ? " \( modifyValueByKey!( . code, ref) ) " : ref
327
+ let attributedRef = NSAttributedString ( string: ref)
328
+ if let modifyValueByKey = modifyValueByKey {
329
+ wayName = modifyValueByKey ( . code, attributedRef)
330
+ } else {
331
+ wayName = attributedRef
332
+ }
333
+ } else if let name = name {
334
+ let attributedName = NSAttributedString ( string: name)
335
+ if let modifyValueByKey = modifyValueByKey {
336
+ wayName = modifyValueByKey ( . wayName, attributedName)
337
+ } else {
338
+ wayName = attributedName
339
+ }
253
340
} else {
254
- wayName = name != nil ? modifyValueByKey != nil ? " \( modifyValueByKey! ( . wayName , name! ) ) " : name! : " "
341
+ wayName = NSAttributedString ( )
255
342
}
256
343
}
257
344
@@ -284,7 +371,7 @@ public class OSRMInstructionFormatter: Formatter {
284
371
instruction = obj
285
372
} else if let _ = step. exitCodes? . first, let obj = instructionObject [ " exit " ] {
286
373
instruction = obj
287
- } else if !wayName. isEmpty, let obj = instructionObject [ " name " ] {
374
+ } else if !wayName. string . isEmpty, let obj = instructionObject [ " name " ] {
288
375
instruction = obj
289
376
} else {
290
377
instruction = instructionObject [ " default " ] !
@@ -307,11 +394,11 @@ public class OSRMInstructionFormatter: Formatter {
307
394
if step. finalHeading != nil { bearing = Int ( step. finalHeading! as Double ) }
308
395
309
396
// Replace tokens
310
- let result = instruction. replacingTokens { ( tokenType) -> String in
397
+ let result = NSAttributedString ( string : instruction) . replacingTokens { ( tokenType) -> NSAttributedString in
311
398
var replacement : String
312
399
switch tokenType {
313
400
case . code: replacement = step. codes? . first ?? " "
314
- case . wayName: replacement = wayName
401
+ case . wayName: replacement = " " // ignored
315
402
case . destination: replacement = destination
316
403
case . exitCode: replacement = exitCode
317
404
case . exitIndex: replacement = exitOrdinal
@@ -324,9 +411,10 @@ public class OSRMInstructionFormatter: Formatter {
324
411
fatalError ( " Unexpected token type \( tokenType) in individual instruction " )
325
412
}
326
413
if tokenType == . wayName {
327
- return replacement // already modified above
414
+ return wayName // already modified above
328
415
} else {
329
- return modifyValueByKey ? ( tokenType, replacement) ?? replacement
416
+ let attributedReplacement = NSAttributedString ( string: replacement)
417
+ return modifyValueByKey ? ( tokenType, attributedReplacement) ?? attributedReplacement
330
418
}
331
419
}
332
420
0 commit comments