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 const List<String> specialElements = const [ | 18 const List<String> specialElements = const [ |
19 'domain', | 19 'domain', |
20 'feedback', | 20 'feedback', |
21 'object', | 21 'object', |
22 'refactorings', | 22 'refactorings', |
23 'refactoring', | 23 'refactoring', |
24 'type', | 24 'type', |
25 'types', | 25 'types', |
26 'request', | 26 'request', |
27 'notification', | 27 'notification', |
28 'params', | 28 'params', |
29 'result', | 29 'result', |
30 'field', | 30 'field', |
31 'list', | 31 'list', |
32 'map', | 32 'map', |
33 'enum', | 33 'enum', |
34 'key', | 34 'key', |
35 'value', | 35 'value', |
36 'options', | 36 'options', |
37 'ref', | 37 'ref', |
38 'code', | 38 'code', |
39 'version', | 39 'version', |
40 'union']; | 40 'union' |
| 41 ]; |
41 | 42 |
42 /** | 43 /** |
43 * Create an [Api] object from an HTML representation such as: | 44 * Create an [Api] object from an HTML representation such as: |
44 * | 45 * |
45 * <html> | 46 * <html> |
46 * ... | 47 * ... |
47 * <body> | 48 * <body> |
48 * ... <version>1.0</version> ... | 49 * ... <version>1.0</version> ... |
49 * <domain name="...">...</domain> <!-- zero or more --> | 50 * <domain name="...">...</domain> <!-- zero or more --> |
50 * <types>...</types> | 51 * <types>...</types> |
(...skipping 28 matching lines...) Expand all Loading... |
79 } | 80 } |
80 api = new Api(versions[0], domains, types, refactorings, html); | 81 api = new Api(versions[0], domains, types, refactorings, html); |
81 return api; | 82 return api; |
82 } | 83 } |
83 | 84 |
84 /** | 85 /** |
85 * Check that the given [element] has all of the attributes in | 86 * Check that the given [element] has all of the attributes in |
86 * [requiredAttributes], possibly some of the attributes in | 87 * [requiredAttributes], possibly some of the attributes in |
87 * [optionalAttributes], and no others. | 88 * [optionalAttributes], and no others. |
88 */ | 89 */ |
89 void checkAttributes(dom.Element element, List<String> requiredAttributes, | 90 void checkAttributes( |
90 String context, {List<String> optionalAttributes: const [ | 91 dom.Element element, List<String> requiredAttributes, String context, |
91 ]}) { | 92 {List<String> optionalAttributes: const []}) { |
92 Set<String> attributesFound = new Set<String>(); | 93 Set<String> attributesFound = new Set<String>(); |
93 element.attributes.forEach((String name, String value) { | 94 element.attributes.forEach((String name, String value) { |
94 if (!requiredAttributes.contains(name) && | 95 if (!requiredAttributes.contains(name) && |
95 !optionalAttributes.contains(name)) { | 96 !optionalAttributes.contains(name)) { |
96 throw new Exception( | 97 throw new Exception( |
97 '$context: Unexpected attribute in ${element.localName}: $name'); | 98 '$context: Unexpected attribute in ${element.localName}: $name'); |
98 } | 99 } |
99 attributesFound.add(name); | 100 attributesFound.add(name); |
100 }); | 101 }); |
101 for (String expectedAttribute in requiredAttributes) { | 102 for (String expectedAttribute in requiredAttributes) { |
(...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
283 /** | 284 /** |
284 * Read the API description from the file 'spec_input.html'. | 285 * Read the API description from the file 'spec_input.html'. |
285 */ | 286 */ |
286 Api readApi() { | 287 Api readApi() { |
287 File htmlFile = new File('spec_input.html'); | 288 File htmlFile = new File('spec_input.html'); |
288 String htmlContents = htmlFile.readAsStringSync(); | 289 String htmlContents = htmlFile.readAsStringSync(); |
289 dom.Document document = parser.parse(htmlContents); | 290 dom.Document document = parser.parse(htmlContents); |
290 return apiFromHtml(document.firstChild); | 291 return apiFromHtml(document.firstChild); |
291 } | 292 } |
292 | 293 |
293 void recurse(dom.Element parent, String context, Map<String, | 294 void recurse(dom.Element parent, String context, |
294 ElementProcessor> elementProcessors) { | 295 Map<String, ElementProcessor> elementProcessors) { |
295 for (String key in elementProcessors.keys) { | 296 for (String key in elementProcessors.keys) { |
296 if (!specialElements.contains(key)) { | 297 if (!specialElements.contains(key)) { |
297 throw new Exception('$context: $key is not a special element'); | 298 throw new Exception('$context: $key is not a special element'); |
298 } | 299 } |
299 } | 300 } |
300 for (dom.Node node in parent.nodes) { | 301 for (dom.Node node in parent.nodes) { |
301 if (node is dom.Element) { | 302 if (node is dom.Element) { |
302 if (elementProcessors.containsKey(node.localName)) { | 303 if (elementProcessors.containsKey(node.localName)) { |
303 elementProcessors[node.localName](node); | 304 elementProcessors[node.localName](node); |
304 } else if (specialElements.contains(node.localName)) { | 305 } else if (specialElements.contains(node.localName)) { |
(...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
469 * field is optional, and the attribute value="..." may be used to specify that | 470 * field is optional, and the attribute value="..." may be used to specify that |
470 * the field is required to have a certain value. | 471 * the field is required to have a certain value. |
471 * | 472 * |
472 * Child elements can occur in any order. | 473 * Child elements can occur in any order. |
473 */ | 474 */ |
474 TypeObjectField typeObjectFieldFromHtml(dom.Element html, String context) { | 475 TypeObjectField typeObjectFieldFromHtml(dom.Element html, String context) { |
475 checkName(html, 'field', context); | 476 checkName(html, 'field', context); |
476 String name = html.attributes['name']; | 477 String name = html.attributes['name']; |
477 context = '$context.${name != null ? name : 'field'}'; | 478 context = '$context.${name != null ? name : 'field'}'; |
478 checkAttributes( | 479 checkAttributes( |
479 html, | 480 html, ['name'], context, optionalAttributes: ['optional', 'value']); |
480 ['name'], | |
481 context, | |
482 optionalAttributes: ['optional', 'value']); | |
483 bool optional = false; | 481 bool optional = false; |
484 String optionalString = html.attributes['optional']; | 482 String optionalString = html.attributes['optional']; |
485 if (optionalString != null) { | 483 if (optionalString != null) { |
486 switch (optionalString) { | 484 switch (optionalString) { |
487 case 'true': | 485 case 'true': |
488 optional = true; | 486 optional = true; |
489 break; | 487 break; |
490 case 'false': | 488 case 'false': |
491 optional = false; | 489 optional = false; |
492 break; | 490 break; |
493 default: | 491 default: |
494 throw new Exception( | 492 throw new Exception( |
495 '$context: field contains invalid "optional" attribute: "$optionalSt
ring"'); | 493 '$context: field contains invalid "optional" attribute: "$optionalSt
ring"'); |
496 } | 494 } |
497 } | 495 } |
498 String value = html.attributes['value']; | 496 String value = html.attributes['value']; |
499 TypeDecl type = processContentsAsType(html, context); | 497 TypeDecl type = processContentsAsType(html, context); |
500 return new TypeObjectField( | 498 return new TypeObjectField(name, type, html, |
501 name, | 499 optional: optional, value: value); |
502 type, | |
503 html, | |
504 optional: optional, | |
505 value: value); | |
506 } | 500 } |
507 | 501 |
508 /** | 502 /** |
509 * Create a [TypeObject] from an HTML description. | 503 * Create a [TypeObject] from an HTML description. |
510 */ | 504 */ |
511 TypeObject typeObjectFromHtml(dom.Element html, String context) { | 505 TypeObject typeObjectFromHtml(dom.Element html, String context) { |
512 checkAttributes(html, [], context); | 506 checkAttributes(html, [], context); |
513 List<TypeObjectField> fields = <TypeObjectField>[]; | 507 List<TypeObjectField> fields = <TypeObjectField>[]; |
514 recurse(html, context, { | 508 recurse(html, context, { |
515 'field': (dom.Element child) { | 509 'field': (dom.Element child) { |
(...skipping 20 matching lines...) Expand all Loading... |
536 TypeDefinition typeDefinition = typeDefinitionFromHtml(child); | 530 TypeDefinition typeDefinition = typeDefinitionFromHtml(child); |
537 types[typeDefinition.name] = typeDefinition; | 531 types[typeDefinition.name] = typeDefinition; |
538 } | 532 } |
539 }); | 533 }); |
540 return new Types(types, html); | 534 return new Types(types, html); |
541 } | 535 } |
542 | 536 |
543 typedef void ElementProcessor(dom.Element element); | 537 typedef void ElementProcessor(dom.Element element); |
544 | 538 |
545 typedef void TextProcessor(dom.Text text); | 539 typedef void TextProcessor(dom.Text text); |
OLD | NEW |