OSDN Git Service

Fix the bug of X.NodeAnime.
[pettanr/clientJs.git] / 0.6.x / js / 02_dom / 09_XHTMLParser.js
1 \r
2 /*\r
3  * Original code by Erik John Resig (ejohn.org)\r
4  * http://ejohn.org/blog/pure-javascript-html-parser/\r
5  *\r
6  */\r
7 \r
8 var X_HTMLParser_CHARS = {\r
9                 'A':1,'B':1,'C':1,'D':1,'E':1,'F':1,'G':1,'H':1,'I':1,'J':1,'K':1,'L':1,'M':1,'N':1,'O':1,'P':1,'Q':1,'R':1,'S':1,'T':1,'U':1,'V':1,'W':1,'X':1,'Y':1,'Z':1,\r
10                 'a':2,'b':2,'c':2,'d':2,'e':2,'f':2,'g':2,'h':2,'i':2,'j':2,'k':2,'l':2,'m':2,'n':2,'o':2,'p':2,'q':2,'r':2,'s':2,'t':2,'u':2,'v':2,'w':2,'x':2,'y':2,'z':2,\r
11                 '!':1,// "0" ': 4, "1" : 4, "2" : 4, "3" : 4, "4" : 4, "5" : 4, "6" : 4, "7" : 4, "8" : 4, "9" : 4, closure compiler で minify すると ie4 で error、eval使う\r
12                 \r
13                 '\t' : 16, '\r\n' : 16, '\r' : 16, '\n' : 16, '\f' : 16, '\b' : 16, ' ' : 16\r
14         },\r
15 \r
16         // Empty Elements - HTML 4.01\r
17         // X_Dom_DTD_EMPTY\r
18 \r
19         // Block Elements - HTML 4.01\r
20         X_HTMLParser_block = {'ADDRESS':1,'APPLET':1,'BLOCKQUOTE':1,'BUTTON':1,'CENTER':1,'DD':1,'DEL':1,'DIR':1,'DIV':1,'DL':1,'DT':1,'FIELDSET':1,'FORM':1,'FRAMESET':1,'HR':1,'IFRAME':1,'INS':1,\r
21                 'ISINDEX':1,'LI':1,'MAP':1,'MENU':1,'NOFRAMES':1,'NOSCRIPT':1,'OBJECT':1,'OL':1,'P':1,'PRE':1,'SCRIPT':1,'TABLE':1,'TBODY':1,'TD':1,'TFOOT':1,'TH':1,'THEAD':1,'TR':1,'UL':1 },\r
22         // Inline Elements - HTML 4.01\r
23         X_HTMLParser_inline = {/*'A':1,*/'ABBR':1,'ACRONYM':1,'APPLET':1,'B':1,'BASEFONT':1,'BDO':1,'BIG':1,'BR':1,'BUTTON':1,'CITE':1,'CODE':1,'DEL':1,'DFN':1,'EM':1,'FONT':1,'I':1,'IFRAME':1,'IMG':1,\r
24                 'INPUT':1,'INS':1,'KBD':1,'LABEL':1,'MAP':1,'OBJECT':1,'Q':1,'S':1,'SAMP':1,'SCRIPT':1,'SELECT':1,'SMALL':1,'SPAN':1,'STRIKE':1,'STRONG':1,'SUB':1,'SUP':1,'TEXTAREA':1,'TT':1,'U':1,'VAR':1},\r
25         // Elements that you can,' intentionally,' leave open\r
26         // (and which close themselves)\r
27         X_HTMLParser_closeSelf = {'OLGROUP':1,'DD':1,'DT':1,'LI':1,'OPTIONS':1,'P':1,'TBODY':1,'TD':1,'TFOOT':1,'TH':1,'THEAD':1,'TR':1}, // add tbody\r
28 \r
29         X_HTMLParser_sisters = {\r
30                 'TH' : { 'TD' : 1 },\r
31                 'TD' : { 'TH' : 1 },\r
32                 'DT' : { 'DD' : 1 },\r
33                 'DD' : { 'DT' : 1 },\r
34                 'COLGROUP' : { 'CAPTION' : 1 },\r
35                 'THEAD'    : { 'CAPTION' : 1, 'COLGROUP' : 1 },\r
36                 'TFOOT'    : { 'CAPTION' : 1, 'COLGROUP' : 1, 'THEAD' : 1, 'TBODY' : 1 },\r
37                 'TBODY'    : { 'CAPTION' : 1, 'COLGROUP' : 1, 'THEAD' : 1, 'TFOOT' : 1 }\r
38         },\r
39         /*\r
40          * http://www.tohoho-web.com/html/tbody.htm\r
41          * HTML4.01では、ヘッダとフッタを先読みして表示するために、<tbody> よりも <tfoot> の方を先に記述しなくてはならないと定義されています。\r
42          * IE5.0 などでは HEAD → BODY → FOOT の順に表示するのですが、\r
43          * <tfoot> に未対応の古いブラウザでは、HEAD → FOOT → BODY の順に表示されてしまいます。\r
44          * また、HTML5 では、<tfoot> と <tbody> の順番はどちらでもよいことになりました。\r
45          */\r
46 \r
47         // Attributes that have their values filled in disabled="disabled"\r
48 \r
49         // Special Elements (can contain anything)\r
50         X_HTMLParser_special = { 'SCRIPT' : 1, 'STYLE' : 1, 'PLAINTEXT' : 1, 'XMP' : 1, 'TEXTAREA' : 1 },\r
51         \r
52         X_HTMLParser_skipFixNesting = false;\r
53         \r
54         function X_HTMLParser_exec( html, handler, async ){\r
55                 var special        = X_HTMLParser_special,\r
56                         //plainText      = X_HTMLParser_plainText,\r
57                         startTime      = async && X_Timer_now(),\r
58                         stack          = async ? async[ 1 ] : [],\r
59                         lastHtml       = html,\r
60                         chars, last, text, index;\r
61 \r
62                 while( html ){\r
63                         chars = true;\r
64                         last  = stack[ stack.length - 1 ];\r
65                         \r
66                         // Make sure we're not in a script or style element\r
67                         if( last && special[ handler.isXML ? last.toUpperCase() : last ] === 1 ){\r
68                                 if( 0 <= ( index = html.toUpperCase().indexOf( '</' + ( handler.isXML ? last.toUpperCase() : last ) ) ) ){\r
69                                         handler.chars( html.substring( 0, index ) );\r
70                                         if( index = X_HTMLParser__parseEndTag( stack, handler, html ) ){\r
71                                                 html = html.substring( index );\r
72                                         } else {\r
73                                                 handler.chars( html );\r
74                                                 html = '';\r
75                                         };\r
76                                 } else {\r
77                                         handler.chars( html );\r
78                                         html = '';\r
79                                 };\r
80                         } else {\r
81                                 // Comment\r
82                                 if( html.indexOf("<!--") === 0 ){\r
83                                         if( 0 < ( index = html.indexOf("-->") ) ){\r
84                                                 handler.comment( html.substring( 4, index ) );\r
85                                                 html = html.substring( index + 3 );\r
86                                                 chars = false;\r
87                                         };\r
88         \r
89                                 // end tag\r
90                                 } else if( html.indexOf("</") === 0 ){\r
91                                         if( 2 < ( index = X_HTMLParser__parseEndTag( stack, handler, html ) ) ){\r
92                                                 html = html.substring( index );\r
93                                                 chars = false;\r
94                                         };\r
95         \r
96                                 // start tag\r
97                                 } else if( html.indexOf("<") === 0 ){\r
98                                         if( index = X_HTMLParser__parseStartTag( stack, last, handler, html ) ){\r
99                                                 html  = html.substring( index );\r
100                                                 chars = false;\r
101                                         } else\r
102                                         if( index === false ){\r
103                                                 return;\r
104                                         };\r
105                                 };\r
106 \r
107                                 if( chars ){\r
108                                         index = html.indexOf("<");\r
109                                         \r
110                                         text = index < 0 ? html : html.substring( 0, index );\r
111                                         html = index < 0 ? '' : html.substring( index );\r
112                                         \r
113                                         handler.chars( text );\r
114                                 };\r
115 \r
116                         };\r
117 \r
118                         if( html === lastHtml ){\r
119                                 handler.err( html );\r
120                                 return;\r
121                         };\r
122                         \r
123                         if( async && startTime + X_Timer_INTERVAL_TIME <= X_Timer_now() && html ){\r
124                                 handler.progress( 1 - html.length / async[ 0 ] );\r
125                                 X_Timer_once( 0, X_HTMLParser_exec, [ html, handler, async ] );\r
126                                 return;\r
127                         };\r
128                         \r
129                         lastHtml = html;\r
130                 };\r
131                 \r
132                 // Clean up any remaining tags\r
133                 X_HTMLParser_parseEndTag( stack, handler );\r
134                 \r
135                 async && handler.asyncComplete();\r
136         };\r
137 \r
138         function X_HTMLParser__parseStartTag( stack, last, handler, html ){\r
139                 var alphabets = X_HTMLParser_CHARS,\r
140                         whiteSpace = X_HTMLParser_CHARS,\r
141                         saveAttr = X_HTMLParser_saveAttr,\r
142                         uri   = X_Dom_DTD_ATTR_VAL_IS_URI,\r
143                         phase = 0,\r
144                         l     = html.length,\r
145                         i     = 0,\r
146                         attrs = [],\r
147                         tagName, empty = false,\r
148                         chr, start, attrName, quot, escape, tagUpper;\r
149                 \r
150                 while( i < l && phase < 9 ){\r
151                         chr = html.charAt( i );\r
152                         switch( phase ){\r
153                                 case 0 :\r
154                                         chr === '<' && ( ++phase );\r
155                                         break;\r
156                                 case 1 : // タグ名の開始を待つ\r
157                                         ( alphabets[ chr ] & 3 ) && ( ++phase && ( start = i ) );\r
158                                         break;\r
159                                 case 2 : // タグ名の終わりの空白文字を待つ\r
160                                         ( whiteSpace[ chr ] & 16 ) ?\r
161                                                 ( ++phase && ( tagName = html.substring( start, i ) ) ) :\r
162                                         ( chr === '>' || ( empty = html.substr( i, 2 ) === '/>' ) ) &&\r
163                                                 ( ( tagName = html.substring( start, i ) ) && ( phase = 9 ) );\r
164                                         break;\r
165                                 case 3 : // 属性名の開始を待つ\r
166                                         ( alphabets[ chr ] & 3 ) ?\r
167                                                 ( ++phase && ( start = i ) ) :\r
168                                         ( chr === '>' || ( empty = html.substr( i, 2 ) === '/>' ) ) &&\r
169                                                 ( phase = 9 );\r
170                                         break;\r
171                                 case 4 : // 属性名の終わりを待つ\r
172                                         chr === '=' ?\r
173                                                 ( ( phase = 6 ) && ( attrName = html.substring( start, i ) ) ) :\r
174                                         ( whiteSpace[ chr ] & 16 ) ?\r
175                                                 ( ( phase = 5 ) && ( attrName = html.substring( start, i ) ) ) :\r
176                                         ( chr === '>' || ( empty = html.substr( i, 2 ) === '/>' ) ) &&\r
177                                                 ( ( phase = 9 ) && ( attrs[ attrs.length ] = html.substring( start, i ) ) );\r
178                                         break;\r
179                                 case 5 : // 属性の = または次の属性または htmlタグの閉じ\r
180                                         !( whiteSpace[ chr ] & 16 ) &&// ie4 未対応の属性には cite = http:// となる\r
181                                         //      1 :\r
182                                         ( alphabets[ chr ] & 3 ) ?\r
183                                                 ( ( phase = 3 ) && ( attrs[ attrs.length ] = attrName ) && ( start = i ) ) : // <textarea readonly>\r
184                                         chr === '=' ?\r
185                                                 ( phase = 6 ) :\r
186                                         ( chr === '>' || ( empty = html.substr( i, 2 ) === '/>' ) ) &&\r
187                                                 ( ( phase = 9 ) && ( attrs[ attrs.length ] = attrName ) );\r
188                                         break;\r
189                                 case 6 : // 属性値の開始 quot を待つ\r
190                                         ( chr === '"' || chr === "'" ) ?\r
191                                                 ( ( phase = 7 ) && ( quot = chr ) && ( start = i + 1 ) ):\r
192                                         !( whiteSpace[ chr ] & 16 ) &&\r
193                                                 ( ( phase = 8 ) && ( start = i ) ); // no quot\r
194                                         break;\r
195                                 case 7 : //属性値の閉じ quot を待つ\r
196                                         !escape && ( chr === quot ) && ( phase = 3 ) && saveAttr( attrs, attrName, html.substring( start, i ) );\r
197                                         break;\r
198                                 case 8 : //閉じ quot のない属性の値\r
199                                         ( whiteSpace[ chr ] & 16 ) ?\r
200                                                 ( ( phase = 3 ) && saveAttr( attrs, attrName, html.substring( start, i ) ) ) :\r
201                                         ( chr === '>' ) ?\r
202                                                 ( ( phase = 9 ) && saveAttr( attrs, attrName, html.substring( start, i ) ) ) :\r
203                                         !escape && !uri[ attrName ] && ( empty = html.substr( i, 2 ) === '/>' ) && // attr の val が uri で / で終わりかつ、未対応属性の場合\r
204                                                 ( ( phase = 9 ) && saveAttr( attrs, attrName, html.substring( start, i ) ) );\r
205                                         break;\r
206                         };\r
207                         escape = chr === '\\' && !escape; // \\\\ is not escape for "\r
208                         ++i;\r
209                 };\r
210                 if( phase === 9 ){\r
211                         if( empty ) ++i;\r
212                         //if( X_HTMLParser_parseStartTag( stack, last, handler, tagName, attrs, empty, i ) === false ) return false;\r
213                         \r
214                         tagUpper = tagName.toUpperCase();\r
215                         \r
216                         if( !X_HTMLParser_skipFixNesting && X_HTMLParser_block[ tagUpper ] === 1 ){\r
217                                 while( last && X_HTMLParser_inline[ handler.isXML ? last.toUpperCase() : last ] === 1 ){\r
218                                         X_HTMLParser_parseEndTag( stack, handler, last );\r
219                                         last = stack[ stack.length - 1 ];\r
220                                 };\r
221                         };\r
222                         last && X_HTMLParser_closeSelf[ tagUpper ] === 1 &&\r
223                                 ( last === tagName || ( X_HTMLParser_sisters[ tagUpper ] && X_HTMLParser_sisters[ tagUpper ][ handler.isXML ? last.toUpperCase() : last ] === 1 ) ) &&\r
224                                         X_HTMLParser_parseEndTag( stack, handler, last );\r
225                         empty = empty || X_Dom_DTD_EMPTY[ tagUpper ];\r
226                         !empty && ( stack[ stack.length ] = handler.isXML ? tagName : tagUpper );\r
227                         \r
228                         if( handler.tagStart( handler.isXML ? tagName : tagUpper, attrs, empty, i ) === false ) return false;\r
229                         \r
230                         return i;\r
231                 };\r
232                 return 0; // error\r
233         };\r
234 \r
235         function X_HTMLParser__parseEndTag( stack, handler, html ){\r
236                 var alphabets = X_HTMLParser_CHARS,\r
237                         whiteSpace = X_HTMLParser_CHARS,\r
238                         phase = 0,\r
239                         l     = html.length,\r
240                         i     = 0,\r
241                         tagName,\r
242                         chr, start;\r
243                 \r
244                 while( i < l && phase < 9 ){\r
245                         chr = html.charAt( i );\r
246                         switch( phase ){\r
247                                 case 0 :\r
248                                         html.substr( i, 2 ) === '</' && ( ++phase && ++i );\r
249                                         break;\r
250                                 case 1 : // タグ名の開始を待つ\r
251                                         ( alphabets[ chr ] & 3 ) && ( ++phase && ( start = i ) );\r
252                                         break;\r
253                                 case 2 : // タグ名の終わりの空白文字を待つ\r
254                                         ( whiteSpace[ chr ] & 16 ) && ( ++phase );\r
255                                         ( chr === '>' ) && ( phase = 9 );\r
256                                         ( phase !== 2 ) && ( tagName = html.substring( start, i ) );\r
257                                         break;\r
258                                 case 3 : // タグの終了を待つ\r
259                                         chr === '>' && ( phase = 9 );\r
260                                         break;\r
261                         };\r
262                         ++i;\r
263                 };\r
264                 if( phase === 9 ){\r
265                         X_HTMLParser_parseEndTag( stack, handler, handler.isXML ? tagName : tagName.toUpperCase() );\r
266                         return i;\r
267                 };\r
268                 return 0; // error\r
269         };\r
270 \r
271         function X_HTMLParser_saveAttr( attrs, name, value ){\r
272                 name  = name.toLowerCase();\r
273                 value = X_Node_Attr_noValue[ name ] === 1 ? name : value;\r
274                 attrs[ attrs.length ] = {\r
275                         attName   : name,\r
276                         // attrValue : value,\r
277                         escaped   :\r
278                                 value.indexOf( '"' ) !== -1 ?\r
279                                         value.split( '"' ).join( '\\"' ).split( '\\\\"' ).join( '\\"' ) :\r
280                                         value\r
281                 };\r
282         };\r
283 \r
284         function X_HTMLParser_parseEndTag( stack, handler, tagName ) {\r
285                 var pos = 0, i = stack.length;\r
286                 // If no tag name is provided, clean shop\r
287                 \r
288                 // Find the closest opened tag of the same type\r
289                 if ( tagName )\r
290                         for ( pos = i; 0 <= pos; )\r
291                                 if ( stack[ --pos ] === tagName )\r
292                                         break;\r
293                 \r
294                 if ( 0 <= pos ) {\r
295                         // Close all the open elements, up the stack\r
296                         for ( ; pos < i; )\r
297                                 handler.tagEnd( stack[ --i ] );\r
298                         \r
299                         // Remove the open elements from the stack\r
300                         stack.length = pos;\r
301                 };\r
302         };\r
303 \r
304 var X_HTMLParser_htmlStringToXNode = {\r
305         flat : null,\r
306         nest : [],\r
307         isXML : false,\r
308         err : function( html ){\r
309                 X_HTMLParser_htmlStringToXNode.flat.length = 0;\r
310                 !X_HTMLParser_htmlStringToXNode.ignoreError && X.Logger.warn( 'X_Dom_Parser() error ' + html );\r
311         },\r
312         tagStart : function( tagName, attrs, noChild, length ){\r
313                 var xnode,\r
314                         nest   = X_HTMLParser_htmlStringToXNode.nest,\r
315                         flat   = X_HTMLParser_htmlStringToXNode.flat,\r
316                         l      = nest.length,\r
317                         attr, name, i, _attrs; //, toIndex;\r
318                 if( l ){\r
319                         xnode = nest[ l - 1 ][ 'create' ]( tagName );\r
320                 } else {\r
321                         xnode = flat[ flat.length ] = X_Doc_create( tagName );\r
322                 };\r
323                 if( !noChild ) nest[ l ] = xnode;\r
324                 if( i = attrs.length ){\r
325                         _attrs = {};\r
326                         for( ; i; ){\r
327                                 if( attr = attrs[ --i ] ){\r
328                                         if( X_Type_isString( attr ) ){\r
329                                                 name = attr;\r
330                                                 _attrs[ name ] = true;\r
331                                         } else {\r
332                                                 name = attr.attName;\r
333                                                 _attrs[ name ] = attr.escaped;\r
334                                         };\r
335                                 };\r
336                         };\r
337                         xnode[ 'attr' ]( _attrs );\r
338                 };\r
339         },\r
340         tagEnd : function(){\r
341                 0 < X_HTMLParser_htmlStringToXNode.nest.length && ( --X_HTMLParser_htmlStringToXNode.nest.length );\r
342         },\r
343         chars : function( text ){\r
344                 if( X_HTMLParser_htmlStringToXNode.nest.length ){\r
345                         X_HTMLParser_htmlStringToXNode.nest[ X_HTMLParser_htmlStringToXNode.nest.length - 1 ][ 'createText' ]( text );\r
346                 } else {\r
347                         X_HTMLParser_htmlStringToXNode.flat[ X_HTMLParser_htmlStringToXNode.flat.length ] = X_Doc_createText( text );\r
348                 };\r
349         },\r
350         comment : X_emptyFunction\r
351 };\r
352 \r
353 function X_HtmlParser_parse( html, ignoreError ){\r
354         var worker = X_HTMLParser_htmlStringToXNode, ret;\r
355         worker.flat = [];\r
356         worker.nest.length = 0;\r
357         worker.ignoreError = ignoreError;\r
358         X_HTMLParser_exec( html, worker );\r
359         ret = worker.flat;\r
360         delete worker.flat;\r
361         return ret;\r
362 };\r
363 \r
364 var X_HTMLParser_asyncHtmlStringToXNode = {\r
365         isXML : false,\r
366         err : function( html ){\r
367                 X_HTMLParser_htmlStringToXNode.err( html );\r
368                 this[ 'asyncDispatch' ]( X_EVENT_ERROR );\r
369         },\r
370         tagStart : X_HTMLParser_htmlStringToXNode.tagStart,\r
371         tagEnd   : X_HTMLParser_htmlStringToXNode.tagEnd,\r
372         chars    : X_HTMLParser_htmlStringToXNode.chars,\r
373         comment  : X_emptyFunction,\r
374         \r
375         progress : function( pct ){\r
376                 this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, percent : pct } );\r
377         },\r
378         asyncComplete : function(){\r
379                 var ret = X_HTMLParser_htmlStringToXNode.flat;\r
380                 delete X_HTMLParser_htmlStringToXNode.flat;\r
381                 this[ 'asyncDispatch' ]( { type : X_EVENT_SUCCESS, xnodes : ret } );\r
382         }\r
383 };\r
384 \r
385 function X_HTMLParser_asyncParse( html, ignoreError ){\r
386         var dispatcher = X_Class_override( X_EventDispatcher(), X_HTMLParser_asyncHtmlStringToXNode ),\r
387                 worker = X_HTMLParser_htmlStringToXNode;\r
388         dispatcher[ 'listenOnce' ]( X_EVENT_SUCCESS, dispatcher, dispatcher[ 'kill' ] );\r
389         worker.flat = [];\r
390         worker.nest.length = 0;\r
391         worker.ignoreError = ignoreError;\r
392         X_HTMLParser_exec( html, dispatcher, [ html.length, [] ] );\r
393         return dispatcher;\r
394 };\r