OLD | NEW |
(Empty) | |
| 1 #import ("dart:html"); |
| 2 #import ("dart:htmlimpl"); |
| 3 #import ("dart:dom", prefix:"dom"); |
| 4 #import ("dart:json"); |
| 5 |
| 6 // Workaround for HTML lib missing feature. |
| 7 Range newRange() { |
| 8 return LevelDom.wrapRange(dom.document.createRange()); |
| 9 } |
| 10 |
| 11 // Temporary range object to optimize performance computing client rects |
| 12 // from text nodes. |
| 13 Range _tempRange; |
| 14 // Hacks because ASYNC measurement is annoying when just writing a script. |
| 15 ClientRect getClientRect(Node n) { |
| 16 if (n is Element) { |
| 17 Element e = n; |
| 18 dom.Element raw = unwrapDomObject(e.dynamic); |
| 19 return LevelDom.wrapClientRect(raw.getBoundingClientRect()); |
| 20 } else { |
| 21 // Crazy hacks that works for nodes.... create a range and measure it. |
| 22 if (_tempRange == null) { |
| 23 _tempRange = newRange(); |
| 24 } |
| 25 _tempRange.setStartBefore(n); |
| 26 _tempRange.setEndAfter(n); |
| 27 return _tempRange.getBoundingClientRect(); |
| 28 } |
| 29 } |
| 30 |
| 31 final DART_REMOVED = "dart_removed"; |
| 32 |
| 33 final DEBUG_CSS = """ |
| 34 <style type="text/css"> |
| 35 .dart_removed { |
| 36 background-color: rgba(255, 0, 0, 0.5); |
| 37 } |
| 38 </style>"""; |
| 39 |
| 40 final MIN_PIXELS_DIFFERENT_LINES = 10; |
| 41 |
| 42 final IDL_SELECTOR = "pre.eval, pre.idl"; |
| 43 |
| 44 Map data; |
| 45 |
| 46 // TODO(rnystrom): Hack! Copied from domTypes.json. Instead of hard-coding |
| 47 // these, should use the same mapping that the DOM/HTML code generators use. |
| 48 var domTypes; |
| 49 final domTypesRaw = const [ |
| 50 "AbstractWorker", "ArrayBuffer", "ArrayBufferView", "Attr", |
| 51 "AudioBuffer", "AudioBufferSourceNode", "AudioChannelMerger", |
| 52 "AudioChannelSplitter", "AudioContext", "AudioDestinationNode", |
| 53 "AudioGain", "AudioGainNode", "AudioListener", "AudioNode", |
| 54 "AudioPannerNode", "AudioParam", "AudioProcessingEvent", |
| 55 "AudioSourceNode", "BarInfo", "BeforeLoadEvent", "BiquadFilterNode", |
| 56 "Blob", "CDATASection", "CSSCharsetRule", "CSSFontFaceRule", |
| 57 "CSSImportRule", "CSSMediaRule", "CSSPageRule", "CSSPrimitiveValue", |
| 58 "CSSRule", "CSSRuleList", "CSSStyleDeclaration", "CSSStyleRule", |
| 59 "CSSStyleSheet", "CSSUnknownRule", "CSSValue", "CSSValueList", |
| 60 "CanvasGradient", "CanvasPattern", "CanvasPixelArray", |
| 61 "CanvasRenderingContext", "CanvasRenderingContext2D", |
| 62 "CharacterData", "ClientRect", "ClientRectList", "Clipboard", |
| 63 "CloseEvent", "Comment", "CompositionEvent", "Console", |
| 64 "ConvolverNode", "Coordinates", "Counter", "Crypto", "CustomEvent", |
| 65 "DOMApplicationCache", "DOMException", "DOMFileSystem", |
| 66 "DOMFileSystemSync", "DOMFormData", "DOMImplementation", |
| 67 "DOMMimeType", "DOMMimeTypeArray", "DOMParser", "DOMPlugin", |
| 68 "DOMPluginArray", "DOMSelection", "DOMSettableTokenList", |
| 69 "DOMTokenList", "DOMURL", "DOMWindow", "DataTransferItem", |
| 70 "DataTransferItemList", "DataView", "Database", "DatabaseSync", |
| 71 "DedicatedWorkerContext", "DelayNode", "DeviceMotionEvent", |
| 72 "DeviceOrientationEvent", "DirectoryEntry", "DirectoryEntrySync", |
| 73 "DirectoryReader", "DirectoryReaderSync", "Document", |
| 74 "DocumentFragment", "DocumentType", "DynamicsCompressorNode", |
| 75 "Element", "ElementTimeControl", "ElementTraversal", "Entity", |
| 76 "EntityReference", "Entry", "EntryArray", "EntryArraySync", |
| 77 "EntrySync", "ErrorEvent", "Event", "EventException", "EventSource", |
| 78 "EventTarget", "File", "FileEntry", "FileEntrySync", "FileError", |
| 79 "FileException", "FileList", "FileReader", "FileReaderSync", |
| 80 "FileWriter", "FileWriterSync", "Float32Array", "Float64Array", |
| 81 "Geolocation", "Geoposition", "HTMLAllCollection", |
| 82 "HTMLAnchorElement", "HTMLAppletElement", "HTMLAreaElement", |
| 83 "HTMLAudioElement", "HTMLBRElement", "HTMLBaseElement", |
| 84 "HTMLBaseFontElement", "HTMLBodyElement", "HTMLButtonElement", |
| 85 "HTMLCanvasElement", "HTMLCollection", "HTMLDListElement", |
| 86 "HTMLDataListElement", "HTMLDetailsElement", "HTMLDirectoryElement", |
| 87 "HTMLDivElement", "HTMLDocument", "HTMLElement", "HTMLEmbedElement", |
| 88 "HTMLFieldSetElement", "HTMLFontElement", "HTMLFormElement", |
| 89 "HTMLFrameElement", "HTMLFrameSetElement", "HTMLHRElement", |
| 90 "HTMLHeadElement", "HTMLHeadingElement", "HTMLHtmlElement", |
| 91 "HTMLIFrameElement", "HTMLImageElement", "HTMLInputElement", |
| 92 "HTMLIsIndexElement", "HTMLKeygenElement", "HTMLLIElement", |
| 93 "HTMLLabelElement", "HTMLLegendElement", "HTMLLinkElement", |
| 94 "HTMLMapElement", "HTMLMarqueeElement", "HTMLMediaElement", |
| 95 "HTMLMenuElement", "HTMLMetaElement", "HTMLMeterElement", |
| 96 "HTMLModElement", "HTMLOListElement", "HTMLObjectElement", |
| 97 "HTMLOptGroupElement", "HTMLOptionElement", "HTMLOptionsCollection", |
| 98 "HTMLOutputElement", "HTMLParagraphElement", "HTMLParamElement", |
| 99 "HTMLPreElement", "HTMLProgressElement", "HTMLQuoteElement", |
| 100 "HTMLScriptElement", "HTMLSelectElement", "HTMLSourceElement", |
| 101 "HTMLSpanElement", "HTMLStyleElement", "HTMLTableCaptionElement", |
| 102 "HTMLTableCellElement", "HTMLTableColElement", "HTMLTableElement", |
| 103 "HTMLTableRowElement", "HTMLTableSectionElement", |
| 104 "HTMLTextAreaElement", "HTMLTitleElement", "HTMLTrackElement", |
| 105 "HTMLUListElement", "HTMLUnknownElement", "HTMLVideoElement", |
| 106 "HashChangeEvent", "HighPass2FilterNode", "History", "IDBAny", |
| 107 "IDBCursor", "IDBCursorWithValue", "IDBDatabase", |
| 108 "IDBDatabaseError", "IDBDatabaseException", "IDBFactory", |
| 109 "IDBIndex", "IDBKey", "IDBKeyRange", "IDBObjectStore", "IDBRequest", |
| 110 "IDBTransaction", "IDBVersionChangeEvent", |
| 111 "IDBVersionChangeRequest", "ImageData", "InjectedScriptHost", |
| 112 "InspectorFrontendHost", "Int16Array", "Int32Array", "Int8Array", |
| 113 "JavaScriptAudioNode", "JavaScriptCallFrame", "KeyboardEvent", |
| 114 "Location", "LowPass2FilterNode", "MediaElementAudioSourceNode", |
| 115 "MediaError", "MediaList", "MediaQueryList", |
| 116 "MediaQueryListListener", "MemoryInfo", "MessageChannel", |
| 117 "MessageEvent", "MessagePort", "Metadata", "MouseEvent", |
| 118 "MutationCallback", "MutationEvent", "MutationRecord", |
| 119 "NamedNodeMap", "Navigator", "NavigatorUserMediaError", |
| 120 "NavigatorUserMediaSuccessCallback", "Node", "NodeFilter", |
| 121 "NodeIterator", "NodeList", "NodeSelector", "Notation", |
| 122 "Notification", "NotificationCenter", "OESStandardDerivatives", |
| 123 "OESTextureFloat", "OESVertexArrayObject", |
| 124 "OfflineAudioCompletionEvent", "OperationNotAllowedException", |
| 125 "OverflowEvent", "PageTransitionEvent", "Performance", |
| 126 "PerformanceNavigation", "PerformanceTiming", "PopStateEvent", |
| 127 "PositionError", "ProcessingInstruction", "ProgressEvent", |
| 128 "RGBColor", "Range", "RangeException", "RealtimeAnalyserNode", |
| 129 "Rect", "SQLError", "SQLException", "SQLResultSet", |
| 130 "SQLResultSetRowList", "SQLTransaction", "SQLTransactionSync", |
| 131 "SVGAElement", "SVGAltGlyphDefElement", "SVGAltGlyphElement", |
| 132 "SVGAltGlyphItemElement", "SVGAngle", "SVGAnimateColorElement", |
| 133 "SVGAnimateElement", "SVGAnimateMotionElement", |
| 134 "SVGAnimateTransformElement", "SVGAnimatedAngle", |
| 135 "SVGAnimatedBoolean", "SVGAnimatedEnumeration", |
| 136 "SVGAnimatedInteger", "SVGAnimatedLength", "SVGAnimatedLengthList", |
| 137 "SVGAnimatedNumber", "SVGAnimatedNumberList", |
| 138 "SVGAnimatedPreserveAspectRatio", "SVGAnimatedRect", |
| 139 "SVGAnimatedString", "SVGAnimatedTransformList", |
| 140 "SVGAnimationElement", "SVGCircleElement", "SVGClipPathElement", |
| 141 "SVGColor", "SVGComponentTransferFunctionElement", |
| 142 "SVGCursorElement", "SVGDefsElement", "SVGDescElement", |
| 143 "SVGDocument", "SVGElement", "SVGElementInstance", |
| 144 "SVGElementInstanceList", "SVGEllipseElement", "SVGException", |
| 145 "SVGExternalResourcesRequired", "SVGFEBlendElement", |
| 146 "SVGFEColorMatrixElement", "SVGFEComponentTransferElement", |
| 147 "SVGFECompositeElement", "SVGFEConvolveMatrixElement", |
| 148 "SVGFEDiffuseLightingElement", "SVGFEDisplacementMapElement", |
| 149 "SVGFEDistantLightElement", "SVGFEDropShadowElement", |
| 150 "SVGFEFloodElement", "SVGFEFuncAElement", "SVGFEFuncBElement", |
| 151 "SVGFEFuncGElement", "SVGFEFuncRElement", |
| 152 "SVGFEGaussianBlurElement", "SVGFEImageElement", |
| 153 "SVGFEMergeElement", "SVGFEMergeNodeElement", |
| 154 "SVGFEMorphologyElement", "SVGFEOffsetElement", |
| 155 "SVGFEPointLightElement", "SVGFESpecularLightingElement", |
| 156 "SVGFESpotLightElement", "SVGFETileElement", |
| 157 "SVGFETurbulenceElement", "SVGFilterElement", |
| 158 "SVGFilterPrimitiveStandardAttributes", "SVGFitToViewBox", |
| 159 "SVGFontElement", "SVGFontFaceElement", "SVGFontFaceFormatElement", |
| 160 "SVGFontFaceNameElement", "SVGFontFaceSrcElement", |
| 161 "SVGFontFaceUriElement", "SVGForeignObjectElement", "SVGGElement", |
| 162 "SVGGlyphElement", "SVGGlyphRefElement", "SVGGradientElement", |
| 163 "SVGHKernElement", "SVGImageElement", "SVGLangSpace", "SVGLength", |
| 164 "SVGLengthList", "SVGLineElement", "SVGLinearGradientElement", |
| 165 "SVGLocatable", "SVGMPathElement", "SVGMarkerElement", |
| 166 "SVGMaskElement", "SVGMatrix", "SVGMetadataElement", |
| 167 "SVGMissingGlyphElement", "SVGNumber", "SVGNumberList", "SVGPaint", |
| 168 "SVGPathElement", "SVGPathSeg", "SVGPathSegArcAbs", |
| 169 "SVGPathSegArcRel", "SVGPathSegClosePath", |
| 170 "SVGPathSegCurvetoCubicAbs", "SVGPathSegCurvetoCubicRel", |
| 171 "SVGPathSegCurvetoCubicSmoothAbs", |
| 172 "SVGPathSegCurvetoCubicSmoothRel", "SVGPathSegCurvetoQuadraticAbs", |
| 173 "SVGPathSegCurvetoQuadraticRel", |
| 174 "SVGPathSegCurvetoQuadraticSmoothAbs", |
| 175 "SVGPathSegCurvetoQuadraticSmoothRel", "SVGPathSegLinetoAbs", |
| 176 "SVGPathSegLinetoHorizontalAbs", "SVGPathSegLinetoHorizontalRel", |
| 177 "SVGPathSegLinetoRel", "SVGPathSegLinetoVerticalAbs", |
| 178 "SVGPathSegLinetoVerticalRel", "SVGPathSegList", |
| 179 "SVGPathSegMovetoAbs", "SVGPathSegMovetoRel", "SVGPatternElement", |
| 180 "SVGPoint", "SVGPointList", "SVGPolygonElement", |
| 181 "SVGPolylineElement", "SVGPreserveAspectRatio", |
| 182 "SVGRadialGradientElement", "SVGRect", "SVGRectElement", |
| 183 "SVGRenderingIntent", "SVGSVGElement", "SVGScriptElement", |
| 184 "SVGSetElement", "SVGStopElement", "SVGStringList", "SVGStylable", |
| 185 "SVGStyleElement", "SVGSwitchElement", "SVGSymbolElement", |
| 186 "SVGTRefElement", "SVGTSpanElement", "SVGTests", |
| 187 "SVGTextContentElement", "SVGTextElement", "SVGTextPathElement", |
| 188 "SVGTextPositioningElement", "SVGTitleElement", "SVGTransform", |
| 189 "SVGTransformList", "SVGTransformable", "SVGURIReference", |
| 190 "SVGUnitTypes", "SVGUseElement", "SVGVKernElement", |
| 191 "SVGViewElement", "SVGViewSpec", "SVGZoomAndPan", "SVGZoomEvent", |
| 192 "Screen", "ScriptProfile", "ScriptProfileNode", "SharedWorker", |
| 193 "SharedWorkercontext", "SpeechInputEvent", "SpeechInputResult", |
| 194 "SpeechInputResultList", "Storage", "StorageEvent", "StorageInfo", |
| 195 "StyleMedia", "StyleSheet", "StyleSheetList", "Text", "TextEvent", |
| 196 "TextMetrics", "TextTrack", "TextTrackCue", "TextTrackCueList", |
| 197 "TimeRanges", "Touch", "TouchEvent", "TouchList", "TreeWalker", |
| 198 "UIEvent", "Uint16Array", "Uint32Array", "Uint8Array", |
| 199 "ValidityState", "VoidCallback", "WaveShaperNode", |
| 200 "WebGLActiveInfo", "WebGLBuffer", "WebGLContextAttributes", |
| 201 "WebGLContextEvent", "WebGLDebugRendererInfo", "WebGLDebugShaders", |
| 202 "WebGLFramebuffer", "WebGLProgram", "WebGLRenderbuffer", |
| 203 "WebGLRenderingContext", "WebGLShader", "WebGLTexture", |
| 204 "WebGLUniformLocation", "WebGLVertexArrayObjectOES", |
| 205 "WebKitAnimation", "WebKitAnimationEvent", "WebKitAnimationList", |
| 206 "WebKitBlobBuilder", "WebKitCSSFilterValue", |
| 207 "WebKitCSSKeyframeRule", "WebKitCSSKeyframesRule", |
| 208 "WebKitCSSMatrix", "WebKitCSSTransformValue", "WebKitFlags", |
| 209 "WebKitLoseContext", "WebKitMutationObserver", "WebKitPoint", |
| 210 "WebKitTransitionEvent", "WebSocket", "WheelEvent", "Worker", |
| 211 "WorkerContext", "WorkerLocation", "WorkerNavigator", |
| 212 "XMLHttpRequest", "XMLHttpRequestException", |
| 213 "XMLHttpRequestProgressEvent", "XMLHttpRequestUpload", |
| 214 "XMLSerializer", "XPathEvaluator", "XPathException", |
| 215 "XPathExpression", "XPathNSResolver", "XPathResult", |
| 216 "XSLTProcessor", "AudioBufferCallback", "DatabaseCallback", |
| 217 "EntriesCallback", "EntryCallback", "ErrorCallback", "FileCallback", |
| 218 "FileSystemCallback", "FileWriterCallback", "MetadataCallback", |
| 219 "NavigatorUserMediaErrorCallback", "PositionCallback", |
| 220 "PositionErrorCallback", "SQLStatementCallback", |
| 221 "SQLStatementErrorCallback", "SQLTransactionCallback", |
| 222 "SQLTransactionErrorCallback", "SQLTransactionSyncCallback", |
| 223 "StorageInfoErrorCallback", "StorageInfoQuotaCallback", |
| 224 "StorageInfoUsageCallback", "StringCallback" |
| 225 ]; |
| 226 |
| 227 Map dbEntry; |
| 228 |
| 229 Map get dartIdl() => data['dartIdl']; |
| 230 String get currentType() => data['type']; |
| 231 |
| 232 String _currentTypeShort; |
| 233 String get currentTypeShort() { |
| 234 if (_currentTypeShort == null) { |
| 235 _currentTypeShort = currentType; |
| 236 _currentTypeShort = trimPrefix(_currentTypeShort, "HTML"); |
| 237 _currentTypeShort = trimPrefix(_currentTypeShort, "SVG"); |
| 238 _currentTypeShort = trimPrefix(_currentTypeShort, "DOM"); |
| 239 _currentTypeShort = trimPrefix(_currentTypeShort, "WebKit"); |
| 240 _currentTypeShort = trimPrefix(_currentTypeShort, "Webkit"); |
| 241 } |
| 242 return _currentTypeShort; |
| 243 } |
| 244 |
| 245 String _currentTypeTiny; |
| 246 String get currentTypeTiny() { |
| 247 if (_currentTypeTiny == null) { |
| 248 _currentTypeTiny = currentTypeShort; |
| 249 _currentTypeTiny = trimEnd(_currentTypeTiny, "Element"); |
| 250 } |
| 251 return _currentTypeTiny; |
| 252 } |
| 253 |
| 254 Map get searchResult() => data['searchResult']; |
| 255 String get pageUrl() => searchResult['link']; |
| 256 |
| 257 String _pageDomain; |
| 258 String get pageDomain() { |
| 259 if (_pageDomain == null) { |
| 260 _pageDomain = pageUrl.substring(0, pageUrl.indexOf("/", "https://".length)); |
| 261 } |
| 262 return _pageDomain; |
| 263 } |
| 264 |
| 265 String get pageDir() { |
| 266 return pageUrl.substring(0, pageUrl.lastIndexOf('/') + 1); |
| 267 } |
| 268 |
| 269 String getAbsoluteUrl(AnchorElement anchor) { |
| 270 if (anchor == null || anchor.href.length == 0) return ''; |
| 271 String path = anchor.href; |
| 272 RegExp fullUrlRegExp = new RegExp("^https?://"); |
| 273 if (fullUrlRegExp.hasMatch(path)) return path; |
| 274 if (path.startsWith('/')) { |
| 275 return "$pageDomain$path"; |
| 276 } else if (path.startsWith("#")) { |
| 277 return "$pageUrl$path"; |
| 278 } else { |
| 279 return "$pageDir$path"; |
| 280 } |
| 281 } |
| 282 |
| 283 bool inTable(Node n) { |
| 284 while(n != null) { |
| 285 if (n is TableElement) return true; |
| 286 n = n.parent; |
| 287 } |
| 288 return false; |
| 289 } |
| 290 |
| 291 String escapeHTML(str) { |
| 292 Element e = new Element.tag("div"); |
| 293 e.text = str; |
| 294 return e.innerHTML; |
| 295 } |
| 296 |
| 297 List<Text> getAllTextNodes(Element elem) { |
| 298 List<Text> nodes = <Text>[]; |
| 299 helper(Node n) { |
| 300 if (n is Text) { |
| 301 nodes.add(n); |
| 302 } else { |
| 303 for (Node child in n.nodes) { |
| 304 helper(child); |
| 305 } |
| 306 } |
| 307 }; |
| 308 |
| 309 helper(elem); |
| 310 return nodes; |
| 311 } |
| 312 |
| 313 /** |
| 314 * Whether a node and its children are all types that are safe to skip if the |
| 315 * nodes have no text content. |
| 316 */ |
| 317 bool isSkippableType(Node n) { |
| 318 // TODO(jacobr): are there any types we don't want to skip even if they |
| 319 // have no text content? |
| 320 if (n is ImageElement || n is CanvasElement || n is InputElement |
| 321 || n is ObjectElement) { |
| 322 return false; |
| 323 } |
| 324 if (n is Text) return true; |
| 325 |
| 326 for (Node child in n.nodes) { |
| 327 if (isSkippableType(child) == false) { |
| 328 return false; |
| 329 } |
| 330 } |
| 331 return true; |
| 332 } |
| 333 |
| 334 bool isSkippable(Node n) { |
| 335 if (!isSkippableType(n)) return false; |
| 336 return n.text.trim().length == 0; |
| 337 } |
| 338 |
| 339 void onEnd() { |
| 340 // Hideous hack to send JSON back to JS. |
| 341 String dbJson = JSON.stringify(dbEntry); |
| 342 // workaround bug in JSON parser. |
| 343 dbJson = dbJson.replaceAll("ZDARTIUMDOESNTESCAPESLASHNJXXXX", "\\n"); |
| 344 |
| 345 window.postMessage("START_DART_MESSAGE_UNIQUE_IDENTIFIER$dbJson", "*"); |
| 346 } |
| 347 |
| 348 class SectionParseResult { |
| 349 final String html; |
| 350 final String url; |
| 351 final String idl; |
| 352 SectionParseResult(this.html, this.url, this.idl); |
| 353 } |
| 354 |
| 355 String genCleanHtml(Element root) { |
| 356 for (Element e in root.queryAll(".$DART_REMOVED")) { |
| 357 e.classes.remove(DART_REMOVED); |
| 358 } |
| 359 |
| 360 // Ditch inline styles. |
| 361 for (Element e in root.queryAll('[style]')) { |
| 362 e.attributes.remove('style'); |
| 363 } |
| 364 |
| 365 // These elements are just tags that we should suppress. |
| 366 for (Element e in root.queryAll(".lang.lang-en")) { |
| 367 e.remove(); |
| 368 } |
| 369 |
| 370 bool changed = true; |
| 371 while (changed) { |
| 372 changed = false; |
| 373 while (root.nodes.length == 1) { |
| 374 Node child = root.nodes.first; |
| 375 if (child is Element) { |
| 376 root = child; |
| 377 changed = true; |
| 378 } else { |
| 379 // Just calling innerHTML on the parent will be sufficient... |
| 380 // and insures the output is properly escaped. |
| 381 break; |
| 382 } |
| 383 } |
| 384 |
| 385 // Trim useless nodes from the front. |
| 386 while(root.nodes.length > 0 && |
| 387 isSkippable(root.nodes.first)) { |
| 388 root.nodes.first.remove(); |
| 389 changed = true; |
| 390 } |
| 391 |
| 392 // Trim useless nodes from the back. |
| 393 while(root.nodes.length > 0 && |
| 394 isSkippable(root.nodes.last())) { |
| 395 root.nodes.last().remove(); |
| 396 changed = true; |
| 397 } |
| 398 } |
| 399 return JSONFIXUPHACK(root.innerHTML); |
| 400 } |
| 401 |
| 402 String genPrettyHtml(DocumentFragment fragment) { |
| 403 return genCleanHtml(fragment); |
| 404 } |
| 405 |
| 406 String genPrettyHtmlFromElement(Element e) { |
| 407 e = e.clone(true); |
| 408 return genCleanHtml(e); |
| 409 } |
| 410 |
| 411 class PostOrderTraversalIterator implements Iterator<Node> { |
| 412 |
| 413 Node _next; |
| 414 |
| 415 PostOrderTraversalIterator(Node start) { |
| 416 _next = _leftMostDescendent(start); |
| 417 } |
| 418 |
| 419 bool hasNext() => _next != null; |
| 420 |
| 421 Node next() { |
| 422 if (_next == null) return null; |
| 423 Node ret = _next; |
| 424 if (_next.nextNode != null) { |
| 425 _next = _leftMostDescendent(_next.nextNode); |
| 426 } else { |
| 427 _next = _next.parent; |
| 428 } |
| 429 return ret; |
| 430 } |
| 431 |
| 432 static Node _leftMostDescendent(Node n) { |
| 433 while (n.nodes.length > 0) { |
| 434 n = n.nodes.first; |
| 435 } |
| 436 return n; |
| 437 } |
| 438 } |
| 439 |
| 440 class PostOrderTraversal implements Iterable<Node> { |
| 441 final Node _node; |
| 442 PostOrderTraversal(this._node); |
| 443 |
| 444 Iterator<Node> iterator() => new PostOrderTraversalIterator(_node); |
| 445 } |
| 446 |
| 447 Range findFirstLine(Range section, String prop) { |
| 448 Range firstLine = newRange(); |
| 449 firstLine.setStart(section.startContainer, section.startOffset); |
| 450 |
| 451 num maxBottom = null; |
| 452 for (Node n in new PostOrderTraversal(section.startContainer)) { |
| 453 int compareResult = section.comparePoint(n, 0); |
| 454 if (compareResult == -1) { |
| 455 // before range so skip. |
| 456 continue; |
| 457 } else if (compareResult > 0) { |
| 458 // After range so exit. |
| 459 break; |
| 460 } |
| 461 |
| 462 final rect = getClientRect(n); |
| 463 num bottom = rect.bottom; |
| 464 if (rect.height > 0 && rect.width > 0) { |
| 465 if (maxBottom != null && ( |
| 466 maxBottom + MIN_PIXELS_DIFFERENT_LINES < bottom |
| 467 )) { |
| 468 break; |
| 469 } else if (maxBottom == null || maxBottom > bottom) { |
| 470 maxBottom = bottom; |
| 471 } |
| 472 } |
| 473 |
| 474 firstLine.setEndAfter(n); |
| 475 } |
| 476 |
| 477 if (firstLine.toString().indexOf(stripWebkit(prop)) == -1) { |
| 478 return null; |
| 479 } |
| 480 return firstLine; |
| 481 } |
| 482 |
| 483 AnchorElement findAnchorElement(Element root, String prop) { |
| 484 for (AnchorElement a in root.queryAll("a")) { |
| 485 if (a.text.indexOf(prop) != -1) { |
| 486 return a; |
| 487 } |
| 488 } |
| 489 return null; |
| 490 } |
| 491 |
| 492 // First surrounding element with an ID is safe enough. |
| 493 Element findTigherRoot(Element elem, Element root) { |
| 494 Element candidate = elem; |
| 495 while(root != candidate) { |
| 496 candidate = candidate.parent; |
| 497 if (candidate.id.length > 0 && candidate.id.indexOf("section_") != 0) { |
| 498 break; |
| 499 } |
| 500 } |
| 501 return candidate; |
| 502 } |
| 503 |
| 504 // this is very slow and ugly.. consider rewriting. |
| 505 SectionParseResult filteredHtml(Element elem, Element root, String prop, |
| 506 Function fragmentGeneratedCallback) { |
| 507 // Using a tighter root avoids false positives at the risk of trimming |
| 508 // text we shouldn't. |
| 509 root = findTigherRoot(elem, root); |
| 510 Range range = newRange(); |
| 511 range.setStartBefore(elem); |
| 512 |
| 513 Element current = elem; |
| 514 while (current != null) { |
| 515 range.setEndBefore(current); |
| 516 if (current.classes.contains(DART_REMOVED)) { |
| 517 if (range.toString().trim().length > 0) { |
| 518 break; |
| 519 } |
| 520 } |
| 521 if (current.firstElementChild != null) { |
| 522 current = current.firstElementChild; |
| 523 } else { |
| 524 while (current != null) { |
| 525 range.setEndAfter(current); |
| 526 if (current == root) { |
| 527 current = null; |
| 528 break; |
| 529 } |
| 530 if (current.nextElementSibling != null) { |
| 531 current = current.nextElementSibling; |
| 532 break; |
| 533 } |
| 534 current = current.parent; |
| 535 } |
| 536 } |
| 537 } |
| 538 String url = null; |
| 539 if (prop != null) { |
| 540 Range firstLine = findFirstLine(range, prop); |
| 541 if (firstLine != null) { |
| 542 range.setStart(firstLine.endContainer, firstLine.endOffset); |
| 543 DocumentFragment firstLineClone = firstLine.cloneContents(); |
| 544 AnchorElement anchor = findAnchorElement(firstLineClone, prop); |
| 545 if (anchor != null) { |
| 546 url = getAbsoluteUrl(anchor); |
| 547 } |
| 548 } |
| 549 } |
| 550 DocumentFragment fragment = range.cloneContents(); |
| 551 if (fragmentGeneratedCallback != null) { |
| 552 fragmentGeneratedCallback(fragment); |
| 553 } |
| 554 // Strip tags we don't want |
| 555 for (Element e in fragment.queryAll("script, object, style")) { |
| 556 e.remove(); |
| 557 } |
| 558 |
| 559 // Extract idl |
| 560 StringBuffer idl = new StringBuffer(); |
| 561 if (prop != null && prop.length > 0) { |
| 562 // Only expect properties to have HTML. |
| 563 for(Element e in fragment.queryAll(IDL_SELECTOR)) { |
| 564 idl.add(e.outerHTML); |
| 565 e.remove(); |
| 566 } |
| 567 // TODO(jacobr) this is a very basic regex to see if text looks like IDL |
| 568 RegExp likelyIdl = new RegExp(" $prop\\w*\\("); |
| 569 |
| 570 for (Element e in fragment.queryAll("pre")) { |
| 571 // Check if it looks like idl... |
| 572 String txt = e.text.trim(); |
| 573 if (likelyIdl.hasMatch(txt) && txt.indexOf("\n") != -1 |
| 574 && txt.indexOf(")") != -1) { |
| 575 idl.add(e.outerHTML); |
| 576 e.remove(); |
| 577 } |
| 578 } |
| 579 } |
| 580 return new SectionParseResult(genPrettyHtml(fragment), url, idl.toString()); |
| 581 } |
| 582 |
| 583 Element findBest(Element root, List<Text> allText, String prop, String propType)
{ |
| 584 // Best bet: match an id |
| 585 Element cand; |
| 586 cand = root.query("#" + prop); |
| 587 |
| 588 if (cand == null && propType == "methods") { |
| 589 cand = root.query("[id=" + prop + "\\(\\)]"); |
| 590 } |
| 591 if (cand != null) { |
| 592 while (cand != null && cand.text.trim().length == 0) { |
| 593 // We found the bookmark for the element but sadly it is just an empty |
| 594 // placeholder. Find the first real element. |
| 595 cand = cand.nextElementSibling; |
| 596 } |
| 597 if (cand != null) { |
| 598 return cand; |
| 599 } |
| 600 } |
| 601 |
| 602 // If you are at least 70 pixels from the left, something is definitely fishy
and we shouldn't even consider this candidate. |
| 603 num candLeft = 70; |
| 604 |
| 605 for (Text text in allText) { |
| 606 Element proposed = null; |
| 607 |
| 608 // var t = safeNameCleanup(text.text); |
| 609 // TODO(jacobr): does it hurt precision to use the full cleanup? |
| 610 String t = fullNameCleanup(text.text); |
| 611 if (t == prop) { |
| 612 proposed = text.parent; |
| 613 ClientRect candRect = getClientRect(proposed); |
| 614 |
| 615 // TODO(jacobr): this is a good heuristic |
| 616 // if (selObj.selector.indexOf(" > DD ") == -1 |
| 617 if (candRect.left < candLeft) { |
| 618 cand = proposed; |
| 619 candLeft = candRect.left; |
| 620 } |
| 621 } |
| 622 } |
| 623 return cand; |
| 624 } |
| 625 |
| 626 bool isObsolete(Element e) { |
| 627 RegExp obsoleteRegExp = new RegExp(@"(^|\s)obsolete(?=\s|$)"); |
| 628 RegExp deprecatedRegExp = new RegExp(@"(^|\s)deprecated(?=\s|$)"); |
| 629 for (Element child in e.queryAll("span")) { |
| 630 String t = child.text.toLowerCase(); |
| 631 if (t.startsWith("obsolete") || t.startsWith("deprecated")) return true; |
| 632 } |
| 633 |
| 634 String text = e.text.toLowerCase(); |
| 635 return obsoleteRegExp.hasMatch(text) || deprecatedRegExp.hasMatch(text); |
| 636 } |
| 637 |
| 638 bool isFirstCharLowerCase(String str) { |
| 639 RegExp firstLower = new RegExp("^[a-z]"); |
| 640 return firstLower.hasMatch(str); |
| 641 } |
| 642 |
| 643 void scrapeSection(Element root, String sectionSelector, |
| 644 String currentType, |
| 645 List members, |
| 646 String propType) { |
| 647 Map expectedProps = dartIdl[propType]; |
| 648 |
| 649 Set<String> alreadyMatchedProperties = new Set<String>(); |
| 650 bool onlyConsiderTables = false; |
| 651 ElementList allMatches = root.queryAll(sectionSelector); |
| 652 if (allMatches.length == 0) { |
| 653 allMatches = root.queryAll(".fullwidth-table"); |
| 654 onlyConsiderTables = true; |
| 655 } |
| 656 for (Element matchElement in allMatches) { |
| 657 DivElement match = matchElement.parent; |
| 658 if (!match.id.startsWith("section") && !(match.id == "pageText")) { |
| 659 throw "Enexpected element $match"; |
| 660 } |
| 661 match.classes.add(DART_REMOVED); |
| 662 |
| 663 bool foundProps = false; |
| 664 |
| 665 // TODO(jacobr): we should really look for the table tag instead |
| 666 // add an assert if we are missing something that is a table... |
| 667 // TODO(jacobr) ignore tables in tables.... |
| 668 for (Element t in match.queryAll('.standard-table, .fullwidth-table')) { |
| 669 int helpIndex = -1; |
| 670 num i = 0; |
| 671 for (Element r in t.queryAll("th, td.header")) { |
| 672 var txt = r.text.trim().split(" ")[0].toLowerCase(); |
| 673 if (txt == "description") { |
| 674 helpIndex = i; |
| 675 break; |
| 676 } |
| 677 i++; |
| 678 } |
| 679 |
| 680 List<int> numMatches = new List<int>(i); |
| 681 for (int j = 0; j < i; j++) { |
| 682 numMatches[j] = 0; |
| 683 } |
| 684 |
| 685 // Find the row that seems to have the most names that look like |
| 686 // expected properties. |
| 687 for (Element r in t.queryAll("tbody tr")) { |
| 688 ElementList $row = r.elements; |
| 689 if ($row.length == 0 || $row.first.classes.contains(".header")) { |
| 690 continue; |
| 691 } |
| 692 |
| 693 for (int k = 0; k < numMatches.length && k < $row.length; k++) { |
| 694 Element e = $row[k]; |
| 695 if (expectedProps.containsKey(fullNameCleanup(e.text))) { |
| 696 numMatches[k]++; |
| 697 break; |
| 698 } |
| 699 } |
| 700 } |
| 701 |
| 702 int propNameIndex = 0; |
| 703 { |
| 704 int bestCount = numMatches[0]; |
| 705 for (int k = 1; k < numMatches.length; k++) { |
| 706 if (numMatches[k] > bestCount) { |
| 707 bestCount = numMatches[k]; |
| 708 propNameIndex = k; |
| 709 } |
| 710 } |
| 711 } |
| 712 |
| 713 for (Element r in t.queryAll("tbody tr")) { |
| 714 ElementList $row = r.elements; |
| 715 if ($row.length > propNameIndex && $row.length > helpIndex ) { |
| 716 if ($row.first.classes.contains(".header")) { |
| 717 continue; |
| 718 } |
| 719 // TODO(jacobr): this code for determining the namestr is needlessly |
| 720 // messy. |
| 721 Element nameRow = $row[propNameIndex]; |
| 722 AnchorElement a = nameRow.query("a"); |
| 723 String goodName = ''; |
| 724 if (a != null) { |
| 725 goodName = a.text.trim(); |
| 726 } |
| 727 String nameStr = nameRow.text; |
| 728 |
| 729 Map entry = new Map<String, String>(); |
| 730 |
| 731 // "currentType": $($row[1]).text().trim(), // find("code") ? |
| 732 entry["name"] = fullNameCleanup(nameStr.length > 0 ? nameStr : goodNam
e); |
| 733 |
| 734 final parse = filteredHtml(nameRow, nameRow, entry["name"], null); |
| 735 String altHelp = parse.html; |
| 736 |
| 737 // "jsSignature": nameStr, |
| 738 entry["help"] = (helpIndex == -1 || $row[helpIndex] == null) ? altHelp
: genPrettyHtmlFromElement($row[helpIndex]); |
| 739 // "altHelp" : altHelp, |
| 740 if (parse.url != null) { |
| 741 entry["url"] = parse.url; |
| 742 } |
| 743 |
| 744 if (parse.idl.length > 0) { |
| 745 entry["idl"] = parse.idl; |
| 746 } |
| 747 |
| 748 entry["obsolete"] = isObsolete(r); |
| 749 |
| 750 if (entry["name"].length > 0) { |
| 751 cleanupEntry(members, entry); |
| 752 alreadyMatchedProperties.add(entry['name']); |
| 753 foundProps = true; |
| 754 } |
| 755 } |
| 756 } |
| 757 } |
| 758 |
| 759 if (onlyConsiderTables) { |
| 760 continue; |
| 761 } |
| 762 // After this point we have higher risk tests that attempt to perform |
| 763 // rudimentary page segmentation. |
| 764 |
| 765 // Search for expected matching names. |
| 766 List<Text> allText = getAllTextNodes(match); |
| 767 |
| 768 Map<String, Element> pmap = new Map<String, Element>(); |
| 769 for (String prop in expectedProps.getKeys()) { |
| 770 if (alreadyMatchedProperties.contains(prop)) { |
| 771 continue; |
| 772 } |
| 773 Element e = findBest(match, allText, prop, propType); |
| 774 if (e != null && !inTable(e)) { |
| 775 pmap[prop] = e; |
| 776 } |
| 777 } |
| 778 |
| 779 for (String prop in pmap.getKeys()) { |
| 780 Element e = pmap[prop]; |
| 781 e.classes.add(DART_REMOVED); |
| 782 } |
| 783 |
| 784 for (String prop in pmap.getKeys()) { |
| 785 Element e = pmap[prop]; |
| 786 ClientRect r = getClientRect(e); |
| 787 // TODO(jacobr): a lot of these queries are identical. |
| 788 for (Element cand in match.queryAll(e.tagName)) { |
| 789 if (!cand.classes.contains(DART_REMOVED) && !inTable(cand) ) { // XXX us
e a neg selector. |
| 790 ClientRect candRect = getClientRect(cand); |
| 791 // TODO(jacobr): this is somewhat loose. |
| 792 if (candRect.left == r.left && |
| 793 (candRect.height - r.height).abs() < 5) { |
| 794 String propName = fullNameCleanup(cand.text); |
| 795 if (isFirstCharLowerCase(propName) && pmap.containsKey(propName) ==
false && alreadyMatchedProperties.contains(propName) == false) { |
| 796 // Don't set here to avoid layouts... cand.classes.add(DART_REMOVE
D); |
| 797 pmap[propName] = cand; |
| 798 } |
| 799 } |
| 800 } |
| 801 } |
| 802 } |
| 803 |
| 804 for (String prop in pmap.getKeys()) { |
| 805 Element e = pmap[prop]; |
| 806 e.classes.add(DART_REMOVED); |
| 807 } |
| 808 |
| 809 // Find likely "subsections" of the main section and mark them with |
| 810 // DART_REMOVED so we don't include them in member descriptions... which |
| 811 // would suck. |
| 812 for (Element e in match.queryAll("[id]")) { |
| 813 if (e.id.indexOf(matchElement.id) != -1) { |
| 814 e.classes.add(DART_REMOVED); |
| 815 } |
| 816 } |
| 817 |
| 818 for (String prop in pmap.getKeys()) { |
| 819 Element elem = pmap[prop]; |
| 820 bool obsolete = false; |
| 821 final parse = filteredHtml( |
| 822 elem, match, prop, |
| 823 (Element e) { |
| 824 obsolete = isObsolete(e); |
| 825 }); |
| 826 Map entry = { |
| 827 "url" : parse.url, |
| 828 "name" : prop, |
| 829 "help" : parse.html, |
| 830 "obsolete" : obsolete |
| 831 //"jsSignature" : nameStr |
| 832 }; |
| 833 if (parse.idl.length > 0) { |
| 834 entry["idl"] = parse.idl; |
| 835 } |
| 836 cleanupEntry(members, entry); |
| 837 } |
| 838 } |
| 839 } |
| 840 |
| 841 String trimHtml(String html) { |
| 842 // TODO(jacobr): impl. |
| 843 return html; |
| 844 } |
| 845 |
| 846 bool maybeName(String name) { |
| 847 RegExp nameRegExp = new RegExp("^[a-z][a-z0-9A-Z]+\$"); |
| 848 if (nameRegExp.hasMatch(name)) return true; |
| 849 RegExp constRegExp = new RegExp("^[A-Z][A-Z_]*\$"); |
| 850 if (constRegExp.hasMatch(name)) return true; |
| 851 } |
| 852 |
| 853 void markRemoved(var e) { |
| 854 if (e != null) { |
| 855 // TODO( remove) |
| 856 if (e is Element) { |
| 857 e.classes.add(DART_REMOVED); |
| 858 } else { |
| 859 for (Element el in e) { |
| 860 el.classes.add(DART_REMOVED); |
| 861 } |
| 862 } |
| 863 } |
| 864 } |
| 865 |
| 866 String JSONFIXUPHACK(String value) { |
| 867 return value.replaceAll("\n", "ZDARTIUMDOESNTESCAPESLASHNJXXXX"); |
| 868 } |
| 869 |
| 870 String mozToWebkit(String name) { |
| 871 RegExp regExp = new RegExp("^moz"); |
| 872 name = name.replaceFirst(regExp, "webkit"); |
| 873 return name; |
| 874 } |
| 875 |
| 876 String stripWebkit(String name) { |
| 877 return trimPrefix(name, "webkit"); |
| 878 } |
| 879 |
| 880 String fullNameCleanup(String name) { |
| 881 int parenIndex = name.indexOf('('); |
| 882 if (parenIndex != -1) { |
| 883 // TODO(jacobr): workaround bug in: |
| 884 // name = name.split("(")[0]; |
| 885 name = name.substring(0, parenIndex); |
| 886 } |
| 887 name = name.split(" ")[0]; |
| 888 name = name.split("\n")[0]; |
| 889 name = name.split("\t")[0]; |
| 890 name = name.split("*")[0]; |
| 891 name = name.trim(); |
| 892 name = safeNameCleanup(name); |
| 893 return name; |
| 894 } |
| 895 |
| 896 // Less agressive than the full cleanup to avoid overeager matching of |
| 897 // everytyhing |
| 898 String safeNameCleanup(String name) { |
| 899 int parenIndex = name.indexOf('('); |
| 900 if (parenIndex != -1 && name.indexOf(")") != -1) { |
| 901 // TODO(jacobr): workaround bug in: |
| 902 // name = name.split("(")[0]; |
| 903 name = name.substring(0, parenIndex); |
| 904 } |
| 905 name = name.trim(); |
| 906 name = trimPrefix(name, currentType + "."); |
| 907 name = trimPrefix(name, currentType.toLowerCase() + "."); |
| 908 name = trimPrefix(name, currentTypeShort + "."); |
| 909 name = trimPrefix(name, currentTypeShort.toLowerCase() + "."); |
| 910 name = trimPrefix(name, currentTypeTiny + "."); |
| 911 name = trimPrefix(name, currentTypeTiny.toLowerCase() + "."); |
| 912 name = name.trim(); |
| 913 name = mozToWebkit(name); |
| 914 return name; |
| 915 } |
| 916 |
| 917 void removeHeaders(DocumentFragment fragment) { |
| 918 for (Element e in fragment.queryAll("h1, h2, h3")) { |
| 919 e.remove(); |
| 920 } |
| 921 } |
| 922 |
| 923 void cleanupEntry(List members, Map entry) { |
| 924 if (entry.containsKey('help')) { |
| 925 entry['help'] = trimHtml(entry['help']); |
| 926 } |
| 927 String name = fullNameCleanup(entry['name']); |
| 928 entry['name'] = name; |
| 929 if (maybeName(name)) { |
| 930 for (String key in entry.getKeys()) { |
| 931 var value = entry[key]; |
| 932 if (value == null) { |
| 933 entry.remove(key); |
| 934 continue; |
| 935 } |
| 936 if (value is String) { |
| 937 entry[key] = JSONFIXUPHACK(value); |
| 938 } |
| 939 } |
| 940 members.add(entry); |
| 941 } |
| 942 } |
| 943 |
| 944 // TODO(jacobr) dup with trim start.... |
| 945 String trimPrefix(String str, String prefix) { |
| 946 if (str.indexOf(prefix) == 0) { |
| 947 return str.substring(prefix.length); |
| 948 } else { |
| 949 return str; |
| 950 } |
| 951 } |
| 952 |
| 953 void resourceLoaded() { |
| 954 if (data != null) run(); |
| 955 } |
| 956 |
| 957 String trimStart(String str, String start) { |
| 958 if (str.startsWith(start) && str.length > start.length) { |
| 959 return str.substring(start.length); |
| 960 } |
| 961 return str; |
| 962 } |
| 963 |
| 964 String trimEnd(String str, String end) { |
| 965 if (str.endsWith(end) && str.length > end.length) { |
| 966 return str.substring(0, str.length - end.length); |
| 967 } |
| 968 return str; |
| 969 } |
| 970 |
| 971 void extractSection(String selector, String key) { |
| 972 for (Element e in document.queryAll(selector)) { |
| 973 e = e.parent; |
| 974 for (Element skip in e.queryAll("h1, h2, $IDL_SELECTOR")) { |
| 975 skip.remove(); |
| 976 } |
| 977 String html = filteredHtml(e, e, null, removeHeaders).html; |
| 978 if (html.length > 0) { |
| 979 if (dbEntry.containsKey(key)) { |
| 980 dbEntry[key] += html; |
| 981 } else { |
| 982 dbEntry[key] = html; |
| 983 } |
| 984 } |
| 985 e.classes.add(DART_REMOVED); |
| 986 } |
| 987 } |
| 988 |
| 989 void run() { |
| 990 // Inject CSS to insure lines don't wrap unless it was intentional. |
| 991 document.head.nodes.add(new Element.html(""" |
| 992 <style type="text/css"> |
| 993 body { |
| 994 width: 10000px; |
| 995 } |
| 996 </style>""")); |
| 997 |
| 998 String title = trimEnd(window.document.title.trim(), " - MDN"); |
| 999 dbEntry['title'] = title; |
| 1000 |
| 1001 // TODO(rnystrom): Clean up the page a bunch. Not sure if this is the best |
| 1002 // place to do this... |
| 1003 |
| 1004 // Remove the "Introduced in HTML <version>" boxes. |
| 1005 for (Element e in document.queryAll('.htmlVersionHeaderTemplate')) { |
| 1006 e.remove(); |
| 1007 } |
| 1008 |
| 1009 // Flatten the list of known DOM types into a faster and case-insensitive map. |
| 1010 domTypes = {}; |
| 1011 for (final domType in domTypesRaw) { |
| 1012 domTypes[domType.toLowerCase()] = domType; |
| 1013 } |
| 1014 |
| 1015 // Fix up links. |
| 1016 final SHORT_LINK = const RegExp(@'^[\w/]+$'); |
| 1017 final INNER_LINK = const RegExp(@'[Ee]n/(?:[\w/]+/|)([\w#.]+)(?:\(\))?$'); |
| 1018 final MEMBER_LINK = const RegExp(@'(\w+)[.#](\w+)'); |
| 1019 final RELATIVE_LINK = const RegExp(@'^(?:../)*/?[Ee][Nn]/(.+)'); |
| 1020 |
| 1021 // - Make relative links absolute. |
| 1022 // - If we can, take links that point to other MDN pages and retarget them |
| 1023 // to appropriate pages in our docs. |
| 1024 // TODO(rnystrom): Add rel external to links we didn't fix. |
| 1025 for (AnchorElement a in document.queryAll('a')) { |
| 1026 // Get the raw attribute because we *don't* want the browser to fully- |
| 1027 // qualify the name for us since it has the wrong base address for the page. |
| 1028 var href = a.attributes['href']; |
| 1029 |
| 1030 // Ignore busted links. |
| 1031 if (href == null) continue; |
| 1032 |
| 1033 // If we can recognize what it's pointing to, point it to our page instead. |
| 1034 tryToLinkToRealType(maybeType) { |
| 1035 // See if we know a type with that name. |
| 1036 final realType = domTypes[maybeType.toLowerCase()]; |
| 1037 if (realType != null) { |
| 1038 href = '../html/$realType.html'; |
| 1039 } |
| 1040 } |
| 1041 |
| 1042 // If it's a relative link (that we know how to root), make it absolute. |
| 1043 var match = RELATIVE_LINK.firstMatch(href); |
| 1044 if (match != null) { |
| 1045 href = 'https://developer.mozilla.org/en/${match[1]}'; |
| 1046 } |
| 1047 |
| 1048 // If it's a word link like "foo" find a type or make it absolute. |
| 1049 match = SHORT_LINK.firstMatch(href); |
| 1050 if (match != null) { |
| 1051 href = 'https://developer.mozilla.org/en/DOM/${match[0]}'; |
| 1052 } |
| 1053 |
| 1054 // TODO(rnystrom): This is a terrible way to do this. Should use the real |
| 1055 // mapping from DOM names to html class names that we use elsewhere in the |
| 1056 // DOM scripts. |
| 1057 match = INNER_LINK.firstMatch(href); |
| 1058 if (match != null) { |
| 1059 // See if we're linking to a member ("type.name" or "type#name") or just |
| 1060 // a type ("type"). |
| 1061 final member = MEMBER_LINK.firstMatch(match[1]); |
| 1062 if (member != null) { |
| 1063 tryToLinkToRealType(member[1]); |
| 1064 } else { |
| 1065 tryToLinkToRealType(match[1]); |
| 1066 } |
| 1067 } |
| 1068 |
| 1069 // Put it back into the element. |
| 1070 a.attributes['href'] = href; |
| 1071 } |
| 1072 |
| 1073 if (title.toLowerCase().indexOf(currentTypeTiny.toLowerCase()) == -1) { |
| 1074 bool foundMatch = false; |
| 1075 // Test out if the title is really an HTML tag that matches the |
| 1076 // current class name. |
| 1077 for (String tag in [title.split(" ")[0], title.split(".").last()]) { |
| 1078 try { |
| 1079 dom.Element element = dom.document.createElement(tag); |
| 1080 if (element.typeName == currentType) { |
| 1081 foundMatch = true; |
| 1082 break; |
| 1083 } |
| 1084 } catch(e) {} |
| 1085 } |
| 1086 if (foundMatch == false) { |
| 1087 dbEntry['skipped'] = true; |
| 1088 dbEntry['cause'] = "Suspect title"; |
| 1089 onEnd(); |
| 1090 return; |
| 1091 } |
| 1092 } |
| 1093 |
| 1094 Element root = document.query(".pageText"); |
| 1095 if (root == null) { |
| 1096 dbEntry['cause'] = '.pageText not found'; |
| 1097 onEnd(); |
| 1098 return; |
| 1099 } |
| 1100 |
| 1101 markRemoved(root.query("#Notes")); |
| 1102 List members = dbEntry['members']; |
| 1103 |
| 1104 markRemoved(document.queryAll(".pageToc, footer, header, #nav-toolbar")); |
| 1105 markRemoved(document.queryAll("#article-nav")); |
| 1106 markRemoved(document.queryAll(".hideforedit")); |
| 1107 markRemoved(document.queryAll(".navbox")); |
| 1108 markRemoved(document.query("#Method_overview")); |
| 1109 markRemoved(document.queryAll("h1, h2")); |
| 1110 |
| 1111 scrapeSection(root, "#Methods", currentType, members, 'methods'); |
| 1112 scrapeSection(root, "#Constants, #Error_codes, #State_constants", currentType,
members, 'constants'); |
| 1113 // TODO(jacobr): infer tables based on multiple matches rather than |
| 1114 // using a hard coded list of section ids. |
| 1115 scrapeSection(root, |
| 1116 "[id^=Properties], #Notes, [id^=Other_properties], #Attributes, #DOM_prope
rties, #Event_handlers, #Event_Handlers", |
| 1117 currentType, members, 'properties'); |
| 1118 |
| 1119 // Avoid doing this till now to avoid messing up the section scrape. |
| 1120 markRemoved(document.queryAll("h3")); |
| 1121 |
| 1122 ElementList $examples = root.queryAll("span[id^=example], span[id^=Example]"); |
| 1123 |
| 1124 extractSection("#See_also", 'seeAlso'); |
| 1125 extractSection("#Specification, #Specifications", "specification"); |
| 1126 // $("#Methods").parent().remove(); // not safe (e.g. Document) |
| 1127 |
| 1128 // TODO(jacobr): actually extract the constructor(s) |
| 1129 extractSection("#Constructor, #Constructors", 'constructor'); |
| 1130 extractSection("#Browser_compatibility, #Compatibility", 'compatibility'); |
| 1131 |
| 1132 List<String> exampleHtml = []; |
| 1133 for (Element e in $examples) { |
| 1134 e.classes.add(DART_REMOVED); |
| 1135 } |
| 1136 for (Element e in $examples) { |
| 1137 String html = filteredHtml(e, root, null, |
| 1138 (DocumentFragment fragment) { |
| 1139 removeHeaders(fragment); |
| 1140 if (fragment.text.trim().toLowerCase() == "example") { |
| 1141 // Degenerate example. |
| 1142 fragment.nodes.clear(); |
| 1143 } |
| 1144 }).html; |
| 1145 if (html.length > 0) { |
| 1146 exampleHtml.add(html); |
| 1147 } |
| 1148 } |
| 1149 if (exampleHtml.length > 0) { |
| 1150 dbEntry['examples'] = exampleHtml; |
| 1151 } |
| 1152 |
| 1153 StringBuffer summary = new StringBuffer(); |
| 1154 |
| 1155 for (Element e in root.queryAll("#Summary, #Description")) { |
| 1156 summary.add(filteredHtml(root, e, null, removeHeaders).html); |
| 1157 } |
| 1158 |
| 1159 if (summary.length == 0) { |
| 1160 // Remove the "Gecko DOM Reference text" |
| 1161 Element ref = root.query(".lang.lang-en"); |
| 1162 if (ref != null) { |
| 1163 ref = ref.parent; |
| 1164 String refText = ref.text.trim(); |
| 1165 if (refText == "Gecko DOM Reference" || |
| 1166 refText == "« Gecko DOM Reference") { |
| 1167 ref.remove(); |
| 1168 } |
| 1169 } |
| 1170 // Risky... this might add stuff we shouldn't. |
| 1171 summary.add(filteredHtml(root, root, null, removeHeaders).html); |
| 1172 } |
| 1173 |
| 1174 if (summary.length > 0) { |
| 1175 dbEntry['summary'] = summary.toString(); |
| 1176 } |
| 1177 |
| 1178 // Inject CSS to aid debugging in the browser. |
| 1179 document.head.nodes.add(new Element.html(DEBUG_CSS)); |
| 1180 |
| 1181 onEnd(); |
| 1182 } |
| 1183 |
| 1184 void main() { |
| 1185 window.on.load.add(documentLoaded); |
| 1186 } |
| 1187 |
| 1188 void documentLoaded(event) { |
| 1189 new XMLHttpRequest.getTEMPNAME('${window.location}.json', (req) { |
| 1190 data = JSON.parse(req.responseText); |
| 1191 dbEntry = {'members': [], 'srcUrl': pageUrl}; |
| 1192 resourceLoaded(); |
| 1193 }); |
| 1194 } |
OLD | NEW |