OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * Code for reading an HTML API description. | 6 * Code for reading an HTML API description. |
7 */ | 7 */ |
8 library from.html; | 8 library from.html; |
9 | 9 |
10 import 'dart:io'; | 10 import 'dart:io'; |
11 | 11 |
12 import 'package:html5lib/dom.dart' as dom; | 12 import 'package:html5lib/dom.dart' as dom; |
13 import 'package:html5lib/parser.dart' as parser; | 13 import 'package:html5lib/parser.dart' as parser; |
14 | 14 |
15 import 'api.dart'; | 15 import 'api.dart'; |
16 import 'html_tools.dart'; | 16 import 'html_tools.dart'; |
17 | 17 |
18 /** | 18 const List<String> specialElements = const [ |
19 * Check that the given [element] has the given [expectedName]. | 19 'domain', |
20 */ | 20 'feedback', |
21 void checkName(dom.Element element, String expectedName, [String context]) { | 21 'object', |
22 if (element.localName != expectedName) { | 22 'refactorings', |
23 if (context == null) { | 23 'refactoring', |
24 context = element.localName; | 24 'type', |
25 } | 25 'types', |
26 throw new Exception( | 26 'request', |
27 '$context: Expected $expectedName, found ${element.localName}'); | 27 'notification', |
28 } | 28 'params', |
29 } | 29 'result', |
30 | 30 'field', |
31 /** | 31 'list', |
32 * Check that the given [element] has all of the attributes in | 32 'map', |
33 * [requiredAttributes], possibly some of the attributes in | 33 'enum', |
34 * [optionalAttributes], and no others. | 34 'key', |
35 */ | 35 'value', |
36 void checkAttributes(dom.Element element, List<String> | 36 'options', |
37 requiredAttributes, String context, {List<String> optionalAttributes: const
[]}) | 37 'ref', |
38 { | 38 'code', |
39 Set<String> attributesFound = new Set<String>(); | 39 'version', |
40 element.attributes.forEach((String name, String value) { | 40 'union']; |
41 if (!requiredAttributes.contains(name) && !optionalAttributes.contains(name | |
42 )) { | |
43 throw new Exception( | |
44 '$context: Unexpected attribute in ${element.localName}: $name'); | |
45 } | |
46 attributesFound.add(name); | |
47 }); | |
48 for (String expectedAttribute in requiredAttributes) { | |
49 if (!attributesFound.contains(expectedAttribute)) { | |
50 throw new Exception( | |
51 '$context: ${element.localName} must contain attribute ${expectedAttri
bute}'); | |
52 } | |
53 } | |
54 } | |
55 | |
56 const List<String> specialElements = const ['domain', 'feedback', 'object', | |
57 'refactorings', 'refactoring', 'type', 'types', 'request', 'notification', | |
58 'params', 'result', 'field', 'list', 'map', 'enum', 'key', 'value', 'options
', | |
59 'ref', 'code', 'version', 'union']; | |
60 | |
61 typedef void ElementProcessor(dom.Element element); | |
62 typedef void TextProcessor(dom.Text text); | |
63 | |
64 void recurse(dom.Element parent, String context, Map<String, ElementProcessor> | |
65 elementProcessors) { | |
66 for (String key in elementProcessors.keys) { | |
67 if (!specialElements.contains(key)) { | |
68 throw new Exception('$context: $key is not a special element'); | |
69 } | |
70 } | |
71 for (dom.Node node in parent.nodes) { | |
72 if (node is dom.Element) { | |
73 if (elementProcessors.containsKey(node.localName)) { | |
74 elementProcessors[node.localName](node); | |
75 } else if (specialElements.contains(node.localName)) { | |
76 throw new Exception('$context: Unexpected use of <${node.localName}'); | |
77 } else { | |
78 recurse(node, context, elementProcessors); | |
79 } | |
80 } | |
81 } | |
82 } | |
83 | |
84 dom.Element getAncestor(dom.Element html, String name, String context) { | |
85 dom.Element ancestor = html.parent; | |
86 while (ancestor != null) { | |
87 if (ancestor.localName == name) { | |
88 return ancestor; | |
89 } | |
90 ancestor = ancestor.parent; | |
91 } | |
92 throw new Exception( | |
93 '$context: <${html.localName}> must be nested within <$name>'); | |
94 } | |
95 | 41 |
96 /** | 42 /** |
97 * Create an [Api] object from an HTML representation such as: | 43 * Create an [Api] object from an HTML representation such as: |
98 * | 44 * |
99 * <html> | 45 * <html> |
100 * ... | 46 * ... |
101 * <body> | 47 * <body> |
102 * ... <version>1.0</version> ... | 48 * ... <version>1.0</version> ... |
103 * <domain name="...">...</domain> <!-- zero or more --> | 49 * <domain name="...">...</domain> <!-- zero or more --> |
104 * <types>...</types> | 50 * <types>...</types> |
(...skipping 24 matching lines...) Expand all Loading... |
129 } | 75 } |
130 }); | 76 }); |
131 if (versions.length != 1) { | 77 if (versions.length != 1) { |
132 throw new Exception('The API must contain exactly one <version> element'); | 78 throw new Exception('The API must contain exactly one <version> element'); |
133 } | 79 } |
134 api = new Api(versions[0], domains, types, refactorings, html); | 80 api = new Api(versions[0], domains, types, refactorings, html); |
135 return api; | 81 return api; |
136 } | 82 } |
137 | 83 |
138 /** | 84 /** |
139 * Create a [Refactorings] object from an HTML representation such as: | 85 * Check that the given [element] has all of the attributes in |
140 * | 86 * [requiredAttributes], possibly some of the attributes in |
141 * <refactorings> | 87 * [optionalAttributes], and no others. |
142 * <refactoring kind="...">...</refactoring> <!-- zero or more --> | |
143 * </refactorings> | |
144 */ | 88 */ |
145 Refactorings refactoringsFromHtml(dom.Element html) { | 89 void checkAttributes(dom.Element element, List<String> requiredAttributes, |
146 checkName(html, 'refactorings'); | 90 String context, {List<String> optionalAttributes: const [ |
147 String context = 'refactorings'; | 91 ]}) { |
148 checkAttributes(html, [], context); | 92 Set<String> attributesFound = new Set<String>(); |
149 List<Refactoring> refactorings = <Refactoring>[]; | 93 element.attributes.forEach((String name, String value) { |
150 recurse(html, context, { | 94 if (!requiredAttributes.contains(name) && |
151 'refactoring': (dom.Element child) { | 95 !optionalAttributes.contains(name)) { |
152 refactorings.add(refactoringFromHtml(child)); | 96 throw new Exception( |
| 97 '$context: Unexpected attribute in ${element.localName}: $name'); |
153 } | 98 } |
| 99 attributesFound.add(name); |
154 }); | 100 }); |
155 return new Refactorings(refactorings, html); | 101 for (String expectedAttribute in requiredAttributes) { |
| 102 if (!attributesFound.contains(expectedAttribute)) { |
| 103 throw new Exception( |
| 104 '$context: ${element.localName} must contain attribute ${expectedAttri
bute}'); |
| 105 } |
| 106 } |
156 } | 107 } |
157 | 108 |
158 /** | 109 /** |
159 * Create a [Refactoring] object from an HTML representation such as: | 110 * Check that the given [element] has the given [expectedName]. |
160 * | |
161 * <refactoring kind="refactoringKind"> | |
162 * <feedback>...</feedback> <!-- optional --> | |
163 * <options>...</options> <!-- optional --> | |
164 * </refactoring> | |
165 * | |
166 * <feedback> and <options> have the same form as <object>, as described in | |
167 * [typeDeclFromHtml]. | |
168 * | |
169 * Child elements can occur in any order. | |
170 */ | 111 */ |
171 Refactoring refactoringFromHtml(dom.Element html) { | 112 void checkName(dom.Element element, String expectedName, [String context]) { |
172 checkName(html, 'refactoring'); | 113 if (element.localName != expectedName) { |
173 String kind = html.attributes['kind']; | 114 if (context == null) { |
174 String context = kind != null ? kind : 'refactoring'; | 115 context = element.localName; |
175 checkAttributes(html, ['kind'], context); | |
176 TypeDecl feedback; | |
177 TypeDecl options; | |
178 recurse(html, context, { | |
179 'feedback': (dom.Element child) { | |
180 feedback = typeObjectFromHtml(child, '$context.feedback'); | |
181 }, | |
182 'options': (dom.Element child) { | |
183 options = typeObjectFromHtml(child, '$context.options'); | |
184 } | 116 } |
185 }); | 117 throw new Exception( |
186 return new Refactoring(kind, feedback, options, html); | 118 '$context: Expected $expectedName, found ${element.localName}'); |
| 119 } |
187 } | 120 } |
188 | |
189 /** | |
190 * Create a [Types] object from an HTML representation such as: | |
191 * | |
192 * <types> | |
193 * <type name="...">...</type> <!-- zero or more --> | |
194 * </types> | |
195 */ | |
196 Types typesFromHtml(dom.Element html) { | |
197 checkName(html, 'types'); | |
198 String context = 'types'; | |
199 checkAttributes(html, [], context); | |
200 Map<String, TypeDefinition> types = <String, TypeDefinition> {}; | |
201 recurse(html, context, { | |
202 'type': (dom.Element child) { | |
203 TypeDefinition typeDefinition = typeDefinitionFromHtml(child); | |
204 types[typeDefinition.name] = typeDefinition; | |
205 } | |
206 }); | |
207 return new Types(types, html); | |
208 } | |
209 | |
210 /** | |
211 * Create a [TypeDefinition] object from an HTML representation such as: | |
212 * | |
213 * <type name="typeName"> | |
214 * TYPE | |
215 * </type> | |
216 * | |
217 * Where TYPE is any HTML that can be parsed by [typeDeclFromHtml]. | |
218 * | |
219 * Child elements can occur in any order. | |
220 */ | |
221 TypeDefinition typeDefinitionFromHtml(dom.Element html) { | |
222 checkName(html, 'type'); | |
223 String name = html.attributes['name']; | |
224 String context = name != null ? name : 'type'; | |
225 checkAttributes(html, ['name'], context); | |
226 TypeDecl type = processContentsAsType(html, context); | |
227 return new TypeDefinition(name, type, html); | |
228 } | |
229 | |
230 /** | 121 /** |
231 * Create a [Domain] object from an HTML representation such as: | 122 * Create a [Domain] object from an HTML representation such as: |
232 * | 123 * |
233 * <domain name="domainName"> | 124 * <domain name="domainName"> |
234 * <request method="...">...</request> <!-- zero or more --> | 125 * <request method="...">...</request> <!-- zero or more --> |
235 * <notification event="...">...</notification> <!-- zero or more --> | 126 * <notification event="...">...</notification> <!-- zero or more --> |
236 * </domain> | 127 * </domain> |
237 * | 128 * |
238 * Child elements can occur in any order. | 129 * Child elements can occur in any order. |
239 */ | 130 */ |
240 Domain domainFromHtml(dom.Element html) { | 131 Domain domainFromHtml(dom.Element html) { |
241 checkName(html, 'domain'); | 132 checkName(html, 'domain'); |
242 String name = html.attributes['name']; | 133 String name = html.attributes['name']; |
243 String context = name != null ? name : 'domain'; | 134 String context = name != null ? name : 'domain'; |
244 checkAttributes(html, ['name'], context); | 135 checkAttributes(html, ['name'], context); |
245 List<Request> requests = <Request>[]; | 136 List<Request> requests = <Request>[]; |
246 List<Notification> notifications = <Notification>[]; | 137 List<Notification> notifications = <Notification>[]; |
247 recurse(html, context, { | 138 recurse(html, context, { |
248 'request': (dom.Element child) { | 139 'request': (dom.Element child) { |
249 requests.add(requestFromHtml(child, context)); | 140 requests.add(requestFromHtml(child, context)); |
250 }, | 141 }, |
251 'notification': (dom.Element child) { | 142 'notification': (dom.Element child) { |
252 notifications.add(notificationFromHtml(child, context)); | 143 notifications.add(notificationFromHtml(child, context)); |
253 } | 144 } |
254 }); | 145 }); |
255 return new Domain(name, requests, notifications, html); | 146 return new Domain(name, requests, notifications, html); |
256 } | 147 } |
257 | 148 |
258 /** | 149 dom.Element getAncestor(dom.Element html, String name, String context) { |
259 * Create a [Request] object from an HTML representation such as: | 150 dom.Element ancestor = html.parent; |
260 * | 151 while (ancestor != null) { |
261 * <request method="methodName"> | 152 if (ancestor.localName == name) { |
262 * <params>...</params> <!-- optional --> | 153 return ancestor; |
263 * <result>...</result> <!-- optional --> | |
264 * </request> | |
265 * | |
266 * Note that the method name should not include the domain name. | |
267 * | |
268 * <params> and <result> have the same form as <object>, as described in | |
269 * [typeDeclFromHtml]. | |
270 * | |
271 * Child elements can occur in any order. | |
272 */ | |
273 Request requestFromHtml(dom.Element html, String context) { | |
274 String domainName = getAncestor(html, 'domain', context).attributes['name']; | |
275 checkName(html, 'request', context); | |
276 String method = html.attributes['method']; | |
277 context = '$context.${method != null ? method : 'method'}'; | |
278 checkAttributes(html, ['method'], context); | |
279 TypeDecl params; | |
280 TypeDecl result; | |
281 recurse(html, context, { | |
282 'params': (dom.Element child) { | |
283 params = typeObjectFromHtml(child, '$context.params'); | |
284 }, | |
285 'result': (dom.Element child) { | |
286 result = typeObjectFromHtml(child, '$context.result'); | |
287 } | 154 } |
288 }); | 155 ancestor = ancestor.parent; |
289 return new Request(domainName, method, params, result, html); | 156 } |
| 157 throw new Exception( |
| 158 '$context: <${html.localName}> must be nested within <$name>'); |
290 } | 159 } |
291 | 160 |
292 /** | 161 /** |
293 * Create a [Notification] object from an HTML representation such as: | 162 * Create a [Notification] object from an HTML representation such as: |
294 * | 163 * |
295 * <notification event="methodName"> | 164 * <notification event="methodName"> |
296 * <params>...</params> <!-- optional --> | 165 * <params>...</params> <!-- optional --> |
297 * </notification> | 166 * </notification> |
298 * | 167 * |
299 * Note that the event name should not include the domain name. | 168 * Note that the event name should not include the domain name. |
300 * | 169 * |
301 * <params> has the same form as <object>, as described in [typeDeclFromHtml]. | 170 * <params> has the same form as <object>, as described in [typeDeclFromHtml]. |
302 * | 171 * |
303 * Child elements can occur in any order. | 172 * Child elements can occur in any order. |
304 */ | 173 */ |
305 Notification notificationFromHtml(dom.Element html, String context) { | 174 Notification notificationFromHtml(dom.Element html, String context) { |
306 String domainName = getAncestor(html, 'domain', context).attributes['name']; | 175 String domainName = getAncestor(html, 'domain', context).attributes['name']; |
307 checkName(html, 'notification', context); | 176 checkName(html, 'notification', context); |
308 String event = html.attributes['event']; | 177 String event = html.attributes['event']; |
309 context = '$context.${event != null ? event : 'event'}'; | 178 context = '$context.${event != null ? event : 'event'}'; |
310 checkAttributes(html, ['event'], context); | 179 checkAttributes(html, ['event'], context); |
311 TypeDecl params; | 180 TypeDecl params; |
312 recurse(html, context, { | 181 recurse(html, context, { |
313 'params': (dom.Element child) { | 182 'params': (dom.Element child) { |
314 params = typeObjectFromHtml(child, '$context.params'); | 183 params = typeObjectFromHtml(child, '$context.params'); |
315 } | 184 } |
316 }); | 185 }); |
317 return new Notification(domainName, event, params, html); | 186 return new Notification(domainName, event, params, html); |
318 } | 187 } |
| 188 |
319 /** | 189 /** |
320 * Create a single of [TypeDecl] corresponding to the type defined inside the | 190 * Create a single of [TypeDecl] corresponding to the type defined inside the |
321 * given HTML element. | 191 * given HTML element. |
322 */ | 192 */ |
323 TypeDecl processContentsAsType(dom.Element html, String context) { | 193 TypeDecl processContentsAsType(dom.Element html, String context) { |
324 List<TypeDecl> types = processContentsAsTypes(html, context); | 194 List<TypeDecl> types = processContentsAsTypes(html, context); |
325 if (types.length != 1) { | 195 if (types.length != 1) { |
326 throw new Exception('$context: Exactly one type must be specified'); | 196 throw new Exception('$context: Exactly one type must be specified'); |
327 } | 197 } |
328 return types[0]; | 198 return types[0]; |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
396 'enum': (dom.Element child) { | 266 'enum': (dom.Element child) { |
397 types.add(typeEnumFromHtml(child, context)); | 267 types.add(typeEnumFromHtml(child, context)); |
398 }, | 268 }, |
399 'ref': (dom.Element child) { | 269 'ref': (dom.Element child) { |
400 checkAttributes(child, [], context); | 270 checkAttributes(child, [], context); |
401 types.add(new TypeReference(innerText(child), child)); | 271 types.add(new TypeReference(innerText(child), child)); |
402 }, | 272 }, |
403 'union': (dom.Element child) { | 273 'union': (dom.Element child) { |
404 checkAttributes(child, ['field'], context); | 274 checkAttributes(child, ['field'], context); |
405 String field = child.attributes['field']; | 275 String field = child.attributes['field']; |
406 types.add(new TypeUnion(processContentsAsTypes(child, context), field, | 276 types.add( |
407 child)); | 277 new TypeUnion(processContentsAsTypes(child, context), field, child)); |
408 } | 278 } |
409 }); | 279 }); |
410 return types; | 280 return types; |
411 } | 281 } |
412 | 282 |
413 /** | 283 /** |
| 284 * Read the API description from the file 'spec_input.html'. |
| 285 */ |
| 286 Api readApi() { |
| 287 File htmlFile = new File('spec_input.html'); |
| 288 String htmlContents = htmlFile.readAsStringSync(); |
| 289 dom.Document document = parser.parse(htmlContents); |
| 290 return apiFromHtml(document.firstChild); |
| 291 } |
| 292 |
| 293 void recurse(dom.Element parent, String context, Map<String, |
| 294 ElementProcessor> elementProcessors) { |
| 295 for (String key in elementProcessors.keys) { |
| 296 if (!specialElements.contains(key)) { |
| 297 throw new Exception('$context: $key is not a special element'); |
| 298 } |
| 299 } |
| 300 for (dom.Node node in parent.nodes) { |
| 301 if (node is dom.Element) { |
| 302 if (elementProcessors.containsKey(node.localName)) { |
| 303 elementProcessors[node.localName](node); |
| 304 } else if (specialElements.contains(node.localName)) { |
| 305 throw new Exception('$context: Unexpected use of <${node.localName}'); |
| 306 } else { |
| 307 recurse(node, context, elementProcessors); |
| 308 } |
| 309 } |
| 310 } |
| 311 } |
| 312 |
| 313 /** |
| 314 * Create a [Refactoring] object from an HTML representation such as: |
| 315 * |
| 316 * <refactoring kind="refactoringKind"> |
| 317 * <feedback>...</feedback> <!-- optional --> |
| 318 * <options>...</options> <!-- optional --> |
| 319 * </refactoring> |
| 320 * |
| 321 * <feedback> and <options> have the same form as <object>, as described in |
| 322 * [typeDeclFromHtml]. |
| 323 * |
| 324 * Child elements can occur in any order. |
| 325 */ |
| 326 Refactoring refactoringFromHtml(dom.Element html) { |
| 327 checkName(html, 'refactoring'); |
| 328 String kind = html.attributes['kind']; |
| 329 String context = kind != null ? kind : 'refactoring'; |
| 330 checkAttributes(html, ['kind'], context); |
| 331 TypeDecl feedback; |
| 332 TypeDecl options; |
| 333 recurse(html, context, { |
| 334 'feedback': (dom.Element child) { |
| 335 feedback = typeObjectFromHtml(child, '$context.feedback'); |
| 336 }, |
| 337 'options': (dom.Element child) { |
| 338 options = typeObjectFromHtml(child, '$context.options'); |
| 339 } |
| 340 }); |
| 341 return new Refactoring(kind, feedback, options, html); |
| 342 } |
| 343 |
| 344 /** |
| 345 * Create a [Refactorings] object from an HTML representation such as: |
| 346 * |
| 347 * <refactorings> |
| 348 * <refactoring kind="...">...</refactoring> <!-- zero or more --> |
| 349 * </refactorings> |
| 350 */ |
| 351 Refactorings refactoringsFromHtml(dom.Element html) { |
| 352 checkName(html, 'refactorings'); |
| 353 String context = 'refactorings'; |
| 354 checkAttributes(html, [], context); |
| 355 List<Refactoring> refactorings = <Refactoring>[]; |
| 356 recurse(html, context, { |
| 357 'refactoring': (dom.Element child) { |
| 358 refactorings.add(refactoringFromHtml(child)); |
| 359 } |
| 360 }); |
| 361 return new Refactorings(refactorings, html); |
| 362 } |
| 363 |
| 364 /** |
| 365 * Create a [Request] object from an HTML representation such as: |
| 366 * |
| 367 * <request method="methodName"> |
| 368 * <params>...</params> <!-- optional --> |
| 369 * <result>...</result> <!-- optional --> |
| 370 * </request> |
| 371 * |
| 372 * Note that the method name should not include the domain name. |
| 373 * |
| 374 * <params> and <result> have the same form as <object>, as described in |
| 375 * [typeDeclFromHtml]. |
| 376 * |
| 377 * Child elements can occur in any order. |
| 378 */ |
| 379 Request requestFromHtml(dom.Element html, String context) { |
| 380 String domainName = getAncestor(html, 'domain', context).attributes['name']; |
| 381 checkName(html, 'request', context); |
| 382 String method = html.attributes['method']; |
| 383 context = '$context.${method != null ? method : 'method'}'; |
| 384 checkAttributes(html, ['method'], context); |
| 385 TypeDecl params; |
| 386 TypeDecl result; |
| 387 recurse(html, context, { |
| 388 'params': (dom.Element child) { |
| 389 params = typeObjectFromHtml(child, '$context.params'); |
| 390 }, |
| 391 'result': (dom.Element child) { |
| 392 result = typeObjectFromHtml(child, '$context.result'); |
| 393 } |
| 394 }); |
| 395 return new Request(domainName, method, params, result, html); |
| 396 } |
| 397 |
| 398 /** |
| 399 * Create a [TypeDefinition] object from an HTML representation such as: |
| 400 * |
| 401 * <type name="typeName"> |
| 402 * TYPE |
| 403 * </type> |
| 404 * |
| 405 * Where TYPE is any HTML that can be parsed by [typeDeclFromHtml]. |
| 406 * |
| 407 * Child elements can occur in any order. |
| 408 */ |
| 409 TypeDefinition typeDefinitionFromHtml(dom.Element html) { |
| 410 checkName(html, 'type'); |
| 411 String name = html.attributes['name']; |
| 412 String context = name != null ? name : 'type'; |
| 413 checkAttributes(html, ['name'], context); |
| 414 TypeDecl type = processContentsAsType(html, context); |
| 415 return new TypeDefinition(name, type, html); |
| 416 } |
| 417 /** |
414 * Create a [TypeEnum] from an HTML description. | 418 * Create a [TypeEnum] from an HTML description. |
415 */ | 419 */ |
416 TypeEnum typeEnumFromHtml(dom.Element html, String context) { | 420 TypeEnum typeEnumFromHtml(dom.Element html, String context) { |
417 checkName(html, 'enum', context); | 421 checkName(html, 'enum', context); |
418 checkAttributes(html, [], context); | 422 checkAttributes(html, [], context); |
419 List<TypeEnumValue> values = <TypeEnumValue>[]; | 423 List<TypeEnumValue> values = <TypeEnumValue>[]; |
420 recurse(html, context, { | 424 recurse(html, context, { |
421 'value': (dom.Element child) { | 425 'value': (dom.Element child) { |
422 values.add(typeEnumValueFromHtml(child, context)); | 426 values.add(typeEnumValueFromHtml(child, context)); |
423 } | 427 } |
(...skipping 22 matching lines...) Expand all Loading... |
446 values.add(text); | 450 values.add(text); |
447 } | 451 } |
448 }); | 452 }); |
449 if (values.length != 1) { | 453 if (values.length != 1) { |
450 throw new Exception('$context: Exactly one value must be specified'); | 454 throw new Exception('$context: Exactly one value must be specified'); |
451 } | 455 } |
452 return new TypeEnumValue(values[0], html); | 456 return new TypeEnumValue(values[0], html); |
453 } | 457 } |
454 | 458 |
455 /** | 459 /** |
456 * Create a [TypeObject] from an HTML description. | |
457 */ | |
458 TypeObject typeObjectFromHtml(dom.Element html, String context) { | |
459 checkAttributes(html, [], context); | |
460 List<TypeObjectField> fields = <TypeObjectField>[]; | |
461 recurse(html, context, { | |
462 'field': (dom.Element child) { | |
463 fields.add(typeObjectFieldFromHtml(child, context)); | |
464 } | |
465 }); | |
466 return new TypeObject(fields, html); | |
467 } | |
468 | |
469 /** | |
470 * Create a [TypeObjectField] from an HTML description such as: | 460 * Create a [TypeObjectField] from an HTML description such as: |
471 * | 461 * |
472 * <field name="fieldName"> | 462 * <field name="fieldName"> |
473 * TYPE | 463 * TYPE |
474 * </field> | 464 * </field> |
475 * | 465 * |
476 * Where TYPE is any HTML that can be parsed by [typeDeclFromHtml]. | 466 * Where TYPE is any HTML that can be parsed by [typeDeclFromHtml]. |
477 * | 467 * |
478 * In addition, the attribute optional="true" may be used to specify that the | 468 * In addition, the attribute optional="true" may be used to specify that the |
479 * field is optional, and the attribute value="..." may be used to specify that | 469 * field is optional, and the attribute value="..." may be used to specify that |
480 * the field is required to have a certain value. | 470 * the field is required to have a certain value. |
481 * | 471 * |
482 * Child elements can occur in any order. | 472 * Child elements can occur in any order. |
483 */ | 473 */ |
484 TypeObjectField typeObjectFieldFromHtml(dom.Element html, String context) { | 474 TypeObjectField typeObjectFieldFromHtml(dom.Element html, String context) { |
485 checkName(html, 'field', context); | 475 checkName(html, 'field', context); |
486 String name = html.attributes['name']; | 476 String name = html.attributes['name']; |
487 context = '$context.${name != null ? name : 'field'}'; | 477 context = '$context.${name != null ? name : 'field'}'; |
488 checkAttributes(html, ['name'], context, optionalAttributes: ['optional', | 478 checkAttributes( |
489 'value']); | 479 html, |
| 480 ['name'], |
| 481 context, |
| 482 optionalAttributes: ['optional', 'value']); |
490 bool optional = false; | 483 bool optional = false; |
491 String optionalString = html.attributes['optional']; | 484 String optionalString = html.attributes['optional']; |
492 if (optionalString != null) { | 485 if (optionalString != null) { |
493 switch (optionalString) { | 486 switch (optionalString) { |
494 case 'true': | 487 case 'true': |
495 optional = true; | 488 optional = true; |
496 break; | 489 break; |
497 case 'false': | 490 case 'false': |
498 optional = false; | 491 optional = false; |
499 break; | 492 break; |
500 default: | 493 default: |
501 throw new Exception( | 494 throw new Exception( |
502 '$context: field contains invalid "optional" attribute: "$optionalSt
ring"'); | 495 '$context: field contains invalid "optional" attribute: "$optionalSt
ring"'); |
503 } | 496 } |
504 } | 497 } |
505 String value = html.attributes['value']; | 498 String value = html.attributes['value']; |
506 TypeDecl type = processContentsAsType(html, context); | 499 TypeDecl type = processContentsAsType(html, context); |
507 return new TypeObjectField(name, type, html, optional: optional, value: value | 500 return new TypeObjectField( |
508 ); | 501 name, |
| 502 type, |
| 503 html, |
| 504 optional: optional, |
| 505 value: value); |
509 } | 506 } |
510 | 507 |
511 /** | 508 /** |
512 * Read the API description from the file 'spec_input.html'. | 509 * Create a [TypeObject] from an HTML description. |
513 */ | 510 */ |
514 Api readApi() { | 511 TypeObject typeObjectFromHtml(dom.Element html, String context) { |
515 File htmlFile = new File('spec_input.html'); | 512 checkAttributes(html, [], context); |
516 String htmlContents = htmlFile.readAsStringSync(); | 513 List<TypeObjectField> fields = <TypeObjectField>[]; |
517 dom.Document document = parser.parse(htmlContents); | 514 recurse(html, context, { |
518 return apiFromHtml(document.firstChild); | 515 'field': (dom.Element child) { |
| 516 fields.add(typeObjectFieldFromHtml(child, context)); |
| 517 } |
| 518 }); |
| 519 return new TypeObject(fields, html); |
519 } | 520 } |
| 521 |
| 522 /** |
| 523 * Create a [Types] object from an HTML representation such as: |
| 524 * |
| 525 * <types> |
| 526 * <type name="...">...</type> <!-- zero or more --> |
| 527 * </types> |
| 528 */ |
| 529 Types typesFromHtml(dom.Element html) { |
| 530 checkName(html, 'types'); |
| 531 String context = 'types'; |
| 532 checkAttributes(html, [], context); |
| 533 Map<String, TypeDefinition> types = <String, TypeDefinition>{}; |
| 534 recurse(html, context, { |
| 535 'type': (dom.Element child) { |
| 536 TypeDefinition typeDefinition = typeDefinitionFromHtml(child); |
| 537 types[typeDefinition.name] = typeDefinition; |
| 538 } |
| 539 }); |
| 540 return new Types(types, html); |
| 541 } |
| 542 |
| 543 typedef void ElementProcessor(dom.Element element); |
| 544 |
| 545 typedef void TextProcessor(dom.Text text); |
OLD | NEW |