OSDN Git Service

Version 0.6.27, bugfix for X.Dom.Parser.
[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 uri   = X.Dom.DTD.ATTR_VAL_IS_URI,
118                         phase = 0,
119                         l     = html.length,
120                         i     = 0,
121                         attrs = [],
122                         tagName, empty,
123                         chr, start, attrName, quot, escape;
124                 
125                 while( i < l && phase < 9 ){
126                         chr = html.charAt( i );
127                         switch( phase ){
128                                 case 0 :
129                                         chr === '<' && ( ++phase );
130                                         break;
131                                 case 1 : // タグ名の開始を待つ
132                                         alphabets.indexOf( chr ) !== -1 && ( ++phase && ( start = i ) );
133                                         break;
134                                 case 2 : // タグ名の終わりの空白文字を待つ
135                                         whiteSpace.indexOf( chr ) !== -1 ?
136                                                 ( ++phase && ( tagName = html.substring( start, i ) ) ) :
137                                         ( chr === '>' || ( empty = html.substr( i, 2 ) === '/>' ) ) &&
138                                                 ( ( tagName = html.substring( start, i ) ) && ( phase = 9 ) );
139                                         break;
140                                 case 3 : // 属性名の開始を待つ
141                                         alphabets.indexOf( chr ) !== -1 ?
142                                                 ( ++phase && ( start = i ) ) :
143                                         ( chr === '>' || ( empty = html.substr( i, 2 ) === '/>' ) ) &&
144                                                 ( phase = 9 );
145                                         break;
146                                 case 4 : // 属性名の終わりを待つ
147                                         chr === '=' ?
148                                                 ( ( phase = 6 ) && ( attrName = html.substring( start, i ) ) ) :
149                                         whiteSpace.indexOf( chr ) !== -1 &&
150                                                 ( ( phase = 5 ) && ( attrName = html.substring( start, i ) ) );
151                                         break;
152                                 case 5 : // 属性の = または次の属性または htmlタグの閉じ
153                                         whiteSpace.indexOf( chr ) !== -1 ?// ie4 未対応の属性には cite = http:// となる
154                                                 1 :
155                                         alphabets.indexOf( chr ) !== -1 ?
156                                                 ( ( phase = 4 ) && ( attrs[ attrs.length ] = attrName ) && ( start = i ) ) :
157                                         chr === '=' ?
158                                                 ( phase = 6 ) :
159                                         ( chr === '>' || ( empty = html.substr( i, 2 ) === '/>' ) ) &&
160                                                 ( ( phase = 9 ) && ( attrs[ attrs.length ] = attrName ) );
161                                         break;
162                                 case 6 : // 属性値の開始 quot を待つ
163                                         ( chr === '"' || chr === "'" ) ?
164                                                 ( ( phase = 7 ) && ( quot = chr ) && ( start = i + 1 ) ):
165                                         whiteSpace.indexOf( chr ) === -1 &&
166                                                 ( ( phase = 8 ) && ( start = i ) ); // no quot
167                                         break;
168                                 case 7 : //属性値の閉じ quot を待つ
169                                         !escape && ( chr === quot ) && ( phase = 3 ) && saveAttr( attrs, attrName, html.substring( start, i ) );
170                                         break;
171                                 case 8 : //閉じ quot のない属性の値
172                                         whiteSpace.indexOf( chr ) !== -1 ?
173                                                 ( ( phase = 3 ) && saveAttr( attrs, attrName, html.substring( start, i ) ) ) :
174                                         ( chr === '>' ) ?
175                                                 ( ( phase = 9 ) && saveAttr( attrs, attrName, html.substring( start, i ) ) ) :
176                                         ( !escape && uri.indexOf( attrName ) === -1 && html.substr( i, 2 ) === '/>' ) && // attr の val が uri で / で終わりかつ、未対応属性の場合
177                                                 ( empty = true );
178                                         break;
179                         };
180                         escape = chr === '\\' && !escape; // \\\\ is not escape for "
181                         ++i;
182                 };
183                 if( phase === 9 ){
184                         if( parseStartTag( stack, last, handler, tagName, attrs, empty, i ) === false ) return false;
185                         return i;
186                 };
187                 return 0; // error
188         };
189
190         function _parseEndTag( stack, handler, html ){
191                 var phase = 0,
192                         l     = html.length,
193                         i     = 0,
194                         tagName,
195                         chr, start;
196                 
197                 while( i < l && phase < 9 ){
198                         chr = html.charAt( i );
199                         switch( phase ){
200                                 case 0 :
201                                         html.substr( i, 2 ) === '</' && ( ++phase && ++i );
202                                         break;
203                                 case 1 : // タグ名の開始を待つ
204                                         alphabets.indexOf( chr ) !== -1 && ( ++phase && ( start = i ) );
205                                         break;
206                                 case 2 : // タグ名の終わりの空白文字を待つ
207                                         whiteSpace.indexOf( chr ) !== -1 && ( ++phase );
208                                         ( chr === '>' ) && ( phase = 9 );
209                                         ( phase !== 2 ) && ( tagName = html.substring( start, i ) );
210                                         break;
211                                 case 3 : // 属性名の開始を待つ
212                                         chr === '>' && ( phase = 9 );
213                                         break;
214                         };
215                         ++i;
216                 };
217                 if( phase === 9 ){
218                         parseEndTag( stack, handler, tagName );
219                         return i;
220                 };
221                 return 0; // error
222         };
223
224         function saveAttr( attrs, name, value ){
225                 name  = name.toLowerCase();
226                 value = fillAttrs[ name ] === 1 ? name : value;
227                 attrs[ attrs.length ] = {
228                         name    : name,
229                         value   : value,
230                         escaped :
231                                 value.indexOf( '"' ) !== -1 ?
232                                         value.split( '"' ).join( '\\"' ).split( '\\\\"' ).join( '\\"' ) :
233                                         value
234                 };
235         };
236
237         function parseStartTag( stack, last, handler, tagName, attrs, unary, index ) {
238                 var tagLower = tagName.toLowerCase();
239                 if ( block[ tagLower ] === 1 ) {
240                         while ( last && inline[ last.toLowerCase() ] === 1 ) {
241                                 parseEndTag( stack, handler, last );
242                                 last = stack[ stack.length - 1 ];
243                         };
244                 };
245                 closeSelf[ tagLower ] === 1 && ( last === tagName || ( sisters[ tagLower ] && sisters[ tagLower ][ last.toLowerCase() ] === 1 ) ) && parseEndTag( stack, handler, last );
246                 unary = empty[ tagLower ] === 1 || !!unary;
247                 !unary && ( stack[ stack.length ] = tagName );
248                 
249                 return handler.start( tagName, attrs, unary, index );
250         };
251
252         function parseEndTag( stack, handler, tagName ) {
253                 var pos = 0, i = stack.length;
254                 // If no tag name is provided, clean shop
255                 
256                 // Find the closest opened tag of the same type
257                 if ( tagName )
258                         for ( pos = i; 0 <= pos; )
259                                 if ( stack[ --pos ] === tagName )
260                                         break;
261                 
262                 if ( 0 <= pos ) {
263                         // Close all the open elements, up the stack
264                         for ( ; pos < i; )
265                                 handler.end( stack[ --i ] );
266                         
267                         // Remove the open elements from the stack
268                         stack.length = pos;
269                 };
270         };
271
272 })();
273
274 X.Dom._htmlStringToXNode = {
275         flat : null,
276         nest : [],
277         err : function( html ){
278                 this.flat.length = 0;
279                 this.ignoreError !== true && X.Notification.warn( 'X.Dom.Parser() error ' + html );
280         },
281         start : function( tagName, attrs, noChild, length ){
282                 var xnode,
283                         nest   = this.nest,
284                         flat   = this.flat,
285                         l      = nest.length,
286                         attr, name, i, _attrs; //, toIndex;
287                 if( l ){
288                         xnode = nest[ l - 1 ].create( tagName );
289                 } else {
290                         xnode = flat[ flat.length ] = X.Dom.Node.create( tagName );
291                 };
292                 if( !noChild ) nest[ l ] = xnode;
293                 if( i = attrs.length ){
294                         _attrs = {};
295                         for( ; i; ){
296                                 if( attr = attrs[ --i ] ){
297                                         if( typeof attr === 'string' ){
298                                                 name = attr;
299                                                 _attrs[ name ] = true;
300                                         } else {
301                                                 name = attr.name;
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