Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(348)

Side by Side Diff: pkg/analysis_server/tool/spec/from_html.dart

Issue 2879273002: Make server use the common protocol classes (Closed)
Patch Set: Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 import 'dart:io'; 8 import 'dart:io';
9 9
10 import 'package:analyzer/src/codegen/html.dart'; 10 import 'package:analyzer/src/codegen/html.dart';
11 import 'package:html/dom.dart' as dom; 11 import 'package:html/dom.dart' as dom;
12 import 'package:html/parser.dart' as parser; 12 import 'package:html/parser.dart' as parser;
13 import 'package:path/path.dart'; 13 import 'package:path/path.dart';
14 14
15 import 'api.dart'; 15 import 'api.dart';
16 16
17 const List<String> specialElements = const [
18 'domain',
19 'feedback',
20 'object',
21 'refactorings',
22 'refactoring',
23 'type',
24 'types',
25 'request',
26 'notification',
27 'params',
28 'result',
29 'field',
30 'list',
31 'map',
32 'enum',
33 'key',
34 'value',
35 'options',
36 'ref',
37 'code',
38 'version',
39 'union',
40 'index'
41 ];
42
43 /** 17 /**
44 * Create an [Api] object from an HTML representation such as: 18 * Read the API description from the file 'plugin_spec.html'. [pkgPath] is the
45 *
46 * <html>
47 * ...
48 * <body>
49 * ... <version>1.0</version> ...
50 * <domain name="...">...</domain> <!-- zero or more -->
51 * <types>...</types>
52 * <refactorings>...</refactorings>
53 * </body>
54 * </html>
55 *
56 * Child elements of <api> can occur in any order.
57 */
58 Api apiFromHtml(dom.Element html) {
59 Api api;
60 List<String> versions = <String>[];
61 List<Domain> domains = <Domain>[];
62 Types types = null;
63 Refactorings refactorings = null;
64 recurse(html, 'api', {
65 'domain': (dom.Element element) {
66 domains.add(domainFromHtml(element));
67 },
68 'refactorings': (dom.Element element) {
69 refactorings = refactoringsFromHtml(element);
70 },
71 'types': (dom.Element element) {
72 types = typesFromHtml(element);
73 },
74 'version': (dom.Element element) {
75 versions.add(innerText(element));
76 },
77 'index': (dom.Element element) {
78 /* Ignore; generated dynamically. */
79 }
80 });
81 if (versions.length != 1) {
82 throw new Exception('The API must contain exactly one <version> element');
83 }
84 api = new Api(versions[0], domains, types, refactorings, html);
85 return api;
86 }
87
88 /**
89 * Check that the given [element] has all of the attributes in
90 * [requiredAttributes], possibly some of the attributes in
91 * [optionalAttributes], and no others.
92 */
93 void checkAttributes(
94 dom.Element element, List<String> requiredAttributes, String context,
95 {List<String> optionalAttributes: const []}) {
96 Set<String> attributesFound = new Set<String>();
97 element.attributes.forEach((String name, String value) {
98 if (!requiredAttributes.contains(name) &&
99 !optionalAttributes.contains(name)) {
100 throw new Exception(
101 '$context: Unexpected attribute in ${element.localName}: $name');
102 }
103 attributesFound.add(name);
104 });
105 for (String expectedAttribute in requiredAttributes) {
106 if (!attributesFound.contains(expectedAttribute)) {
107 throw new Exception(
108 '$context: ${element.localName} must contain attribute $expectedAttrib ute');
109 }
110 }
111 }
112
113 /**
114 * Check that the given [element] has the given [expectedName].
115 */
116 void checkName(dom.Element element, String expectedName, [String context]) {
117 if (element.localName != expectedName) {
118 if (context == null) {
119 context = element.localName;
120 }
121 throw new Exception(
122 '$context: Expected $expectedName, found ${element.localName}');
123 }
124 }
125
126 /**
127 * Create a [Domain] object from an HTML representation such as:
128 *
129 * <domain name="domainName">
130 * <request method="...">...</request> <!-- zero or more -->
131 * <notification event="...">...</notification> <!-- zero or more -->
132 * </domain>
133 *
134 * Child elements can occur in any order.
135 */
136 Domain domainFromHtml(dom.Element html) {
137 checkName(html, 'domain');
138 String name = html.attributes['name'];
139 String context = name ?? 'domain';
140 bool experimental = html.attributes['experimental'] == 'true';
141 checkAttributes(html, ['name'], context,
142 optionalAttributes: ['experimental']);
143 List<Request> requests = <Request>[];
144 List<Notification> notifications = <Notification>[];
145 recurse(html, context, {
146 'request': (dom.Element child) {
147 requests.add(requestFromHtml(child, context));
148 },
149 'notification': (dom.Element child) {
150 notifications.add(notificationFromHtml(child, context));
151 }
152 });
153 return new Domain(name, requests, notifications, html,
154 experimental: experimental);
155 }
156
157 dom.Element getAncestor(dom.Element html, String name, String context) {
158 dom.Element ancestor = html.parent;
159 while (ancestor != null) {
160 if (ancestor.localName == name) {
161 return ancestor;
162 }
163 ancestor = ancestor.parent;
164 }
165 throw new Exception(
166 '$context: <${html.localName}> must be nested within <$name>');
167 }
168
169 /**
170 * Create a [Notification] object from an HTML representation such as:
171 *
172 * <notification event="methodName">
173 * <params>...</params> <!-- optional -->
174 * </notification>
175 *
176 * Note that the event name should not include the domain name.
177 *
178 * <params> has the same form as <object>, as described in [typeDeclFromHtml].
179 *
180 * Child elements can occur in any order.
181 */
182 Notification notificationFromHtml(dom.Element html, String context) {
183 String domainName = getAncestor(html, 'domain', context).attributes['name'];
184 checkName(html, 'notification', context);
185 String event = html.attributes['event'];
186 context = '$context.${event != null ? event : 'event'}';
187 checkAttributes(html, ['event'], context);
188 TypeDecl params;
189 recurse(html, context, {
190 'params': (dom.Element child) {
191 params = typeObjectFromHtml(child, '$context.params');
192 }
193 });
194 return new Notification(domainName, event, params, html);
195 }
196
197 /**
198 * Create a single of [TypeDecl] corresponding to the type defined inside the
199 * given HTML element.
200 */
201 TypeDecl processContentsAsType(dom.Element html, String context) {
202 List<TypeDecl> types = processContentsAsTypes(html, context);
203 if (types.length != 1) {
204 throw new Exception('$context: Exactly one type must be specified');
205 }
206 return types[0];
207 }
208
209 /**
210 * Create a list of [TypeDecl]s corresponding to the types defined inside the
211 * given HTML element. The following forms are supported.
212 *
213 * To refer to a type declared elsewhere (or a built-in type):
214 *
215 * <ref>typeName</ref>
216 *
217 * For a list: <list>ItemType</list>
218 *
219 * For a map: <map><key>KeyType</key><value>ValueType</value></map>
220 *
221 * For a JSON object:
222 *
223 * <object>
224 * <field name="...">...</field> <!-- zero or more -->
225 * </object>
226 *
227 * For an enum:
228 *
229 * <enum>
230 * <value>...</value> <!-- zero or more -->
231 * </enum>
232 *
233 * For a union type:
234 * <union>
235 * TYPE <!-- zero or more -->
236 * </union>
237 */
238 List<TypeDecl> processContentsAsTypes(dom.Element html, String context) {
239 List<TypeDecl> types = <TypeDecl>[];
240 recurse(html, context, {
241 'object': (dom.Element child) {
242 types.add(typeObjectFromHtml(child, context));
243 },
244 'list': (dom.Element child) {
245 checkAttributes(child, [], context);
246 types.add(new TypeList(processContentsAsType(child, context), child));
247 },
248 'map': (dom.Element child) {
249 checkAttributes(child, [], context);
250 TypeDecl keyType;
251 TypeDecl valueType;
252 recurse(child, context, {
253 'key': (dom.Element child) {
254 if (keyType != null) {
255 throw new Exception('$context: Key type already specified');
256 }
257 keyType = processContentsAsType(child, '$context.key');
258 },
259 'value': (dom.Element child) {
260 if (valueType != null) {
261 throw new Exception('$context: Value type already specified');
262 }
263 valueType = processContentsAsType(child, '$context.value');
264 }
265 });
266 if (keyType == null) {
267 throw new Exception('$context: Key type not specified');
268 }
269 if (valueType == null) {
270 throw new Exception('$context: Value type not specified');
271 }
272 types.add(new TypeMap(keyType, valueType, child));
273 },
274 'enum': (dom.Element child) {
275 types.add(typeEnumFromHtml(child, context));
276 },
277 'ref': (dom.Element child) {
278 checkAttributes(child, [], context);
279 types.add(new TypeReference(innerText(child), child));
280 },
281 'union': (dom.Element child) {
282 checkAttributes(child, ['field'], context);
283 String field = child.attributes['field'];
284 types.add(
285 new TypeUnion(processContentsAsTypes(child, context), field, child));
286 }
287 });
288 return types;
289 }
290
291 /**
292 * Read the API description from the file 'spec_input.html'. [pkgPath] is the
293 * path to the current package. 19 * path to the current package.
294 */ 20 */
295 Api readApi(String pkgPath) { 21 Api readApi(String pkgPath) {
296 File htmlFile = new File(join(pkgPath, 'tool', 'spec', 'spec_input.html')); 22 ApiReader reader =
297 String htmlContents = htmlFile.readAsStringSync(); 23 new ApiReader(join(pkgPath, 'tool', 'spec', 'spec_input.html'));
298 dom.Document document = parser.parse(htmlContents); 24 return reader.readApi();
299 dom.Element htmlElement = document.children
300 .singleWhere((element) => element.localName.toLowerCase() == 'html');
301 return apiFromHtml(htmlElement);
302 } 25 }
303 26
304 void recurse(dom.Element parent, String context, 27 typedef void ElementProcessor(dom.Element element);
305 Map<String, ElementProcessor> elementProcessors) { 28
306 for (String key in elementProcessors.keys) { 29 typedef void TextProcessor(dom.Text text);
307 if (!specialElements.contains(key)) { 30
308 throw new Exception('$context: $key is not a special element'); 31 class ApiReader {
309 } 32 static const List<String> specialElements = const [
310 } 33 'domain',
311 for (dom.Node node in parent.nodes) { 34 'feedback',
312 if (node is dom.Element) { 35 'object',
313 if (elementProcessors.containsKey(node.localName)) { 36 'refactorings',
314 elementProcessors[node.localName](node); 37 'refactoring',
315 } else if (specialElements.contains(node.localName)) { 38 'type',
316 throw new Exception('$context: Unexpected use of <${node.localName}>'); 39 'types',
317 } else { 40 'request',
318 recurse(node, context, elementProcessors); 41 'notification',
319 } 42 'params',
320 } 43 'result',
44 'field',
45 'list',
46 'map',
47 'enum',
48 'key',
49 'value',
50 'options',
51 'ref',
52 'code',
53 'version',
54 'union',
55 'index',
56 'include'
57 ];
58
59 /**
60 * The absolute and normalized path to the file being read.
61 */
62 final String filePath;
63
64 /**
65 * Initialize a newly created API reader to read from the file with the given
66 * [filePath].
67 */
68 ApiReader(this.filePath);
69
70 /**
71 * Create an [Api] object from an HTML representation such as:
72 *
73 * <html>
74 * ...
75 * <body>
76 * ... <version>1.0</version> ...
77 * <domain name="...">...</domain> <!-- zero or more -->
78 * <types>...</types>
79 * <refactorings>...</refactorings>
80 * </body>
81 * </html>
82 *
83 * Child elements of <api> can occur in any order.
84 */
85 Api apiFromHtml(dom.Element html) {
86 Api api;
87 List<String> versions = <String>[];
88 List<Domain> domains = <Domain>[];
89 Types types = null;
90 Refactorings refactorings = null;
91 recurse(html, 'api', {
92 'domain': (dom.Element element) {
93 domains.add(domainFromHtml(element));
94 },
95 'refactorings': (dom.Element element) {
96 refactorings = refactoringsFromHtml(element);
97 },
98 'types': (dom.Element element) {
99 types = typesFromHtml(element);
100 },
101 'version': (dom.Element element) {
102 versions.add(innerText(element));
103 },
104 'index': (dom.Element element) {
105 /* Ignore; generated dynamically. */
106 }
107 });
108 if (versions.length != 1) {
109 throw new Exception('The API must contain exactly one <version> element');
110 }
111 api = new Api(versions[0], domains, types, refactorings, html);
112 return api;
113 }
114
115 /**
116 * Check that the given [element] has all of the attributes in
117 * [requiredAttributes], possibly some of the attributes in
118 * [optionalAttributes], and no others.
119 */
120 void checkAttributes(
121 dom.Element element, List<String> requiredAttributes, String context,
122 {List<String> optionalAttributes: const []}) {
123 Set<String> attributesFound = new Set<String>();
124 element.attributes.forEach((String name, String value) {
125 if (!requiredAttributes.contains(name) &&
126 !optionalAttributes.contains(name)) {
127 throw new Exception(
128 '$context: Unexpected attribute in ${element.localName}: $name');
129 }
130 attributesFound.add(name);
131 });
132 for (String expectedAttribute in requiredAttributes) {
133 if (!attributesFound.contains(expectedAttribute)) {
134 throw new Exception(
135 '$context: ${element.localName} must contain attribute $expectedAttr ibute');
136 }
137 }
138 }
139
140 /**
141 * Check that the given [element] has the given [expectedName].
142 */
143 void checkName(dom.Element element, String expectedName, [String context]) {
144 if (element.localName != expectedName) {
145 if (context == null) {
146 context = element.localName;
147 }
148 throw new Exception(
149 '$context: Expected $expectedName, found ${element.localName}');
150 }
151 }
152
153 /**
154 * Create a [Domain] object from an HTML representation such as:
155 *
156 * <domain name="domainName">
157 * <request method="...">...</request> <!-- zero or more -->
158 * <notification event="...">...</notification> <!-- zero or more -->
159 * </domain>
160 *
161 * Child elements can occur in any order.
162 */
163 Domain domainFromHtml(dom.Element html) {
164 checkName(html, 'domain');
165 String name = html.attributes['name'];
166 String context = name ?? 'domain';
167 bool experimental = html.attributes['experimental'] == 'true';
168 checkAttributes(html, ['name'], context,
169 optionalAttributes: ['experimental']);
170 List<Request> requests = <Request>[];
171 List<Notification> notifications = <Notification>[];
172 recurse(html, context, {
173 'request': (dom.Element child) {
174 requests.add(requestFromHtml(child, context));
175 },
176 'notification': (dom.Element child) {
177 notifications.add(notificationFromHtml(child, context));
178 }
179 });
180 return new Domain(name, requests, notifications, html,
181 experimental: experimental);
182 }
183
184 dom.Element getAncestor(dom.Element html, String name, String context) {
185 dom.Element ancestor = html.parent;
186 while (ancestor != null) {
187 if (ancestor.localName == name) {
188 return ancestor;
189 }
190 ancestor = ancestor.parent;
191 }
192 throw new Exception(
193 '$context: <${html.localName}> must be nested within <$name>');
194 }
195
196 /**
197 * Create a [Notification] object from an HTML representation such as:
198 *
199 * <notification event="methodName">
200 * <params>...</params> <!-- optional -->
201 * </notification>
202 *
203 * Note that the event name should not include the domain name.
204 *
205 * <params> has the same form as <object>, as described in [typeDeclFromHtml].
206 *
207 * Child elements can occur in any order.
208 */
209 Notification notificationFromHtml(dom.Element html, String context) {
210 String domainName = getAncestor(html, 'domain', context).attributes['name'];
211 checkName(html, 'notification', context);
212 String event = html.attributes['event'];
213 context = '$context.${event != null ? event : 'event'}';
214 checkAttributes(html, ['event'], context);
215 TypeDecl params;
216 recurse(html, context, {
217 'params': (dom.Element child) {
218 params = typeObjectFromHtml(child, '$context.params');
219 }
220 });
221 return new Notification(domainName, event, params, html);
222 }
223
224 /**
225 * Create a single of [TypeDecl] corresponding to the type defined inside the
226 * given HTML element.
227 */
228 TypeDecl processContentsAsType(dom.Element html, String context) {
229 List<TypeDecl> types = processContentsAsTypes(html, context);
230 if (types.length != 1) {
231 throw new Exception('$context: Exactly one type must be specified');
232 }
233 return types[0];
234 }
235
236 /**
237 * Create a list of [TypeDecl]s corresponding to the types defined inside the
238 * given HTML element. The following forms are supported.
239 *
240 * To refer to a type declared elsewhere (or a built-in type):
241 *
242 * <ref>typeName</ref>
243 *
244 * For a list: <list>ItemType</list>
245 *
246 * For a map: <map><key>KeyType</key><value>ValueType</value></map>
247 *
248 * For a JSON object:
249 *
250 * <object>
251 * <field name="...">...</field> <!-- zero or more -->
252 * </object>
253 *
254 * For an enum:
255 *
256 * <enum>
257 * <value>...</value> <!-- zero or more -->
258 * </enum>
259 *
260 * For a union type:
261 * <union>
262 * TYPE <!-- zero or more -->
263 * </union>
264 */
265 List<TypeDecl> processContentsAsTypes(dom.Element html, String context) {
266 List<TypeDecl> types = <TypeDecl>[];
267 recurse(html, context, {
268 'object': (dom.Element child) {
269 types.add(typeObjectFromHtml(child, context));
270 },
271 'list': (dom.Element child) {
272 checkAttributes(child, [], context);
273 types.add(new TypeList(processContentsAsType(child, context), child));
274 },
275 'map': (dom.Element child) {
276 checkAttributes(child, [], context);
277 TypeDecl keyType;
278 TypeDecl valueType;
279 recurse(child, context, {
280 'key': (dom.Element child) {
281 if (keyType != null) {
282 throw new Exception('$context: Key type already specified');
283 }
284 keyType = processContentsAsType(child, '$context.key');
285 },
286 'value': (dom.Element child) {
287 if (valueType != null) {
288 throw new Exception('$context: Value type already specified');
289 }
290 valueType = processContentsAsType(child, '$context.value');
291 }
292 });
293 if (keyType == null) {
294 throw new Exception('$context: Key type not specified');
295 }
296 if (valueType == null) {
297 throw new Exception('$context: Value type not specified');
298 }
299 types.add(new TypeMap(keyType, valueType, child));
300 },
301 'enum': (dom.Element child) {
302 types.add(typeEnumFromHtml(child, context));
303 },
304 'ref': (dom.Element child) {
305 checkAttributes(child, [], context);
306 types.add(new TypeReference(innerText(child), child));
307 },
308 'union': (dom.Element child) {
309 checkAttributes(child, ['field'], context);
310 String field = child.attributes['field'];
311 types.add(new TypeUnion(
312 processContentsAsTypes(child, context), field, child));
313 }
314 });
315 return types;
316 }
317
318 /**
319 * Read the API description from file with the given [filePath].
320 */
321 Api readApi() {
322 String htmlContents = new File(filePath).readAsStringSync();
323 dom.Document document = parser.parse(htmlContents);
324 dom.Element htmlElement = document.children
325 .singleWhere((element) => element.localName.toLowerCase() == 'html');
326 return apiFromHtml(htmlElement);
327 }
328
329 void recurse(dom.Element parent, String context,
330 Map<String, ElementProcessor> elementProcessors) {
331 for (String key in elementProcessors.keys) {
332 if (!specialElements.contains(key)) {
333 throw new Exception('$context: $key is not a special element');
334 }
335 }
336 for (dom.Node node in parent.nodes) {
337 if (node is dom.Element) {
338 if (elementProcessors.containsKey(node.localName)) {
339 elementProcessors[node.localName](node);
340 } else if (specialElements.contains(node.localName)) {
341 throw new Exception(
342 '$context: Unexpected use of <${node.localName}>');
343 } else {
344 recurse(node, context, elementProcessors);
345 }
346 }
347 }
348 }
349
350 /**
351 * Create a [Refactoring] object from an HTML representation such as:
352 *
353 * <refactoring kind="refactoringKind">
354 * <feedback>...</feedback> <!-- optional -->
355 * <options>...</options> <!-- optional -->
356 * </refactoring>
357 *
358 * <feedback> and <options> have the same form as <object>, as described in
359 * [typeDeclFromHtml].
360 *
361 * Child elements can occur in any order.
362 */
363 Refactoring refactoringFromHtml(dom.Element html) {
364 checkName(html, 'refactoring');
365 String kind = html.attributes['kind'];
366 String context = kind != null ? kind : 'refactoring';
367 checkAttributes(html, ['kind'], context);
368 TypeDecl feedback;
369 TypeDecl options;
370 recurse(html, context, {
371 'feedback': (dom.Element child) {
372 feedback = typeObjectFromHtml(child, '$context.feedback');
373 },
374 'options': (dom.Element child) {
375 options = typeObjectFromHtml(child, '$context.options');
376 }
377 });
378 return new Refactoring(kind, feedback, options, html);
379 }
380
381 /**
382 * Create a [Refactorings] object from an HTML representation such as:
383 *
384 * <refactorings>
385 * <refactoring kind="...">...</refactoring> <!-- zero or more -->
386 * </refactorings>
387 */
388 Refactorings refactoringsFromHtml(dom.Element html) {
389 checkName(html, 'refactorings');
390 String context = 'refactorings';
391 checkAttributes(html, [], context);
392 List<Refactoring> refactorings = <Refactoring>[];
393 recurse(html, context, {
394 'refactoring': (dom.Element child) {
395 refactorings.add(refactoringFromHtml(child));
396 }
397 });
398 return new Refactorings(refactorings, html);
399 }
400
401 /**
402 * Create a [Request] object from an HTML representation such as:
403 *
404 * <request method="methodName">
405 * <params>...</params> <!-- optional -->
406 * <result>...</result> <!-- optional -->
407 * </request>
408 *
409 * Note that the method name should not include the domain name.
410 *
411 * <params> and <result> have the same form as <object>, as described in
412 * [typeDeclFromHtml].
413 *
414 * Child elements can occur in any order.
415 */
416 Request requestFromHtml(dom.Element html, String context) {
417 String domainName = getAncestor(html, 'domain', context).attributes['name'];
418 checkName(html, 'request', context);
419 String method = html.attributes['method'];
420 context = '$context.${method != null ? method : 'method'}';
421 checkAttributes(html, ['method'], context,
422 optionalAttributes: ['experimental', 'deprecated']);
423 bool experimental = html.attributes['experimental'] == 'true';
424 bool deprecated = html.attributes['deprecated'] == 'true';
425 TypeDecl params;
426 TypeDecl result;
427 recurse(html, context, {
428 'params': (dom.Element child) {
429 params = typeObjectFromHtml(child, '$context.params');
430 },
431 'result': (dom.Element child) {
432 result = typeObjectFromHtml(child, '$context.result');
433 }
434 });
435 return new Request(domainName, method, params, result, html,
436 experimental: experimental, deprecated: deprecated);
437 }
438
439 /**
440 * Create a [TypeDefinition] object from an HTML representation such as:
441 *
442 * <type name="typeName">
443 * TYPE
444 * </type>
445 *
446 * Where TYPE is any HTML that can be parsed by [typeDeclFromHtml].
447 *
448 * Child elements can occur in any order.
449 */
450 TypeDefinition typeDefinitionFromHtml(dom.Element html) {
451 checkName(html, 'type');
452 String name = html.attributes['name'];
453 String context = name != null ? name : 'type';
454 checkAttributes(html, ['name'], context,
455 optionalAttributes: ['experimental', 'deprecated']);
456 TypeDecl type = processContentsAsType(html, context);
457 bool experimental = html.attributes['experimental'] == 'true';
458 bool deprecated = html.attributes['deprecated'] == 'true';
459 return new TypeDefinition(name, type, html,
460 experimental: experimental, deprecated: deprecated);
461 }
462
463 /**
464 * Create a [TypeEnum] from an HTML description.
465 */
466 TypeEnum typeEnumFromHtml(dom.Element html, String context) {
467 checkName(html, 'enum', context);
468 checkAttributes(html, [], context);
469 List<TypeEnumValue> values = <TypeEnumValue>[];
470 recurse(html, context, {
471 'value': (dom.Element child) {
472 values.add(typeEnumValueFromHtml(child, context));
473 }
474 });
475 return new TypeEnum(values, html);
476 }
477
478 /**
479 * Create a [TypeEnumValue] from an HTML description such as:
480 *
481 * <enum>
482 * <code>VALUE</code>
483 * </enum>
484 *
485 * Where VALUE is the text of the enumerated value.
486 *
487 * Child elements can occur in any order.
488 */
489 TypeEnumValue typeEnumValueFromHtml(dom.Element html, String context) {
490 checkName(html, 'value', context);
491 checkAttributes(html, [], context, optionalAttributes: ['deprecated']);
492 bool deprecated = html.attributes['deprecated'] == 'true';
493 List<String> values = <String>[];
494 recurse(html, context, {
495 'code': (dom.Element child) {
496 String text = innerText(child).trim();
497 values.add(text);
498 }
499 });
500 if (values.length != 1) {
501 throw new Exception('$context: Exactly one value must be specified');
502 }
503 return new TypeEnumValue(values[0], html, deprecated: deprecated);
504 }
505
506 /**
507 * Create a [TypeObjectField] from an HTML description such as:
508 *
509 * <field name="fieldName">
510 * TYPE
511 * </field>
512 *
513 * Where TYPE is any HTML that can be parsed by [typeDeclFromHtml].
514 *
515 * In addition, the attribute optional="true" may be used to specify that the
516 * field is optional, and the attribute value="..." may be used to specify tha t
517 * the field is required to have a certain value.
518 *
519 * Child elements can occur in any order.
520 */
521 TypeObjectField typeObjectFieldFromHtml(dom.Element html, String context) {
522 checkName(html, 'field', context);
523 String name = html.attributes['name'];
524 context = '$context.${name != null ? name : 'field'}';
525 checkAttributes(html, ['name'], context,
526 optionalAttributes: ['optional', 'value', 'deprecated']);
527 bool deprecated = html.attributes['deprecated'] == 'true';
528 bool optional = false;
529 String optionalString = html.attributes['optional'];
530 if (optionalString != null) {
531 switch (optionalString) {
532 case 'true':
533 optional = true;
534 break;
535 case 'false':
536 optional = false;
537 break;
538 default:
539 throw new Exception(
540 '$context: field contains invalid "optional" attribute: "$optional String"');
541 }
542 }
543 String value = html.attributes['value'];
544 TypeDecl type = processContentsAsType(html, context);
545 return new TypeObjectField(name, type, html,
546 optional: optional, value: value, deprecated: deprecated);
547 }
548
549 /**
550 * Create a [TypeObject] from an HTML description.
551 */
552 TypeObject typeObjectFromHtml(dom.Element html, String context) {
553 checkAttributes(html, [], context, optionalAttributes: ['experimental']);
554 List<TypeObjectField> fields = <TypeObjectField>[];
555 recurse(html, context, {
556 'field': (dom.Element child) {
557 fields.add(typeObjectFieldFromHtml(child, context));
558 }
559 });
560 bool experimental = html.attributes['experimental'] == 'true';
561 return new TypeObject(fields, html, experimental: experimental);
562 }
563
564 /**
565 * Create a [Types] object from an HTML representation such as:
566 *
567 * <types>
568 * <type name="...">...</type> <!-- zero or more -->
569 * </types>
570 */
571 Types typesFromHtml(dom.Element html) {
572 checkName(html, 'types');
573 String context = 'types';
574 checkAttributes(html, [], context);
575 List<String> importUris = <String>[];
576 Map<String, TypeDefinition> typeMap = <String, TypeDefinition>{};
577 List<dom.Element> childElements = <dom.Element>[];
578 recurse(html, context, {
579 'include': (dom.Element child) {
580 String importUri = child.attributes['import'];
581 if (importUri != null) {
582 importUris.add(importUri);
583 }
584 String relativePath = child.attributes['path'];
585 String path = normalize(join(dirname(filePath), relativePath));
586 ApiReader reader = new ApiReader(path);
587 Api api = reader.readApi();
588 for (TypeDefinition typeDefinition in api.types) {
589 typeDefinition.isExternal = true;
590 childElements.add(typeDefinition.html);
591 typeMap[typeDefinition.name] = typeDefinition;
592 }
593 },
594 'type': (dom.Element child) {
595 TypeDefinition typeDefinition = typeDefinitionFromHtml(child);
596 typeMap[typeDefinition.name] = typeDefinition;
597 }
598 });
599 for (dom.Element element in childElements) {
600 html.append(element);
601 }
602 Types types = new Types(typeMap, html);
603 types.importUris.addAll(importUris);
604 return types;
321 } 605 }
322 } 606 }
323
324 /**
325 * Create a [Refactoring] object from an HTML representation such as:
326 *
327 * <refactoring kind="refactoringKind">
328 * <feedback>...</feedback> <!-- optional -->
329 * <options>...</options> <!-- optional -->
330 * </refactoring>
331 *
332 * <feedback> and <options> have the same form as <object>, as described in
333 * [typeDeclFromHtml].
334 *
335 * Child elements can occur in any order.
336 */
337 Refactoring refactoringFromHtml(dom.Element html) {
338 checkName(html, 'refactoring');
339 String kind = html.attributes['kind'];
340 String context = kind != null ? kind : 'refactoring';
341 checkAttributes(html, ['kind'], context);
342 TypeDecl feedback;
343 TypeDecl options;
344 recurse(html, context, {
345 'feedback': (dom.Element child) {
346 feedback = typeObjectFromHtml(child, '$context.feedback');
347 },
348 'options': (dom.Element child) {
349 options = typeObjectFromHtml(child, '$context.options');
350 }
351 });
352 return new Refactoring(kind, feedback, options, html);
353 }
354
355 /**
356 * Create a [Refactorings] object from an HTML representation such as:
357 *
358 * <refactorings>
359 * <refactoring kind="...">...</refactoring> <!-- zero or more -->
360 * </refactorings>
361 */
362 Refactorings refactoringsFromHtml(dom.Element html) {
363 checkName(html, 'refactorings');
364 String context = 'refactorings';
365 checkAttributes(html, [], context);
366 List<Refactoring> refactorings = <Refactoring>[];
367 recurse(html, context, {
368 'refactoring': (dom.Element child) {
369 refactorings.add(refactoringFromHtml(child));
370 }
371 });
372 return new Refactorings(refactorings, html);
373 }
374
375 /**
376 * Create a [Request] object from an HTML representation such as:
377 *
378 * <request method="methodName">
379 * <params>...</params> <!-- optional -->
380 * <result>...</result> <!-- optional -->
381 * </request>
382 *
383 * Note that the method name should not include the domain name.
384 *
385 * <params> and <result> have the same form as <object>, as described in
386 * [typeDeclFromHtml].
387 *
388 * Child elements can occur in any order.
389 */
390 Request requestFromHtml(dom.Element html, String context) {
391 String domainName = getAncestor(html, 'domain', context).attributes['name'];
392 checkName(html, 'request', context);
393 String method = html.attributes['method'];
394 context = '$context.${method != null ? method : 'method'}';
395 checkAttributes(html, ['method'], context,
396 optionalAttributes: ['experimental', 'deprecated']);
397 bool experimental = html.attributes['experimental'] == 'true';
398 bool deprecated = html.attributes['deprecated'] == 'true';
399 TypeDecl params;
400 TypeDecl result;
401 recurse(html, context, {
402 'params': (dom.Element child) {
403 params = typeObjectFromHtml(child, '$context.params');
404 },
405 'result': (dom.Element child) {
406 result = typeObjectFromHtml(child, '$context.result');
407 }
408 });
409 return new Request(domainName, method, params, result, html,
410 experimental: experimental, deprecated: deprecated);
411 }
412
413 /**
414 * Create a [TypeDefinition] object from an HTML representation such as:
415 *
416 * <type name="typeName">
417 * TYPE
418 * </type>
419 *
420 * Where TYPE is any HTML that can be parsed by [typeDeclFromHtml].
421 *
422 * Child elements can occur in any order.
423 */
424 TypeDefinition typeDefinitionFromHtml(dom.Element html) {
425 checkName(html, 'type');
426 String name = html.attributes['name'];
427 String context = name != null ? name : 'type';
428 checkAttributes(html, ['name'], context,
429 optionalAttributes: ['experimental', 'deprecated']);
430 TypeDecl type = processContentsAsType(html, context);
431 bool experimental = html.attributes['experimental'] == 'true';
432 bool deprecated = html.attributes['deprecated'] == 'true';
433 return new TypeDefinition(name, type, html,
434 experimental: experimental, deprecated: deprecated);
435 }
436
437 /**
438 * Create a [TypeEnum] from an HTML description.
439 */
440 TypeEnum typeEnumFromHtml(dom.Element html, String context) {
441 checkName(html, 'enum', context);
442 checkAttributes(html, [], context);
443 List<TypeEnumValue> values = <TypeEnumValue>[];
444 recurse(html, context, {
445 'value': (dom.Element child) {
446 values.add(typeEnumValueFromHtml(child, context));
447 }
448 });
449 return new TypeEnum(values, html);
450 }
451
452 /**
453 * Create a [TypeEnumValue] from an HTML description such as:
454 *
455 * <enum>
456 * <code>VALUE</code>
457 * </enum>
458 *
459 * Where VALUE is the text of the enumerated value.
460 *
461 * Child elements can occur in any order.
462 */
463 TypeEnumValue typeEnumValueFromHtml(dom.Element html, String context) {
464 checkName(html, 'value', context);
465 checkAttributes(html, [], context, optionalAttributes: ['deprecated']);
466 bool deprecated = html.attributes['deprecated'] == 'true';
467 List<String> values = <String>[];
468 recurse(html, context, {
469 'code': (dom.Element child) {
470 String text = innerText(child).trim();
471 values.add(text);
472 }
473 });
474 if (values.length != 1) {
475 throw new Exception('$context: Exactly one value must be specified');
476 }
477 return new TypeEnumValue(values[0], html, deprecated: deprecated);
478 }
479
480 /**
481 * Create a [TypeObjectField] from an HTML description such as:
482 *
483 * <field name="fieldName">
484 * TYPE
485 * </field>
486 *
487 * Where TYPE is any HTML that can be parsed by [typeDeclFromHtml].
488 *
489 * In addition, the attribute optional="true" may be used to specify that the
490 * field is optional, and the attribute value="..." may be used to specify that
491 * the field is required to have a certain value.
492 *
493 * Child elements can occur in any order.
494 */
495 TypeObjectField typeObjectFieldFromHtml(dom.Element html, String context) {
496 checkName(html, 'field', context);
497 String name = html.attributes['name'];
498 context = '$context.${name != null ? name : 'field'}';
499 checkAttributes(html, ['name'], context,
500 optionalAttributes: ['optional', 'value', 'deprecated']);
501 bool deprecated = html.attributes['deprecated'] == 'true';
502 bool optional = false;
503 String optionalString = html.attributes['optional'];
504 if (optionalString != null) {
505 switch (optionalString) {
506 case 'true':
507 optional = true;
508 break;
509 case 'false':
510 optional = false;
511 break;
512 default:
513 throw new Exception(
514 '$context: field contains invalid "optional" attribute: "$optionalSt ring"');
515 }
516 }
517 String value = html.attributes['value'];
518 TypeDecl type = processContentsAsType(html, context);
519 return new TypeObjectField(name, type, html,
520 optional: optional, value: value, deprecated: deprecated);
521 }
522
523 /**
524 * Create a [TypeObject] from an HTML description.
525 */
526 TypeObject typeObjectFromHtml(dom.Element html, String context) {
527 checkAttributes(html, [], context, optionalAttributes: ['experimental']);
528 List<TypeObjectField> fields = <TypeObjectField>[];
529 recurse(html, context, {
530 'field': (dom.Element child) {
531 fields.add(typeObjectFieldFromHtml(child, context));
532 }
533 });
534 bool experimental = html.attributes['experimental'] == 'true';
535 return new TypeObject(fields, html, experimental: experimental);
536 }
537
538 /**
539 * Create a [Types] object from an HTML representation such as:
540 *
541 * <types>
542 * <type name="...">...</type> <!-- zero or more -->
543 * </types>
544 */
545 Types typesFromHtml(dom.Element html) {
546 checkName(html, 'types');
547 String context = 'types';
548 checkAttributes(html, [], context);
549 Map<String, TypeDefinition> types = <String, TypeDefinition>{};
550 recurse(html, context, {
551 'type': (dom.Element child) {
552 TypeDefinition typeDefinition = typeDefinitionFromHtml(child);
553 types[typeDefinition.name] = typeDefinition;
554 }
555 });
556 return new Types(types, html);
557 }
558
559 typedef void ElementProcessor(dom.Element element);
560
561 typedef void TextProcessor(dom.Text text);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698