Rewrite the img/form document property lookup strategy. This
[WebKit-https.git] / WebCore / khtml / html / html_miscimpl.cpp
1 /**
2  * This file is part of the DOM implementation for KDE.
3  *
4  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
5  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  *
22  */
23 // -------------------------------------------------------------------------
24 #include "html/html_miscimpl.h"
25 #include "html/html_formimpl.h"
26
27 #include "misc/htmlhashes.h"
28 #include "dom/dom_node.h"
29
30 using namespace DOM;
31
32 #include <kdebug.h>
33
34 HTMLBaseFontElementImpl::HTMLBaseFontElementImpl(DocumentPtr *doc)
35     : HTMLElementImpl(doc)
36 {
37 }
38
39 HTMLBaseFontElementImpl::~HTMLBaseFontElementImpl()
40 {
41 }
42
43 NodeImpl::Id HTMLBaseFontElementImpl::id() const
44 {
45     return ID_BASEFONT;
46 }
47
48 // -------------------------------------------------------------------------
49
50 HTMLCollectionImpl::HTMLCollectionImpl(NodeImpl *_base, int _type)
51 {
52     base = _base;
53     base->ref();
54     type = _type;
55     currentItem = 0L;
56     idsDone = false;
57 }
58
59 HTMLCollectionImpl::~HTMLCollectionImpl()
60 {
61     base->deref();
62 }
63
64 unsigned long HTMLCollectionImpl::calcLength(NodeImpl *current) const
65 {
66     unsigned long len = 0;
67     while(current)
68     {
69         if(current->nodeType() == Node::ELEMENT_NODE)
70         {
71             bool deep = true;
72             HTMLElementImpl *e = static_cast<HTMLElementImpl *>(current);
73             switch(type)
74             {
75             case DOC_IMAGES:
76                 if(e->id() == ID_IMG)
77                     len++;
78                 break;
79             case DOC_FORMS:
80                 if(e->id() == ID_FORM)
81                     len++;
82                 break;
83             case TABLE_TBODIES:
84                 if(e->id() == ID_TBODY)
85                     len++;
86                 else if(e->id() == ID_TABLE)
87                     deep = false;
88                 break;
89             case TR_CELLS:
90                 if(e->id() == ID_TD)
91                     len++;
92                 else if(e->id() == ID_TABLE)
93                     deep = false;
94                 break;
95             case TABLE_ROWS:
96             case TSECTION_ROWS:
97                 if(e->id() == ID_TR || e->id() == ID_TH)
98                     len++;
99                 else if(e->id() == ID_TABLE)
100                     deep = false;
101                 break;
102             case SELECT_OPTIONS:
103                 if(e->id() == ID_OPTION)
104                     len++;
105                 break;
106             case MAP_AREAS:
107                 if(e->id() == ID_AREA)
108                     len++;
109                 break;
110             case DOC_APPLETS:   // all OBJECT and APPLET elements
111                 if(e->id() == ID_OBJECT || e->id() == ID_APPLET)
112                     len++;
113                 break;
114             case DOC_LINKS:     // all A _and_ AREA elements with a value for href
115                 if(e->id() == ID_A || e->id() == ID_AREA)
116                     if(!e->getAttribute(ATTR_HREF).isNull())
117                         len++;
118                 break;
119             case DOC_ANCHORS:      // all A elements with a value for name and all elements with an id attribute
120                 if ( e->hasID() )
121                     len++;
122                 else if(e->id() == ID_A) {
123                     if(!e->getAttribute(ATTR_NAME).isNull())
124                         len++;
125                 }
126                 break;
127             case DOC_ALL:      // "all" elements
128                 len++;
129                 break;
130             case NODE_CHILDREN: // first-level children
131                 len++;
132                 deep = false;
133                 break;
134             default:
135                 kdDebug( 6030 ) << "Error in HTMLCollection, wrong tagId!" << endl;
136             }
137             if(deep && current->firstChild())
138                 len += calcLength(current->firstChild());
139         }
140         current = current->nextSibling();
141     }
142     return len;
143 }
144
145 // since the collections are to be "live", we have to do the
146 // calculation every time...
147 unsigned long HTMLCollectionImpl::length() const
148 {
149     return calcLength(base->firstChild());
150 }
151
152 NodeImpl *HTMLCollectionImpl::getItem(NodeImpl *current, int index, int &len) const
153 {
154     while(current)
155     {
156         if(current->nodeType() == Node::ELEMENT_NODE)
157         {
158             bool deep = true;
159             HTMLElementImpl *e = static_cast<HTMLElementImpl *>(current);
160             switch(type)
161             {
162             case DOC_IMAGES:
163                 if(e->id() == ID_IMG)
164                     len++;
165                 break;
166             case DOC_FORMS:
167                 if(e->id() == ID_FORM)
168                     len++;
169                 break;
170             case TABLE_TBODIES:
171                 if(e->id() == ID_TBODY)
172                     len++;
173                 else if(e->id() == ID_TABLE)
174                     deep = false;
175                 break;
176             case TR_CELLS:
177                 if(e->id() == ID_TD)
178                     len++;
179                 else if(e->id() == ID_TABLE)
180                     deep = false;
181                 break;
182             case TABLE_ROWS:
183             case TSECTION_ROWS:
184                 if(e->id() == ID_TR || e->id() == ID_TH)
185                     len++;
186                 else if(e->id() == ID_TABLE)
187                     deep = false;
188                 break;
189             case SELECT_OPTIONS:
190                 if(e->id() == ID_OPTION)
191                     len++;
192                 break;
193             case MAP_AREAS:
194                 if(e->id() == ID_AREA)
195                     len++;
196                 break;
197             case DOC_APPLETS:   // all OBJECT and APPLET elements
198                 if(e->id() == ID_OBJECT || e->id() == ID_APPLET)
199                     len++;
200                 break;
201             case DOC_LINKS:     // all A _and_ AREA elements with a value for href
202                 if(e->id() == ID_A || e->id() == ID_AREA)
203                     if(!e->getAttribute(ATTR_HREF).isNull())
204                         len++;
205                 break;
206             case DOC_ANCHORS:      // all A elements with a value for name or an id attribute
207                 if( e->hasID() )
208                     len++;
209                 else if(e->id() == ID_A)
210                     if(!e->getAttribute(ATTR_NAME).isNull())
211                         len++;
212                 break;
213             case DOC_ALL:
214                 len++;
215                 break;
216             case NODE_CHILDREN:
217                 len++;
218                 deep = false;
219                 break;
220             default:
221                 kdDebug( 6030 ) << "Error in HTMLCollection, wrong tagId!" << endl;
222             }
223             if(len == (index + 1)) return current;
224             NodeImpl *retval=0;
225             if(deep && current->firstChild())
226                 retval = getItem(current->firstChild(), index, len);
227             if(retval) return retval;
228         }
229         current = current->nextSibling();
230     }
231     return 0;
232 }
233
234 NodeImpl *HTMLCollectionImpl::item( unsigned long index ) const
235 {
236     int pos = 0;
237     return getItem(base->firstChild(), index, pos);
238 }
239
240 NodeImpl *HTMLCollectionImpl::firstItem() const
241 {
242     int pos = 0;
243     currentItem = getItem(base->firstChild(), 0, pos);
244     return currentItem;
245 }
246
247 NodeImpl *HTMLCollectionImpl::nextItem() const
248 {
249     int pos = 0;
250     // Look for the 'second' item. The first one is currentItem, already given back.
251     NodeImpl *retval = getItem(currentItem, 1, pos);
252     if (retval)
253     {
254         currentItem = retval;
255         return retval;
256     }
257     // retval was 0, means we have to go up
258     while( !retval && currentItem && currentItem->parentNode()
259            && currentItem->parentNode() != base )
260     {
261         currentItem = currentItem->parentNode();
262         if (currentItem->nextSibling())
263         {
264             // ... and to take the first one from there
265             pos = 0;
266             retval = getItem(currentItem->nextSibling(), 0, pos);
267         }
268     }
269     currentItem = retval;
270     return currentItem;
271 }
272
273 NodeImpl *HTMLCollectionImpl::getNamedItem( NodeImpl *current, int attr_id,
274                                             const DOMString &name, bool caseSensitive ) const
275 {
276     if(name.isEmpty())
277         return 0;
278
279     while(current)
280     {
281         if(current->nodeType() == Node::ELEMENT_NODE)
282         {
283             bool deep = true;
284             bool check = false;
285             HTMLElementImpl *e = static_cast<HTMLElementImpl *>(current);
286             switch(type)
287             {
288             case DOC_IMAGES:
289                 if(e->id() == ID_IMG)
290                     check = true;
291                 break;
292             case DOC_FORMS:
293                 if(e->id() == ID_FORM)
294                     check = true;
295                 break;
296             case TABLE_TBODIES:
297                 if(e->id() == ID_TBODY)
298                     check = true;
299                 else if(e->id() == ID_TABLE)
300                     deep = false;
301                 break;
302             case TR_CELLS:
303                 if(e->id() == ID_TD)
304                     check = true;
305                 else if(e->id() == ID_TABLE)
306                     deep = false;
307                 break;
308             case TABLE_ROWS:
309             case TSECTION_ROWS:
310                 if(e->id() == ID_TR || e->id() == ID_TH)
311                     check = true;
312                 else if(e->id() == ID_TABLE)
313                     deep = false;
314                 break;
315             case SELECT_OPTIONS:
316                 if(e->id() == ID_OPTION)
317                     check = true;
318                 break;
319             case MAP_AREAS:
320                 if(e->id() == ID_AREA)
321                     check = true;
322                 break;
323             case DOC_APPLETS:   // all OBJECT and APPLET elements
324                 if(e->id() == ID_OBJECT || e->id() == ID_APPLET)
325                     check = true;
326                 break;
327             case DOC_LINKS:     // all A _and_ AREA elements with a value for href
328                 if(e->id() == ID_A || e->id() == ID_AREA)
329                     if(!e->getAttribute(ATTR_HREF).isNull())
330                         check = true;
331                 break;
332             case DOC_ANCHORS:      // all A elements with a value for name
333                 if( e->hasID() )
334                     check = true;
335                 else if(e->id() == ID_A)
336                     if(!e->getAttribute(ATTR_NAME).isNull())
337                         check = true;
338                 break;
339             case DOC_ALL:
340                 check = true;
341                 break;
342             case NODE_CHILDREN:
343                 check = true;
344                 deep = false;
345                 break;
346             default:
347                 kdDebug( 6030 ) << "Error in HTMLCollection, wrong tagId!" << endl;
348                 break;
349             }
350             if (check) {
351                 bool found;
352                 if (caseSensitive)
353                     found = e->getAttribute(attr_id) == name;
354                 else
355                     found = e->getAttribute(attr_id).lower() == name.lower();
356                 if (found) {
357                     //kdDebug( 6030 ) << "found node: " << e << " " << current << " " << e->id() << " " << e->tagName().string() << endl;
358                     return current;
359                 }
360             }
361             NodeImpl *retval = 0;
362             if(deep && current->firstChild())
363                 retval = getNamedItem(current->firstChild(), attr_id, name, caseSensitive);
364             if(retval)
365             {
366                 //kdDebug( 6030 ) << "got a return value " << retval << endl;
367                 return retval;
368             }
369         }
370         current = current->nextSibling();
371     }
372     return 0;
373 }
374
375 NodeImpl *HTMLCollectionImpl::namedItem( const DOMString &name, bool caseSensitive ) const
376 {
377     // http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp
378     // This method first searches for an object with a matching id
379     // attribute. If a match is not found, the method then searches for an
380     // object with a matching name attribute, but only on those elements
381     // that are allowed a name attribute.
382     idsDone = false;
383     currentItem = getNamedItem(base->firstChild(), ATTR_ID, name, caseSensitive);
384     if(currentItem)
385         return currentItem;
386     idsDone = true;
387     currentItem = getNamedItem(base->firstChild(), ATTR_NAME, name, caseSensitive);
388     return currentItem;
389 }
390
391 NodeImpl *HTMLCollectionImpl::nextNamedItem( const DOMString &name ) const
392 {
393     // nextNamedItemInternal can return an item that has both id=<name> and name=<name>
394     // Here, we have to filter out such cases.
395     NodeImpl *impl = nextNamedItemInternal( name );
396     if (!idsDone) // looking for id=<name> -> no filtering
397         return impl;
398     // looking for name=<name> -> filter out if id=<name>
399     bool ok = false;
400     while (impl && !ok)
401     {
402         if(impl->nodeType() == Node::ELEMENT_NODE)
403         {
404             HTMLElementImpl *e = static_cast<HTMLElementImpl *>(impl);
405             ok = (e->getAttribute(ATTR_ID) != name);
406             if (!ok)
407                 impl = nextNamedItemInternal( name );
408         } else // can't happen
409             ok = true;
410     }
411     return impl;
412 }
413
414 NodeImpl *HTMLCollectionImpl::nextNamedItemInternal( const DOMString &name ) const
415 {
416     //kdDebug() << "\nHTMLCollectionImpl::nextNamedItem starting at " << currentItem << endl;
417     // Go to next item first (to avoid returning the same)
418     currentItem = nextItem();
419     //kdDebug() << "*HTMLCollectionImpl::nextNamedItem next item is " << currentItem << endl;
420
421     if ( currentItem )
422     {
423         // Then look for next matching named item
424         NodeImpl *retval = getNamedItem(currentItem, idsDone ? ATTR_NAME : ATTR_ID, name);
425         if ( retval )
426         {
427             //kdDebug() << "*HTMLCollectionImpl::nextNamedItem found " << retval << endl;
428             currentItem = retval;
429             return retval;
430         }
431
432         // retval was 0, means we have to go up
433         while( !retval && currentItem->parentNode()
434                && currentItem->parentNode() != base )
435         {
436             currentItem = currentItem->parentNode();
437             if (currentItem->nextSibling())
438             {
439                 // ... and to take the first one from there
440                 retval = getNamedItem(currentItem->nextSibling(), idsDone ? ATTR_NAME : ATTR_ID, name);
441             }
442         }
443         if ( retval )
444         {
445             //kdDebug() << "*HTMLCollectionImpl::nextNamedItem found after going up " << retval << endl;
446             currentItem = retval;
447             return currentItem;
448         }
449     }
450
451     if ( idsDone )
452         return 0;
453     // After doing all ATTR_ID, do ATTR_NAME
454     //kdDebug() << "*HTMLCollectionImpl::nextNamedItem going to ATTR_NAME now" << endl;
455     idsDone = true;
456     currentItem = getNamedItem(base->firstChild(), ATTR_NAME, name);
457     return currentItem;
458
459 }
460
461 // -----------------------------------------------------------------------------
462
463 unsigned long HTMLFormCollectionImpl::calcLength(NodeImpl*) const
464 {
465     QPtrList<HTMLGenericFormElementImpl> l = static_cast<HTMLFormElementImpl*>( base )->formElements;
466
467     int len = 0;
468     for ( unsigned i = 0; i < l.count(); i++ )
469         if ( l.at( i )->isEnumeratable() )
470             ++len;
471
472     return len;
473 }
474
475 NodeImpl* HTMLFormCollectionImpl::getItem(NodeImpl *, int index, int&) const
476 {
477     QPtrList<HTMLGenericFormElementImpl> l = static_cast<HTMLFormElementImpl*>( base )->formElements;
478
479     for ( unsigned i = 0; i < l.count(); i++ ) {
480
481         if( l.at( i )->isEnumeratable() ) {
482             if ( !index )
483                 return l.at( i );
484
485             --index;
486         }
487     }
488
489     return 0;
490 }
491
492 NodeImpl* HTMLFormCollectionImpl::getNamedItem(NodeImpl*, int attr_id, const DOMString& name, bool caseSensitive) const
493 {
494     currentPos = 0;
495     return getNamedFormItem( attr_id, name, 0, caseSensitive );
496 }
497
498 NodeImpl* HTMLFormCollectionImpl::getNamedFormItem(int attr_id, const DOMString& name, int duplicateNumber, bool caseSensitive) const
499 {
500     if(base->nodeType() == Node::ELEMENT_NODE)
501     {
502         HTMLElementImpl* e = static_cast<HTMLElementImpl*>(base);
503         if(e->id() == ID_FORM)
504         {
505             HTMLFormElementImpl* f = static_cast<HTMLFormElementImpl*>(e);
506
507             for(HTMLGenericFormElementImpl* e = f->formElements.first(); e; e = f->formElements.next())
508                 if(e->isEnumeratable()) {
509                     bool found;
510                     if (caseSensitive)
511                         found = e->getAttribute(attr_id) == name;
512                     else
513                         found = e->getAttribute(attr_id).lower() == name.lower();
514                     if (found) {
515                         if (!duplicateNumber)
516                             return e;
517                         --duplicateNumber;
518                     }
519                 }
520         }
521         NodeImpl* retval = getNamedImgItem( base->firstChild(), attr_id, name, duplicateNumber, caseSensitive );
522         if ( retval )
523             return retval;
524     }
525     return 0;
526 }
527
528 NodeImpl* HTMLFormCollectionImpl::getNamedImgItem(NodeImpl* current, int attr_id, const DOMString& name, int& duplicateNumber, bool caseSensitive) const
529 {
530     // strange case. IE and NS allow to get hold of <img> tags,
531     // but they don't include them in the elements() collection.
532     while ( current )
533     {
534         if(current->nodeType() == Node::ELEMENT_NODE)
535         {
536             HTMLElementImpl *e = static_cast<HTMLElementImpl *>(current);
537             if(e->id() == ID_IMG) {
538                 bool found;
539                 if (caseSensitive)
540                     found = e->getAttribute(attr_id) == name;
541                 else
542                     found = e->getAttribute(attr_id).lower() == name.lower();
543                 if (found)
544                 {
545                     if (!duplicateNumber)
546                         return current;
547                     --duplicateNumber;
548                 }
549             }
550             if(current->firstChild())
551             {
552                 // The recursion here is the reason why this is a separate method
553                 NodeImpl *retval = getNamedImgItem(current->firstChild(), attr_id, name, duplicateNumber, caseSensitive);
554                 if(retval)
555                 {
556                     //kdDebug( 6030 ) << "got a return value " << retval << endl;
557                     return retval;
558                 }
559             }
560         }
561         current = current->nextSibling();
562     } // while
563     return 0;
564 }
565
566 NodeImpl * HTMLFormCollectionImpl::firstItem() const
567 {
568     currentPos = 0;
569     int dummy = 0;
570     return getItem(0 /*base->firstChild() unused*/, currentPos, dummy);
571 }
572
573 NodeImpl * HTMLFormCollectionImpl::nextItem() const
574 {
575     // This implementation loses the whole benefit of firstItem/nextItem :(
576     int dummy = 0;
577     return getItem(0 /*base->firstChild() unused*/, ++currentPos, dummy);
578 }
579
580 NodeImpl * HTMLFormCollectionImpl::nextNamedItemInternal( const DOMString &name ) const
581 {
582     NodeImpl *retval = getNamedFormItem( idsDone ? ATTR_NAME : ATTR_ID, name, ++currentPos, true );
583     if ( retval )
584         return retval;
585     if ( idsDone ) // we're done
586         return 0;
587     // After doing all ATTR_ID, do ATTR_NAME
588     idsDone = true;
589     return getNamedItem(base->firstChild(), ATTR_NAME, name, true);
590 }