Parent Directory
|
Revision Log
|
Patch
| revision 1.6 by wakaba, Fri Apr 25 13:42:51 2008 UTC | revision 1.13 by wakaba, Sun Apr 27 11:27:04 2008 UTC | |
|---|---|---|
| # | Line 1 | Line 1 |
| 1 | <!DOCTYPE HTML> | <!DOCTYPE HTML> |
| 2 | <html lang=en> | <html lang=en> |
| 3 | <head> | <head> |
| 4 | <title>Demo of HTML5 Parsing Algorithm with Scripting Enabled</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 { | |
| 10 | margin: 0; | |
| 11 | font-size: 150%; | |
| 12 | } | |
| 13 | h2 { | |
| 14 | margin: 0; | |
| 15 | font-size: 100%; | |
| 16 | } | |
| 17 | p { | |
| 18 | margin: 0 1em; | |
| 19 | } | |
| 20 | textarea { | textarea { |
| 21 | display: block; | width: 100%; |
| 22 | width: 80%; | -width: 99%; |
| 23 | margin-left: auto; | height: 10em; |
| margin-right: auto; | ||
| min-height: 20em; | ||
| 24 | } | } |
| 25 | output { | output { |
| 26 | display: block; | display: block; |
| # | Line 18 | Line 30 |
| 30 | } | } |
| 31 | </style> | </style> |
| 32 | <script> | <script> |
| 33 | var delayedUpdater = 0; | |
| 34 | ||
| 35 | function update () { | function update () { |
| 36 | document.logElement.textContent = ''; | if (delayedUpdater) { |
| 37 | var p = new Parser (new InputStream (document.sourceElement.value)); | clearTimeout (delayedUpdater); |
| 38 | var doc = p.doc; | delayedUpdater = 0; |
| 39 | p.parse (); | } |
| 40 | log (dumpTree (doc, '')); | delayedUpdater = setTimeout (update2, 100); |
| 41 | } // update | } // update |
| 42 | ||
| 43 | function update2 () { | |
| 44 | var v = document.sourceElement.value; | |
| 45 | if (v != document.previousSourceText) { | |
| 46 | document.previousSourceText = v; | |
| 47 | document.links['permalink'].href | |
| 48 | = location.pathname + '?s=' + encodeURIComponent (v); | |
| 49 | document.links['ldvlink'].href | |
| 50 | = 'http://software.hixie.ch/utilities/js/live-dom-viewer/?' | |
| 51 | + encodeURIComponent (v); | |
| 52 | ||
| 53 | document.logElement.textContent = ''; | |
| 54 | var p = new Parser (new InputStream (v)); | |
| 55 | var doc = p.doc; | |
| 56 | p.parse (); | |
| 57 | ||
| 58 | 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 | |
| 65 | ||
| 66 | var logIndentLevel = 0; | var logIndentLevel = 0; |
| 67 | function log (s) { | function log (s) { |
| 68 | for (var i = 0; i < logIndentLevel; i++) { | for (var i = 0; i < logIndentLevel; i++) { |
| # | Line 46 | Line 83 |
| 83 | } | } |
| 84 | this.doc = doc; | this.doc = doc; |
| 85 | this.openElements = [doc]; | this.openElements = [doc]; |
| 86 | this.in = 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 () { |
| 93 | var p = this; | var p = this; |
| 94 | var i = this.in; | var i = this.input; |
| 95 | if (this.parseMode == 'script') { | if (this.parseMode == 'script') { |
| 96 | var token; | var token; |
| 97 | if (p.insertionPoint <= 0) { | if (p.insertionPoint <= 0) { |
| # | Line 125 | 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 235 | Line 279 |
| 279 | this.openElements[this.openElements.length - 1].appendChild (el); | this.openElements[this.openElements.length - 1].appendChild (el); |
| 280 | ||
| 281 | // 11. Let the insertion point have the value of the old ... | // 11. Let the insertion point have the value of the old ... |
| 282 | ||
| 283 | oldInsertionPoint += this.insertionPoint; | oldInsertionPoint += this.insertionPoint; |
| 284 | this.setInsertionPoint (oldInsertionPoint); | this.setInsertionPoint (oldInsertionPoint); |
| 285 | ||
| # | Line 298 | 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 330 | Line 408 |
| 408 | if (ip == undefined || ip == null || isNaN (ip)) { | if (ip == undefined || ip == null || isNaN (ip)) { |
| 409 | log ('insertion point: set to undefined'); | log ('insertion point: set to undefined'); |
| 410 | this.insertionPoint = undefined; | this.insertionPoint = undefined; |
| 411 | } else if (ip == this.in.s.length) { | } else if (ip == this.input.s.length) { |
| 412 | log ('insertion point: end of file'); | log ('insertion point: end of file'); |
| 413 | this.insertionPoint = ip; | this.insertionPoint = ip; |
| 414 | } else { | } else { |
| 415 | log ('insertion point: set to ' + ip + | log ('insertion point: set to ' + ip + |
| 416 | ' (before "' + this.in.s.substring (0, 10) + '")'); | ' (before "' + this.input.s.substring (0, 10) + '")'); |
| 417 | this.insertionPoint = ip; | this.insertionPoint = ip; |
| 418 | } | } |
| 419 | }; // setInsertionPoint | }; // setInsertionPoint |
| # | Line 394 | 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); | |