Reviewed by Dave.
[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         info->current = 0;
74         info->position = 0;
75         info->length = 0;
76         info->haslength = false;
77         info->version = docversion;
78     }
79 }
80
81
82 NodeImpl *HTMLCollectionImpl::traverseNextItem(NodeImpl *current) const
83 {
84     current = current->traverseNextNode();
85
86     while (current) {
87         if(current->nodeType() == Node::ELEMENT_NODE) {
88             bool found = false;
89             bool deep = true;
90             HTMLElementImpl *e = static_cast<HTMLElementImpl *>(current);
91             switch(type) {
92             case DOC_IMAGES:
93                 if(e->id() == ID_IMG)
94                     found = true;
95                 break;
96             case DOC_FORMS:
97                 if(e->id() == ID_FORM)
98                     found = true;
99                 break;
100             case TABLE_TBODIES:
101                 if(e->id() == ID_TBODY)
102                     found = true;
103                 else if(e->id() == ID_TABLE)
104                     deep = false;
105                 break;
106             case TR_CELLS:
107                 if(e->id() == ID_TD || e->id() == ID_TH)
108                     found = true;
109                 else if(e->id() == ID_TABLE)
110                     deep = false;
111                 break;
112             case TABLE_ROWS:
113             case TSECTION_ROWS:
114                 if(e->id() == ID_TR)
115                     found = true;
116                 else if(e->id() == ID_TABLE)
117                     deep = false;
118                 break;
119             case SELECT_OPTIONS:
120                 if(e->id() == ID_OPTION)
121                     found = true;
122                 break;
123             case MAP_AREAS:
124                 if(e->id() == ID_AREA)
125                     found = true;
126                 break;
127             case DOC_APPLETS:   // all OBJECT and APPLET elements
128                 if(e->id() == ID_OBJECT || e->id() == ID_APPLET)
129                     found = true;
130                 break;
131             case DOC_EMBEDS:   // all EMBED elements
132                 if(e->id() == ID_EMBED)
133                     found = true;
134                 break;
135             case DOC_LINKS:     // all A _and_ AREA elements with a value for href
136                 if(e->id() == ID_A || e->id() == ID_AREA)
137                     if(!e->getAttribute(ATTR_HREF).isNull())
138                         found = true;
139                 break;
140             case DOC_ANCHORS:      // all A elements with a value for name or an id attribute
141                 if(e->id() == ID_A)
142                     if(!e->getAttribute(ATTR_NAME).isNull())
143                         found = true;
144                 break;
145             case DOC_ALL:
146                 found = true;
147                 break;
148             case NODE_CHILDREN:
149                 found = true;
150                 deep = false;
151                 break;
152             default:
153                 kdDebug( 6030 ) << "Error in HTMLCollection, wrong tagId!" << endl;
154             }
155             if (found) {
156                 return current;
157             }
158             if (deep) {
159                 current = current->traverseNextNode(base);
160                 continue;
161             } 
162         }
163         current = current->traverseNextSibling(base);
164     }
165     return 0;
166 }
167
168
169 unsigned long HTMLCollectionImpl::calcLength() const
170 {
171     unsigned long len = 0;
172
173     for (NodeImpl *current = traverseNextItem(base); current; current = traverseNextItem(current)) {
174         len++;
175     }
176
177     return len;
178 }
179
180 // since the collections are to be "live", we have to do the
181 // calculation every time if anything has changed
182 unsigned long HTMLCollectionImpl::length() const
183 {
184     resetCollectionInfo();
185     if (!info->haslength) {
186         info->length = calcLength();
187         info->haslength = true;
188     }
189     return info->length;
190 }
191
192 NodeImpl *HTMLCollectionImpl::item( unsigned long index ) const
193 {
194      resetCollectionInfo();
195      if (info->current && info->position == index) {
196          return info->current;
197      }
198      if (info->haslength && info->length <= index) {
199          return 0;
200      }
201      if (!info->current || info->position > index) {
202          info->current = traverseNextItem(base);
203          info->position = 0;
204          if (!info->current)
205              return 0;
206      }
207      NodeImpl *node = info->current;
208      for (unsigned pos = info->position; pos < index; pos++) {
209          node = traverseNextItem(node);
210      }     
211      info->current = node;
212      info->position = index;
213      return info->current;
214 }
215
216 NodeImpl *HTMLCollectionImpl::firstItem() const
217 {
218      return item(0);
219 }
220
221 NodeImpl *HTMLCollectionImpl::nextItem() const
222 {
223      resetCollectionInfo();
224  
225      // Look for the 'second' item. The first one is currentItem, already given back.
226      NodeImpl *retval = traverseNextItem(info->current);
227      info->current = retval;
228      info->position++;
229      return retval;
230 }
231
232 bool HTMLCollectionImpl::checkForNameMatch(NodeImpl *node, bool checkName, const DOMString &name, bool caseSensitive) const
233 {
234     ElementImpl *e = static_cast<ElementImpl *>(node);
235     if (caseSensitive) {
236         if (checkName) {
237             return e->getAttribute(ATTR_NAME) == name && e->getAttribute(ATTR_ID) != name;
238         } else {
239             return e->getAttribute(ATTR_ID) == name;
240         }
241     } else {
242         if (checkName) {
243             return e->getAttribute(ATTR_NAME).domString().lower() == name.lower() &&
244                 e->getAttribute(ATTR_ID).domString().lower() != name.lower();
245         } else {
246             return e->getAttribute(ATTR_ID).domString().lower() == name.lower();
247         }
248     }
249 }
250
251
252 NodeImpl *HTMLCollectionImpl::namedItem( const DOMString &name, bool caseSensitive ) const
253 {
254     // http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp
255     // This method first searches for an object with a matching id
256     // attribute. If a match is not found, the method then searches for an
257     // object with a matching name attribute, but only on those elements
258     // that are allowed a name attribute.
259     resetCollectionInfo();
260     idsDone = false;
261
262     NodeImpl *n;
263     for (n = traverseNextItem(base); n; n = traverseNextItem(n)) {
264         if (checkForNameMatch(n, idsDone, name, caseSensitive)) {
265             break;
266         }
267     }
268         
269     info->current = n;
270     if(info->current)
271         return info->current;
272     idsDone = true;
273
274     for (n = traverseNextItem(base); n; n = traverseNextItem(n)) {
275         if (checkForNameMatch(n, idsDone, name, caseSensitive)) {
276             break;
277         }
278     }
279
280     info->current = n;
281     return info->current;
282 }
283
284 QValueList<Node> HTMLCollectionImpl::namedItems(const DOMString &name) const
285 {
286     resetCollectionInfo();
287     
288     QValueList<Node> idResults;
289     QValueList<Node> nameResults;
290
291     for (NodeImpl *n = traverseNextItem(base); n; n = traverseNextItem(n)) {
292         if (checkForNameMatch(n, false, name, true)) {
293             idResults.append(Node(n));
294         } 
295         if (checkForNameMatch(n, true, name, true)) {
296             nameResults.append(Node(n));
297         }
298     }
299
300     if (idResults.isEmpty())
301         return nameResults;
302
303     for (QValueListIterator<Node> iter = nameResults.begin(); iter != nameResults.end(); ++iter) {
304         idResults.append(*iter);
305     }
306
307     return idResults;
308 }
309
310
311 NodeImpl *HTMLCollectionImpl::nextNamedItem( const DOMString &name ) const
312 {
313     resetCollectionInfo();
314
315     for (NodeImpl *n = traverseNextItem(info->current ? info->current : base); n; n = traverseNextItem(n)) {
316         if (checkForNameMatch(n, idsDone, name, true)) {
317             info->current = n;
318             return n;
319         }
320     }
321     
322     if (idsDone) {
323         info->current = 0; 
324         return 0;
325     }
326     idsDone = true;
327
328     for (NodeImpl *n = traverseNextItem(info->current ? info->current : base); n; n = traverseNextItem(n)) {
329         if (checkForNameMatch(n, idsDone, name, true)) {
330             info->current = n;
331             return n;
332         }
333     }
334
335     return 0;
336 }
337
338 // -----------------------------------------------------------------------------
339
340 HTMLFormCollectionImpl::FormCollectionInfo::FormCollectionInfo()
341 {
342     reset();
343 }
344
345 void::HTMLFormCollectionImpl::FormCollectionInfo::reset()
346 {
347     elementsArrayPosition = 0;
348 }
349
350 void HTMLFormCollectionImpl::resetCollectionInfo() const
351 {
352     unsigned int docversion = static_cast<HTMLDocumentImpl*>(base->getDocument())->domTreeVersion();
353     if (info->version != docversion) {
354         formInfo.reset();
355     }
356     HTMLCollectionImpl::resetCollectionInfo();
357 }
358
359 unsigned long HTMLFormCollectionImpl::calcLength() const
360 {
361     QPtrVector<HTMLGenericFormElementImpl> &l = static_cast<HTMLFormElementImpl*>( base )->formElements;
362
363     int len = 0;
364     for ( unsigned i = 0; i < l.count(); i++ )
365         if ( l.at( i )->isEnumeratable() )
366             ++len;
367
368     return len;
369 }
370
371 NodeImpl *HTMLFormCollectionImpl::item(unsigned long index) const
372 {
373     resetCollectionInfo();
374
375     if (info->current && info->position == index) {
376         return info->current;
377     }
378     if (info->haslength && info->length <= index) {
379         return 0;
380     }
381     if (!info->current || info->position > index) {
382         info->current = 0;
383         info->position = 0;
384         formInfo.elementsArrayPosition = 0;
385     }
386
387     QPtrVector<HTMLGenericFormElementImpl> &l = static_cast<HTMLFormElementImpl*>( base )->formElements;
388     unsigned currentIndex = info->position;
389     
390     for (unsigned i = formInfo.elementsArrayPosition; i < l.count(); i++) {
391         if (l[i]->isEnumeratable() ) {
392             if (index == currentIndex) {
393                 info->position = index;
394                 info->current = l[i];
395                 formInfo.elementsArrayPosition = i;
396                 return l[i];
397             }
398
399             currentIndex++;
400         }
401     }
402
403     return 0;
404 }
405
406 NodeImpl* HTMLFormCollectionImpl::getNamedItem(NodeImpl*, int attr_id, const DOMString& name, bool caseSensitive) const
407 {
408     info->position = 0;
409     return getNamedFormItem( attr_id, name, 0, caseSensitive );
410 }
411
412 NodeImpl* HTMLFormCollectionImpl::getNamedFormItem(int attr_id, const DOMString& name, int duplicateNumber, bool caseSensitive) const
413 {
414     if(base->nodeType() == Node::ELEMENT_NODE)
415     {
416         HTMLElementImpl* baseElement = static_cast<HTMLElementImpl*>(base);
417         bool foundInputElements = false;
418         if(baseElement->id() == ID_FORM)
419         {
420             HTMLFormElementImpl* f = static_cast<HTMLFormElementImpl*>(baseElement);
421             for (unsigned i = 0; i < f->formElements.count(); ++i) {
422                 HTMLGenericFormElementImpl* e = f->formElements[i];
423                 if (e->isEnumeratable()) {
424                     bool found;
425                     if (caseSensitive)
426                         found = e->getAttribute(attr_id) == name;
427                     else
428                         found = e->getAttribute(attr_id).domString().lower() == name.lower();
429                     if (found) {
430                         foundInputElements = true;
431                         if (!duplicateNumber)
432                             return e;
433                         --duplicateNumber;
434                     }
435                 }
436             }
437         }
438
439         if ( !foundInputElements )
440         {
441             HTMLFormElementImpl* f = static_cast<HTMLFormElementImpl*>(baseElement);
442
443             for(unsigned i = 0; i < f->imgElements.count(); ++i)
444             {
445                 HTMLImageElementImpl* e = f->imgElements[i];
446                 bool found;
447                 if (caseSensitive)
448                     found = e->getAttribute(attr_id) == name;
449                 else
450                     found = e->getAttribute(attr_id).domString().lower() == name.lower();
451                 if (found) {
452                     if (!duplicateNumber)
453                         return e;
454                     --duplicateNumber;
455                 }
456             }
457         }
458     }
459     return 0;
460 }
461
462 NodeImpl * HTMLFormCollectionImpl::firstItem() const
463 {
464     return item(0);
465 }
466
467 NodeImpl * HTMLFormCollectionImpl::nextItem() const
468 {
469     return item(info->position + 1);
470 }
471
472 NodeImpl * HTMLFormCollectionImpl::nextNamedItemInternal( const DOMString &name ) const
473 {
474     NodeImpl *retval = getNamedFormItem( idsDone ? ATTR_NAME : ATTR_ID, name, ++info->position, true );
475     if ( retval )
476         return retval;
477     if ( idsDone ) // we're done
478         return 0;
479     // After doing all ATTR_ID, do ATTR_NAME
480     idsDone = true;
481     return getNamedItem(base->firstChild(), ATTR_NAME, name, true);
482 }
483
484 NodeImpl *HTMLFormCollectionImpl::namedItem( const DOMString &name, bool caseSensitive ) const
485 {
486     // http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp
487     // This method first searches for an object with a matching id
488     // attribute. If a match is not found, the method then searches for an
489     // object with a matching name attribute, but only on those elements
490     // that are allowed a name attribute.
491     resetCollectionInfo();
492     idsDone = false;
493     info->current = getNamedItem(base->firstChild(), ATTR_ID, name, true);
494     if(info->current)
495         return info->current;
496     idsDone = true;
497     info->current = getNamedItem(base->firstChild(), ATTR_NAME, name, true);
498     return info->current;
499 }
500
501
502 NodeImpl *HTMLFormCollectionImpl::nextNamedItem( const DOMString &name ) const
503 {
504     // nextNamedItemInternal can return an item that has both id=<name> and name=<name>
505     // Here, we have to filter out such cases.
506     NodeImpl *impl = nextNamedItemInternal( name );
507     if (!idsDone) // looking for id=<name> -> no filtering
508         return impl;
509     // looking for name=<name> -> filter out if id=<name>
510     bool ok = false;
511     while (impl && !ok)
512     {
513         if(impl->nodeType() == Node::ELEMENT_NODE)
514         {
515             HTMLElementImpl *e = static_cast<HTMLElementImpl *>(impl);
516             ok = (e->getAttribute(ATTR_ID) != name);
517             if (!ok)
518                 impl = nextNamedItemInternal( name );
519         } else // can't happen
520             ok = true;
521     }
522     return impl;
523 }
524
525 QValueList<Node> HTMLFormCollectionImpl::namedItems(const DOMString &name) const
526 {
527     if (name.length()) {
528         return QValueList<Node>();
529     }
530
531     resetCollectionInfo();
532
533     QValueList<Node> idResults;
534     QValueList<Node> nameResults;
535
536     if (base->nodeType() != Node::ELEMENT_NODE ||static_cast<HTMLElementImpl*>(base)->id() != ID_FORM)
537         return idResults;
538
539     HTMLElementImpl* baseElement = static_cast<HTMLElementImpl*>(base);
540
541     bool foundInputElements = false;
542     HTMLFormElementImpl* f = static_cast<HTMLFormElementImpl*>(baseElement);
543     for (unsigned i = 0; i < f->formElements.count(); ++i) {
544         HTMLGenericFormElementImpl* e = f->formElements[i];
545         if (e->isEnumeratable()) {
546             if (checkForNameMatch(e, false, name, true)) {
547                 idResults.append(Node(e));
548                 foundInputElements = true;
549             } 
550             if (checkForNameMatch(e, true, name, true)) {
551                 nameResults.append(Node(e));
552                 foundInputElements = true;
553             }
554         }
555     }
556
557     if (!foundInputElements) {
558         HTMLFormElementImpl* f = static_cast<HTMLFormElementImpl*>(baseElement);
559
560         for (unsigned i = 0; i < f->imgElements.count(); ++i) {
561             HTMLImageElementImpl* e = f->imgElements[i];
562
563             if (checkForNameMatch(e, false, name, true)) {
564                 idResults.append(Node(e));
565             } 
566             if (checkForNameMatch(e, true, name, true)) {
567                 nameResults.append(Node(e));
568             }
569         }
570     }
571
572     if (idResults.isEmpty())
573         return nameResults;
574
575     for (QValueListIterator<Node> iter = nameResults.begin(); iter != nameResults.end(); ++iter) {
576         idResults.append(*iter);
577     }
578
579     return idResults;
580 }
581