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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.12 - (hide annotations) (download) (as text)
Sun Apr 27 11:21:09 2008 UTC (16 years, 7 months ago) by wakaba
Branch: MAIN
Changes since 1.11: +51 -17 lines
File MIME type: text/html
async script support

1 wakaba 1.1 <!DOCTYPE HTML>
2     <html lang=en>
3     <head>
4 wakaba 1.8 <title>Live Scripting HTML Parser</title>
5 wakaba 1.1 <style>
6 wakaba 1.7 h1, h2 {
7     margin: 0;
8     font-size: 100%;
9     }
10     p, pre {
11     margin: 0;
12     }
13 wakaba 1.1 textarea {
14 wakaba 1.7 width: 100%;
15     -width: 99%;
16     height: 10em;
17 wakaba 1.1 }
18     output {
19     display: block;
20     font-family: monospace;
21 wakaba 1.4 white-space: -moz-pre-wrap;
22     white-space: pre-wrap;
23 wakaba 1.1 }
24     </style>
25     <script>
26 wakaba 1.7 var delayedUpdater = 0;
27    
28 wakaba 1.1 function update () {
29 wakaba 1.7 if (delayedUpdater) {
30     clearTimeout (delayedUpdater);
31     delayedUpdater = 0;
32     }
33     delayedUpdater = setTimeout (update2, 100);
34     } // update
35    
36     function update2 () {
37     var v = document.sourceElement.value;
38 wakaba 1.8 if (v != document.previousSourceText) {
39     document.previousSourceText = v;
40     document.links['permalink'].href
41     = location.pathname + '?s=' + encodeURIComponent (v);
42     document.links['ldvlink'].href
43     = 'http://software.hixie.ch/utilities/js/live-dom-viewer/?'
44     + encodeURIComponent (v);
45    
46     document.logElement.textContent = '';
47     var p = new Parser (new InputStream (v));
48     var doc = p.doc;
49     p.parse ();
50 wakaba 1.10
51 wakaba 1.8 log (dumpTree (doc, ''));
52 wakaba 1.10
53     if (p.hasAsyncScript) {
54     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');
55     }
56 wakaba 1.8 }
57 wakaba 1.7 } // update2
58 wakaba 1.1
59 wakaba 1.6 var logIndentLevel = 0;
60 wakaba 1.1 function log (s) {
61 wakaba 1.6 for (var i = 0; i < logIndentLevel; i++) {
62     s = ' ' + s;
63     }
64 wakaba 1.1 document.logElement.appendChild (document.createTextNode (s + "\n"));
65     } // log
66    
67     function InputStream (s) {
68     this.s = s;
69     } // InputStream
70    
71 wakaba 1.4 function Parser (i, doc) {
72 wakaba 1.1 this.parseMode = 'pcdata';
73 wakaba 1.4 if (!doc) {
74     doc = new JSDocument (this);
75     doc.manakaiIsHTML = true;
76     }
77     this.doc = doc;
78     this.openElements = [doc];
79 wakaba 1.8 this.input = i;
80 wakaba 1.4 this.scriptsExecutedAfterParsing = [];
81 wakaba 1.10 this.scriptsExecutedSoon = [];
82 wakaba 1.12 this.scriptsExecutedAsynchronously = [];
83 wakaba 1.1 } // Parser
84    
85 wakaba 1.2 Parser.prototype.getNextToken = function () {
86 wakaba 1.3 var p = this;
87 wakaba 1.8 var i = this.input;
88 wakaba 1.1 if (this.parseMode == 'script') {
89     var token;
90 wakaba 1.3 if (p.insertionPoint <= 0) {
91     return {type: 'abort'};
92     }
93 wakaba 1.4 i.s = i.s.replace (/^([^<]+)/,
94 wakaba 1.1 function (s, t) {
95 wakaba 1.3 if (0 < p.insertionPoint && p.insertionPoint < t.length) {
96     token = {type: 'char', value: t.substring (0, p.insertionPoint)};
97     var ip = p.insertionPoint;
98     p.insertionPoint = 0;
99 wakaba 1.4 return t.substring (ip, t.length);
100 wakaba 1.3 }
101 wakaba 1.1 token = {type: 'char', value: t};
102 wakaba 1.4 p.insertionPoint -= t.length;
103     return '';
104 wakaba 1.1 });
105     if (token) return token;
106 wakaba 1.3 i.s = i.s.replace (/^<\/[Ss][Cc][Rr][Ii][Pp][Tt]>/, function (s) {
107 wakaba 1.4 if (p.insertionPoint < s.length) {
108 wakaba 1.3 token = {type: 'abort'};
109     return s;
110     }
111 wakaba 1.1 token = {type: 'end-tag', value: 'script'};
112 wakaba 1.3 p.insertionPoint -= s.length;
113 wakaba 1.1 return '';
114     });
115     if (token) return token;
116 wakaba 1.5 var m;
117     if ((p.insertionPoint < '</script'.length) &&
118     (m = i.s.match (/^<\/([SCRIPTscript]+)/))) {
119     var v = m[1].substring (0, p.insertionPoint).toLowerCase ();
120     if (v == 'script'.substring (0, p.insertionPoint - '</'.length)) {
121     return {type: 'abort'};
122     }
123     }
124 wakaba 1.4 i.s = i.s.replace (/^</,
125     function (s) {
126     token = {type: 'char', value: s};
127     p.insertionPoint -= s.length;
128     return '';
129     });
130     if (token) return token;
131 wakaba 1.1 return {type: 'eof'};
132     }
133    
134     var token;
135 wakaba 1.5 i.s = i.s.replace (/^<\/([^>]+)(?:>|$)/, function (s, e) {
136     if (p.insertionPoint < s.length ||
137     (p.insertionPoint <= s.length &&
138     s.substring (s.length - 1, 1) != '>')) {
139 wakaba 1.3 token = {type: 'abort'};
140     return s;
141     }
142 wakaba 1.1 token = {type: 'end-tag', value: e.toLowerCase ()};
143 wakaba 1.3 p.insertionPoint -= s.length;
144 wakaba 1.1 return '';
145     });
146     if (token) return token;
147 wakaba 1.5 i.s = i.s.replace (/^<([^>]+)(?:>|$)/, function (s, e) {
148     if (p.insertionPoint < s.length ||
149     (p.insertionPoint <= s.length &&
150     s.substring (s.length - 1, 1) != '>')) {
151 wakaba 1.3 token = {type: 'abort'};
152     return s;
153     }
154 wakaba 1.4 var tagName;
155     var attrs = {};
156     e = e.replace (/^[\S]+/, function (v) {
157     tagName = v.toLowerCase ();
158     return '';
159     });
160 wakaba 1.9 while (true) {
161     var m = false;
162     e = e.replace (/^\s*([^\s=]+)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|([^"'\s]*)))?/,
163     function (x, attrName, attrValue1, attrValue2, attrValue3) {
164     v = attrValue1 || attrValue2 || attrValue3;
165     v = v.replace (/&quot;/g, '"').replace (/&apos;/g, "'")
166     .replace (/&amp;/g, '&');
167     attrs[attrName.toLowerCase ()] = v;
168     m = true;
169     return '';
170     });
171     if (!m) break;
172     }
173 wakaba 1.6 if (e.length) {
174     log ('Broken start tag: "' + e + '"');
175     }
176 wakaba 1.4 token = {type: 'start-tag', value: tagName, attrs: attrs};
177 wakaba 1.3 p.insertionPoint -= s.length;
178 wakaba 1.1 return '';
179     });
180     if (token) return token;
181 wakaba 1.3 if (p.insertionPoint <= 0) {
182     return {type: 'abort'};
183     }
184 wakaba 1.1 i.s = i.s.replace (/^[^<]+/, function (s) {
185 wakaba 1.3 if (p.insertionPoint < s.length) {
186     token = {type: 'char', value: s.substring (0, p.insertionPoint)};
187     var ip = p.insertionPoint;
188     p.insertionPoint = 0;
189     return s.substring (ip, s.length);
190     }
191 wakaba 1.1 token = {type: 'char', value: s};
192 wakaba 1.3 p.insertionPoint -= s.length;
193 wakaba 1.1 return '';
194     });
195     if (token) return token;
196     i.s = i.s.replace (/^[\s\S]/, function (s) {
197     token = {type: 'char', value: s};
198 wakaba 1.3 p.insertionPoint -= s.length;
199 wakaba 1.1 return '';
200     });
201     if (token) return token;
202     return {type: 'eof'};
203     } // getNextToken
204    
205 wakaba 1.2 Parser.prototype.parse = function () {
206 wakaba 1.6 logIndentLevel++;
207     log ('parse: start');
208 wakaba 1.1
209     while (true) {
210 wakaba 1.2 var token = this.getNextToken ();
211 wakaba 1.1 log ('token: ' + token.type + ' "' + token.value + '"');
212    
213     if (token.type == 'start-tag') {
214     if (token.value == 'script') {
215 wakaba 1.2 // 1. Create an element for the token in the HTML namespace.
216     var el = new JSElement (this.doc, token.value);
217 wakaba 1.4 if (token.attrs.async != null) el.async = true;
218     if (token.attrs.defer != null) el.defer = true;
219     if (token.attrs.src != null) el.src = token.attrs.src;
220 wakaba 1.2
221     // 2. Mark the element as being "parser-inserted".
222     el.manakaiParserInserted = true;
223    
224     // 3. Switch the tokeniser's content model flag to the CDATA state.
225 wakaba 1.1 this.parseMode = 'script';
226    
227 wakaba 1.2 // 4.1. Collect all the character tokens.
228 wakaba 1.1 while (true) {
229 wakaba 1.2 var token = this.getNextToken ();
230 wakaba 1.1 log ('token: ' + token.type + ' "' + token.value + '"');
231    
232     if (token.type == 'char') {
233 wakaba 1.2 // 5. Append a single Text node to the script element node.
234 wakaba 1.1 el.manakaiAppendText (token.value);
235 wakaba 1.2
236     // 4.2. Until it returns a token that is not a character token, or
237 wakaba 1.3 // until it stops tokenising.
238 wakaba 1.1 } else if (token.type == 'eof' ||
239 wakaba 1.3 (token.type == 'end-tag' && token.value == 'script') ||
240     token.type == 'abort') {
241 wakaba 1.2 // 6. Switched back to the PCDATA state.
242 wakaba 1.1 this.parseMode = 'pcdata';
243 wakaba 1.2
244     // 7.1. If the next token is not an end tag token with ...
245     if (token.type != 'end-tag') {
246     // 7.2. This is a parse error.
247     log ('Parse error: no </' + 'script>');
248    
249     // 7.3. Mark the script element as "already executed".
250     el.manakaiAlreadyExecuted = true;
251     } else {
252     // 7.4. Ignore it.
253     //
254     }
255 wakaba 1.1 break;
256     }
257     }
258    
259 wakaba 1.2 // 8.1. If the parser were originally created for the ...
260     if (this.fragmentParsingMode) {
261     // 8.2. Mark the script element as "already executed" and ...
262     el.alreadyExecuted = true;
263     continue;
264     }
265    
266     // 9.1. Let the old insertion point have the same value as the ...
267 wakaba 1.3 var oldInsertionPoint = this.insertionPoint;
268 wakaba 1.2 // 9.2. Let the insertion point be just before the next input ...
269 wakaba 1.3 this.setInsertionPoint (0);
270 wakaba 1.2
271     // 10. Append the new element to the current node.
272 wakaba 1.1 this.openElements[this.openElements.length - 1].appendChild (el);
273 wakaba 1.2
274     // 11. Let the insertion point have the value of the old ...
275 wakaba 1.7
276 wakaba 1.5 oldInsertionPoint += this.insertionPoint;
277 wakaba 1.3 this.setInsertionPoint (oldInsertionPoint);
278 wakaba 1.2
279     // 12. If there is a script that will execute as soon as ...
280 wakaba 1.6 while (this.scriptExecutedWhenParserResumes) {
281     // 12.1. If the tree construction stage is being called reentrantly
282     if (this.reentrant) {
283     log ('parse: abort (reentrance)');
284     logIndentLevel--;
285     return;
286    
287     // 12.2. Otherwise
288     } else {
289     // 1.
290     var script = this.scriptExecutedWhenParserResumes;
291     this.scriptExecutedWhenParserResumes = null;
292    
293     // 2. Pause until the script has completed loading.
294     //
295    
296     // 3. Let the insertion point to just before the next input char.
297     this.setInsertionPoint (0);
298    
299     // 4. Execute the script.
300     executeScript (this.doc, script);
301    
302     // 5. Let the insertion point be undefined again.
303     this.setInsertionPoint (undefined);
304 wakaba 1.2
305 wakaba 1.6 // 6. If there is once again a script that will execute ...
306     //
307     }
308     }
309 wakaba 1.1 } else {
310 wakaba 1.2 var el = new JSElement (this.doc, token.value);
311 wakaba 1.1 this.openElements[this.openElements.length - 1].appendChild (el);
312     this.openElements.push (el);
313     }
314     } else if (token.type == 'end-tag') {
315     if (this.openElements[this.openElements.length - 1].localName ==
316     token.value) {
317     this.openElements.pop ();
318     } else {
319     log ('parse error: unmatched end tag: ' + token.value);
320     }
321 wakaba 1.3 } else if (token.type == 'char') {
322     this.openElements[this.openElements.length - 1].manakaiAppendText
323     (token.value);
324 wakaba 1.1 } else if (token.type == 'eof') {
325     break;
326 wakaba 1.3 } else if (token.type == 'abort') {
327     log ('parse: abort');
328 wakaba 1.6 logIndentLevel--;
329 wakaba 1.3 return;
330 wakaba 1.1 }
331     }
332    
333     log ('stop parsing');
334 wakaba 1.4
335     // readyState = 'interactive'
336    
337     // "When a script completes loading" rules start applying.
338    
339 wakaba 1.12 while (this.scriptsExecutedSoon.length > 0 ||
340     this.scriptsExecutedAsynchronously.length > 0) {
341     // Handle "list of scripts that will execute as soon as possible".
342     while (this.scriptsExecutedSoon.length > 0) {
343     var e = this.scriptsExecutedSoon.shift ();
344    
345     // If it has completed loading
346     log ('Execute an external script not inserted by parser...');
347     executeScript (this.doc, e);
348    
349     // NOTE: It MAY be executed before the end of the parsing, according
350     // to the spec.
351     this.hasAsyncScript = true;
352     }
353    
354     // Handle "list of scripts that will execute asynchronously".
355     while (this.scriptsExecutedAsynchronously.length > 0) {
356     var e = this.scriptsExecutedAsynchronously.shift ();
357    
358     // Step 1.
359     // We assume that all scripts have been loaded at this time.
360    
361     // Step 2.
362     log ('Execute an asynchronous script...');
363     executeScript (this.doc, e);
364    
365     // Step 3.
366     //
367    
368     // Step 4.
369     //
370 wakaba 1.10
371 wakaba 1.12 this.hasAsyncScript = true;
372     }
373 wakaba 1.10 }
374    
375 wakaba 1.4 // Handle "list of scripts that will execute when the document has finished
376     // parsing".
377     var list = this.scriptsExecutedAfterParsing;
378     while (list.length > 0) {
379     // TODO: break unless completed loading
380    
381     // Step 1.
382     //
383    
384     // Step 2. and Step 3.
385     log ('Executing a |defer|red script...');
386     executeScript (this.doc, list.shift ());
387    
388     // Step 4.
389     }
390    
391     log ('DOMContentLoaded event fired');
392    
393     // "delays tha load event" things has completed:
394     // readyState = 'complete'
395     log ('load event fired');
396 wakaba 1.6
397     logIndentLevel--;
398 wakaba 1.1 } // parse
399    
400 wakaba 1.3 Parser.prototype.setInsertionPoint = function (ip) {
401     if (ip == undefined || ip == null || isNaN (ip)) {
402     log ('insertion point: set to undefined');
403     this.insertionPoint = undefined;
404 wakaba 1.8 } else if (ip == this.input.s.length) {
405 wakaba 1.4 log ('insertion point: end of file');
406     this.insertionPoint = ip;
407 wakaba 1.3 } else {
408     log ('insertion point: set to ' + ip +
409 wakaba 1.8 ' (before "' + this.input.s.substring (0, 10) + '")');
410 wakaba 1.3 this.insertionPoint = ip;
411     }
412     }; // setInsertionPoint
413    
414 wakaba 1.2 function JSDocument (p) {
415 wakaba 1.1 this.childNodes = [];
416 wakaba 1.2 this._parser = p;
417 wakaba 1.1 } // JSDocument
418    
419 wakaba 1.2 function JSElement (doc, localName) {
420 wakaba 1.1 this.localName = localName;
421 wakaba 1.2 this.ownerDocument = doc;
422 wakaba 1.1 this.childNodes = [];
423     } // JSElement
424    
425     JSDocument.prototype.appendChild = JSElement.prototype.appendChild =
426     function (e) {
427     this.childNodes.push (e);
428     e.parentNode = this;
429 wakaba 1.2
430     if (e.localName == 'script') {
431 wakaba 1.6 logIndentLevel++;
432 wakaba 1.4 log ('Running a script: start');
433 wakaba 1.2
434 wakaba 1.3 var doc = this.ownerDocument || this;
435 wakaba 1.2 var p = doc._parser;
436    
437     // 1. Script type
438     //
439    
440     // 2.1. If scripting is disabled
441     //
442     // 2.2. If the script element was created by an XML ... innerHTML ...
443     //
444     // 2.3. If the user agent does not support the scripting language ...
445     //
446     // 2.4. If the script element has its "already executed" flag set
447     if (e.manakaiAlreadyExecuted) {
448     // 2.5. Abort these steps at this point.
449 wakaba 1.4 log ('Running a script: aborted');
450 wakaba 1.6 logIndentLevel--;
451 wakaba 1.2 return e;
452     }
453    
454     // 3. Set the element's "already executed" flag.
455     e.manakaiAlreadyExecuted = true;
456    
457     // 4. If the element has a src attribute, then a load for ...
458     // TODO: load an external resource
459    
460     // 5. The first of the following options:
461    
462     // 5.1.
463     if (/* TODO: If the document is still being parsed && */
464     e.defer && !e.async) {
465 wakaba 1.4 p.scriptsExecutedAfterParsing.push (e);
466     log ('Running a script: aborted (defer)');
467 wakaba 1.2 } else if (e.async && e.src != null) {
468 wakaba 1.12 p.scriptsExecutedAsynchronously.push (e);
469     log ('Running a script: aborted (async src)');
470     } else if (e.async && e.src == null &&
471     p.scriptsExecutedAsynchronously.length > 0) {
472     p.scriptsExecutedAsynchronously.push (e);
473     log ('Running a script: aborted (async)');
474     // ISSUE: What is the difference with the case above?
475 wakaba 1.2 } else if (e.src != null && e.manakaiParserInserted) {
476 wakaba 1.6 if (p.scriptExecutedWhenParserResumes) {
477     log ('Error: There is a script that will execute as soon as the parser resumes.');
478     }
479     p.scriptExecutedWhenParserResumes = e;
480 wakaba 1.10 log ('Running a script: aborted (src parser-inserted)');
481     } else if (e.src != null) {
482     p.scriptsExecutedSoon.push (e);
483 wakaba 1.6 log ('Running a script: aborted (src)');
484 wakaba 1.2 } else {
485     executeScript (doc, e); // even if other scripts are already executing.
486     }
487    
488 wakaba 1.4 log ('Running a script: end');
489 wakaba 1.6 logIndentLevel--;
490 wakaba 1.2 }
491    
492 wakaba 1.1 return e;
493     }; // appendChild
494    
495 wakaba 1.2 function executeScript (doc, e) {
496     log ('executing a script block: start');
497    
498 wakaba 1.6 var s;
499     if (e.src != null) {
500     s = getExternalScript (e.src);
501    
502     // If the load resulted in an error, then ... firing an error event ...
503     if (s == null) {
504     log ('error event fired at the script element');
505     return;
506     }
507    
508     log ('External script loaded: "' + s + '"');
509     } else {
510     s = e.text;
511     }
512 wakaba 1.2
513     // If the load was successful
514     log ('load event fired at the script element');
515    
516     if (true) {
517     // Scripting is enabled, Document.designMode is disabled,
518     // Document is the active document in its browsing context
519    
520     parseAndRunScript (doc, s);
521     }
522    
523     log ('executing a script block: end');
524     } // executeScript
525    
526 wakaba 1.6 function getExternalScript (uri) {
527     if (uri.match (/^javascript:/i)) {
528     var m;
529     if (m = uri.match (/^javascript:\s*(?:'([^']*)'|"([^"]+)")\s*$/i)) {
530     if (m[1]) {
531 wakaba 1.11 return unescapeJSLiteral (m[1]);
532 wakaba 1.6 } else if (m[2]) {
533 wakaba 1.11 return unescapeJSLiteral (m[2]);
534 wakaba 1.6 } else {
535     return null;
536     }
537     } else {
538     log ('Complex javascript: URI is not supported: <' + uri + '>');
539     return null;
540     }
541     } else {
542     log ('URI scheme not supported: <' + uri + '>');
543     return null;
544     }
545     } // getExternalScript
546    
547 wakaba 1.2 function parseAndRunScript (doc, s) {
548     while (true) {
549     var matched = false;
550     s = s.replace (/^\s*document\.write\s*\(((?:'[^']*'|"[^"]*")\s*(?:,\s*(?:'[^']*'|"[^"]*"))*)\)\s*;\s*/, function (s, t) {
551     matched = true;
552     var args = [];
553     t.replace (/('[^']*'|"[^"]*")/g, function (s, v) {
554 wakaba 1.11 args.push (unescapeJSLiteral (v.substring (1, v.length - 1)));
555 wakaba 1.2 return '';
556     });
557     doc.write.apply (doc, args);
558     return '';
559     });
560 wakaba 1.10 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*/,
561     function (s, t, u) {
562     matched = true;
563 wakaba 1.11 var args = [unescapeJSLiteral (t ? t : u)];
564 wakaba 1.10 doc._insertExternalScript.apply (doc, args);
565     return '';
566     });
567 wakaba 1.2 if (s == '') break;
568     if (!matched) {
569     log ('Script parse error: "' + s + '"');
570     break;
571     }
572     }
573     } // parseAndRunScript
574    
575 wakaba 1.11 function unescapeJSLiteral (s) {
576     return s.replace (/\\u([0-9A-Fa-f]{4})/g, function (t, v) {
577     return String.fromCharCode (parseInt ('0x' + v));
578     });
579     } // unescapeJSLiteral
580    
581 wakaba 1.1 function JSText (data) {
582     this.data = data;
583     } // JSText
584    
585     JSDocument.prototype.manakaiAppendText =
586     JSElement.prototype.manakaiAppendText =
587     function (s) {
588     if (this.childNodes.length > 0 &&
589     this.childNodes[this.childNodes.length - 1] instanceof JSText) {
590     this.childNodes[this.childNodes.length - 1].data += s;
591     } else {
592     this.childNodes.push (new JSText (s));
593     }
594     }; // manakaiAppendText
595 wakaba 1.2
596 wakaba 1.4 JSDocument.prototype.open = function () {
597     // Two or fewer arguments
598    
599     // Step 1.
600     var type = arguments[0] || 'text/html';
601    
602     // Step 2.
603     var replace = arguments[1] == 'replace';
604    
605     // Step 3.
606     if (this._parser &&
607     !this._parser.scriptCreated &&
608 wakaba 1.8 this._parser.input.insertionPoint != undefined) {
609 wakaba 1.4 log ('document.open () in parsing mode is ignored');
610     return this;
611     }
612    
613     // Step 4.
614     log ('onbeforeunload event fired');
615     log ('onunload event fired');
616    
617     // Step 5.
618     if (this._parser) {
619     // Discard the parser.
620     }
621    
622     // Step 6.
623     log ('document cleared by document.open ()');
624     this.childNodes = [];
625    
626     // Step 7.
627     this._parser = new Parser (new InputStream (''), this);
628     this._parser.scriptCreated = true;
629    
630     // Step 8.
631     this.manakaiIsHTML = true;
632    
633     // Step 9.
634     // If not text/html, ...
635    
636     // Step 10.
637     if (!replace) {
638     // History
639     }
640    
641     // Step 11.
642 wakaba 1.8 this._parser.setInsertionPoint (this._parser.input.s.length);
643 wakaba 1.4
644     // Step 12.
645     return this;
646     }; // document.open
647    
648 wakaba 1.2 JSDocument.prototype.write = function () {
649 wakaba 1.6 logIndentLevel++;
650    
651 wakaba 1.3 var p = this._parser;
652    
653 wakaba 1.2 // 1. If the insertion point is undefined, the open() method must be ...
654 wakaba 1.4 if (isNaN (p.insertionPoint) || p.insertionPoint == undefined) {
655     this.open ();
656     p = this._parser;
657 wakaba 1.3 }
658 wakaba 1.2
659     // 2. ... inserted into the input stream just before the insertion point.
660 wakaba 1.3 var s = Array.join (arguments, '');
661     log ('document.write: insert "' + s + '"' +
662 wakaba 1.8 ' before "' +
663     p.input.s.substring (p.insertionPoint, p.insertionPoint + 10) + '"');
664     p.input.s = p.input.s.substring (0, p.insertionPoint) + s
665     + p.input.s.substring (p.insertionPoint, p.input.s.length);
666 wakaba 1.3 p.insertionPoint += s.length;
667 wakaba 1.2
668     // 3. If there is a script that will execute as soon as the parser resumes
669 wakaba 1.6 if (p.scriptExecutedAfterParserResumes) {
670     log ('document.write: processed later (there is an unprocessed <script src>)');
671     logIndentLevel--;
672     return;
673     }
674 wakaba 1.2
675     // 4. Process the characters that were inserted, ...
676 wakaba 1.6 var originalReentrant = p.reentrant;
677     p.reentrant = true;
678 wakaba 1.3 p.parse ();
679 wakaba 1.6 p.reentrant = originalReentrant;
680     // TODO: "Abort the processing of any nested invokations of the tokeniser,
681     // yielding control back to the caller." (<script> parsing). Do we need
682     // to do something here?
683 wakaba 1.2
684     // 5. Return
685     log ('document.write: return');
686 wakaba 1.6
687     logIndentLevel--;
688 wakaba 1.2 return;
689     }; // document.write
690    
691 wakaba 1.10 JSDocument.prototype._insertExternalScript = function (uri) {
692     var s = new JSElement (this, 'script');
693     s.src = uri;
694     this.documentElement.appendChild (s);
695     }; // _insertExternalScript
696    
697     JSDocument.prototype.__defineGetter__ ('documentElement', function () {
698     var cn = this.childNodes;
699     for (var i = 0; i < cn.length; i++) {
700     if (cn[i] instanceof JSElement) {
701     return cn[i]
702     }
703     }
704     return null;
705     });
706    
707 wakaba 1.2 JSElement.prototype.__defineGetter__ ('text', function () {
708     var r = '';
709     for (var i = 0; i < this.childNodes.length; i++) {
710     if (this.childNodes[i] instanceof JSText) {
711     r += this.childNodes[i].data;
712     }
713     }
714     return r;
715     });
716 wakaba 1.1
717     function dumpTree (n, indent) {
718     var r = '';
719     for (var i = 0; i < n.childNodes.length; i++) {
720     var node = n.childNodes[i];
721     if (node instanceof JSElement) {
722     r += '| ' + indent + node.localName + '\n';
723 wakaba 1.4 if (node.async) r += '| ' + indent + ' async=""\n';
724     if (node.defer) r += '| ' + indent + ' defer=""\n';
725 wakaba 1.9 if (node.src != null) {
726     r += '| ' + indent + ' src="' + node.src + '"\n';
727     }
728 wakaba 1.1 r += dumpTree (node, indent + ' ');
729     } else if (node instanceof JSText) {
730     r += '| ' + indent + '"' + node.data + '"\n';
731     } else {
732     r += '| ' + indent + node + '\n';
733     }
734     }
735     return r;
736     } // dumpTree
737     </script>
738     </head>
739     <body onload="
740     document.sourceElement = document.getElementsByTagName ('textarea')[0];
741 wakaba 1.8
742     var q = location.search;
743     if (q != null) {
744     q = q.substring (1).split (/;/);
745     for (var i = 0; i < q.length; i++) {
746     var v = q[i].split (/=/, 2);
747     v[0] = decodeURIComponent (v[0]);
748     v[1] = decodeURIComponent (v[1] || '');
749     if (v[0] == 's') {
750     document.sourceElement.value = v[1];
751     }
752     }
753     }
754    
755 wakaba 1.1 document.logElement = document.getElementsByTagName ('output')[0];
756     update ();
757     ">
758 wakaba 1.8 <h1>Live Scripting <abbr title="Hypertext Markup Language">HTML</abbr>
759     Parser</h1>
760 wakaba 1.1
761 wakaba 1.7 <h2>Markup to test
762 wakaba 1.8 (<a href=data:, id=permalink rel=bookmark>permalink</a>,
763     <a href="http://software.hixie.ch/utilities/js/live-dom-viewer/"
764     id=ldvlink>Live <abbr title="Document Object Model">DOM</abbr>
765     Viewer</a>)</h2>
766 wakaba 1.7 <p>
767     <textarea onkeydown=" update () " onchange=" update () " oninput=" update () ">&lt;html>
768 wakaba 1.1 &lt;head>&lt;/head>&lt;body>
769     &lt;p>
770     &lt;script>
771 wakaba 1.3 document.write ('aaaaaaa&lt;/p>&lt;script>document.write("cccccc");&lt;/', 'script>bbbbbb');
772 wakaba 1.1 &lt;/script>
773     &lt;p>
774     </textarea>
775    
776 wakaba 1.10 <h2 id=log>Log</h2>
777 wakaba 1.7 <p><output></output>
778    
779 wakaba 1.10 <h2 id=notes>Notes</h2>
780 wakaba 1.8
781     <p>This is a <em>simplified</em> implementation of
782     <a href="http://www.whatwg.org/specs/web-apps/current-work/#parsing">HTML5
783     Parsing Algorithm</a>. It only implements script-related part of the
784     algorithm. Especially, this parser:
785     <ul>
786     <li>Does not support <code>DOCTYPE</code> and comment tokens.
787     <li>Does not support entities except for <code>&amp;quot;</code>,
788     <code>&amp;apos;</code>, and <code>&amp;amp;</code> in <code>script</code>
789     <code>src</code> attribute value.
790     <li>Does not support omissions of start or end tags, the <abbr>AAA</abbr>
791     algorithm, and so on.
792     <li>Does not raise parse errors for invalid attribute specifications in start
793     or end tags.
794     <li>Does not support CDATA/PCDATA element other than <code>script</code>.
795     <li>Does not support <code>&lt;!--</code>..<code>--></code> parsing rule
796     in <code>script</code> element.
797     <li>Does not support foreign (SVG or MathML) elements.
798     <li>Only supports <code>script</code> <code>type</code>
799     <code>text/javascript</code>. <code>type</code> and <code>language</code>
800     attributes are ignored.
801 wakaba 1.10 <li>Only supports limited statements. It must consist of zero or more
802     of statements looking similar to the following statements, possibly
803     introduced, followed, or separated by white space characters:
804     <ul>
805     <li><code>document.write ("<var>string</var>", ["<var>string</var>", ...]);</code>.
806     <li><code>var s = document.createElement ("script");
807     s.src = "<var>string</var>";
808     document.documentElement.appendChild (s);</code>
809     </ul>
810     Note that strings may be delimited by <code>'</code>s instead of
811     <code>"</code>s.
812 wakaba 1.8 <li>Only supports <code>javascript:</code>
813     <abbr title="Uniform Resourace Identifiers">URI</abbr> scheme in the
814     <code>src</code> attribute of the <code>script</code> element. In addition,
815     the <abbr title="Uniform Resource Identifiers">URI</abbr> must be conform to
816     the regular expression <code>^javascript:\s*(?:"[^"]*"|'[^']*')\s*$</code>.
817 wakaba 1.11 <li>Only supports <code>\u<var>HHHH</var></code> escapes in JavaScript
818     string literals.
819 wakaba 1.12 <li>Does not handle <i>stop parsing</i> phase correctly if the document is
820     replaced by <code>document.open ()</code> call. In other word, delayed
821     (deferred or asynchronous) script executions and event firings might be
822     treated in a wrong way if a <code>document.open ()</code> invocation
823     is implicitly done by <code>document.write ()</code> in a delayed script.
824 wakaba 1.8 </ul>
825 wakaba 1.7
826 wakaba 1.8 <p>For some reason, this parser does not work in browsers that do
827     not support JavaScript 1.5.
828 wakaba 1.12
829     <!-- TODO: |src| attribute value should refer the value at the time
830     when it is inserted into the document, not the value when the script is
831     executed. Currently it does not matter, since we don't allow dynamic
832     modification to the |src| content/DOM attribute value yet. -->
833 wakaba 1.10
834     <!-- TODO: license -->
835 wakaba 1.1
836     </body>
837     </html>

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24