dbd4d26393b9a42c747494fa668ce07698a45e65
[WebKit-https.git] / Source / WebCore / html / DOMTokenList.cpp
1 /*
2  * Copyright (C) 2010 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1.  Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  * 2.  Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16  * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24
25 #include "config.h"
26 #include "DOMTokenList.h"
27
28 #include "ExceptionCode.h"
29 #include "HTMLParserIdioms.h"
30 #include <wtf/text/StringBuilder.h>
31
32 namespace WebCore {
33
34 bool DOMTokenList::validateToken(const AtomicString& token, ExceptionCode& ec)
35 {
36     if (token.isEmpty()) {
37         ec = SYNTAX_ERR;
38         return false;
39     }
40
41     unsigned length = token.length();
42     for (unsigned i = 0; i < length; ++i) {
43         if (isHTMLSpace(token[i])) {
44             ec = INVALID_CHARACTER_ERR;
45             return false;
46         }
47     }
48
49     return true;
50 }
51
52 bool DOMTokenList::validateTokens(const Vector<String>& tokens, ExceptionCode& ec)
53 {
54     for (size_t i = 0; i < tokens.size(); ++i) {
55         if (!validateToken(tokens[i], ec))
56             return false;
57     }
58
59     return true;
60 }
61
62 bool DOMTokenList::contains(const AtomicString& token, ExceptionCode& ec) const
63 {
64     if (!validateToken(token, ec))
65         return false;
66     return containsInternal(token);
67 }
68
69 void DOMTokenList::add(const AtomicString& token, ExceptionCode& ec)
70 {
71     Vector<String> tokens;
72     tokens.append(token.string());
73     add(tokens, ec);
74 }
75
76 void DOMTokenList::add(const Vector<String>& tokens, ExceptionCode& ec)
77 {
78     Vector<String> filteredTokens;
79     for (size_t i = 0; i < tokens.size(); ++i) {
80         if (!validateToken(tokens[i], ec))
81             return;
82         if (!containsInternal(tokens[i]) && !filteredTokens.contains(tokens[i]))
83             filteredTokens.append(tokens[i]);
84     }
85
86     if (filteredTokens.isEmpty())
87         return;
88
89     setValue(addTokens(value(), filteredTokens));
90 }
91
92 void DOMTokenList::remove(const AtomicString& token, ExceptionCode& ec)
93 {
94     Vector<String> tokens;
95     tokens.append(token.string());
96     remove(tokens, ec);
97 }
98
99 void DOMTokenList::remove(const Vector<String>& tokens, ExceptionCode& ec)
100 {
101     if (!validateTokens(tokens, ec))
102         return;
103
104     // Check using containsInternal first since it is a lot faster than going
105     // through the string character by character.
106     bool found = false;
107     for (size_t i = 0; i < tokens.size(); ++i) {
108         if (containsInternal(tokens[i])) {
109             found = true;
110             break;
111         }
112     }
113
114     if (found)
115         setValue(removeTokens(value(), tokens));
116 }
117
118 bool DOMTokenList::toggle(const AtomicString& token, ExceptionCode& ec)
119 {
120     if (!validateToken(token, ec))
121         return false;
122
123     if (containsInternal(token)) {
124         removeInternal(token);
125         return false;
126     }
127     addInternal(token);
128     return true;
129 }
130
131 bool DOMTokenList::toggle(const AtomicString& token, bool force, ExceptionCode& ec)
132 {
133     if (!validateToken(token, ec))
134         return false;
135
136     if (force)
137         addInternal(token);
138     else
139         removeInternal(token);
140
141     return force;
142 }
143
144 void DOMTokenList::addInternal(const AtomicString& token)
145 {
146     if (!containsInternal(token))
147         setValue(addToken(value(), token));
148 }
149
150 void DOMTokenList::removeInternal(const AtomicString& token)
151 {
152     // Check using contains first since it uses AtomicString comparisons instead
153     // of character by character testing.
154     if (!containsInternal(token))
155         return;
156     setValue(removeToken(value(), token));
157 }
158
159 String DOMTokenList::addToken(const AtomicString& input, const AtomicString& token)
160 {
161     Vector<String> tokens;
162     tokens.append(token.string());
163     return addTokens(input, tokens);
164 }
165
166 String DOMTokenList::addTokens(const AtomicString& input, const Vector<String>& tokens)
167 {
168     bool needsSpace = false;
169
170     StringBuilder builder;
171     if (!input.isEmpty()) {
172         builder.append(input);
173         needsSpace = !isHTMLSpace(input[input.length() - 1]);
174     }
175
176     for (size_t i = 0; i < tokens.size(); ++i) {
177         if (needsSpace)
178             builder.append(' ');
179         builder.append(tokens[i]);
180         needsSpace = true;
181     }
182
183     return builder.toString();
184 }
185
186 String DOMTokenList::removeToken(const AtomicString& input, const AtomicString& token)
187 {
188     Vector<String> tokens;
189     tokens.append(token.string());
190     return removeTokens(input, tokens);
191 }
192
193 String DOMTokenList::removeTokens(const AtomicString& input, const Vector<String>& tokens)
194 {
195     // Algorithm defined at http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#remove-a-token-from-a-string
196     // New spec is at http://dom.spec.whatwg.org/#remove-a-token-from-a-string
197
198     unsigned inputLength = input.length();
199     StringBuilder output; // 3
200     output.reserveCapacity(inputLength);
201     unsigned position = 0; // 4
202
203     // Step 5
204     while (position < inputLength) {
205         if (isHTMLSpace(input[position])) { // 6
206             output.append(input[position++]); // 6.1, 6.2
207             continue; // 6.3
208         }
209
210         // Step 7
211         StringBuilder s;
212         while (position < inputLength && isNotHTMLSpace(input[position]))
213             s.append(input[position++]);
214
215         // Step 8
216         if (tokens.contains(s.toStringPreserveCapacity())) {
217             // Step 8.1
218             while (position < inputLength && isHTMLSpace(input[position]))
219                 ++position;
220
221             // Step 8.2
222             size_t j = output.length();
223             while (j > 0 && isHTMLSpace(output[j - 1]))
224                 --j;
225             output.resize(j);
226
227             // Step 8.3
228             if (position < inputLength && !output.isEmpty())
229                 output.append(' ');
230         } else
231             output.append(s.toStringPreserveCapacity()); // Step 9
232     }
233
234     return output.toString();
235 }
236
237 } // namespace WebCore