OSDN Git Service

Version 0.6.18, rewrite for async DOM update.
[pettanr/clientJs.git] / 0.6.x / js / dom / 19_XDomParser.js
1 /*
2  * Original code by Erik John Resig (ejohn.org)
3  * http://ejohn.org/blog/pure-javascript-html-parser/
4  *
5  */
6
7 (function(){
8
9         var alphabets  = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
10                 whiteSpace = '\t\r\n\f\b ';
11
12         // Empty Elements - HTML 4.01
13         var empty = X.Dom.DTD.EMPTY;
14
15         // Block Elements - HTML 4.01
16         var 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,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};
17
18         // Inline Elements - HTML 4.01
19         var 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,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};
20
21         // Elements that you can, intentionally, leave open
22         // (and which close themselves)
23         var closeSelf = {colgroup: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
24
25         // todo:
26         var plainText = { plaintext : 1, xmp : 1 };
27
28         var sisters = {
29                 th : { td : 1 },
30                 td : { th : 1 },
31                 dt : { dd : 1 },
32                 dd : { dt : 1 },
33                 colgroup : { caption : 1 },
34                 thead    : { caption : 1, colgroup : 1 },
35                 tfoot    : { caption : 1, colgroup : 1, thead : 1, tbody : 1 },
36                 tbody    : { caption : 1, colgroup : 1, thead : 1, tfoot : 1 }
37         };
38         /*
39          * http://www.tohoho-web.com/html/tbody.htm
40          * HTML4.01では、ヘッダとフッタを先読みして表示するために、<tbody> よりも <tfoot> の方を先に記述しなくてはならないと定義されています。
41          * IE5.0 などでは HEAD → BODY → FOOT の順に表示するのですが、
42          * <tfoot> に未対応の古いブラウザでは、HEAD → FOOT → BODY の順に表示されてしまいます。
43          * また、HTML5 では、<tfoot> と <tbody> の順番はどちらでもよいことになりました。
44          */
45
46         // Attributes that have their values filled in disabled="disabled"
47         var fillAttrs = X.Dom.Attr.noValue; //{checked:1,compact:1,declare:1,defer:1,disabled:1,ismap:1,multiple:1,nohref:1,noresize:1,noshade:1,nowrap:1,readonly:1,selected:1};
48
49         // Special Elements (can contain anything)
50         var special = {script:1,style:1};
51
52         X.Dom.Parser = function( html, handler ) {
53                 var stack    = [],
54                         lastHtml = html,
55                         chars, last, text, index;
56
57                 while ( html ) {
58                         chars = true;
59                         last  = stack[ stack.length - 1 ];
60                         
61                         // Make sure we're not in a script or style element
62                         if ( last && special[ last.toLowerCase() ] === 1 ) {
63                                 if( 0 <= ( index = _parseEndTag( stack, handler, html ) ) ){
64                                         //handler.chars( html.substring( 0, index ) );
65                                         html = html.substring( index );
66                                 };
67                         } else {
68                                 // Comment
69                                 if ( html.indexOf("<!--") === 0 ) {
70                                         if ( 0 < ( index = html.indexOf("-->") ) ) {
71                                                 handler.comment( html.substring( 4, index ) );
72                                                 html = html.substring( index + 3 );
73                                                 chars = false;
74                                         };
75         
76                                 // end tag
77                                 } else if ( html.indexOf("</") === 0 ) {
78                                         if ( 2 < ( index = _parseEndTag( stack, handler, html ) ) ) {
79                                                 html = html.substring( index );
80                                                 chars = false;
81                                         };
82         
83                                 // start tag
84                                 } else if ( html.indexOf("<") === 0 ) {
85                                         if( index = _parseStartTag( stack, last, handler, html ) ){
86                                                 html  = html.substring( index );
87                                                 chars = false;
88                                         } else
89                                         if( index === false ){
90                                                 return;
91                                         };
92                                 };
93
94                                 if ( chars ) {
95                                         index = html.indexOf("<");
96                                         
97                                         text = index < 0 ? html : html.substring( 0, index );
98                                         html = index < 0 ? '' : html.substring( index );
99                                         
100                                         handler.chars( text );
101                                 };
102
103                         };
104
105                         if ( html === lastHtml ){
106                                 handler.err( html );
107                                 return;
108                         };
109                         lastHtml = html;
110                 };
111                 
112                 // Clean up any remaining tags
113                 parseEndTag( stack, handler );
114         };
115
116         function _parseStartTag( stack, last, handler, html ){
117                 var phase = 0,
118                         l     = html.length,
119                         i     = 0,
120                         attrs = [],
121                         tagName, empty,
122                         chr, start, attrName, quot, escape;
123                 
124                 while( i < l && phase < 9 ){
125                         chr = html.charAt( i );
126                         switch( phase ){
127                                 case 0 :
128                                         chr === '<' && ( ++phase );
129                                         break;
130                                 case 1 : // タグ名の開始を待つ
131                                         alphabets.indexOf( chr ) !== -1 && ( ++phase && ( start = i ) );
132                                         break;
133                                 case 2 : // タグ名の終わりの空白文字を待つ
134                                         whiteSpace.indexOf( chr ) !== -1 ?
135                                                 ( ++phase && ( tagName = html.substring( start, i ) ) ) :
136                                         ( chr === '>' || ( empty = html.substr( i, 2 ) === '/>' ) ) &&
137                                                 ( ( tagName = html.substring( start, i ) ) && ( phase = 9 ) );
138                                         break;
139                                 case 3 : // 属性名の開始を待つ
140                                         alphabets.indexOf( chr ) !== -1 ?
141                                                 ( ++phase && ( start = i ) ) :
142                                         ( chr === '>' || ( empty = html.substr( i, 2 ) === '/>' ) ) &&
143                                                 ( phase = 9 );
144                                         break;
145                                 case 4 : // 属性名の終わりを待つ
146                                         chr === '=' ?
147                                                 ( ( phase = 6 ) && ( attrName = html.substring( start, i ) ) ) :
148                                         whiteSpace.indexOf( chr ) !== -1 &&
149                                                 ( ( phase = 5 ) && ( attrName = html.substring( start, i ) ) );
150                                         break;
151                                 case 5 : // 属性の = または次の属性または htmlタグの閉じ
152                                         alphabets.indexOf( chr ) !== -1 ?
153                                                 ( ( phase = 4 ) && ( attrs[ attrs.length ] = attrName ) && ( start = i ) ) :
154                                         chr === '=' ?
155                                                 ( phase = 6 ) :
156                                         ( chr === '>' || ( empty = html.substr( i, 2 ) === '/>' ) ) &&
157                                                 ( ( phase = 9 ) && ( attrs[ attrs.length ] = attrName ) );
158                                         break;
159                                 case 6 : // 属性値の開始 quot を待つ
160                                         ( chr === '"' || chr === "'" ) ?
161                                                 ( ( phase = 7 ) && ( quot = chr ) && ( start = i + 1 ) ):
162                                         whiteSpace.indexOf( chr ) === -1 &&
163                                                 ( ( phase = 8 ) && ( start = i ) ); // no quot
164                                         break;
165                                 case 7 : //属性値の閉じ quot を待つ
166                                         !escape && ( chr === quot ) && ( phase = 3 ) && saveAttr( attrs, attrName, html.substring( start, i ) );
167                                         break;
168                                 case 8 : //閉じ quot のない属性の値
169                                         whiteSpace.indexOf( chr ) !== -1 ?
170                                                 ( ( phase = 3 ) && saveAttr( attrs, attrName, html.substring( start, i ) ) ) :
171                                         ( chr === '>' || ( empty = html.substr( i, 2 ) === '/>' ) ) &&
172                                                 ( ( phase = 9 ) && saveAttr( attrs, attrName, html.substring( start, i ) ) );
173                                         break;
174                         };
175                         escape = chr === '\\' && !escape; // \\\\ is not escape for "
176                         ++i;
177                 };
178                 if( phase === 9 ){
179                         if( parseStartTag( stack, last, handler, tagName, attrs, empty, i ) === false ) return false;
180                         return i;
181                 };
182                 return 0; // error
183         };
184
185         function _parseEndTag( stack, handler, html ){
186                 var phase = 0,
187                         l     = html.length,
188                         i     = 0,
189                         tagName,
190                         chr, start;
191                 
192                 while( i < l && phase < 9 ){
193                         chr = html.charAt( i );
194                         switch( phase ){
195                                 case 0 :
196                                         html.substr( i, 2 ) === '</' && ( ++phase && ++i );
197                                         break;
198                                 case 1 : // タグ名の開始を待つ
199                                         alphabets.indexOf( chr ) !== -1 && ( ++phase && ( start = i ) );
200                                         break;
201                                 case 2 : // タグ名の終わりの空白文字を待つ
202                                         whiteSpace.indexOf( chr ) !== -1 && ( ++phase );
203                                         ( chr === '>' ) && ( phase = 9 );
204                                         ( phase !== 2 ) && ( tagName = html.substring( start, i ) );
205                                         break;
206                                 case 3 : // 属性名の開始を待つ
207                                         chr === '>' && ( phase = 9 );
208                                         break;
209                         };
210                         ++i;
211                 };
212                 if( phase === 9 ){
213                         parseEndTag( stack, handler, tagName );
214                         return i;
215                 };
216                 return 0; // error
217         };
218
219         function saveAttr( attrs, name, value ){
220                 name  = name.toLowerCase();
221                 value = fillAttrs[ name ] === 1 ? name : value;
222                 attrs[ attrs.length ] = {
223                         name    : name,
224                         value   : value,
225                         escaped :
226                                 value.indexOf( '"' ) !== -1 ?
227                                         value.split( '"' ).join( '\\"' ).split( '\\\\"' ).join( '\\"' ) :
228                                         value
229                 };
230         };
231
232         function parseStartTag( stack, last, handler, tagName, attrs, unary, index ) {
233                 var tagLower = tagName.toLowerCase();
234                 if ( block[ tagLower ] === 1 ) {
235                         while ( last && inline[ last.toLowerCase() ] === 1 ) {
236                                 parseEndTag( stack, handler, last );
237                                 last = stack[ stack.length - 1 ];
238                         };
239                 };
240                 closeSelf[ tagLower ] === 1 && ( last === tagName || ( sisters[ tagLower ] && sisters[ tagLower ][ last.toLowerCase() ] === 1 ) ) && parseEndTag( stack, handler, last );
241                 unary = empty[ tagLower ] === 1 || !!unary;
242                 !unary && ( stack[ stack.length ] = tagName );
243                 
244                 return handler.start( tagName, attrs, unary, index );
245         };
246
247         function parseEndTag( stack, handler, tagName ) {
248                 var pos = 0, i = stack.length;
249                 // If no tag name is provided, clean shop
250                 
251                 // Find the closest opened tag of the same type
252                 if ( tagName )
253                         for ( pos = i; 0 <= pos; )
254                                 if ( stack[ --pos ] === tagName )
255                                         break;
256                 
257                 if ( 0 <= pos ) {
258                         // Close all the open elements, up the stack
259                         for ( ; pos < i; )
260                                 handler.end( stack[ --i ] );
261                         
262                         // Remove the open elements from the stack
263                         stack.length = pos;
264                 };
265         };
266
267 })();
268
269 X.Dom._htmlStringToXNode = {
270         flat : null,
271         nest : [],
272         err : function( html ){
273                 this.flat.length = 0;
274                 this.ignoreError !== true && X.Notification.warn( 'X.Dom.Parser() error ' + html );
275         },
276         start : function( tagName, attrs, noChild, length ){
277                 var xnode,
278                         nest   = this.nest,
279                         flat   = this.flat,
280                         l      = nest.length,
281                         attr, name, i, _attrs; //, toIndex;
282                 if( l ){
283                         xnode = nest[ l - 1 ].create( tagName );
284                 } else {
285                         xnode = flat[ flat.length ] = X.Dom.Node.create( tagName );
286                 };
287                 if( !noChild ) nest[ l ] = xnode;
288                 if( i = attrs.length ){
289                         //toIndex = X.Dom.Attr.toIndex;
290                         _attrs = {};
291                         for( ; i; ){
292                                 if( attr = attrs[ --i ] ){
293                                         if( typeof attr === 'string' ){
294                                                 name = attr;
295                                                 //i    = toIndex[ name ];
296                                                 //_attrs[ ( i || i === 0 ) ? i : name ] = name;
297                                                 _attrs[ name ] = true;
298                                         } else {
299                                                 name = attr.name;
300                                                 //i    = toIndex[ name ];
301                                                 //_attrs[ ( i || i === 0 ) ? i : name ] = attr.escaped;
302                                                 _attrs[ name ] = attr.escaped;
303                                         };
304                                 };
305                         };
306                         xnode.attr( _attrs );
307                 };
308         },
309         end : function(){
310                 0 < this.nest.length && ( --this.nest.length );
311         },
312         chars : function( text ){
313                 if( this.nest.length ){
314                         this.nest[ this.nest.length - 1 ].createText( text );
315                 } else {
316                         this.flat[ this.flat.length ] = X.Dom.Node.createText( text );
317                 };
318         },
319         comment : X.emptyFunction
320 };
321
322 X.Dom.parse = function( html, ignoreError ){
323         var worker = X.Dom._htmlStringToXNode, ret;
324         worker.flat = [];
325         worker.nest.length = 0;
326         worker.ignoreError = ignoreError;
327         X.Dom.Parser( html, worker );
328         ret = worker.flat;
329         delete worker.flat;
330         return ret;
331 };
332