Parent Directory
|
Revision Log
|
Patch
| revision 1.8 by wakaba, Sun Apr 27 08:56:34 2008 UTC | revision 1.13 by wakaba, Sun Apr 27 11:27:04 2008 UTC | |
|---|---|---|
| # | Line 2 | Line 2 |
| 2 | <html lang=en> | <html lang=en> |
| 3 | <head> | <head> |
| 4 | <title>Live Scripting HTML Parser</title> | <title>Live Scripting HTML Parser</title> |
| 5 | <link rel=author href="http://suika.fam.cx/~wakaba/who?"> | |
| 6 | <link rel=license href="http://suika.fam.cx/c/gnu/gpl" | |
| 7 | title="GNU GPL2 or later"> | |
| 8 | <style> | <style> |
| 9 | h1, h2 { | h1 { |
| 10 | margin: 0; | |
| 11 | font-size: 150%; | |
| 12 | } | |
| 13 | h2 { | |
| 14 | margin: 0; | margin: 0; |
| 15 | font-size: 100%; | font-size: 100%; |
| 16 | } | } |
| 17 | p, pre { | p { |
| 18 | margin: 0; | margin: 0 1em; |
| 19 | } | } |
| 20 | textarea { | textarea { |
| 21 | width: 100%; | width: 100%; |
| # | Line 47 | Line 54 |
| 54 | var p = new Parser (new InputStream (v)); | var p = new Parser (new InputStream (v)); |
| 55 | var doc = p.doc; | var doc = p.doc; |
| 56 | p.parse (); | p.parse (); |
| 57 | ||
| 58 | log (dumpTree (doc, '')); | log (dumpTree (doc, '')); |
| 59 | ||
| 60 | if (p.hasAsyncScript) { | |
| 61 | log ('Some script codes are executed asynchronously; it means that the document might be rendered in different ways depending on the network condition and other factors'); | |
| 62 | } | |
| 63 | } | } |
| 64 | } // update2 | } // update2 |
| 65 | ||
| # | Line 73 | Line 85 |
| 85 | this.openElements = [doc]; | this.openElements = [doc]; |
| 86 | this.input = i; | this.input = i; |
| 87 | this.scriptsExecutedAfterParsing = []; | this.scriptsExecutedAfterParsing = []; |
| 88 | this.scriptsExecutedSoon = []; | |
| 89 | this.scriptsExecutedAsynchronously = []; | |
| 90 | } // Parser | } // Parser |
| 91 | ||
| 92 | Parser.prototype.getNextToken = function () { | Parser.prototype.getNextToken = function () { |
| # | Line 150 | Line 164 |
| 164 | tagName = v.toLowerCase (); | tagName = v.toLowerCase (); |
| 165 | return ''; | return ''; |
| 166 | }); | }); |
| 167 | e = e.replace (/^\s*([^\s=]+)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|([^"']+)))?/, | while (true) { |
| 168 | function (x, attrName, attrValue1, attrValue2, attrValue3) { | var m = false; |
| 169 | v = attrValue1 || attrValue2 || attrValue3; | e = e.replace (/^\s*([^\s=]+)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|([^"'\s]*)))?/, |
| 170 | v = v.replace (/"/g, '"').replace (/'/g, "'") | function (x, attrName, attrValue1, attrValue2, attrValue3) { |
| 171 | .replace (/&/g, '&'); | v = attrValue1 || attrValue2 || attrValue3; |
| 172 | attrs[attrName.toLowerCase ()] = v; | v = v.replace (/"/g, '"').replace (/'/g, "'") |
| 173 | return ''; | .replace (/&/g, '&'); |
| 174 | }); | attrs[attrName.toLowerCase ()] = v; |
| 175 | m = true; | |
| 176 | return ''; | |
| 177 | }); | |
| 178 | if (!m) break; | |
| 179 | } | |
| 180 | if (e.length) { | if (e.length) { |
| 181 | log ('Broken start tag: "' + e + '"'); | log ('Broken start tag: "' + e + '"'); |
| 182 | } | } |
| # | Line 324 | Line 343 |
| 343 | ||
| 344 | // "When a script completes loading" rules start applying. | // "When a script completes loading" rules start applying. |
| 345 | ||
| 346 | // TODO: Handles "list of scripts that will execute as soon as possible" | while (this.scriptsExecutedSoon.length > 0 || |
| 347 | // and "list of scripts that will execute asynchronously" | this.scriptsExecutedAsynchronously.length > 0) { |
| 348 | // Handle "list of scripts that will execute as soon as possible". | |
| 349 | while (this.scriptsExecutedSoon.length > 0) { | |
| 350 | var e = this.scriptsExecutedSoon.shift (); | |
| 351 | ||
| 352 | // If it has completed loading | |
| 353 | log ('Execute an external script not inserted by parser...'); | |
| 354 | executeScript (this.doc, e); | |
| 355 | ||
| 356 | // NOTE: It MAY be executed before the end of the parsing, according | |
| 357 | // to the spec. | |
| 358 | this.hasAsyncScript = true; | |
| 359 | } | |
| 360 | ||
| 361 | // Handle "list of scripts that will execute asynchronously". | |
| 362 | while (this.scriptsExecutedAsynchronously.length > 0) { | |
| 363 | var e = this.scriptsExecutedAsynchronously.shift (); | |
| 364 | ||
| 365 | // Step 1. | |
| 366 | // We assume that all scripts have been loaded at this time. | |
| 367 | ||
| 368 | // Step 2. | |
| 369 | log ('Execute an asynchronous script...'); | |
| 370 | executeScript (this.doc, e); | |
| 371 | ||
| 372 | // Step 3. | |
| 373 | // | |
| 374 | ||
| 375 | // Step 4. | |
| 376 | // | |
| 377 | ||
| 378 | this.hasAsyncScript = true; | |
| 379 | } | |
| 380 | } | |
| 381 | ||
| 382 | // Handle "list of scripts that will execute when the document has finished | // Handle "list of scripts that will execute when the document has finished |
| 383 | // parsing". | // parsing". |
| # | Line 420 | Line 472 |
| 472 | p.scriptsExecutedAfterParsing.push (e); | p.scriptsExecutedAfterParsing.push (e); |
| 473 | log ('Running a script: aborted (defer)'); | log ('Running a script: aborted (defer)'); |
| 474 | } else if (e.async && e.src != null) { | } else if (e.async && e.src != null) { |
| 475 | // TODO | p.scriptsExecutedAsynchronously.push (e); |
| 476 | } else if (e.async && e.src == null | log ('Running a script: aborted (async src)'); |
| 477 | /* && list of scripts that will execute asynchronously is not empty */) { | } else if (e.async && e.src == null && |
| 478 | // TODO | p.scriptsExecutedAsynchronously.length > 0) { |
| 479 | p.scriptsExecutedAsynchronously.push (e); | |
| 480 | log ('Running a script: aborted (async)'); | |
| 481 | // ISSUE: What is the difference with the case above? | |
| 482 | } else if (e.src != null && e.manakaiParserInserted) { | } else if (e.src != null && e.manakaiParserInserted) { |
| 483 | if (p.scriptExecutedWhenParserResumes) { | if (p.scriptExecutedWhenParserResumes) { |
| 484 | log ('Error: There is a script that will execute as soon as the parser resumes.'); | log ('Error: There is a script that will execute as soon as the parser resumes.'); |
| 485 | } | } |
| 486 | p.scriptExecutedWhenParserResumes = e; | p.scriptExecutedWhenParserResumes = e; |
| 487 | log ('Running a script: aborted (src)'); | log ('Running a script: aborted (src parser-inserted)'); |
| 488 | } else if (e.src != null) { | } else if (e.src != null) { |
| 489 | // TODO | p.scriptsExecutedSoon.push (e); |
| 490 | log ('Running a script: aborted (src)'); | |
| 491 | } else { | } else { |
| 492 | executeScript (doc, e); // even if other scripts are already executing. | executeScript (doc, e); // even if other scripts are already executing. |
| 493 | } | } |
| # | Line 479 | Line 535 |
| 535 | var m; | var m; |
| 536 | if (m = uri.match (/^javascript:\s*(?:'([^']*)'|"([^"]+)")\s*$/i)) { | if (m = uri.match (/^javascript:\s*(?:'([^']*)'|"([^"]+)")\s*$/i)) { |
| 537 | if (m[1]) { | if (m[1]) { |
| 538 | return m[1]; | return unescapeJSLiteral (m[1]); |
| 539 | } else if (m[2]) { | } else if (m[2]) { |
| 540 | return m[2]; | return unescapeJSLiteral (m[2]); |
| 541 | } else { | } else { |
| 542 | return null; | return null; |
| 543 | } | } |
| # | Line 502 | Line 558 |
| 558 | matched = true; | matched = true; |
| 559 | var args = []; | var args = []; |
| 560 | t.replace (/('[^']*'|"[^"]*")/g, function (s, v) { | t.replace (/('[^']*'|"[^"]*")/g, function (s, v) { |
| 561 | args.push (v.substring (1, v.length - 1)); | args.push (unescapeJSLiteral (v.substring (1, v.length - 1))); |
| 562 | return ''; | return ''; |
| 563 | }); | }); |
| 564 | doc.write.apply (doc, args); | doc.write.apply (doc, args); |
| 565 | return ''; | return ''; |
| 566 | }); | }); |
| 567 | s = s.replace (/^\s*var\s+s\s*=\s*document\.createElement\s*\(\s*['"]script['"]\s*\)\s*;\s*s\.src\s*=\s*(?:'(javascript:[^']*)'|"(javascript:[^"]*)")\s*;\s*document\.documentElement\.appendChild\s*\(\s*s\s*\)\s*;\s*/, | |
| 568 | function (s, t, u) { | |
| 569 | matched = true; | |
| 570 | var args = [unescapeJSLiteral (t ? t : u)]; | |
| 571 | doc._insertExternalScript.apply (doc, args); | |
| 572 | return ''; | |
| 573 | }); | |
| 574 | if (s == '') break; | if (s == '') break; |
| 575 | if (!matched) { | if (!matched) { |
| 576 | log ('Script parse error: "' + s + '"'); | log ('Script parse error: "' + s + '"'); |
| # | Line 516 | Line 579 |
| 579 | } | } |
| 580 | } // parseAndRunScript | } // parseAndRunScript |
| 581 | ||
| 582 | function unescapeJSLiteral (s) { | |
| 583 | return s.replace (/\\u([0-9A-Fa-f]{4})/g, function (t, v) { | |
| 584 | return String.fromCharCode (parseInt ('0x' + v)); | |
| 585 | }); | |
| 586 | } // unescapeJSLiteral | |