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

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

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

Powered by Google App Engine
This is Rietveld 408576698