/[suikacvs]/markup/html/scripting-parser/parser.html
Suika

Diff of /markup/html/scripting-parser/parser.html

Parent Directory Parent Directory | Revision Log Revision Log | View Patch 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 (/&quot;/g, '"').replace (/&apos;/g, "'")          function (x, attrName, attrValue1, attrValue2, attrValue3) {
171              .replace (/&amp;/g, '&');            v = attrValue1 || attrValue2 || attrValue3;
172          attrs[attrName.toLowerCase ()] = v;            v = v.replace (/&quot;/g, '"').replace (/&apos;/g, "'")
173          return '';                .replace (/&amp;/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