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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: pkg/analysis_server/spec/from_html.dart
diff --git a/pkg/analysis_server/spec/from_html.dart b/pkg/analysis_server/spec/from_html.dart
new file mode 100644
index 0000000000000000000000000000000000000000..cb2f17389ea03768fa277cb367574be6a2d80566
--- /dev/null
+++ b/pkg/analysis_server/spec/from_html.dart
@@ -0,0 +1,485 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * Code for reading an HTML API description.
+ */
+library fromHtml;
+
+import 'dart:io';
+
+import 'package:html5lib/dom.dart' as dom;
+import 'package:html5lib/parser.dart' as parser;
+
+import 'api.dart';
+import 'html_tools.dart';
+
+/**
+ * Check that the given [element] has the given [expectedName].
+ */
+void checkName(dom.Element element, String expectedName) {
+ if (element.localName != expectedName) {
+ throw new Exception('Expected $expectedName, found ${element.localName}');
+ }
+}
+
+/**
+ * Check that the given [element] has all of the attributes in
+ * [requiredAttributes], possibly some of the attributes in
+ * [optionalAttributes], and no others.
+ */
+void checkAttributes(dom.Element element, List<String>
+ requiredAttributes, {List<String> optionalAttributes: const []}) {
+ Set<String> attributesFound = new Set<String>();
+ element.attributes.forEach((String name, String value) {
+ if (!requiredAttributes.contains(name) && !optionalAttributes.contains(name
+ )) {
+ throw new Exception('Unexpected attribute in ${element.localName}: $name'
+ );
+ }
+ attributesFound.add(name);
+ });
+ for (String expectedAttribute in requiredAttributes) {
+ if (!attributesFound.contains(expectedAttribute)) {
+ throw new Exception(
+ '${element.localName} must contain attribute ${expectedAttribute}');
+ }
+ }
+}
+
+const List<String> specialElements = const ['domain', 'feedback',
+ 'object', 'refactorings', 'refactoring', 'type', 'types', 'request',
+ 'notification', 'params', 'result', 'field', 'list', 'map', 'enum', 'key',
+ 'value', 'options', 'ref', 'code', 'version'];
+
+typedef void ElementProcessor(dom.Element element);
+typedef void TextProcessor(dom.Text text);
+
+void recurse(dom.Element parent, Map<String, ElementProcessor>
+ elementProcessors) {
+ for (String key in elementProcessors.keys) {
+ if (!specialElements.contains(key)) {
+ throw new Exception('$key is not a special element');
+ }
+ }
+ for (dom.Node node in parent.nodes) {
+ if (node is dom.Element) {
+ if (elementProcessors.containsKey(node.localName)) {
+ elementProcessors[node.localName](node);
+ } else if (specialElements.contains(node.localName)) {
+ throw new Exception('Unexpected use of <${node.localName}');
+ } else {
+ recurse(node, elementProcessors);
+ }
+ }
+ }
+}
+
+dom.Element getAncestor(dom.Element html, String name) {
+ dom.Element ancestor = html.parent;
+ while (ancestor != null) {
+ if (ancestor.localName == name) {
+ return ancestor;
+ }
+ ancestor = ancestor.parent;
+ }
+ throw new Exception('<${html.localName}> must be nested within <$name>');
+}
+
+/**
+ * Create an [Api] object from an HTML representation such as:
+ *
+ * <html>
+ * ...
+ * <body>
+ * ... <version>1.0</version> ...
+ * <domain name="...">...</domain> <!-- zero or more -->
+ * <types>...</types>
+ * <refactorings>...</refactorings>
+ * </body>
+ * </html>
+ *
+ * Child elements of <api> can occur in any order.
+ */
+Api apiFromHtml(dom.Element html) {
+ Api api;
+ List<String> versions = <String>[];
+ List<Domain> domains = <Domain>[];
+ Types types = null;
+ Refactorings refactorings = null;
+ recurse(html, {
+ 'domain': (dom.Element element) {
+ domains.add(domainFromHtml(element));
+ },
+ 'refactorings': (dom.Element element) {
+ refactorings = refactoringsFromHtml(element);
+ },
+ 'types': (dom.Element element) {
+ types = typesFromHtml(element);
+ },
+ 'version': (dom.Element element) {
+ versions.add(innerText(element));
+ }
+ });
+ if (versions.length != 1) {
+ throw new Exception('The API must contain exactly one <version> element');
+ }
+ api = new Api(versions[0], domains, types, refactorings, html);
+ return api;
+}
+
+/**
+ * Create a [Refactorings] object from an HTML representation such as:
+ *
+ * <refactorings>
+ * <refactoring kind="...">...</refactoring> <!-- zero or more -->
+ * </refactorings>
+ */
+Refactorings refactoringsFromHtml(dom.Element html) {
+ checkName(html, 'refactorings');
+ checkAttributes(html, []);
+ List<Refactoring> refactorings = <Refactoring>[];
+ recurse(html, {
+ 'refactoring': (dom.Element child) {
+ refactorings.add(refactoringFromHtml(child));
+ }
+ });
+ return new Refactorings(refactorings, html);
+}
+
+/**
+ * Create a [Refactoring] object from an HTML representation such as:
+ *
+ * <refactoring kind="refactoringKind">
+ * <feedback>...</feedback> <!-- optional -->
+ * <options>...</options> <!-- optional -->
+ * </refactoring>
+ *
+ * <feedback> and <options> have the same form as <object>, as described in
+ * [typeDeclFromHtml].
+ *
+ * Child elements can occur in any order.
+ */
+Refactoring refactoringFromHtml(dom.Element html) {
+ checkName(html, 'refactoring');
+ checkAttributes(html, ['kind']);
+ String kind = html.attributes['kind'];
+ TypeDecl feedback;
+ TypeDecl options;
+ recurse(html, {
+ 'feedback': (dom.Element child) {
+ feedback = typeObjectFromHtml(child);
+ },
+ 'options': (dom.Element child) {
+ options = typeObjectFromHtml(child);
+ }
+ });
+ return new Refactoring(kind, feedback, options, html);
+}
+
+/**
+ * Create a [Types] object from an HTML representation such as:
+ *
+ * <types>
+ * <type name="...">...</type> <!-- zero or more -->
+ * </types>
+ */
+Types typesFromHtml(dom.Element html) {
+ checkName(html, 'types');
+ checkAttributes(html, []);
+ Map<String, TypeDefinition> types = <String, TypeDefinition> {};
+ recurse(html, {
+ 'type': (dom.Element child) {
+ TypeDefinition typeDefinition = typeDefinitionFromHtml(child);
+ types[typeDefinition.name] = typeDefinition;
+ }
+ });
+ return new Types(types, html);
+}
+
+/**
+ * Create a [TypeDefinition] object from an HTML representation such as:
+ *
+ * <type name="typeName">
+ * TYPE
+ * </type>
+ *
+ * Where TYPE is any HTML that can be parsed by [typeDeclFromHtml].
+ *
+ * Child elements can occur in any order.
+ */
+TypeDefinition typeDefinitionFromHtml(dom.Element html) {
+ checkName(html, 'type');
+ checkAttributes(html, ['name']);
+ String name = html.attributes['name'];
+ TypeDecl type = processContentsAsType(html);
+ return new TypeDefinition(name, type, html);
+}
+
+/**
+ * Create a [Domain] object from an HTML representation such as:
+ *
+ * <domain name="domainName">
+ * <request method="...">...</request> <!-- zero or more -->
+ * <notification event="...">...</notification> <!-- zero or more -->
+ * </domain>
+ *
+ * Child elements can occur in any order.
+ */
+Domain domainFromHtml(dom.Element html) {
+ checkName(html, 'domain');
+ checkAttributes(html, ['name']);
+ String name = html.attributes['name'];
+ List<Request> requests = <Request>[];
+ List<Notification> notifications = <Notification>[];
+ recurse(html, {
+ 'request': (dom.Element child) {
+ requests.add(requestFromHtml(child));
+ },
+ 'notification': (dom.Element child) {
+ notifications.add(notificationFromHtml(child));
+ }
+ });
+ return new Domain(name, requests, notifications, html);
+}
+
+/**
+ * Create a [Request] object from an HTML representation such as:
+ *
+ * <request method="methodName">
+ * <params>...</params> <!-- optional -->
+ * <result>...</result> <!-- optional -->
+ * </request>
+ *
+ * Note that the method name should not include the domain name.
+ *
+ * <params> and <result> have the same form as <object>, as described in
+ * [typeDeclFromHtml].
+ *
+ * Child elements can occur in any order.
+ */
+Request requestFromHtml(dom.Element html) {
+ String domainName = getAncestor(html, 'domain').attributes['name'];
+ checkName(html, 'request');
+ checkAttributes(html, ['method']);
+ String method = html.attributes['method'];
+ TypeDecl params;
+ TypeDecl result;
+ recurse(html, {
+ 'params': (dom.Element child) {
+ params = typeObjectFromHtml(child);
+ },
+ 'result': (dom.Element child) {
+ result = typeObjectFromHtml(child);
+ }
+ });
+ return new Request(domainName, method, params, result, html);
+}
+
+/**
+ * Create a [Notification] object from an HTML representation such as:
+ *
+ * <notification event="methodName">
+ * <params>...</params> <!-- optional -->
+ * </notification>
+ *
+ * Note that the event name should not include the domain name.
+ *
+ * <params> has the same form as <object>, as described in [typeDeclFromHtml].
+ *
+ * Child elements can occur in any order.
+ */
+Notification notificationFromHtml(dom.Element html) {
+ String domainName = getAncestor(html, 'domain').attributes['name'];
+ checkName(html, 'notification');
+ checkAttributes(html, ['event']);
+ String event = html.attributes['event'];
+ TypeDecl params;
+ recurse(html, {
+ 'params': (dom.Element child) {
+ params = typeObjectFromHtml(child);
+ }
+ });
+ return new Notification(domainName, event, params, html);
+}
+
+/**
+ * Create a [TypeDecl] from an HTML description. The following forms are
+ * supported.
+ *
+ * To refer to a type declared elsewhere (or a built-in type):
+ *
+ * <ref>typeName</ref>
+ *
+ * For a list: <list>ItemType</list>
+ *
+ * For a map: <map><key>KeyType</key><value>ValueType</value></map>
+ *
+ * For a JSON object:
+ *
+ * <object>
+ * <field name="...">...</field> <!-- zero or more -->
+ * </object>
+ *
+ * For an enum:
+ *
+ * <enum>
+ * <value>...</value> <!-- zero or more -->
+ * </enum>
+ */
+TypeDecl processContentsAsType(dom.Element html) {
+ List<TypeDecl> types = <TypeDecl>[];
+ recurse(html, {
+ 'object': (dom.Element child) {
+ types.add(typeObjectFromHtml(child));
+ },
+ 'list': (dom.Element child) {
+ checkAttributes(child, []);
+ types.add(new TypeList(processContentsAsType(child), child));
+ },
+ 'map': (dom.Element child) {
+ checkAttributes(child, []);
+ TypeDecl keyType;
+ TypeDecl valueType;
+ recurse(child, {
+ 'key': (dom.Element child) {
+ if (keyType != null) {
+ throw new Exception('Key type already specified');
+ }
+ keyType = processContentsAsType(child);
+ },
+ 'value': (dom.Element child) {
+ if (valueType != null) {
+ throw new Exception('Value type already specified');
+ }
+ valueType = processContentsAsType(child);
+ }
+ });
+ if (keyType == null) {
+ throw new Exception('Key type not specified');
+ }
+ if (valueType == null) {
+ throw new Exception('Value type not specified');
+ }
+ types.add(new TypeMap(keyType, valueType, child));
+ },
+ 'enum': (dom.Element child) {
+ types.add(typeEnumFromHtml(child));
+ },
+ 'ref': (dom.Element child) {
+ checkAttributes(child, []);
+ types.add(new TypeReference(innerText(child), child));
+ }
+ });
+ if (types.length != 1) {
+ throw new Exception('Exactly one type must be specified');
+ }
+ return types[0];
+}
+
+/**
+ * Create a [TypeEnum] from an HTML description.
+ */
+TypeEnum typeEnumFromHtml(dom.Element html) {
+ checkName(html, 'enum');
+ checkAttributes(html, []);
+ List<TypeEnumValue> values = <TypeEnumValue>[];
+ recurse(html, {
+ 'value': (dom.Element child) {
+ values.add(typeEnumValueFromHtml(child));
+ }
+ });
+ return new TypeEnum(values, html);
+}
+
+/**
+ * Create a [TypeEnumValue] from an HTML description such as:
+ *
+ * <enum>
+ * <code>VALUE</code>
+ * </enum>
+ *
+ * Where VALUE is the text of the enumerated value.
+ *
+ * Child elements can occur in any order.
+ */
+TypeEnumValue typeEnumValueFromHtml(dom.Element html) {
+ checkName(html, 'value');
+ checkAttributes(html, []);
+ List<String> values = <String>[];
+ recurse(html, {
+ 'code': (dom.Element child) {
+ String text = innerText(child).trim();
+ values.add(text);
+ }
+ });
+ if (values.length != 1) {
+ throw new Exception('Exactly one value must be specified');
+ }
+ return new TypeEnumValue(values[0], html);
+}
+
+/**
+ * Create a [TypeObject] from an HTML description.
+ */
+TypeObject typeObjectFromHtml(dom.Element html) {
+ checkAttributes(html, []);
+ List<TypeObjectField> fields = <TypeObjectField>[];
+ recurse(html, {
+ 'field': (dom.Element child) {
+ fields.add(typeObjectFieldFromHtml(child));
+ }
+ });
+ return new TypeObject(fields, html);
+}
+
+/**
+ * Create a [TypeObjectField] from an HTML description such as:
+ *
+ * <field name="fieldName">
+ * TYPE
+ * </field>
+ *
+ * Where TYPE is any HTML that can be parsed by [typeDeclFromHtml].
+ *
+ * In addition, the attribute optional="true" may be used to specify that the
+ * field is optional, and the attribute value="..." may be used to specify that
+ * the field is required to have a certain value.
+ *
+ * Child elements can occur in any order.
+ */
+TypeObjectField typeObjectFieldFromHtml(dom.Element html) {
+ checkName(html, 'field');
+ checkAttributes(html, ['name'], optionalAttributes: ['optional', 'value']);
+ String name = html.attributes['name'];
+ bool optional = false;
+ String optionalString = html.attributes['optional'];
+ if (optionalString != null) {
+ switch (optionalString) {
+ case 'true':
+ optional = true;
+ break;
+ case 'false':
+ optional = false;
+ break;
+ default:
+ throw new Exception(
+ 'field contains invalid "optional" attribute: "$optionalString"');
+ }
+ }
+ String value = html.attributes['value'];
+ TypeDecl type = processContentsAsType(html);
+ return new TypeObjectField(name, type, html, optional: optional, value: value
+ );
+}
+
+/**
+ * Read the API description from the file 'spec_input.html'.
+ */
+Api readApi() {
+ File htmlFile = new File('spec_input.html');
+ String htmlContents = htmlFile.readAsStringSync();
+ dom.Document document = parser.parse(htmlContents);
+ return apiFromHtml(document.firstChild);
+}

Powered by Google App Engine
This is Rietveld 408576698