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

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

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

Powered by Google App Engine
This is Rietveld 408576698