]>
Commit | Line | Data |
---|---|---|
1b85f723 RBR |
1 | import CoreGraphics |
2 | import Foundation | |
3 | ||
5e8ff485 RBR |
4 | let vertexPattern = |
5 | "([^\\(]+?)[\\s]*\\([\\s]*([0-9]+.?[0-9]*)[\\s]*,[\\s]*([0-9]+.?[0-9]*)[\\s]*\\)" | |
6 | let edgePattern = "(.+?)[\\s]*-([->])[\\s]*(.+)" | |
7 | let blockerPattern = "\\[Blocker\\][\\s]*(.+)" | |
8 | let opportunityPattern = "\\[Opportunity\\][\\s]*(.+)[\\s]+([-+])[\\s]*([0-9]+.?[0-9]*)" | |
9 | let stagePattern = "\\[(I{1,3})\\][\\s]*([0-9]+.?[0-9]*)" | |
1b85f723 RBR |
10 | |
11 | struct ParsedMap { | |
5e8ff485 RBR |
12 | let vertices: [Vertex] |
13 | let edges: [MapEdge] | |
14 | let blockers: [Blocker] | |
15 | let opportunities: [Opportunity] | |
16 | let stages: [CGFloat] | |
1b85f723 RBR |
17 | } |
18 | ||
19 | struct Vertex { | |
5e8ff485 RBR |
20 | let id: Int |
21 | let label: String | |
22 | let position: CGPoint | |
1b85f723 RBR |
23 | } |
24 | ||
25 | struct MapEdge { | |
5e8ff485 RBR |
26 | let id: Int |
27 | let origin: CGPoint | |
28 | let destination: CGPoint | |
29 | let arrowhead: Bool | |
1b85f723 RBR |
30 | } |
31 | ||
5e8ff485 RBR |
32 | struct Blocker { |
33 | let id: Int | |
34 | let position: CGPoint | |
35 | } | |
36 | ||
37 | struct Opportunity { | |
38 | let id: Int | |
39 | let origin: CGPoint | |
40 | let destination: CGPoint | |
41 | } | |
42 | ||
43 | let defaultDimensions: [CGFloat] = [ | |
44 | 25.0, | |
45 | 50.0, | |
46 | 75.0, | |
47 | ] | |
48 | ||
1b85f723 RBR |
49 | // Extracts the vertices from the text |
50 | ||
51 | func parseVertices(_ text: String) -> [String: CGPoint] { | |
5e8ff485 RBR |
52 | |
53 | var result: [String: CGPoint] = [:] | |
54 | let regex = try! NSRegularExpression(pattern: vertexPattern, options: .caseInsensitive) | |
55 | ||
56 | let lines = text.split(whereSeparator: \.isNewline) | |
57 | ||
58 | for line in lines { | |
59 | let range = NSRange(location: 0, length: line.utf16.count) | |
60 | let matches = regex.matches(in: String(line), options: [], range: range) | |
61 | ||
62 | if matches.count > 0 && matches[0].numberOfRanges == 4 { | |
63 | ||
64 | let match = matches[0] | |
65 | let key = String(line[Range(match.range(at: 1), in: line)!]) | |
66 | let xString = String(line[Range(match.range(at: 2), in: line)!]) | |
67 | let yString = String(line[Range(match.range(at: 3), in: line)!]) | |
68 | let x = CGFloat(truncating: NumberFormatter().number(from: xString) ?? 0.0) | |
69 | let y = CGFloat(truncating: NumberFormatter().number(from: yString) ?? 0.0) | |
70 | let point = CGPoint(x: x, y: y) | |
71 | ||
72 | result[key] = point | |
1b85f723 | 73 | } |
5e8ff485 | 74 | } |
1b85f723 | 75 | |
5e8ff485 RBR |
76 | return result |
77 | } | |
1b85f723 RBR |
78 | |
79 | func parseEdges(_ text: String, vertices: [String: CGPoint]) -> [MapEdge] { | |
5e8ff485 RBR |
80 | |
81 | var result: [MapEdge] = [] | |
82 | let regex = try! NSRegularExpression(pattern: edgePattern, options: .caseInsensitive) | |
83 | ||
84 | let lines = text.split(whereSeparator: \.isNewline) | |
85 | ||
86 | for (index, line) in lines.enumerated() { | |
87 | let range = NSRange(location: 0, length: line.utf16.count) | |
88 | let matches = regex.matches(in: String(line), options: [], range: range) | |
89 | ||
90 | if matches.count > 0 && matches[0].numberOfRanges == 4 { | |
91 | ||
92 | let match = matches[0] | |
93 | let arrowhead = String(line[Range(match.range(at: 2), in: line)!]) == ">" | |
94 | let vertexA = String(line[Range(match.range(at: 1), in: line)!]) | |
95 | let vertexB = String(line[Range(match.range(at: 3), in: line)!]) | |
96 | ||
97 | if let origin = vertices[vertexA] { | |
98 | if let destination = vertices[vertexB] { | |
99 | result.append( | |
100 | MapEdge(id: index, origin: origin, destination: destination, arrowhead: arrowhead)) | |
1b85f723 | 101 | } |
5e8ff485 | 102 | } |
1b85f723 | 103 | } |
5e8ff485 RBR |
104 | } |
105 | ||
106 | return result | |
107 | } | |
108 | ||
109 | func parseOpportunities(_ text: String, vertices: [String: CGPoint]) -> [Opportunity] { | |
110 | ||
111 | var result: [Opportunity] = [] | |
112 | let regex = try! NSRegularExpression(pattern: opportunityPattern, options: .caseInsensitive) | |
113 | ||
114 | let lines = text.split(whereSeparator: \.isNewline) | |
115 | ||
116 | for (index, line) in lines.enumerated() { | |
117 | let range = NSRange(location: 0, length: line.utf16.count) | |
118 | let matches = regex.matches(in: String(line), options: [], range: range) | |
119 | ||
120 | if matches.count > 0 && matches[0].numberOfRanges == 4 { | |
121 | ||
122 | let match = matches[0] | |
123 | let multiplier = CGFloat( | |
124 | String(line[Range(match.range(at: 2), in: line)!]) == "-" ? -1.0 : 1.0) | |
125 | let vertex = String(line[Range(match.range(at: 1), in: line)!]) | |
126 | let opportunityString = String(line[Range(match.range(at: 3), in: line)!]) | |
127 | let opportunity = CGFloat( | |
128 | truncating: NumberFormatter().number(from: opportunityString) ?? 0.0) | |
129 | ||
130 | if let origin = vertices[vertex] { | |
131 | let destination = CGPoint(x: origin.x + opportunity * multiplier, y: origin.y) | |
132 | result.append(Opportunity(id: index, origin: origin, destination: destination)) | |
133 | } | |
134 | } | |
135 | } | |
136 | ||
137 | return result | |
138 | } | |
139 | ||
140 | func parseBlockers(_ text: String, vertices: [String: CGPoint]) -> [Blocker] { | |
141 | ||
142 | var result: [Blocker] = [] | |
143 | let regex = try! NSRegularExpression(pattern: blockerPattern, options: .caseInsensitive) | |
144 | ||
145 | let lines = text.split(whereSeparator: \.isNewline) | |
146 | ||
147 | for (index, line) in lines.enumerated() { | |
148 | let range = NSRange(location: 0, length: line.utf16.count) | |
149 | let matches = regex.matches(in: String(line), options: [], range: range) | |
150 | ||
151 | if matches.count > 0 && matches[0].numberOfRanges == 2 { | |
152 | ||
153 | let match = matches[0] | |
154 | let vertexA = String(line[Range(match.range(at: 1), in: line)!]) | |
155 | ||
156 | if let position = vertices[vertexA] { | |
157 | result.append(Blocker(id: index, position: position)) | |
158 | } | |
159 | } | |
160 | } | |
161 | ||
162 | return result | |
163 | } | |
164 | ||
165 | func parseStages(_ text: String) -> [CGFloat] { | |
166 | ||
167 | var result = defaultDimensions | |
168 | let regex = try! NSRegularExpression(pattern: stagePattern, options: .caseInsensitive) | |
169 | ||
170 | let lines = text.split(whereSeparator: \.isNewline) | |
171 | ||
172 | for line in lines { | |
173 | let range = NSRange(location: 0, length: line.utf16.count) | |
174 | let matches = regex.matches(in: String(line), options: [], range: range) | |
175 | ||
176 | if matches.count > 0 && matches[0].numberOfRanges == 3 { | |
177 | ||
178 | let match = matches[0] | |
179 | let stage = String(line[Range(match.range(at: 1), in: line)!]) | |
180 | let dimensionsString = String(line[Range(match.range(at: 2), in: line)!]) | |
181 | let dimensions = CGFloat(truncating: NumberFormatter().number(from: dimensionsString) ?? 0.0) | |
182 | ||
183 | result[stage.count - 1] = dimensions | |
184 | } | |
185 | } | |
186 | ||
187 | return result | |
1b85f723 RBR |
188 | } |
189 | ||
190 | // Converts vetex dictionary to array | |
191 | ||
192 | func mapVertices(_ vertices: [String: CGPoint]) -> [Vertex] { | |
5e8ff485 RBR |
193 | var i = 0 |
194 | return vertices.map { label, position in | |
195 | i += 1 | |
196 | return Vertex(id: i, label: label, position: position) | |
197 | } | |
1b85f723 RBR |
198 | } |
199 | ||
200 | extension Map { | |
5e8ff485 RBR |
201 | func parse() -> ParsedMap { |
202 | ||
203 | let text = self.content ?? "" | |
204 | let vertices = parseVertices(text) | |
205 | let mappedVertices = mapVertices(vertices) | |
206 | let edges = parseEdges(text, vertices: vertices) | |
207 | let blockers = parseBlockers(text, vertices: vertices) | |
208 | let opportunities = parseOpportunities(text, vertices: vertices) | |
209 | let stages = parseStages(text) | |
210 | ||
211 | return ParsedMap( | |
212 | vertices: mappedVertices, edges: edges, blockers: blockers, opportunities: opportunities, | |
213 | stages: stages) | |
214 | } | |
1b85f723 | 215 | } |