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