| Index: utils/apidoc/mdn/extract.dart
|
| diff --git a/utils/apidoc/mdn/extract.dart b/utils/apidoc/mdn/extract.dart
|
| deleted file mode 100644
|
| index 210e84cc208ecd3abd70afe226cc8eb10241e05e..0000000000000000000000000000000000000000
|
| --- a/utils/apidoc/mdn/extract.dart
|
| +++ /dev/null
|
| @@ -1,1321 +0,0 @@
|
| -// Copyright (c) 2012, 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.
|
| -
|
| -import "dart:collection";
|
| -import 'dart:convert';
|
| -import 'dart:html';
|
| -
|
| -// Workaround for HTML lib missing feature.
|
| -Range newRange() {
|
| - return document.createRange();
|
| -}
|
| -
|
| -// Temporary range object to optimize performance computing client rects
|
| -// from text nodes.
|
| -Range _tempRange;
|
| -// Hacks because ASYNC measurement is annoying when just writing a script.
|
| -ClientRect getClientRect(Node n) {
|
| - if (n is Element) {
|
| - return n.$dom_getBoundingClientRect();
|
| - } else {
|
| - // Crazy hacks that works for nodes.... create a range and measure it.
|
| - if (_tempRange == null) {
|
| - _tempRange = newRange();
|
| - }
|
| - _tempRange.setStartBefore(n);
|
| - _tempRange.setEndAfter(n);
|
| - return _tempRange.getBoundingClientRect();
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * CSS class that is added to elements in the DOM to indicate that they should
|
| - * be removed when extracting blocks of documentation. This is helpful when
|
| - * running this script in a web browser as it is easy to visually see what
|
| - * blocks of information were extracted when using CSS such as DEBUG_CSS
|
| - * which highlights elements that should be removed.
|
| - */
|
| -const DART_REMOVED = "dart-removed";
|
| -
|
| -const DEBUG_CSS = """
|
| -<style type="text/css">
|
| - .dart-removed {
|
| - background-color: rgba(255, 0, 0, 0.5);
|
| - }
|
| -</style>""";
|
| -
|
| -const MIN_PIXELS_DIFFERENT_LINES = 10;
|
| -
|
| -const IDL_SELECTOR = "pre.eval, pre.idl";
|
| -
|
| -Map data;
|
| -
|
| -// TODO(rnystrom): Hack! Copied from domTypes.json. Instead of hard-coding
|
| -// these, should use the same mapping that the DOM/HTML code generators use.
|
| -var domTypes;
|
| -const domTypesRaw = const [
|
| - "AbstractWorker", "ArrayBuffer", "ArrayBufferView", "Attr",
|
| - "AudioBuffer", "AudioBufferSourceNode", "AudioChannelMerger",
|
| - "AudioChannelSplitter", "AudioContext", "AudioDestinationNode",
|
| - "AudioGain", "AudioGainNode", "AudioListener", "AudioNode",
|
| - "AudioPannerNode", "AudioParam", "AudioProcessingEvent",
|
| - "AudioSourceNode", "BarInfo", "BeforeLoadEvent", "BiquadFilterNode",
|
| - "Blob", "CDATASection", "CSSCharsetRule", "CSSFontFaceRule",
|
| - "CSSImportRule", "CSSMediaRule", "CSSPageRule", "CSSPrimitiveValue",
|
| - "CSSRule", "CSSRuleList", "CSSStyleDeclaration", "CSSStyleRule",
|
| - "CSSStyleSheet", "CSSUnknownRule", "CSSValue", "CSSValueList",
|
| - "CanvasGradient", "CanvasPattern", "CanvasPixelArray",
|
| - "CanvasRenderingContext", "CanvasRenderingContext2D",
|
| - "CharacterData", "ClientRect", "ClientRectList", "Clipboard",
|
| - "CloseEvent", "Comment", "CompositionEvent", "Console",
|
| - "ConvolverNode", "Coordinates", "Counter", "Crypto", "CustomEvent",
|
| - "DOMApplicationCache", "DOMException", "DOMFileSystem",
|
| - "DOMFileSystemSync", "DOMFormData", "DOMImplementation",
|
| - "DOMMimeType", "DOMMimeTypeArray", "DOMParser", "DOMPlugin",
|
| - "DOMPluginArray", "DOMSelection", "DOMSettableTokenList",
|
| - "DOMTokenList", "DOMURL", "DOMWindow", "DataTransferItem",
|
| - "DataTransferItemList", "DataView", "Database", "DatabaseSync",
|
| - "DedicatedWorkerContext", "DelayNode", "DeviceMotionEvent",
|
| - "DeviceOrientationEvent", "DirectoryEntry", "DirectoryEntrySync",
|
| - "DirectoryReader", "DirectoryReaderSync", "Document",
|
| - "DocumentFragment", "DocumentType", "DynamicsCompressorNode",
|
| - "Element", "ElementTimeControl", "ElementTraversal", "Entity",
|
| - "EntityReference", "Entry", "EntryArray", "EntryArraySync",
|
| - "EntrySync", "ErrorEvent", "Event", "EventException", "EventSource",
|
| - "EventTarget", "File", "FileEntry", "FileEntrySync", "FileError",
|
| - "FileException", "FileList", "FileReader", "FileReaderSync",
|
| - "FileWriter", "FileWriterSync", "Float32Array", "Float64Array",
|
| - "Geolocation", "Geoposition", "HTMLAllCollection",
|
| - "HTMLAnchorElement", "HTMLAppletElement", "HTMLAreaElement",
|
| - "HTMLAudioElement", "HTMLBRElement", "HTMLBaseElement",
|
| - "HTMLBaseFontElement", "HTMLBodyElement", "HTMLButtonElement",
|
| - "HTMLCanvasElement", "HTMLCollection", "HTMLDListElement",
|
| - "HTMLDataListElement", "HTMLDetailsElement", "HTMLDirectoryElement",
|
| - "HTMLDivElement", "HTMLDocument", "HTMLElement", "HTMLEmbedElement",
|
| - "HTMLFieldSetElement", "HTMLFontElement", "HTMLFormElement",
|
| - "HTMLFrameElement", "HTMLFrameSetElement", "HTMLHRElement",
|
| - "HTMLHeadElement", "HTMLHeadingElement", "HTMLHtmlElement",
|
| - "HTMLIFrameElement", "HTMLImageElement", "HTMLInputElement",
|
| - "HTMLIsIndexElement", "HTMLKeygenElement", "HTMLLIElement",
|
| - "HTMLLabelElement", "HTMLLegendElement", "HTMLLinkElement",
|
| - "HTMLMapElement", "HTMLMarqueeElement", "HTMLMediaElement",
|
| - "HTMLMenuElement", "HTMLMetaElement", "HTMLMeterElement",
|
| - "HTMLModElement", "HTMLOListElement", "HTMLObjectElement",
|
| - "HTMLOptGroupElement", "HTMLOptionElement", "HTMLOptionsCollection",
|
| - "HTMLOutputElement", "HTMLParagraphElement", "HTMLParamElement",
|
| - "HTMLPreElement", "HTMLProgressElement", "HTMLQuoteElement",
|
| - "HTMLScriptElement", "HTMLSelectElement", "HTMLSourceElement",
|
| - "HTMLSpanElement", "HTMLStyleElement", "HTMLTableCaptionElement",
|
| - "HTMLTableCellElement", "HTMLTableColElement", "HTMLTableElement",
|
| - "HTMLTableRowElement", "HTMLTableSectionElement",
|
| - "HTMLTextAreaElement", "HTMLTitleElement", "HTMLTrackElement",
|
| - "HTMLUListElement", "HTMLUnknownElement", "HTMLVideoElement",
|
| - "HashChangeEvent", "HighPass2FilterNode", "History", "IDBAny",
|
| - "IDBCursor", "IDBCursorWithValue", "IDBDatabase",
|
| - "IDBDatabaseError", "IDBDatabaseException", "IDBFactory",
|
| - "IDBIndex", "IDBKey", "IDBKeyRange", "IDBObjectStore", "IDBRequest",
|
| - "IDBTransaction", "IDBVersionChangeEvent",
|
| - "IDBVersionChangeRequest", "ImageData", "InjectedScriptHost",
|
| - "InspectorFrontendHost", "Int16Array", "Int32Array", "Int8Array",
|
| - "JavaScriptAudioNode", "JavaScriptCallFrame", "KeyboardEvent",
|
| - "Location", "LowPass2FilterNode", "MediaElementAudioSourceNode",
|
| - "MediaError", "MediaList", "MediaQueryList",
|
| - "MediaQueryListListener", "MemoryInfo", "MessageChannel",
|
| - "MessageEvent", "MessagePort", "Metadata", "MouseEvent",
|
| - "MutationCallback", "MutationEvent", "MutationRecord",
|
| - "NamedNodeMap", "Navigator", "NavigatorUserMediaError",
|
| - "NavigatorUserMediaSuccessCallback", "Node", "NodeFilter",
|
| - "NodeIterator", "NodeList", "NodeSelector", "Notation",
|
| - "Notification", "NotificationCenter", "OESStandardDerivatives",
|
| - "OESTextureFloat", "OESVertexArrayObject",
|
| - "OfflineAudioCompletionEvent", "OperationNotAllowedException",
|
| - "OverflowEvent", "PageTransitionEvent", "Performance",
|
| - "PerformanceNavigation", "PerformanceTiming", "PopStateEvent",
|
| - "PositionError", "ProcessingInstruction", "ProgressEvent",
|
| - "RGBColor", "Range", "RangeException", "RealtimeAnalyserNode",
|
| - "Rect", "SQLError", "SQLException", "SQLResultSet",
|
| - "SQLResultSetRowList", "SQLTransaction", "SQLTransactionSync",
|
| - "SVGAElement", "SVGAltGlyphDefElement", "SVGAltGlyphElement",
|
| - "SVGAltGlyphItemElement", "SVGAngle", "SVGAnimateColorElement",
|
| - "SVGAnimateElement", "SVGAnimateMotionElement",
|
| - "SVGAnimateTransformElement", "SVGAnimatedAngle",
|
| - "SVGAnimatedBoolean", "SVGAnimatedEnumeration",
|
| - "SVGAnimatedInteger", "SVGAnimatedLength", "SVGAnimatedLengthList",
|
| - "SVGAnimatedNumber", "SVGAnimatedNumberList",
|
| - "SVGAnimatedPreserveAspectRatio", "SVGAnimatedRect",
|
| - "SVGAnimatedString", "SVGAnimatedTransformList",
|
| - "SVGAnimationElement", "SVGCircleElement", "SVGClipPathElement",
|
| - "SVGColor", "SVGComponentTransferFunctionElement",
|
| - "SVGCursorElement", "SVGDefsElement", "SVGDescElement",
|
| - "SVGDocument", "SVGElement", "SVGElementInstance",
|
| - "SVGElementInstanceList", "SVGEllipseElement", "SVGException",
|
| - "SVGExternalResourcesRequired", "SVGFEBlendElement",
|
| - "SVGFEColorMatrixElement", "SVGFEComponentTransferElement",
|
| - "SVGFECompositeElement", "SVGFEConvolveMatrixElement",
|
| - "SVGFEDiffuseLightingElement", "SVGFEDisplacementMapElement",
|
| - "SVGFEDistantLightElement", "SVGFEDropShadowElement",
|
| - "SVGFEFloodElement", "SVGFEFuncAElement", "SVGFEFuncBElement",
|
| - "SVGFEFuncGElement", "SVGFEFuncRElement",
|
| - "SVGFEGaussianBlurElement", "SVGFEImageElement",
|
| - "SVGFEMergeElement", "SVGFEMergeNodeElement",
|
| - "SVGFEMorphologyElement", "SVGFEOffsetElement",
|
| - "SVGFEPointLightElement", "SVGFESpecularLightingElement",
|
| - "SVGFESpotLightElement", "SVGFETileElement",
|
| - "SVGFETurbulenceElement", "SVGFilterElement",
|
| - "SVGFilterPrimitiveStandardAttributes", "SVGFitToViewBox",
|
| - "SVGFontElement", "SVGFontFaceElement", "SVGFontFaceFormatElement",
|
| - "SVGFontFaceNameElement", "SVGFontFaceSrcElement",
|
| - "SVGFontFaceUriElement", "SVGForeignObjectElement", "SVGGElement",
|
| - "SVGGlyphElement", "SVGGlyphRefElement", "SVGGradientElement",
|
| - "SVGHKernElement", "SVGImageElement", "SVGLangSpace", "SVGLength",
|
| - "SVGLengthList", "SVGLineElement", "SVGLinearGradientElement",
|
| - "SVGLocatable", "SVGMPathElement", "SVGMarkerElement",
|
| - "SVGMaskElement", "SVGMatrix", "SVGMetadataElement",
|
| - "SVGMissingGlyphElement", "SVGNumber", "SVGNumberList", "SVGPaint",
|
| - "SVGPathElement", "SVGPathSeg", "SVGPathSegArcAbs",
|
| - "SVGPathSegArcRel", "SVGPathSegClosePath",
|
| - "SVGPathSegCurvetoCubicAbs", "SVGPathSegCurvetoCubicRel",
|
| - "SVGPathSegCurvetoCubicSmoothAbs",
|
| - "SVGPathSegCurvetoCubicSmoothRel", "SVGPathSegCurvetoQuadraticAbs",
|
| - "SVGPathSegCurvetoQuadraticRel",
|
| - "SVGPathSegCurvetoQuadraticSmoothAbs",
|
| - "SVGPathSegCurvetoQuadraticSmoothRel", "SVGPathSegLinetoAbs",
|
| - "SVGPathSegLinetoHorizontalAbs", "SVGPathSegLinetoHorizontalRel",
|
| - "SVGPathSegLinetoRel", "SVGPathSegLinetoVerticalAbs",
|
| - "SVGPathSegLinetoVerticalRel", "SVGPathSegList",
|
| - "SVGPathSegMovetoAbs", "SVGPathSegMovetoRel", "SVGPatternElement",
|
| - "SVGPoint", "SVGPointList", "SVGPolygonElement",
|
| - "SVGPolylineElement", "SVGPreserveAspectRatio",
|
| - "SVGRadialGradientElement", "SVGRect", "SVGRectElement",
|
| - "SVGRenderingIntent", "SVGSVGElement", "SVGScriptElement",
|
| - "SVGSetElement", "SVGStopElement", "SVGStringList", "SVGStylable",
|
| - "SVGStyleElement", "SVGSwitchElement", "SVGSymbolElement",
|
| - "SVGTRefElement", "SVGTSpanElement", "SVGTests",
|
| - "SVGTextContentElement", "SVGTextElement", "SVGTextPathElement",
|
| - "SVGTextPositioningElement", "SVGTitleElement", "SVGTransform",
|
| - "SVGTransformList", "SVGTransformable", "SVGURIReference",
|
| - "SVGUnitTypes", "SVGUseElement", "SVGVKernElement",
|
| - "SVGViewElement", "SVGViewSpec", "SVGZoomAndPan", "SVGZoomEvent",
|
| - "Screen", "ScriptProfile", "ScriptProfileNode", "SharedWorker",
|
| - "SharedWorkercontext", "SpeechInputEvent", "SpeechInputResult",
|
| - "SpeechInputResultList", "Storage", "StorageEvent", "StorageInfo",
|
| - "StyleMedia", "StyleSheet", "StyleSheetList", "Text", "TextEvent",
|
| - "TextMetrics", "TextTrack", "TextTrackCue", "TextTrackCueList",
|
| - "TimeRanges", "Touch", "TouchEvent", "TouchList", "TreeWalker",
|
| - "UIEvent", "Uint16Array", "Uint32Array", "Uint8Array",
|
| - "ValidityState", "VoidCallback", "WaveShaperNode",
|
| - "WebGLActiveInfo", "WebGLBuffer", "WebGLContextAttributes",
|
| - "WebGLContextEvent", "WebGLDebugRendererInfo", "WebGLDebugShaders",
|
| - "WebGLFramebuffer", "WebGLProgram", "WebGLRenderbuffer",
|
| - "WebGLRenderingContext", "WebGLShader", "WebGLTexture",
|
| - "WebGLUniformLocation", "WebGLVertexArrayObjectOES",
|
| - "WebKitAnimation", "WebKitAnimationEvent", "WebKitAnimationList",
|
| - "WebKitBlobBuilder", "WebKitCSSFilterValue",
|
| - "WebKitCSSKeyframeRule", "WebKitCSSKeyframesRule",
|
| - "WebKitCSSMatrix", "WebKitCSSTransformValue", "WebKitFlags",
|
| - "WebKitLoseContext", "WebKitMutationObserver", "WebKitPoint",
|
| - "WebKitTransitionEvent", "WebSocket", "WheelEvent", "Worker",
|
| - "WorkerContext", "WorkerLocation", "WorkerNavigator",
|
| - "XMLHttpRequest", "XMLHttpRequestException",
|
| - "XMLHttpRequestProgressEvent", "XMLHttpRequestUpload",
|
| - "XMLSerializer", "XPathEvaluator", "XPathException",
|
| - "XPathExpression", "XPathNSResolver", "XPathResult",
|
| - "XSLTProcessor", "AudioBufferCallback", "DatabaseCallback",
|
| - "EntriesCallback", "EntryCallback", "ErrorCallback", "FileCallback",
|
| - "FileSystemCallback", "FileWriterCallback", "MetadataCallback",
|
| - "NavigatorUserMediaErrorCallback", "PositionCallback",
|
| - "PositionErrorCallback", "SQLStatementCallback",
|
| - "SQLStatementErrorCallback", "SQLTransactionCallback",
|
| - "SQLTransactionErrorCallback", "SQLTransactionSyncCallback",
|
| - "StorageInfoErrorCallback", "StorageInfoQuotaCallback",
|
| - "StorageInfoUsageCallback", "StringCallback"
|
| -];
|
| -
|
| -Map dbEntry;
|
| -
|
| -Map get dartIdl => data['dartIdl'];
|
| -String get currentType => data['type'];
|
| -
|
| -String _currentTypeShort;
|
| -String get currentTypeShort {
|
| - if (_currentTypeShort == null) {
|
| - _currentTypeShort = currentType;
|
| - _currentTypeShort = trimPrefix(_currentTypeShort, "HTML");
|
| - _currentTypeShort = trimPrefix(_currentTypeShort, "SVG");
|
| - _currentTypeShort = trimPrefix(_currentTypeShort, "DOM");
|
| - _currentTypeShort = trimPrefix(_currentTypeShort, "WebKit");
|
| - _currentTypeShort = trimPrefix(_currentTypeShort, "Webkit");
|
| - }
|
| - return _currentTypeShort;
|
| -}
|
| -
|
| -String _currentTypeTiny;
|
| -String get currentTypeTiny {
|
| - if (_currentTypeTiny == null) {
|
| - _currentTypeTiny = currentTypeShort;
|
| - _currentTypeTiny = trimEnd(_currentTypeTiny, "Element");
|
| - }
|
| - return _currentTypeTiny;
|
| -}
|
| -
|
| -Map get searchResult => data['searchResult'];
|
| -String get pageUrl => searchResult['link'];
|
| -
|
| -String _pageDomain;
|
| -String get pageDomain {
|
| - if (_pageDomain == null) {
|
| - _pageDomain = pageUrl.substring(0, pageUrl.indexOf("/", "https://".length));
|
| - }
|
| - return _pageDomain;
|
| -}
|
| -
|
| -String get pageDir {
|
| - return pageUrl.substring(0, pageUrl.lastIndexOf('/') + 1);
|
| -}
|
| -
|
| -String getAbsoluteUrl(AnchorElement anchor) {
|
| - if (anchor == null || anchor.href.length == 0) return '';
|
| - String path = anchor.href;
|
| - RegExp fullUrlRegExp = new RegExp("^https?://");
|
| - if (fullUrlRegExp.hasMatch(path)) return path;
|
| - if (path.startsWith('/')) {
|
| - return "$pageDomain$path";
|
| - } else if (path.startsWith("#")) {
|
| - return "$pageUrl$path";
|
| - } else {
|
| - return "$pageDir$path";
|
| - }
|
| -}
|
| -
|
| -bool inTable(Node n) {
|
| - while (n != null) {
|
| - if (n is TableElement) return true;
|
| - n = n.parent;
|
| - }
|
| - return false;
|
| -}
|
| -
|
| -String escapeHTML(str) {
|
| - Element e = new Element.tag("div");
|
| - e.text = str;
|
| - return e.innerHTML;
|
| -}
|
| -
|
| -List<Text> getAllTextNodes(Element elem) {
|
| - final nodes = <Text>[];
|
| - helper(Node n) {
|
| - if (n is Text) {
|
| - nodes.add(n);
|
| - } else {
|
| - for (Node child in n.nodes) {
|
| - helper(child);
|
| - }
|
| - }
|
| - };
|
| -
|
| - helper(elem);
|
| - return nodes;
|
| -}
|
| -
|
| -/**
|
| - * Whether a node and its children are all types that are safe to skip if the
|
| - * nodes have no text content.
|
| - */
|
| -bool isSkippableType(Node n) {
|
| - // TODO(jacobr): are there any types we don't want to skip even if they
|
| - // have no text content?
|
| - if (n is ImageElement || n is CanvasElement || n is InputElement
|
| - || n is ObjectElement) {
|
| - return false;
|
| - }
|
| - if (n is Text) return true;
|
| -
|
| - for (final child in n.nodes) {
|
| - if (!isSkippableType(child)) {
|
| - return false;
|
| - }
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -bool isSkippable(Node n) {
|
| - if (!isSkippableType(n)) return false;
|
| - return n.text.trim().length == 0;
|
| -}
|
| -
|
| -void onEnd() {
|
| - // Hideous hack to send JSON back to JS.
|
| - String dbJson = JSON.encode(dbEntry);
|
| - // workaround bug in JSON.decode.
|
| - dbJson = dbJson.replaceAll("ZDARTIUMDOESNTESCAPESLASHNJXXXX", "\\n");
|
| -
|
| - // Use postMessage to end the JSON to JavaScript. TODO(jacobr): use a simple
|
| - // isolate based Dart-JS interop solution in the future.
|
| - window.postMessage("START_DART_MESSAGE_UNIQUE_IDENTIFIER$dbJson", "*");
|
| -}
|
| -
|
| -class SectionParseResult {
|
| - final String html;
|
| - final String url;
|
| - final String idl;
|
| - SectionParseResult(this.html, this.url, this.idl);
|
| -}
|
| -
|
| -String genCleanHtml(Element root) {
|
| - for (final e in root.queryAll(".$DART_REMOVED")) {
|
| - e.classes.remove(DART_REMOVED);
|
| - }
|
| -
|
| - // Ditch inline styles.
|
| - for (final e in root.queryAll('[style]')) {
|
| - e.attributes.remove('style');
|
| - }
|
| -
|
| - // These elements are just tags that we should suppress.
|
| - for (final e in root.queryAll(".lang.lang-en")) {
|
| - e.remove();
|
| - }
|
| -
|
| - Element parametersHeader;
|
| - Element returnValueHeader;
|
| - for (final e in root.queryAll("h6")) {
|
| - if (e.text == 'Parameters') {
|
| - parametersHeader = e;
|
| - } else if (e.text == 'Return value') {
|
| - returnValueHeader = e;
|
| - }
|
| - }
|
| -
|
| - if (parametersHeader != null) {
|
| - int numEmptyParameters = 0;
|
| - final parameterDescriptions = root.queryAll("dd");
|
| - for (Element parameterDescription in parameterDescriptions) {
|
| - if (parameterDescription.text.trim().length == 0) {
|
| - numEmptyParameters++;
|
| - }
|
| - }
|
| - if (numEmptyParameters > 0 &&
|
| - numEmptyParameters == parameterDescriptions.length) {
|
| - // Remove the parameter list as it adds zero value as all descriptions
|
| - // are empty.
|
| - parametersHeader.remove();
|
| - for (final e in root.queryAll("dl")) {
|
| - e.remove();
|
| - }
|
| - } else if (parameterDescriptions.length == 0 &&
|
| - parametersHeader.nextElementSibling != null &&
|
| - parametersHeader.nextElementSibling.text.trim() == 'None.') {
|
| - // No need to display that the function takes 0 parameters.
|
| - parametersHeader.nextElementSibling.remove();
|
| - parametersHeader.remove();
|
| - }
|
| - }
|
| -
|
| - // Heuristic: if the return value is a single word it is a type name not a
|
| - // useful text description so suppress it.
|
| - if (returnValueHeader != null &&
|
| - returnValueHeader.nextElementSibling != null &&
|
| - returnValueHeader.nextElementSibling.text.trim().split(' ').length < 2) {
|
| - returnValueHeader.nextElementSibling.remove();
|
| - returnValueHeader.remove();
|
| - }
|
| -
|
| - bool changed = true;
|
| - while (changed) {
|
| - changed = false;
|
| - while (root.nodes.length == 1 && root.nodes.first is Element) {
|
| - root = root.nodes.first;
|
| - changed = true;
|
| - }
|
| -
|
| - // Trim useless nodes from the front.
|
| - while (root.nodes.length > 0 &&
|
| - isSkippable(root.nodes.first)) {
|
| - root.nodes.first.remove();
|
| - changed = true;
|
| - }
|
| -
|
| - // Trim useless nodes from the back.
|
| - while (root.nodes.length > 0 &&
|
| - isSkippable(root.nodes.last)) {
|
| - root.nodes.last.remove();
|
| - changed = true;
|
| - }
|
| - }
|
| - return JSONFIXUPHACK(root.innerHTML);
|
| -}
|
| -
|
| -String genPrettyHtmlFromElement(Element e) {
|
| - e = e.clone(true);
|
| - return genCleanHtml(e);
|
| -}
|
| -
|
| -class PostOrderTraversalIterator implements Iterator<Node> {
|
| -
|
| - Node _next;
|
| - Node _current;
|
| -
|
| - PostOrderTraversalIterator(Node start) {
|
| - _next = _leftMostDescendent(start);
|
| - }
|
| -
|
| - Node get current => _current;
|
| - bool get hasNext => _next != null;
|
| -
|
| - bool moveNext() {
|
| - _current = _next;
|
| - if (_next == null) return false;
|
| - if (_next.nextNode != null) {
|
| - _next = _leftMostDescendent(_next.nextNode);
|
| - } else {
|
| - _next = _next.parent;
|
| - }
|
| - return true;
|
| - }
|
| -
|
| - static Node _leftMostDescendent(Node n) {
|
| - while (n.nodes.length > 0) {
|
| - n = n.nodes.first;
|
| - }
|
| - return n;
|
| - }
|
| -}
|
| -
|
| -class PostOrderTraversal extends IterableBase<Node> {
|
| - final Node _node;
|
| - PostOrderTraversal(this._node);
|
| -
|
| - Iterator<Node> get iterator => new PostOrderTraversalIterator(_node);
|
| -}
|
| -
|
| -/**
|
| - * Estimate what content represents the first line of text within the [section]
|
| - * range returning null if there isn't a plausible first line of text that
|
| - * contains the string [prop]. We measure the actual rendered client rectangle
|
| - * for the text and use heuristics defining how many pixels text can vary by
|
| - * and still be viewed as being on the same line.
|
| - */
|
| -Range findFirstLine(Range section, String prop) {
|
| - final firstLine = newRange();
|
| - firstLine.setStart(section.startContainer, section.startOffset);
|
| -
|
| - num maxBottom = null;
|
| - for (final n in new PostOrderTraversal(section.startContainer)) {
|
| - int compareResult = section.comparePoint(n, 0);
|
| - if (compareResult == -1) {
|
| - // before range so skip.
|
| - continue;
|
| - } else if (compareResult > 0) {
|
| - // After range so exit.
|
| - break;
|
| - }
|
| -
|
| - final rect = getClientRect(n);
|
| - num bottom = rect.bottom;
|
| - if (rect.height > 0 && rect.width > 0) {
|
| - if (maxBottom != null &&
|
| - maxBottom + MIN_PIXELS_DIFFERENT_LINES < bottom) {
|
| - break;
|
| - } else if (maxBottom == null || maxBottom > bottom) {
|
| - maxBottom = bottom;
|
| - }
|
| - }
|
| -
|
| - firstLine.setEndAfter(n);
|
| - }
|
| -
|
| - // If the first line of text in the section does not contain the property
|
| - // name then we're not confident we are able to extract a high accuracy match
|
| - // so we should not return anything.
|
| - if (!firstLine.toString().contains(stripWebkit(prop))) {
|
| - return null;
|
| - }
|
| - return firstLine;
|
| -}
|
| -
|
| -/** Find child anchor elements that contain the text [prop]. */
|
| -AnchorElement findAnchorElement(Element root, String prop) {
|
| - for (AnchorElement a in root.queryAll("a")) {
|
| - if (a.text.contains(prop)) {
|
| - return a;
|
| - }
|
| - }
|
| - return null;
|
| -}
|
| -
|
| -// First surrounding element with an ID is safe enough.
|
| -Element findTighterRoot(Element elem, Element root) {
|
| - Element candidate = elem;
|
| - while (root != candidate) {
|
| - candidate = candidate.parent;
|
| - if (candidate.id.length > 0 && candidate.id.indexOf("section_") != 0) {
|
| - break;
|
| - }
|
| - }
|
| - return candidate;
|
| -}
|
| -
|
| -// TODO(jacobr): this is very slow and ugly.. consider rewriting or at least
|
| -// commenting carefully.
|
| -SectionParseResult filteredHtml(Element elem, Element root, String prop,
|
| - Function fragmentGeneratedCallback) {
|
| - // Using a tighter root avoids false positives at the risk of trimming
|
| - // text we shouldn't.
|
| - root = findTighterRoot(elem, root);
|
| - final range = newRange();
|
| - range.setStartBefore(elem);
|
| -
|
| - Element current = elem;
|
| - while (current != null) {
|
| - range.setEndBefore(current);
|
| - if (current.classes.contains(DART_REMOVED) &&
|
| - range.toString().trim().length > 0) {
|
| - break;
|
| - }
|
| - if (current.firstElementChild != null) {
|
| - current = current.firstElementChild;
|
| - } else {
|
| - while (current != null) {
|
| - range.setEndAfter(current);
|
| - if (current == root) {
|
| - current = null;
|
| - break;
|
| - }
|
| - if (current.nextElementSibling != null) {
|
| - current = current.nextElementSibling;
|
| - break;
|
| - }
|
| - current = current.parent;
|
| - }
|
| - }
|
| - }
|
| - String url = null;
|
| - if (prop != null) {
|
| - Range firstLine = findFirstLine(range, prop);
|
| - if (firstLine != null) {
|
| - range.setStart(firstLine.endContainer, firstLine.endOffset);
|
| - DocumentFragment firstLineClone = firstLine.cloneContents();
|
| - AnchorElement anchor = findAnchorElement(firstLineClone, prop);
|
| - if (anchor != null) {
|
| - url = getAbsoluteUrl(anchor);
|
| - }
|
| - }
|
| - }
|
| - final fragment = range.cloneContents();
|
| - if (fragmentGeneratedCallback != null) {
|
| - fragmentGeneratedCallback(fragment);
|
| - }
|
| - // Strip tags we don't want
|
| - for (Element e in fragment.queryAll("script, object, style")) {
|
| - e.remove();
|
| - }
|
| -
|
| - // Extract idl
|
| - final idl = new StringBuffer();
|
| - if (prop != null && prop.length > 0) {
|
| - // Only expect properties to have HTML.
|
| - for(Element e in fragment.queryAll(IDL_SELECTOR)) {
|
| - idl.write(e.outerHTML);
|
| - e.remove();
|
| - }
|
| - // TODO(jacobr) this is a very basic regex to see if text looks like IDL
|
| - RegExp likelyIdl = new RegExp(" $prop\\w*\\(");
|
| -
|
| - for (Element e in fragment.queryAll("pre")) {
|
| - // Check if it looks like idl...
|
| - String txt = e.text.trim();
|
| - if (likelyIdl.hasMatch(txt) && txt.contains("\n") && txt.contains(")")) {
|
| - idl.write(e.outerHTML);
|
| - e.remove();
|
| - }
|
| - }
|
| - }
|
| - return new SectionParseResult(genCleanHtml(fragment), url, idl.toString());
|
| -}
|
| -
|
| -/**
|
| - * Find the best child element of [root] that appears to be an API definition
|
| - * for [prop]. [allText] is a list of all text nodes under root computed by
|
| - * the caller to improve performance.
|
| - */
|
| -Element findBest(Element root, List<Text> allText, String prop,
|
| - String propType) {
|
| - // Best bet: find a child of root where the id matches the property name.
|
| - Element cand = root.query("#$prop");
|
| -
|
| - if (cand == null && propType == "methods") {
|
| - cand = root.query("[id=$prop\\(\\)]");
|
| - }
|
| - while (cand != null && cand.text.trim().length == 0) {
|
| - // We found the bookmark for the element but sadly it is just an empty
|
| - // placeholder. Find the first real element.
|
| - cand = cand.nextElementSibling;
|
| - }
|
| - if (cand != null) {
|
| - return cand;
|
| - }
|
| -
|
| - // If we are at least 70 pixels from the left, something is definitely
|
| - // fishy and we shouldn't even consider this candidate as nobody visually
|
| - // formats API docs like that.
|
| - num candLeft = 70;
|
| -
|
| - for (Text text in allText) {
|
| - Element proposed = null;
|
| -
|
| - // TODO(jacobr): does it hurt precision to use the full cleanup?
|
| - String t = fullNameCleanup(text.text);
|
| - if (t == prop) {
|
| - proposed = text.parent;
|
| - ClientRect candRect = getClientRect(proposed);
|
| -
|
| - // TODO(jacobr): this is a good heuristic
|
| - // if (selObj.selector.indexOf(" > DD ") == -1
|
| - if (candRect.left < candLeft) {
|
| - cand = proposed;
|
| - candLeft = candRect.left;
|
| - }
|
| - }
|
| - }
|
| - return cand;
|
| -}
|
| -
|
| -/**
|
| - * Checks whether [e] is tagged as obsolete or deprecated using heuristics
|
| - * for what these tags look like in the MDN docs.
|
| - */
|
| -bool isObsolete(Element e) {
|
| - RegExp obsoleteRegExp = new RegExp(r"(^|\s)obsolete(?=\s|$)");
|
| - RegExp deprecatedRegExp = new RegExp(r"(^|\s)deprecated(?=\s|$)");
|
| - for (Element child in e.queryAll("span")) {
|
| - String t = child.text.toLowerCase();
|
| - if (t.startsWith("obsolete") || t.startsWith("deprecated")) return true;
|
| - }
|
| -
|
| - String text = e.text.toLowerCase();
|
| - return obsoleteRegExp.hasMatch(text) || deprecatedRegExp.hasMatch(text);
|
| -}
|
| -
|
| -bool isFirstCharLowerCase(String str) {
|
| - return new RegExp("^[a-z]").hasMatch(str);
|
| -}
|
| -
|
| -/**
|
| - * Extracts information from a fragment of HTML only searching under the [root]
|
| - * html node. [secitonSelector] specifies the query to use to find candidate
|
| - * sections of the document to consider (there may be more than one).
|
| - * [currentType] specifies the name of the current class. [members] specifies
|
| - * the known class members for this class that we are attempting to find
|
| - * documentation for. [propType] indicates whether we are searching for
|
| - * methods, properties, constants, or constructors.
|
| - */
|
| -void scrapeSection(Element root, String sectionSelector, String currentType,
|
| - List members, String propType) {
|
| - Map expectedProps = dartIdl[propType];
|
| -
|
| - Set<String> alreadyMatchedProperties = new Set<String>();
|
| - bool onlyConsiderTables = false;
|
| - ElementList allMatches = root.queryAll(sectionSelector);
|
| - if (allMatches.length == 0) {
|
| - // If we can't find any matches to the sectionSelector, we fall back to
|
| - // considering all tables in the document. This is dangerous so we only
|
| - // allow the safer table matching extraction rules for this case.
|
| - allMatches = root.queryAll(".fullwidth-table");
|
| - onlyConsiderTables = true;
|
| - }
|
| - for (Element matchElement in allMatches) {
|
| - final match = matchElement.parent;
|
| - if (!match.id.startsWith("section") && match.id != "pageText") {
|
| - throw "Unexpected element $match";
|
| - }
|
| - // We don't want to later display this text a second time while for example
|
| - // displaying class level summary information as then we would display
|
| - // the same documentation twice.
|
| - match.classes.add(DART_REMOVED);
|
| -
|
| - bool foundProps = false;
|
| -
|
| - // TODO(jacobr): we should really look for the table tag instead
|
| - // add an assert if we are missing something that is a table...
|
| - // TODO(jacobr) ignore tables in tables.
|
| - for (Element t in match.queryAll('.standard-table, .fullwidth-table')) {
|
| - int helpIndex = -1;
|
| - num i = 0;
|
| - for (Element r in t.queryAll("th, td.header")) {
|
| - final txt = r.text.trim().split(" ")[0].toLowerCase();
|
| - if (txt == "description") {
|
| - helpIndex = i;
|
| - break;
|
| - }
|
| - i++;
|
| - }
|
| -
|
| - // Figure out which column in the table contains member names by
|
| - // tracking how many member names each column contains.
|
| - final numMatches = new List<int>(i);
|
| - for (int j = 0; j < i; j++) {
|
| - numMatches[j] = 0;
|
| - }
|
| -
|
| - // Find the column that seems to have the most names that look like
|
| - // expected properties.
|
| - for (Element r in t.queryAll("tbody tr")) {
|
| - ElementList row = r.elements;
|
| - if (row.length == 0 || row.first.classes.contains(".header")) {
|
| - continue;
|
| - }
|
| -
|
| - for (int k = 0; k < numMatches.length && k < row.length; k++) {
|
| - if (expectedProps.containsKey(fullNameCleanup(row[k].text))) {
|
| - numMatches[k]++;
|
| - break;
|
| - }
|
| - }
|
| - }
|
| -
|
| - int propNameIndex = 0;
|
| - {
|
| - int bestCount = numMatches[0];
|
| - for (int k = 1; k < numMatches.length; k++) {
|
| - if (numMatches[k] > bestCount) {
|
| - bestCount = numMatches[k];
|
| - propNameIndex = k;
|
| - }
|
| - }
|
| - }
|
| -
|
| - for (Element r in t.queryAll("tbody tr")) {
|
| - final row = r.elements;
|
| - if (row.length > propNameIndex && row.length > helpIndex) {
|
| - if (row.first.classes.contains(".header")) {
|
| - continue;
|
| - }
|
| - // TODO(jacobr): this code for determining the namestr is needlessly
|
| - // messy.
|
| - final nameRow = row[propNameIndex];
|
| - AnchorElement a = nameRow.query("a");
|
| - String goodName = '';
|
| - if (a != null) {
|
| - goodName = a.text.trim();
|
| - }
|
| - String nameStr = nameRow.text;
|
| -
|
| - Map entry = new Map<String, String>();
|
| -
|
| - entry["name"] = fullNameCleanup(nameStr.length > 0 ?
|
| - nameStr : goodName);
|
| -
|
| - final parse = filteredHtml(nameRow, nameRow, entry["name"], null);
|
| - String altHelp = parse.html;
|
| -
|
| - entry["help"] = (helpIndex == -1 || row[helpIndex] == null) ?
|
| - altHelp : genPrettyHtmlFromElement(row[helpIndex]);
|
| - if (parse.url != null) {
|
| - entry["url"] = parse.url;
|
| - }
|
| -
|
| - if (parse.idl.length > 0) {
|
| - entry["idl"] = parse.idl;
|
| - }
|
| -
|
| - entry["obsolete"] = isObsolete(r);
|
| -
|
| - if (entry["name"].length > 0) {
|
| - cleanupEntry(members, entry);
|
| - alreadyMatchedProperties.add(entry['name']);
|
| - foundProps = true;
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - if (onlyConsiderTables) {
|
| - continue;
|
| - }
|
| -
|
| - // After this point we have higher risk tests that attempt to perform
|
| - // rudimentary page segmentation. This approach is much more error-prone
|
| - // than using tables because the HTML is far less clearly structured.
|
| -
|
| - final allText = getAllTextNodes(match);
|
| -
|
| - final pmap = new Map<String, Element>();
|
| - for (final prop in expectedProps.keys) {
|
| - if (alreadyMatchedProperties.contains(prop)) {
|
| - continue;
|
| - }
|
| - final e = findBest(match, allText, prop, propType);
|
| - if (e != null && !inTable(e)) {
|
| - pmap[prop] = e;
|
| - }
|
| - }
|
| -
|
| - for (final prop in pmap.keys) {
|
| - pmap[prop].classes.add(DART_REMOVED);
|
| - }
|
| -
|
| - // The problem is the MDN docs do place documentation for each method in a
|
| - // nice self contained subtree. Instead you will see something like:
|
| -
|
| - // <h3>drawImage</h3>
|
| - // <p>Draw image is an awesome method</p>
|
| - // some more info on drawImage here
|
| - // <h3>mozDrawWindow</h3>
|
| - // <p>This API cannot currently be used by Web content.
|
| - // It is chrome only.</p>
|
| - // <h3>drawRect</h3>
|
| - // <p>Always call drawRect instead of drawImage</p>
|
| - // some more info on drawRect here...
|
| -
|
| - // The trouble is we will easily detect that the drawImage and drawRect
|
| - // entries are method definitions because we know to search for these
|
| - // method names but we will not detect that mozDrawWindow is a method
|
| - // definition as that method doesn't exist in our IDL. Thus if we are not
|
| - // careful the definition for the drawImage method will contain the
|
| - // definition for the mozDrawWindow method as well which would result in
|
| - // broken docs. We solve this problem by finding all content with similar
|
| - // visual structure to the already found method definitions. It turns out
|
| - // that using the visual position of each element on the page is much
|
| - // more reliable than using the DOM structure
|
| - // (e.g. section_root > div > h3) for the MDN docs because MDN authors
|
| - // carefully check that the documentation for each method comment is
|
| - // visually consistent but take less care to check that each
|
| - // method comment has identical markup structure.
|
| - for (String prop in pmap.keys) {
|
| - Element e = pmap[prop];
|
| - ClientRect r = getClientRect(e);
|
| - // TODO(jacobr): a lot of these queries are identical and this code
|
| - // could easily be optimized.
|
| - for (final cand in match.queryAll(e.tagName)) {
|
| - // TODO(jacobr): use a negative selector instead.
|
| - if (!cand.classes.contains(DART_REMOVED) && !inTable(cand)) {
|
| - final candRect = getClientRect(cand);
|
| - // Only consider matches that have similar heights and identical left
|
| - // coordinates.
|
| - if (candRect.left == r.left &&
|
| - (candRect.height - r.height).abs() < 5) {
|
| - String propName = fullNameCleanup(cand.text);
|
| - if (isFirstCharLowerCase(propName) && !pmap.containsKey(propName)
|
| - && !alreadyMatchedProperties.contains(propName)) {
|
| - pmap[propName] = cand;
|
| - }
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - // We mark these elements in batch to reduce the number of layouts
|
| - // triggered. TODO(jacobr): use new batch based async measurement to make
|
| - // this code flow simpler.
|
| - for (String prop in pmap.keys) {
|
| - Element e = pmap[prop];
|
| - e.classes.add(DART_REMOVED);
|
| - }
|
| -
|
| - // Find likely "subsections" of the main section and mark them with
|
| - // DART_REMOVED so we don't include them in member descriptions... which
|
| - // would suck.
|
| - for (Element e in match.queryAll("[id]")) {
|
| - if (e.id.contains(matchElement.id)) {
|
| - e.classes.add(DART_REMOVED);
|
| - }
|
| - }
|
| -
|
| - for (String prop in pmap.keys) {
|
| - Element elem = pmap[prop];
|
| - bool obsolete = false;
|
| - final parse = filteredHtml(
|
| - elem, match, prop,
|
| - (Element e) {
|
| - obsolete = isObsolete(e);
|
| - });
|
| - Map entry = {
|
| - "url" : parse.url,
|
| - "name" : prop,
|
| - "help" : parse.html,
|
| - "obsolete" : obsolete
|
| - };
|
| - if (parse.idl.length > 0) {
|
| - entry["idl"] = parse.idl;
|
| - }
|
| - cleanupEntry(members, entry);
|
| - }
|
| - }
|
| -}
|
| -
|
| -String trimHtml(String html) {
|
| - // TODO(jacobr): implement this. Remove spurious enclosing HTML tags, etc.
|
| - return html;
|
| -}
|
| -
|
| -bool maybeName(String name) {
|
| - return new RegExp("^[a-z][a-z0-9A-Z]+\$").hasMatch(name) ||
|
| - new RegExp("^[A-Z][A-Z_]*\$").hasMatch(name);
|
| -}
|
| -
|
| -// TODO(jacobr): this element is ugly at the moment but will become easier to
|
| -// read once ElementList supports most of the Element functionality.
|
| -void markRemoved(var e) {
|
| - if (e != null) {
|
| - if (e is Element) {
|
| - e.classes.add(DART_REMOVED);
|
| - } else {
|
| - for (Element el in e) {
|
| - el.classes.add(DART_REMOVED);
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -// TODO(jacobr): remove this when the dartium JSON parse handles \n correctly.
|
| -String JSONFIXUPHACK(String value) {
|
| - return value.replaceAll("\n", "ZDARTIUMDOESNTESCAPESLASHNJXXXX");
|
| -}
|
| -
|
| -String mozToWebkit(String name) {
|
| - return name.replaceFirst(new RegExp("^moz"), "webkit");
|
| -}
|
| -
|
| -String stripWebkit(String name) {
|
| - return trimPrefix(name, "webkit");
|
| -}
|
| -
|
| -// TODO(jacobr): be more principled about this.
|
| -String fullNameCleanup(String name) {
|
| - int parenIndex = name.indexOf('(');
|
| - if (parenIndex != -1) {
|
| - name = name.substring(0, parenIndex);
|
| - }
|
| - name = name.split(" ")[0];
|
| - name = name.split("\n")[0];
|
| - name = name.split("\t")[0];
|
| - name = name.split("*")[0];
|
| - name = name.trim();
|
| - name = safeNameCleanup(name);
|
| - return name;
|
| -}
|
| -
|
| -// Less agressive than the full name cleanup to avoid overeager matching.
|
| -// TODO(jacobr): be more principled about this.
|
| -String safeNameCleanup(String name) {
|
| - int parenIndex = name.indexOf('(');
|
| - if (parenIndex != -1 && name.indexOf(")") != -1) {
|
| - // TODO(jacobr): workaround bug in:
|
| - // name = name.split("(")[0];
|
| - name = name.substring(0, parenIndex);
|
| - }
|
| - name = name.trim();
|
| - name = trimPrefix(name, currentType + ".");
|
| - name = trimPrefix(name, currentType.toLowerCase() + ".");
|
| - name = trimPrefix(name, currentTypeShort + ".");
|
| - name = trimPrefix(name, currentTypeShort.toLowerCase() + ".");
|
| - name = trimPrefix(name, currentTypeTiny + ".");
|
| - name = trimPrefix(name, currentTypeTiny.toLowerCase() + ".");
|
| - name = name.trim();
|
| - name = mozToWebkit(name);
|
| - return name;
|
| -}
|
| -
|
| -/**
|
| - * Remove h1, h2, and h3 headers.
|
| - */
|
| -void removeHeaders(DocumentFragment fragment) {
|
| - for (Element e in fragment.queryAll("h1, h2, h3")) {
|
| - e.remove();
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * Given an [entry] representing a single method or property cleanup the
|
| - * values performing some simple normalization and only adding the entry to
|
| - * [members] if it has a valid name.
|
| - */
|
| -void cleanupEntry(List members, Map entry) {
|
| - if (entry.containsKey('help')) {
|
| - entry['help'] = trimHtml(entry['help']);
|
| - }
|
| - String name = fullNameCleanup(entry['name']);
|
| - entry['name'] = name;
|
| - if (maybeName(name)) {
|
| - for (String key in entry.keys) {
|
| - var value = entry[key];
|
| - if (value == null) {
|
| - entry.remove(key);
|
| - continue;
|
| - }
|
| - if (value is String) {
|
| - entry[key] = JSONFIXUPHACK(value);
|
| - }
|
| - }
|
| - members.add(entry);
|
| - }
|
| -}
|
| -
|
| -// TODO(jacobr) dup with trim start....
|
| -String trimPrefix(String str, String prefix) {
|
| - if (str.indexOf(prefix) == 0) {
|
| - return str.substring(prefix.length);
|
| - } else {
|
| - return str;
|
| - }
|
| -}
|
| -
|
| -String trimStart(String str, String start) {
|
| - if (str.startsWith(start) && str.length > start.length) {
|
| - return str.substring(start.length);
|
| - }
|
| - return str;
|
| -}
|
| -
|
| -String trimEnd(String str, String end) {
|
| - if (str.endsWith(end) && str.length > end.length) {
|
| - return str.substring(0, str.length - end.length);
|
| - }
|
| - return str;
|
| -}
|
| -
|
| -/**
|
| - * Extract a section with name [key] using [selector] to find start points for
|
| - * the section in the document.
|
| - */
|
| -void extractSection(String selector, String key) {
|
| - for (Element e in document.queryAll(selector)) {
|
| - e = e.parent;
|
| - for (Element skip in e.queryAll("h1, h2, $IDL_SELECTOR")) {
|
| - skip.remove();
|
| - }
|
| - String html = filteredHtml(e, e, null, removeHeaders).html;
|
| - if (html.length > 0) {
|
| - if (dbEntry.containsKey(key)) {
|
| - dbEntry[key] += html;
|
| - } else {
|
| - dbEntry[key] = html;
|
| - }
|
| - }
|
| - e.classes.add(DART_REMOVED);
|
| - }
|
| -}
|
| -
|
| -void run() {
|
| - // Inject CSS to ensure lines don't wrap unless they were intended to.
|
| - // This is needed to make the logic to determine what is a single line
|
| - // behave consistently even for very long method names.
|
| - document.head.nodes.add(new Element.html("""
|
| -<style type="text/css">
|
| - body {
|
| - width: 10000px;
|
| - }
|
| -</style>"""));
|
| -
|
| - String title = trimEnd(window.document.title.trim(), " - MDN");
|
| - dbEntry['title'] = title;
|
| -
|
| - // TODO(rnystrom): Clean up the page a bunch. Not sure if this is the best
|
| - // place to do this...
|
| - // TODO(jacobr): move this to right before we extract HTML.
|
| -
|
| - // Remove the "Introduced in HTML <version>" boxes.
|
| - for (Element e in document.queryAll('.htmlVersionHeaderTemplate')) {
|
| - e.remove();
|
| - }
|
| -
|
| - // Flatten the list of known DOM types into a faster and case-insensitive
|
| - // map.
|
| - domTypes = {};
|
| - for (final domType in domTypesRaw) {
|
| - domTypes[domType.toLowerCase()] = domType;
|
| - }
|
| -
|
| - // Fix up links.
|
| - final SHORT_LINK = new RegExp(r'^[\w/]+$');
|
| - final INNER_LINK = new RegExp(r'[Ee]n/(?:[\w/]+/|)([\w#.]+)(?:\(\))?$');
|
| - final MEMBER_LINK = new RegExp(r'(\w+)[.#](\w+)');
|
| - final RELATIVE_LINK = new RegExp(r'^(?:../)*/?[Ee][Nn]/(.+)');
|
| -
|
| - // - Make relative links absolute.
|
| - // - If we can, take links that point to other MDN pages and retarget them
|
| - // to appropriate pages in our docs.
|
| - // TODO(rnystrom): Add rel external to links we didn't fix.
|
| - for (AnchorElement a in document.queryAll('a')) {
|
| - // Get the raw attribute because we *don't* want the browser to fully-
|
| - // qualify the name for us since it has the wrong base address for the
|
| - // page.
|
| - var href = a.attributes['href'];
|
| -
|
| - // Ignore busted links.
|
| - if (href == null) continue;
|
| -
|
| - // If we can recognize what it's pointing to, point it to our page instead.
|
| - tryToLinkToRealType(maybeType) {
|
| - // See if we know a type with that name.
|
| - final realType = domTypes[maybeType.toLowerCase()];
|
| - if (realType != null) {
|
| - href = '../html/$realType.html';
|
| - }
|
| - }
|
| -
|
| - // If it's a relative link (that we know how to root), make it absolute.
|
| - var match = RELATIVE_LINK.firstMatch(href);
|
| - if (match != null) {
|
| - href = 'https://developer.mozilla.org/en/${match[1]}';
|
| - }
|
| -
|
| - // If it's a word link like "foo" find a type or make it absolute.
|
| - match = SHORT_LINK.firstMatch(href);
|
| - if (match != null) {
|
| - href = 'https://developer.mozilla.org/en/DOM/${match[0]}';
|
| - }
|
| -
|
| - // TODO(rnystrom): This is a terrible way to do this. Should use the real
|
| - // mapping from DOM names to html class names that we use elsewhere in the
|
| - // DOM scripts.
|
| - match = INNER_LINK.firstMatch(href);
|
| - if (match != null) {
|
| - // See if we're linking to a member ("type.name" or "type#name") or just
|
| - // a type ("type").
|
| - final member = MEMBER_LINK.firstMatch(match[1]);
|
| - if (member != null) {
|
| - tryToLinkToRealType(member[1]);
|
| - } else {
|
| - tryToLinkToRealType(match[1]);
|
| - }
|
| - }
|
| -
|
| - // Put it back into the element.
|
| - a.attributes['href'] = href;
|
| - }
|
| -
|
| - if (!title.toLowerCase().contains(currentTypeTiny.toLowerCase())) {
|
| - bool foundMatch = false;
|
| - // Test out if the title is really an HTML tag that matches the
|
| - // current class name.
|
| - for (String tag in [title.split(" ")[0], title.split(".").last]) {
|
| - try {
|
| - Element element = new Element.tag(tag);
|
| - // TODO(jacobr): this is a really ugly way of doing this that will
|
| - // stop working at some point soon.
|
| - if (element.typeName == currentType) {
|
| - foundMatch = true;
|
| - break;
|
| - }
|
| - } catch (e) {}
|
| - }
|
| - if (!foundMatch) {
|
| - dbEntry['skipped'] = true;
|
| - dbEntry['cause'] = "Suspect title";
|
| - onEnd();
|
| - return;
|
| - }
|
| - }
|
| -
|
| - Element root = document.query(".pageText");
|
| - if (root == null) {
|
| - dbEntry['cause'] = '.pageText not found';
|
| - onEnd();
|
| - return;
|
| - }
|
| -
|
| - markRemoved(root.query("#Notes"));
|
| - List members = dbEntry['members'];
|
| -
|
| - // This is a laundry list of CSS selectors for boilerplate content on the
|
| - // MDN pages that we should ignore for the purposes of extracting
|
| - // documentation.
|
| - markRemoved(document.queryAll(".pageToc, footer, header, #nav-toolbar"));
|
| - markRemoved(document.queryAll("#article-nav"));
|
| - markRemoved(document.queryAll(".hideforedit"));
|
| - markRemoved(document.queryAll(".navbox"));
|
| - markRemoved(document.query("#Method_overview"));
|
| - markRemoved(document.queryAll("h1, h2"));
|
| -
|
| - scrapeSection(root, "#Methods", currentType, members, 'methods');
|
| - scrapeSection(root, "#Constants, #Error_codes, #State_constants",
|
| - currentType, members, 'constants');
|
| - // TODO(jacobr): infer tables based on multiple matches rather than
|
| - // using a hard coded list of section ids.
|
| - scrapeSection(root,
|
| - "[id^=Properties], #Notes, [id^=Other_properties], #Attributes, " +
|
| - "#DOM_properties, #Event_handlers, #Event_Handlers",
|
| - currentType, members, 'properties');
|
| -
|
| - // Avoid doing this till now to avoid messing up the section scrape.
|
| - markRemoved(document.queryAll("h3"));
|
| -
|
| - ElementList examples = root.queryAll("span[id^=example], span[id^=Example]");
|
| -
|
| - extractSection("#See_also", 'seeAlso');
|
| - extractSection("#Specification, #Specifications", "specification");
|
| -
|
| - // TODO(jacobr): actually extract the constructor(s)
|
| - extractSection("#Constructor, #Constructors", 'constructor');
|
| - extractSection("#Browser_compatibility, #Compatibility", 'compatibility');
|
| -
|
| - // Extract examples.
|
| - List<String> exampleHtml = [];
|
| - for (Element e in examples) {
|
| - e.classes.add(DART_REMOVED);
|
| - }
|
| - for (Element e in examples) {
|
| - String html = filteredHtml(e, root, null,
|
| - (DocumentFragment fragment) {
|
| - removeHeaders(fragment);
|
| - if (fragment.text.trim().toLowerCase() == "example") {
|
| - // Degenerate example.
|
| - fragment.nodes.clear();
|
| - }
|
| - }).html;
|
| - if (html.length > 0) {
|
| - exampleHtml.add(html);
|
| - }
|
| - }
|
| - if (exampleHtml.length > 0) {
|
| - dbEntry['examples'] = exampleHtml;
|
| - }
|
| -
|
| - // Extract the class summary.
|
| - // Basically everything left over after the #Summary or #Description tag is
|
| - // safe to include in the summary.
|
| - StringBuffer summary = new StringBuffer();
|
| - for (Element e in root.queryAll("#Summary, #Description")) {
|
| - summary.write(filteredHtml(root, e, null, removeHeaders).html);
|
| - }
|
| -
|
| - if (summary.length == 0) {
|
| - // Remove the "Gecko DOM Reference text"
|
| - Element ref = root.query(".lang.lang-en");
|
| - if (ref != null) {
|
| - ref = ref.parent;
|
| - String refText = ref.text.trim();
|
| - if (refText == "Gecko DOM Reference" ||
|
| - refText == "« Gecko DOM Reference") {
|
| - ref.remove();
|
| - }
|
| - }
|
| - // Risky... this might add stuff we shouldn't.
|
| - summary.write(filteredHtml(root, root, null, removeHeaders).html);
|
| - }
|
| -
|
| - if (summary.length > 0) {
|
| - dbEntry['summary'] = summary.toString();
|
| - }
|
| -
|
| - // Inject CSS to aid debugging in the browser.
|
| - // We could avoid doing this if we know we are not running in a browser..
|
| - document.head.nodes.add(new Element.html(DEBUG_CSS));
|
| -
|
| - onEnd();
|
| -}
|
| -
|
| -void main() {
|
| - window.on.load.add(documentLoaded);
|
| -}
|
| -
|
| -void documentLoaded(event) {
|
| - // Load the database of expected methods and properties with an HttpRequest.
|
| - new HttpRequest.get('${window.location}.json', (req) {
|
| - data = JSON.decode(req.responseText);
|
| - dbEntry = {'members': [], 'srcUrl': pageUrl};
|
| - run();
|
| - });
|
| -}
|
|
|