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