3 * Original code by Erik John Resig (ejohn.org)
\r
4 * http://ejohn.org/blog/pure-javascript-html-parser/
\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 // "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
13 '\t' : 16, '\r\n' : 16, '\r' : 16, '\n' : 16, '\f' : 16, '\b' : 16, ' ' : 16
\r
16 // Empty Elements - HTML 4.01
\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
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
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
47 // Attributes that have their values filled in disabled="disabled"
\r
49 // Special Elements (can contain anything)
\r
50 X_HTMLParser_special = { 'SCRIPT' : 1, 'STYLE' : 1, 'PLAINTEXT' : 1, 'XMP' : 1, 'TEXTAREA' : 1 },
\r
52 X_HTMLParser_skipFixNesting = false;
\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 _parseStartTag = X_HTMLParser__parseStartTag,
\r
59 _parseEndTag = X_HTMLParser__parseEndTag,
\r
60 stack = async ? async[ 1 ] : [],
\r
62 chars, last, text, index;
\r
66 last = stack[ stack.length - 1 ];
\r
68 // Make sure we're not in a script or style element
\r
69 if ( last && special[ last ] === 1 ) {
\r
70 if( 0 <= ( index = html.toUpperCase().indexOf( '</' + last ) ) ){
\r
71 handler.chars( html.substring( 0, index ) );
\r
72 if( index = _parseEndTag( stack, handler, html ) ){
\r
73 html = html.substring( index );
\r
75 handler.chars( html );
\r
79 handler.chars( html );
\r
84 if ( html.indexOf("<!--") === 0 ) {
\r
85 if ( 0 < ( index = html.indexOf("-->") ) ) {
\r
86 handler.comment( html.substring( 4, index ) );
\r
87 html = html.substring( index + 3 );
\r
92 } else if ( html.indexOf("</") === 0 ) {
\r
93 if ( 2 < ( index = _parseEndTag( stack, handler, html ) ) ) {
\r
94 html = html.substring( index );
\r
99 } else if ( html.indexOf("<") === 0 ) {
\r
100 if( index = _parseStartTag( stack, last, handler, html ) ){
\r
101 html = html.substring( index );
\r
104 if( index === false ){
\r
110 index = html.indexOf("<");
\r
112 text = index < 0 ? html : html.substring( 0, index );
\r
113 html = index < 0 ? '' : html.substring( index );
\r
115 handler.chars( text );
\r
120 if( html === lastHtml ){
\r
121 handler.err( html );
\r
125 if( async && startTime + 15 <= X_Timer_now() && html ){
\r
126 handler.progress( 1 - html.length / async[ 0 ] );
\r
127 X_Timer_once( 0, X_HTMLParser_exec, [ html, handler, async ] );
\r
134 // Clean up any remaining tags
\r
135 X_HTMLParser_parseEndTag( stack, handler );
\r
137 async && handler.complete();
\r
140 function X_HTMLParser__parseStartTag( stack, last, handler, html ){
\r
141 var alphabets = X_HTMLParser_CHARS,
\r
142 whiteSpace = X_HTMLParser_CHARS,
\r
143 saveAttr = X_HTMLParser_saveAttr,
\r
144 uri = X_Dom_DTD_ATTR_VAL_IS_URI,
\r
149 tagName, empty = false,
\r
150 chr, start, attrName, quot, escape;
\r
152 while( i < l && phase < 9 ){
\r
153 chr = html.charAt( i );
\r
156 chr === '<' && ( ++phase );
\r
158 case 1 : // タグ名の開始を待つ
\r
159 ( alphabets[ chr ] & 3 ) && ( ++phase && ( start = i ) );
\r
161 case 2 : // タグ名の終わりの空白文字を待つ
\r
162 ( whiteSpace[ chr ] & 16 ) ?
\r
163 ( ++phase && ( tagName = html.substring( start, i ) ) ) :
\r
164 ( chr === '>' || ( empty = html.substr( i, 2 ) === '/>' ) ) &&
\r
165 ( ( tagName = html.substring( start, i ) ) && ( phase = 9 ) );
\r
167 case 3 : // 属性名の開始を待つ
\r
168 ( alphabets[ chr ] & 3 ) ?
\r
169 ( ++phase && ( start = i ) ) :
\r
170 ( chr === '>' || ( empty = html.substr( i, 2 ) === '/>' ) ) &&
\r
173 case 4 : // 属性名の終わりを待つ
\r
175 ( ( phase = 6 ) && ( attrName = html.substring( start, i ) ) ) :
\r
176 ( whiteSpace[ chr ] & 16 ) &&
\r
177 ( ( phase = 5 ) && ( attrName = html.substring( start, i ) ) );
\r
179 case 5 : // 属性の = または次の属性または htmlタグの閉じ
\r
180 ( whiteSpace[ chr ] & 16 ) ?// ie4 未対応の属性には cite = http:// となる
\r
182 ( alphabets[ chr ] & 3 ) ?
\r
183 ( ( phase = 4 ) && ( attrs[ attrs.length ] = attrName ) && ( start = i ) ) :
\r
186 ( chr === '>' || ( empty = html.substr( i, 2 ) === '/>' ) ) &&
\r
187 ( ( phase = 9 ) && ( attrs[ attrs.length ] = attrName ) );
\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
195 case 7 : //属性値の閉じ quot を待つ
\r
196 !escape && ( chr === quot ) && ( phase = 3 ) && saveAttr( attrs, attrName, html.substring( start, i ) );
\r
198 case 8 : //閉じ quot のない属性の値
\r
199 ( whiteSpace[ chr ] & 16 ) ?
\r
200 ( ( phase = 3 ) && saveAttr( attrs, attrName, html.substring( start, i ) ) ) :
\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
207 escape = chr === '\\' && !escape; // \\\\ is not escape for "
\r
212 if( X_HTMLParser_parseStartTag( stack, last, handler, tagName.toUpperCase(), attrs, empty, i ) === false ) return false;
\r
218 function X_HTMLParser__parseEndTag( stack, handler, html ){
\r
219 var alphabets = X_HTMLParser_CHARS,
\r
220 whiteSpace = X_HTMLParser_CHARS,
\r
227 while( i < l && phase < 9 ){
\r
228 chr = html.charAt( i );
\r
231 html.substr( i, 2 ) === '</' && ( ++phase && ++i );
\r
233 case 1 : // タグ名の開始を待つ
\r
234 ( alphabets[ chr ] & 3 ) && ( ++phase && ( start = i ) );
\r
236 case 2 : // タグ名の終わりの空白文字を待つ
\r
237 ( whiteSpace[ chr ] & 16 ) && ( ++phase );
\r
238 ( chr === '>' ) && ( phase = 9 );
\r
239 ( phase !== 2 ) && ( tagName = html.substring( start, i ) );
\r
241 case 3 : // タグの終了を待つ
\r
242 chr === '>' && ( phase = 9 );
\r
248 X_HTMLParser_parseEndTag( stack, handler, tagName.toUpperCase() );
\r
254 function X_HTMLParser_saveAttr( attrs, name, value ){
\r
255 name = name.toLowerCase();
\r
256 value = X_Node_Attr_noValue[ name ] === 1 ? name : value;
\r
257 attrs[ attrs.length ] = {
\r
261 value.indexOf( '"' ) !== -1 ?
\r
262 value.split( '"' ).join( '\\"' ).split( '\\\\"' ).join( '\\"' ) :
\r
267 function X_HTMLParser_parseStartTag( stack, last, handler, tagName, attrs, empty, index ) {
\r
268 var inline = X_HTMLParser_inline,
\r
269 parseEndTag = X_HTMLParser_parseEndTag,
\r
270 sisters = X_HTMLParser_sisters;
\r
271 if( !X_HTMLParser_skipFixNesting && X_HTMLParser_block[ tagName ] === 1 ){
\r
272 while( last && inline[ last ] === 1 ){
\r
273 parseEndTag( stack, handler, last );
\r
274 last = stack[ stack.length - 1 ];
\r
277 last && X_HTMLParser_closeSelf[ tagName ] === 1 && ( last === tagName || ( sisters[ tagName ] && sisters[ tagName ][ last ] === 1 ) ) && parseEndTag( stack, handler, last );
\r
278 empty = empty || X_Dom_DTD_EMPTY[ tagName ];
\r
279 !empty && ( stack[ stack.length ] = tagName );
\r
281 return handler.start( tagName, attrs, empty, index );
\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
288 // Find the closest opened tag of the same type
\r
290 for ( pos = i; 0 <= pos; )
\r
291 if ( stack[ --pos ] === tagName )
\r
295 // Close all the open elements, up the stack
\r
297 handler.end( stack[ --i ] );
\r
299 // Remove the open elements from the stack
\r
300 stack.length = pos;
\r
304 var X_HTMLParser_htmlStringToXNode = {
\r
307 err : function( html ){
\r
308 X_HTMLParser_htmlStringToXNode.flat.length = 0;
\r
309 !X_HTMLParser_htmlStringToXNode.ignoreError && X.Logger.warn( 'X_Dom_Parser() error ' + html );
\r
311 start : function( tagName, attrs, noChild, length ){
\r
313 nest = X_HTMLParser_htmlStringToXNode.nest,
\r
314 flat = X_HTMLParser_htmlStringToXNode.flat,
\r
316 attr, name, i, _attrs; //, toIndex;
\r
318 xnode = nest[ l - 1 ][ 'create' ]( tagName );
\r
320 xnode = flat[ flat.length ] = X_Doc_create( tagName );
\r
322 if( !noChild ) nest[ l ] = xnode;
\r
323 if( i = attrs.length ){
\r
326 if( attr = attrs[ --i ] ){
\r
327 if( X_Type_isString( attr ) ){
\r
329 _attrs[ name ] = true;
\r
332 _attrs[ name ] = attr.escaped;
\r
336 xnode[ 'attr' ]( _attrs );
\r
340 0 < X_HTMLParser_htmlStringToXNode.nest.length && ( --X_HTMLParser_htmlStringToXNode.nest.length );
\r
342 chars : function( text ){
\r
343 if( X_HTMLParser_htmlStringToXNode.nest.length ){
\r
344 X_HTMLParser_htmlStringToXNode.nest[ X_HTMLParser_htmlStringToXNode.nest.length - 1 ][ 'createText' ]( text );
\r
346 X_HTMLParser_htmlStringToXNode.flat[ X_HTMLParser_htmlStringToXNode.flat.length ] = X_Doc_createText( text );
\r
349 comment : X_emptyFunction
\r
352 function X_HtmlParser_parse( html, ignoreError ){
\r
353 var worker = X_HTMLParser_htmlStringToXNode, ret;
\r
355 worker.nest.length = 0;
\r
356 worker.ignoreError = ignoreError;
\r
357 X_HTMLParser_exec( html, worker );
\r
359 delete worker.flat;
\r
363 var X_HTMLParser_asyncHtmlStringToXNode = {
\r
364 err : function( html ){
\r
365 X_HTMLParser_htmlStringToXNode.err( html );
\r
366 this[ 'asyncDispatch' ]( X_EVENT_ERROR );
\r
368 start : X_HTMLParser_htmlStringToXNode.start,
\r
369 end : X_HTMLParser_htmlStringToXNode.end,
\r
370 chars : X_HTMLParser_htmlStringToXNode.chars,
\r
371 comment : X_emptyFunction,
\r
373 progress : function( pct ){
\r
374 this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, percent : pct } );
\r
376 complete : function(){
\r
377 var ret = X_HTMLParser_htmlStringToXNode.flat;
\r
378 delete X_HTMLParser_htmlStringToXNode.flat;
\r
379 this[ 'asyncDispatch' ]( { type : X_EVENT_SUCCESS, xnodes : ret } );
\r
383 function X_HTMLParser_asyncParse( html, ignoreError ){
\r
384 var dispatcher = X_Class_override( X_EventDispatcher(), X_HTMLParser_asyncHtmlStringToXNode ),
\r
385 worker = X_HTMLParser_htmlStringToXNode;
\r
386 dispatcher[ 'listenOnce' ]( X_EVENT_SUCCESS, dispatcher, dispatcher[ 'kill' ] );
\r
388 worker.nest.length = 0;
\r
389 worker.ignoreError = ignoreError;
\r
390 X_HTMLParser_exec( html, dispatcher, [ html.length, [] ] );
\r