/[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.1 by wakaba, Sun Apr 20 06:07:24 2008 UTC revision 1.3 by wakaba, Sun Apr 20 10:02:43 2008 UTC
# Line 19  Line 19 
19  <script>  <script>
20    function update () {    function update () {
21      document.logElement.textContent = '';      document.logElement.textContent = '';
22      var p = new Parser ();      var p = new Parser (new InputStream (document.sourceElement.value));
23      p.parse (new InputStream (document.sourceElement.value));      p.parse ();
24      log (dumpTree (p.doc, ''));      log (dumpTree (p.doc, ''));
25    } // update    } // update
26    
# Line 32  Line 32 
32      this.s = s;      this.s = s;
33    } // InputStream    } // InputStream
34    
35    function Parser () {    function Parser (i) {
36      this.parseMode = 'pcdata';      this.parseMode = 'pcdata';
37      this.doc = new JSDocument ();      this.doc = new JSDocument (this);
38      this.openElements = [this.doc];      this.openElements = [this.doc];
39        this.in = i;
40    } // Parser    } // Parser
41    
42    Parser.prototype.getNextToken = function (i) {    Parser.prototype.getNextToken = function () {
43        var p = this;
44        var i = this.in;
45      if (this.parseMode == 'script') {      if (this.parseMode == 'script') {
46        var token;        var token;
47          if (p.insertionPoint <= 0) {
48            return {type: 'abort'};
49          }
50        i.s = i.s.replace (/^([\s\S]+?)<\/[Ss][Cc][Rr][Ii][Pp][Tt]>/,        i.s = i.s.replace (/^([\s\S]+?)<\/[Ss][Cc][Rr][Ii][Pp][Tt]>/,
51        function (s, t) {        function (s, t) {
52            if (0 < p.insertionPoint && p.insertionPoint < t.length) {
53              token = {type: 'char', value: t.substring (0, p.insertionPoint)};
54              var ip = p.insertionPoint;
55              p.insertionPoint = 0;
56              return t.substring (ip, t.length) +
57                  s.substring (s.length - 9, s.length);
58            }
59          token = {type: 'char', value: t};          token = {type: 'char', value: t};
60            p.insertionPoint -= s.length;
61          return '<' + '/script>';          return '<' + '/script>';
62        });        });
63        if (token) return token;        if (token) return token;
64        i.s = i.s.replace (/^<\/[Ss][Cc][Rr][Ii][Pp][Tt]>/, function () {        i.s = i.s.replace (/^<\/[Ss][Cc][Rr][Ii][Pp][Tt]>/, function (s) {
65            if (s.length < p.insertionPoint) {
66              token = {type: 'abort'};
67              return s;
68            }
69          token = {type: 'end-tag', value: 'script'};          token = {type: 'end-tag', value: 'script'};
70            p.insertionPoint -= s.length;
71          return '';          return '';
72        });        });
73        if (token) return token;        if (token) return token;
# Line 57  Line 76 
76    
77      var token;      var token;
78      i.s = i.s.replace (/^<\/([^>]+)>/, function (s, e) {      i.s = i.s.replace (/^<\/([^>]+)>/, function (s, e) {
79          if (p.insertionPoint < s.length) {
80            token = {type: 'abort'};
81            return s;
82          }
83        token = {type: 'end-tag', value: e.toLowerCase ()};        token = {type: 'end-tag', value: e.toLowerCase ()};
84          p.insertionPoint -= s.length;
85        return '';        return '';
86      });      });
87      if (token) return token;      if (token) return token;
88      i.s = i.s.replace (/^<([^>]+)>/, function (s, e) {      i.s = i.s.replace (/^<([^>]+)>/, function (s, e) {
89          if (p.insertionPoint < s.length) {
90            token = {type: 'abort'};
91            return s;
92          }
93        token = {type: 'start-tag', value: e.toLowerCase ()};        token = {type: 'start-tag', value: e.toLowerCase ()};
94          p.insertionPoint -= s.length;
95        return '';        return '';
96      });      });
97      if (token) return token;      if (token) return token;
98        if (p.insertionPoint <= 0) {
99          return {type: 'abort'};
100        }
101      i.s = i.s.replace (/^[^<]+/, function (s) {      i.s = i.s.replace (/^[^<]+/, function (s) {
102          if (p.insertionPoint < s.length) {
103            token = {type: 'char', value: s.substring (0, p.insertionPoint)};
104            var ip = p.insertionPoint;
105            p.insertionPoint = 0;
106            return s.substring (ip, s.length);
107          }
108        token = {type: 'char', value: s};        token = {type: 'char', value: s};
109          p.insertionPoint -= s.length;
110        return '';        return '';
111      });      });
112      if (token) return token;      if (token) return token;
113      i.s = i.s.replace (/^[\s\S]/, function (s) {      i.s = i.s.replace (/^[\s\S]/, function (s) {
114        token = {type: 'char', value: s};        token = {type: 'char', value: s};
115          p.insertionPoint -= s.length;
116        return '';        return '';
117      });      });
118      if (token) return token;      if (token) return token;
119      return {type: 'eof'};      return {type: 'eof'};
120    } // getNextToken    } // getNextToken
121    
122    Parser.prototype.parse = function (i) {    Parser.prototype.parse = function () {
123      log ('start parsing');      log ('start parsing');
124    
125      while (true) {      while (true) {
126        var token = this.getNextToken (i);        var token = this.getNextToken ();
127        log ('token: ' + token.type + ' "' + token.value + '"');        log ('token: ' + token.type + ' "' + token.value + '"');
128    
129        if (token.type == 'start-tag') {        if (token.type == 'start-tag') {
         var el = new JSElement (token.value);  
130          if (token.value == 'script') {          if (token.value == 'script') {
131              // 1. Create an element for the token in the HTML namespace.
132              var el = new JSElement (this.doc, token.value);
133    
134              // 2. Mark the element as being "parser-inserted".
135              el.manakaiParserInserted = true;
136    
137              // 3. Switch the tokeniser's content model flag to the CDATA state.
138            this.parseMode = 'script';            this.parseMode = 'script';
139    
140              // 4.1. Collect all the character tokens.
141            while (true) {            while (true) {
142              var token = this.getNextToken (i);              var token = this.getNextToken ();
143              log ('token: ' + token.type + ' "' + token.value + '"');              log ('token: ' + token.type + ' "' + token.value + '"');
144    
145              if (token.type == 'char') {              if (token.type == 'char') {
146                  // 5. Append a single Text node to the script element node.
147                el.manakaiAppendText (token.value);                el.manakaiAppendText (token.value);
148    
149                // 4.2. Until it returns a token that is not a character token, or
150                // until it stops tokenising.
151              } else if (token.type == 'eof' ||              } else if (token.type == 'eof' ||
152                         (token.type == 'end-tag' && token.value == 'script')) {                         (token.type == 'end-tag' && token.value == 'script') ||
153                           token.type == 'abort') {
154                  // 6. Switched back to the PCDATA state.
155                this.parseMode = 'pcdata';                this.parseMode = 'pcdata';
156    
157                  // 7.1. If the next token is not an end tag token with ...
158                  if (token.type != 'end-tag') {
159                    // 7.2. This is a parse error.
160                    log ('Parse error: no </' + 'script>');
161    
162                    // 7.3. Mark the script element as "already executed".
163                    el.manakaiAlreadyExecuted = true;
164                  } else {
165                    // 7.4. Ignore it.
166                    //
167                  }
168                break;                break;
169              }              }
170            }            }
171    
172              // 8.1. If the parser were originally created for the ...
173              if (this.fragmentParsingMode) {
174                // 8.2. Mark the script element as "already executed" and ...
175                el.alreadyExecuted = true;
176                continue;
177              }
178    
179              // 9.1. Let the old insertion point have the same value as the ...
180              var oldInsertionPoint = this.insertionPoint;
181              // 9.2. Let the insertion point be just before the next input ...
182              this.setInsertionPoint (0);
183    
184              // 10. Append the new element to the current node.
185            this.openElements[this.openElements.length - 1].appendChild (el);            this.openElements[this.openElements.length - 1].appendChild (el);
186    
187              // 11. Let the insertion point have the value of the old ...
188              this.setInsertionPoint (oldInsertionPoint);
189    
190              // 12. If there is a script that will execute as soon as ...
191              
192    
193          } else {          } else {
194              var el = new JSElement (this.doc, token.value);
195            this.openElements[this.openElements.length - 1].appendChild (el);            this.openElements[this.openElements.length - 1].appendChild (el);
196            this.openElements.push (el);            this.openElements.push (el);
197          }          }
# Line 116  Line 202 
202          } else {          } else {
203            log ('parse error: unmatched end tag: ' + token.value);            log ('parse error: unmatched end tag: ' + token.value);
204          }          }
205          } else if (token.type == 'char') {
206            this.openElements[this.openElements.length - 1].manakaiAppendText
207                (token.value);
208        } else if (token.type == 'eof') {        } else if (token.type == 'eof') {
209          break;          break;
210          } else if (token.type == 'abort') {
211            log ('parse: abort');
212            return;
213        }        }
214      }      }
215    
216      log ('stop parsing');      log ('stop parsing');
217    } // parse    } // parse
218    
219    function JSDocument () {    Parser.prototype.setInsertionPoint = function (ip) {
220        if (ip == undefined || ip == null || isNaN (ip)) {
221          log ('insertion point: set to undefined');
222          this.insertionPoint = undefined;
223        } else {
224          log ('insertion point: set to ' + ip +
225               ' (before "' + this.in.s.substring (0, 10) + '")');
226          this.insertionPoint = ip;
227        }
228      }; // setInsertionPoint
229    
230      function JSDocument (p) {
231      this.childNodes = [];      this.childNodes = [];
232        this._parser = p;
233    } // JSDocument    } // JSDocument
234    
235    function JSElement (localName) {    function JSElement (doc, localName) {
236      this.localName = localName;      this.localName = localName;
237        this.ownerDocument = doc;
238      this.childNodes = [];      this.childNodes = [];
239    } // JSElement    } // JSElement
240    
# Line 137  Line 242 
242    function (e) {    function (e) {
243      this.childNodes.push (e);      this.childNodes.push (e);
244      e.parentNode = this;      e.parentNode = this;
245    
246        if (e.localName == 'script') {
247          log ('start running a script');
248    
249          var doc = this.ownerDocument || this;
250          var p = doc._parser;
251    
252          // 1. Script type
253          //
254    
255          // 2.1. If scripting is disabled
256          //
257          // 2.2. If the script element was created by an XML ... innerHTML ...
258          //
259          // 2.3. If the user agent does not support the scripting language ...
260          //
261          // 2.4. If the script element has its "already executed" flag set
262          if (e.manakaiAlreadyExecuted) {
263            // 2.5. Abort these steps at this point.
264            log ('running a script: aborted');
265            return e;
266          }
267    
268          // 3. Set the element's "already executed" flag.
269          e.manakaiAlreadyExecuted = true;
270    
271          // 4. If the element has a src attribute, then a load for ...
272          // TODO: load an external resource
273    
274          // 5. The first of the following options:
275    
276          // 5.1.
277          if (/* TODO: If the document is still being parsed && */
278              e.defer && !e.async) {
279            // TODO
280          } else if (e.async && e.src != null) {
281            // TODO
282          } else if (e.async && e.src == null
283                     /* && list of scripts that will execute asynchronously is not empty */) {
284            // TODO
285          } else if (e.src != null && e.manakaiParserInserted) {
286            // TODO
287          } else if (e.src != null) {
288            // TODO
289          } else {
290            executeScript (doc, e); // even if other scripts are already executing.
291          }
292    
293          log ('end running a script');
294        }
295    
296      return e;      return e;
297    }; // appendChild    }; // appendChild
298    
299      function executeScript (doc, e) {
300        log ('executing a script block: start');
301    
302        // If the load resulted in an error, then ... firing an error event ...
303    
304        // If the load was successful
305        log ('load event fired at the script element');
306    
307        if (true) {
308        // Scripting is enabled, Document.designMode is disabled,
309        // Document is the active document in its browsing context
310    
311          var s;
312          if (e.src != null) {
313            // TODO: from external file
314          } else {
315            s = e.text;
316          }
317    
318          parseAndRunScript (doc, s);
319        }
320    
321        log ('executing a script block: end');
322      } // executeScript
323    
324      function parseAndRunScript (doc, s) {
325        while (true) {
326          var matched = false;
327          s = s.replace (/^\s*document\.write\s*\(((?:'[^']*'|"[^"]*")\s*(?:,\s*(?:'[^']*'|"[^"]*"))*)\)\s*;\s*/, function (s, t) {
328            matched = true;
329            var args = [];
330            t.replace (/('[^']*'|"[^"]*")/g, function (s, v) {
331              args.push (v.substring (1, v.length - 1));
332              return '';
333            });
334            doc.write.apply (doc, args);
335            return '';
336          });
337          if (s == '') break;
338          if (!matched) {
339            log ('Script parse error: "' + s + '"');
340            break;
341          }
342        }
343      } // parseAndRunScript
344    
345    function JSText (data) {    function JSText (data) {
346      this.data = data;      this.data = data;
347    } // JSText    } // JSText
# Line 155  Line 357 
357      }      }
358    }; // manakaiAppendText    }; // manakaiAppendText
359    
360      JSDocument.prototype.write = function () {
361        var p = this._parser;
362    
363        // 1. If the insertion point is undefined, the open() method must be ...
364        if (p.insertionPoint == NaN || p.insertionPoint == undefined) {
365          // TODO: open ()
366        }
367    
368        // 2. ... inserted into the input stream just before the insertion point.
369        var s = Array.join (arguments, '');
370        log ('document.write: insert "' + s + '"' +
371             ' before "' + p.in.s.substring (p.insertionPoint, p.insertionPoint + 10) + '"');
372        p.in.s = p.in.s.substring (0, p.insertionPoint) + s
373            + p.in.s.substring (p.insertionPoint, p.in.s.length);
374        p.insertionPoint += s.length;
375    
376        // 3. If there is a script that will execute as soon as the parser resumes
377        // TODO
378    
379        // 4. Process the characters that were inserted, ...
380        p.parse ();
381    
382        // 5. Return
383        log ('document.write: return');
384        return;
385      }; // document.write
386    
387      JSElement.prototype.__defineGetter__ ('text', function () {
388        var r = '';
389        for (var i = 0; i < this.childNodes.length; i++) {
390          if (this.childNodes[i] instanceof JSText) {
391            r += this.childNodes[i].data;
392          }
393        }
394        return r;
395      });
396    
397    function dumpTree (n, indent) {    function dumpTree (n, indent) {
398      var r = '';      var r = '';
399      for (var i = 0; i < n.childNodes.length; i++) {      for (var i = 0; i < n.childNodes.length; i++) {
# Line 182  Line 421 
421  &lt;head>&lt;/head>&lt;body>  &lt;head>&lt;/head>&lt;body>
422  &lt;p>  &lt;p>
423  &lt;script>  &lt;script>
424  document.write ('aaaaaaa&lt;/p>\n&lt;script>\ndocument.write("cccccc")\n&lt;/', 'script>\nbbbbbb');  document.write ('aaaaaaa&lt;/p>&lt;script>document.write("cccccc");&lt;/', 'script>bbbbbb');
425  &lt;/script>  &lt;/script>
426  &lt;p>  &lt;p>
427  </textarea>  </textarea>

Legend:
Removed from v.1.1  
changed lines
  Added in v.1.3

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24