1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2010 Apple Inc. All rights reserved.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB.  If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 *
21 */
22
23#include "config.h"
24#include "HTMLFormCollection.h"
25
26#include "CollectionCache.h"
27#include "HTMLFormControlElement.h"
28#include "HTMLFormElement.h"
29#include "HTMLImageElement.h"
30#include "HTMLNames.h"
31
32namespace WebCore {
33
34using namespace HTMLNames;
35
36// Since the collections are to be "live", we have to do the
37// calculation every time if anything has changed.
38
39inline CollectionCache* HTMLFormCollection::formCollectionInfo(HTMLFormElement* form)
40{
41    if (!form->m_collectionCache)
42        form->m_collectionCache = adoptPtr(new CollectionCache);
43    return form->m_collectionCache.get();
44}
45
46HTMLFormCollection::HTMLFormCollection(PassRefPtr<HTMLFormElement> form)
47    : HTMLCollection(form.get(), OtherCollection, formCollectionInfo(form.get()))
48{
49}
50
51PassRefPtr<HTMLFormCollection> HTMLFormCollection::create(PassRefPtr<HTMLFormElement> form)
52{
53    return adoptRef(new HTMLFormCollection(form));
54}
55
56HTMLFormCollection::~HTMLFormCollection()
57{
58}
59
60unsigned HTMLFormCollection::calcLength() const
61{
62    return static_cast<HTMLFormElement*>(base())->length();
63}
64
65Node* HTMLFormCollection::item(unsigned index) const
66{
67    resetCollectionInfo();
68
69    if (info()->current && info()->position == index)
70        return info()->current;
71
72    if (info()->hasLength && info()->length <= index)
73        return 0;
74
75    if (!info()->current || info()->position > index) {
76        info()->current = 0;
77        info()->position = 0;
78        info()->elementsArrayPosition = 0;
79    }
80
81    Vector<FormAssociatedElement*>& elementsArray = static_cast<HTMLFormElement*>(base())->m_associatedElements;
82    unsigned currentIndex = info()->position;
83
84    for (unsigned i = info()->elementsArrayPosition; i < elementsArray.size(); i++) {
85        if (elementsArray[i]->isEnumeratable()) {
86            HTMLElement* element = toHTMLElement(elementsArray[i]);
87            if (index == currentIndex) {
88                info()->position = index;
89                info()->current = element;
90                info()->elementsArrayPosition = i;
91                return element;
92            }
93
94            currentIndex++;
95        }
96    }
97
98    return 0;
99}
100
101Element* HTMLFormCollection::getNamedItem(const QualifiedName& attrName, const AtomicString& name) const
102{
103    info()->position = 0;
104    return getNamedFormItem(attrName, name, 0);
105}
106
107Element* HTMLFormCollection::getNamedFormItem(const QualifiedName& attrName, const String& name, int duplicateNumber) const
108{
109    HTMLFormElement* form = static_cast<HTMLFormElement*>(base());
110
111    bool foundInputElements = false;
112    for (unsigned i = 0; i < form->m_associatedElements.size(); ++i) {
113        FormAssociatedElement* associatedElement = form->m_associatedElements[i];
114        HTMLElement* element = toHTMLElement(associatedElement);
115        if (associatedElement->isEnumeratable() && element->getAttribute(attrName) == name) {
116            foundInputElements = true;
117            if (!duplicateNumber)
118                return element;
119            --duplicateNumber;
120        }
121    }
122
123    if (!foundInputElements) {
124        for (unsigned i = 0; i < form->m_imageElements.size(); ++i) {
125            HTMLImageElement* element = form->m_imageElements[i];
126            if (element->getAttribute(attrName) == name) {
127                if (!duplicateNumber)
128                    return element;
129                --duplicateNumber;
130            }
131        }
132    }
133
134    return 0;
135}
136
137Node* HTMLFormCollection::nextItem() const
138{
139    return item(info()->position + 1);
140}
141
142Element* HTMLFormCollection::nextNamedItemInternal(const String &name) const
143{
144    Element* retval = getNamedFormItem(m_idsDone ? nameAttr : idAttr, name, ++info()->position);
145    if (retval)
146        return retval;
147    if (m_idsDone) // we're done
148        return 0;
149    // After doing id, do name
150    m_idsDone = true;
151    return getNamedItem(nameAttr, name);
152}
153
154Node* HTMLFormCollection::namedItem(const AtomicString& name) const
155{
156    // http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp
157    // This method first searches for an object with a matching id
158    // attribute. If a match is not found, the method then searches for an
159    // object with a matching name attribute, but only on those elements
160    // that are allowed a name attribute.
161    resetCollectionInfo();
162    m_idsDone = false;
163    info()->current = getNamedItem(idAttr, name);
164    if (info()->current)
165        return info()->current;
166    m_idsDone = true;
167    info()->current = getNamedItem(nameAttr, name);
168    return info()->current;
169}
170
171Node* HTMLFormCollection::nextNamedItem(const AtomicString& name) const
172{
173    // The nextNamedItemInternal function can return the same item twice if it has
174    // both an id and name that are equal to the name parameter. So this function
175    // checks if we are on the nameAttr half of the iteration and skips over any
176    // that also have the same idAttributeName.
177    Element* impl = nextNamedItemInternal(name);
178    if (m_idsDone) {
179        while (impl && impl->getIdAttribute() == name)
180            impl = nextNamedItemInternal(name);
181    }
182    return impl;
183}
184
185void HTMLFormCollection::updateNameCache() const
186{
187    if (info()->hasNameCache)
188        return;
189
190    HashSet<AtomicStringImpl*> foundInputElements;
191
192    HTMLFormElement* f = static_cast<HTMLFormElement*>(base());
193
194    for (unsigned i = 0; i < f->m_associatedElements.size(); ++i) {
195        FormAssociatedElement* associatedElement = f->m_associatedElements[i];
196        if (associatedElement->isEnumeratable()) {
197            HTMLElement* element = toHTMLElement(associatedElement);
198            const AtomicString& idAttrVal = element->getIdAttribute();
199            const AtomicString& nameAttrVal = element->getAttribute(nameAttr);
200            if (!idAttrVal.isEmpty()) {
201                // add to id cache
202                Vector<Element*>* idVector = info()->idCache.get(idAttrVal.impl());
203                if (!idVector) {
204                    idVector = new Vector<Element*>;
205                    info()->idCache.add(idAttrVal.impl(), idVector);
206                }
207                idVector->append(element);
208                foundInputElements.add(idAttrVal.impl());
209            }
210            if (!nameAttrVal.isEmpty() && idAttrVal != nameAttrVal) {
211                // add to name cache
212                Vector<Element*>* nameVector = info()->nameCache.get(nameAttrVal.impl());
213                if (!nameVector) {
214                    nameVector = new Vector<Element*>;
215                    info()->nameCache.add(nameAttrVal.impl(), nameVector);
216                }
217                nameVector->append(element);
218                foundInputElements.add(nameAttrVal.impl());
219            }
220        }
221    }
222
223    for (unsigned i = 0; i < f->m_imageElements.size(); ++i) {
224        HTMLImageElement* element = f->m_imageElements[i];
225        const AtomicString& idAttrVal = element->getIdAttribute();
226        const AtomicString& nameAttrVal = element->getAttribute(nameAttr);
227        if (!idAttrVal.isEmpty() && !foundInputElements.contains(idAttrVal.impl())) {
228            // add to id cache
229            Vector<Element*>* idVector = info()->idCache.get(idAttrVal.impl());
230            if (!idVector) {
231                idVector = new Vector<Element*>;
232                info()->idCache.add(idAttrVal.impl(), idVector);
233            }
234            idVector->append(element);
235        }
236        if (!nameAttrVal.isEmpty() && idAttrVal != nameAttrVal && !foundInputElements.contains(nameAttrVal.impl())) {
237            // add to name cache
238            Vector<Element*>* nameVector = info()->nameCache.get(nameAttrVal.impl());
239            if (!nameVector) {
240                nameVector = new Vector<Element*>;
241                info()->nameCache.add(nameAttrVal.impl(), nameVector);
242            }
243            nameVector->append(element);
244        }
245    }
246
247    info()->hasNameCache = true;
248}
249
250}
251