@@ -3,22 +3,55 @@ import MapboxDirections
3
3
4
4
// Will automatically read localized Instructions.plist
5
5
let OSRMTextInstructionsStrings = NSDictionary ( contentsOfFile: Bundle ( for: OSRMInstructionFormatter . self) . path ( forResource: " Instructions " , ofType: " plist " ) !) !
6
+ let OSRMTextInstructionsGrammar : NSDictionary ? = {
7
+ guard let path = Bundle ( for: OSRMInstructionFormatter . self) . path ( forResource: " Grammar " , ofType: " plist " ) else {
8
+ return nil
9
+ }
10
+
11
+ return NSDictionary ( contentsOfFile: path)
12
+ } ( )
13
+
14
+ extension NSRegularExpression . Options {
15
+ init ( javaScriptFlags: String ) {
16
+ var options : NSRegularExpression . Options = [ ]
17
+ for flag in javaScriptFlags. characters {
18
+ switch flag {
19
+ case " g " :
20
+ break
21
+ case " i " :
22
+ options. insert ( . caseInsensitive)
23
+ case " m " :
24
+ options. insert ( . anchorsMatchLines)
25
+ case " u " :
26
+ // Character classes are always Unicode-aware in ICU regular expressions.
27
+ options. insert ( . useUnicodeWordBoundaries)
28
+ case " y " :
29
+ break
30
+ default :
31
+ break
32
+ }
33
+ }
34
+ self . init ( rawValue: options. rawValue)
35
+ }
36
+ }
6
37
7
38
protocol Tokenized {
8
39
associatedtype T
9
40
10
41
/**
11
42
Replaces `{tokens}` in the receiver using the given closure.
12
43
*/
13
- func replacingTokens( using interpolator: ( ( TokenType ) -> T ) ) -> T
44
+ func replacingTokens( using interpolator: ( ( TokenType , String ? ) -> T ) ) -> T
45
+
46
+ func inflected( into variant: String , version: String ) -> T
14
47
}
15
48
16
49
extension String : Tokenized {
17
50
public var sentenceCased : String {
18
51
return String ( characters. prefix ( 1 ) ) . uppercased ( ) + String( characters. dropFirst ( ) )
19
52
}
20
53
21
- public func replacingTokens( using interpolator: ( ( TokenType ) -> String ) ) -> String {
54
+ public func replacingTokens( using interpolator: ( ( TokenType , String ? ) -> String ) ) -> String {
22
55
let scanner = Scanner ( string: self )
23
56
scanner. charactersToBeSkipped = nil
24
57
var result = " "
@@ -38,9 +71,17 @@ extension String: Tokenized {
38
71
continue
39
72
}
40
73
74
+ var variant : NSString ?
75
+ if scanner. scanString ( " : " , into: nil ) {
76
+ guard scanner. scanUpTo ( " } " , into: & variant) else {
77
+ result += " : "
78
+ continue
79
+ }
80
+ }
81
+
41
82
if scanner. scanString ( " } " , into: nil ) {
42
83
if let tokenType = TokenType ( description: token! as String ) {
43
- result += interpolator ( tokenType)
84
+ result += interpolator ( tokenType, variant as String ? )
44
85
} else {
45
86
result += " { \( token!) } "
46
87
}
@@ -59,10 +100,34 @@ extension String: Tokenized {
59
100
}
60
101
return result
61
102
}
103
+
104
+ func inflected( into variant: String , version: String ) -> String {
105
+ guard let grammar = OSRMTextInstructionsGrammar ? [ version] as? [ String : Any ] else {
106
+ return self
107
+ }
108
+
109
+ guard let rules = grammar [ variant] as? [ [ String ] ] else {
110
+ return self
111
+ }
112
+
113
+ var grammaticalReplacement = " \( self ) "
114
+ var regularExpressionOptions : NSRegularExpression . Options = [ ]
115
+ if let meta = OSRMTextInstructionsGrammar ? [ " meta " ] as? [ String : String ] ,
116
+ let flags = meta [ " regExpFlags " ] {
117
+ regularExpressionOptions = NSRegularExpression . Options ( javaScriptFlags: flags)
118
+ }
119
+
120
+ for rule in rules {
121
+ let regularExpression = try ! NSRegularExpression ( pattern: rule [ 0 ] , options: regularExpressionOptions)
122
+ grammaticalReplacement = regularExpression. stringByReplacingMatches ( in: grammaticalReplacement, options: [ ] , range: NSRange ( location: 0 , length: grammaticalReplacement. characters. count) , withTemplate: rule [ 1 ] )
123
+ }
124
+
125
+ return grammaticalReplacement. trimmingCharacters ( in: . whitespaces)
126
+ }
62
127
}
63
128
64
129
extension NSAttributedString : Tokenized {
65
- public func replacingTokens( using interpolator: ( ( TokenType ) -> NSAttributedString ) ) -> NSAttributedString {
130
+ public func replacingTokens( using interpolator: ( ( TokenType , String ? ) -> NSAttributedString ) ) -> NSAttributedString {
66
131
let scanner = Scanner ( string: string)
67
132
scanner. charactersToBeSkipped = nil
68
133
let result = NSMutableAttributedString ( )
@@ -78,12 +143,21 @@ extension NSAttributedString: Tokenized {
78
143
79
144
var token : NSString ?
80
145
guard scanner. scanUpTo ( " } " , into: & token) else {
146
+ result. append ( NSAttributedString ( string: " } " ) )
81
147
continue
82
148
}
83
149
150
+ var variant : NSString ?
151
+ if scanner. scanString ( " : " , into: nil ) {
152
+ guard scanner. scanUpTo ( " } " , into: & variant) else {
153
+ result. append ( NSAttributedString ( string: " } " ) )
154
+ continue
155
+ }
156
+ }
157
+
84
158
if scanner. scanString ( " } " , into: nil ) {
85
159
if let tokenType = TokenType ( description: token! as String ) {
86
- result. append ( interpolator ( tokenType) )
160
+ result. append ( interpolator ( tokenType, variant as String ? ) )
87
161
}
88
162
} else {
89
163
result. append ( NSAttributedString ( string: token! as String ) )
@@ -101,6 +175,34 @@ extension NSAttributedString: Tokenized {
101
175
}
102
176
return result as NSAttributedString
103
177
}
178
+
179
+ @nonobjc func inflected( into variant: String , version: String ) -> NSAttributedString {
180
+ guard let grammar = OSRMTextInstructionsGrammar ? [ version] as? [ String : Any ] else {
181
+ return self
182
+ }
183
+
184
+ guard let rules = grammar [ variant] as? [ [ String ] ] else {
185
+ return self
186
+ }
187
+
188
+ let grammaticalReplacement = NSMutableAttributedString ( string: " " )
189
+ grammaticalReplacement. append ( self )
190
+ grammaticalReplacement. append ( NSAttributedString ( string: " " ) )
191
+
192
+ var regularExpressionOptions : NSRegularExpression . Options = [ ]
193
+ if let meta = OSRMTextInstructionsGrammar ? [ " meta " ] as? [ String : String ] ,
194
+ let flags = meta [ " regExpFlags " ] {
195
+ regularExpressionOptions = NSRegularExpression . Options ( javaScriptFlags: flags)
196
+ }
197
+
198
+ for rule in rules {
199
+ let regularExpression = try ! NSRegularExpression ( pattern: rule [ 0 ] , options: regularExpressionOptions)
200
+ regularExpression. replaceMatches ( in: grammaticalReplacement. mutableString, options: [ ] , range: NSRange ( location: 0 , length: grammaticalReplacement. mutableString. length) , withTemplate: rule [ 1 ] )
201
+ }
202
+
203
+ grammaticalReplacement. mutableString. replaceOccurrences ( of: " ^ +| +$ " , with: " " , options: . regularExpression, range: NSRange ( location: 0 , length: grammaticalReplacement. mutableString. length) )
204
+ return grammaticalReplacement
205
+ }
104
206
}
105
207
106
208
public class OSRMInstructionFormatter : Formatter {
@@ -323,15 +425,21 @@ public class OSRMInstructionFormatter: Formatter {
323
425
let attributedName = NSAttributedString ( string: name, attributes: attrs)
324
426
let attributedRef = NSAttributedString ( string: ref, attributes: attrs)
325
427
let phrase = NSAttributedString ( string: self . phrase ( named: . nameWithCode) , attributes: attrs)
326
- wayName = phrase. replacingTokens ( using: { ( tokenType) -> NSAttributedString in
428
+ wayName = phrase. replacingTokens ( using: { ( tokenType, variant) -> NSAttributedString in
429
+ var replacement : NSAttributedString
327
430
switch tokenType {
328
431
case . wayName:
329
- return modifyValueByKey ? ( . wayName , attributedName ) ?? attributedName
432
+ replacement = attributedName
330
433
case . code:
331
- return modifyValueByKey ? ( . code , attributedRef ) ?? attributedRef
434
+ replacement = attributedRef
332
435
default :
333
436
fatalError ( " Unexpected token type \( tokenType) in name-and-ref phrase " )
334
437
}
438
+
439
+ if let variant = variant {
440
+ replacement = replacement. inflected ( into: variant, version: version)
441
+ }
442
+ return modifyValueByKey ? ( tokenType, replacement) ?? replacement
335
443
} )
336
444
} else if let ref = ref, isMotorway, let decimalRange = ref. rangeOfCharacter ( from: . decimalDigits) , !decimalRange. isEmpty {
337
445
let attributedRef = NSAttributedString ( string: ref, attributes: attrs)
@@ -411,7 +519,7 @@ public class OSRMInstructionFormatter: Formatter {
411
519
if step. finalHeading != nil { bearing = Int ( step. finalHeading! as Double ) }
412
520
413
521
// Replace tokens
414
- let result = NSAttributedString ( string: instruction, attributes: attrs) . replacingTokens { ( tokenType) -> NSAttributedString in
522
+ let result = NSAttributedString ( string: instruction, attributes: attrs) . replacingTokens { ( tokenType, variant ) -> NSAttributedString in
415
523
var replacement : String
416
524
switch tokenType {
417
525
case . code: replacement = step. codes? . first ?? " "
@@ -430,6 +538,9 @@ public class OSRMInstructionFormatter: Formatter {
430
538
if tokenType == . wayName {
431
539
return wayName // already modified above
432
540
} else {
541
+ if let variant = variant {
542
+ replacement = replacement. inflected ( into: variant, version: version)
543
+ }
433
544
let attributedReplacement = NSAttributedString ( string: replacement, attributes: attrs)
434
545
return modifyValueByKey ? ( tokenType, attributedReplacement) ?? attributedReplacement
435
546
}
0 commit comments