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