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 "core/dom/DOMTokenList.h" 27 28 #include "bindings/core/v8/ExceptionState.h" 29 #include "core/dom/ExceptionCode.h" 30 #include "core/html/parser/HTMLParserIdioms.h" 31 #include "wtf/text/StringBuilder.h" 32 33 namespace blink { 34 35 bool DOMTokenList::validateToken(const String& token, ExceptionState& exceptionState) 36 { 37 if (token.isEmpty()) { 38 exceptionState.throwDOMException(SyntaxError, "The token provided must not be empty."); 39 return false; 40 } 41 42 if (token.find(isHTMLSpace) != kNotFound) { 43 exceptionState.throwDOMException(InvalidCharacterError, "The token provided ('" + token + "') contains HTML space characters, which are not valid in tokens."); 44 return false; 45 } 46 47 return true; 48 } 49 50 bool DOMTokenList::validateTokens(const Vector<String>& tokens, ExceptionState& exceptionState) 51 { 52 for (size_t i = 0; i < tokens.size(); ++i) { 53 if (!validateToken(tokens[i], exceptionState)) 54 return false; 55 } 56 57 return true; 58 } 59 60 bool DOMTokenList::contains(const AtomicString& token, ExceptionState& exceptionState) const 61 { 62 if (!validateToken(token, exceptionState)) 63 return false; 64 return containsInternal(token); 65 } 66 67 void DOMTokenList::add(const AtomicString& token, ExceptionState& exceptionState) 68 { 69 Vector<String> tokens; 70 tokens.append(token.string()); 71 add(tokens, exceptionState); 72 } 73 74 // Optimally, this should take a Vector<AtomicString> const ref in argument but the 75 // bindings generator does not handle that. 76 void DOMTokenList::add(const Vector<String>& tokens, ExceptionState& exceptionState) 77 { 78 Vector<String> filteredTokens; 79 filteredTokens.reserveCapacity(tokens.size()); 80 for (size_t i = 0; i < tokens.size(); ++i) { 81 if (!validateToken(tokens[i], exceptionState)) 82 return; 83 if (containsInternal(AtomicString(tokens[i]))) 84 continue; 85 if (filteredTokens.contains(tokens[i])) 86 continue; 87 filteredTokens.append(tokens[i]); 88 } 89 90 if (filteredTokens.isEmpty()) 91 return; 92 93 setValue(addTokens(value(), filteredTokens)); 94 } 95 96 void DOMTokenList::remove(const AtomicString& token, ExceptionState& exceptionState) 97 { 98 Vector<String> tokens; 99 tokens.append(token.string()); 100 remove(tokens, exceptionState); 101 } 102 103 // Optimally, this should take a Vector<AtomicString> const ref in argument but the 104 // bindings generator does not handle that. 105 void DOMTokenList::remove(const Vector<String>& tokens, ExceptionState& exceptionState) 106 { 107 if (!validateTokens(tokens, exceptionState)) 108 return; 109 110 // Check using containsInternal first since it is a lot faster than going 111 // through the string character by character. 112 bool found = false; 113 for (size_t i = 0; i < tokens.size(); ++i) { 114 if (containsInternal(AtomicString(tokens[i]))) { 115 found = true; 116 break; 117 } 118 } 119 120 if (found) 121 setValue(removeTokens(value(), tokens)); 122 } 123 124 bool DOMTokenList::toggle(const AtomicString& token, ExceptionState& exceptionState) 125 { 126 if (!validateToken(token, exceptionState)) 127 return false; 128 129 if (containsInternal(token)) { 130 removeInternal(token); 131 return false; 132 } 133 addInternal(token); 134 return true; 135 } 136 137 bool DOMTokenList::toggle(const AtomicString& token, bool force, ExceptionState& exceptionState) 138 { 139 if (!validateToken(token, exceptionState)) 140 return false; 141 142 if (force) 143 addInternal(token); 144 else 145 removeInternal(token); 146 147 return force; 148 } 149 150 void DOMTokenList::addInternal(const AtomicString& token) 151 { 152 if (!containsInternal(token)) 153 setValue(addToken(value(), token)); 154 } 155 156 void DOMTokenList::removeInternal(const AtomicString& token) 157 { 158 // Check using contains first since it uses AtomicString comparisons instead 159 // of character by character testing. 160 if (!containsInternal(token)) 161 return; 162 setValue(removeToken(value(), token)); 163 } 164 165 AtomicString DOMTokenList::addToken(const AtomicString& input, const AtomicString& token) 166 { 167 Vector<String> tokens; 168 tokens.append(token.string()); 169 return addTokens(input, tokens); 170 } 171 172 // This returns an AtomicString because it is always passed as argument to setValue() and setValue() 173 // takes an AtomicString in argument. 174 AtomicString DOMTokenList::addTokens(const AtomicString& input, const Vector<String>& tokens) 175 { 176 bool needsSpace = false; 177 178 StringBuilder builder; 179 if (!input.isEmpty()) { 180 builder.append(input); 181 needsSpace = !isHTMLSpace<UChar>(input[input.length() - 1]); 182 } 183 184 for (size_t i = 0; i < tokens.size(); ++i) { 185 if (needsSpace) 186 builder.append(' '); 187 builder.append(tokens[i]); 188 needsSpace = true; 189 } 190 191 return builder.toAtomicString(); 192 } 193 194 AtomicString DOMTokenList::removeToken(const AtomicString& input, const AtomicString& token) 195 { 196 Vector<String> tokens; 197 tokens.append(token.string()); 198 return removeTokens(input, tokens); 199 } 200 201 // This returns an AtomicString because it is always passed as argument to setValue() and setValue() 202 // takes an AtomicString in argument. 203 AtomicString DOMTokenList::removeTokens(const AtomicString& input, const Vector<String>& tokens) 204 { 205 // Algorithm defined at http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#remove-a-token-from-a-string 206 // New spec is at http://dom.spec.whatwg.org/#remove-a-token-from-a-string 207 208 unsigned inputLength = input.length(); 209 StringBuilder output; // 3 210 output.reserveCapacity(inputLength); 211 unsigned position = 0; // 4 212 213 // Step 5 214 while (position < inputLength) { 215 if (isHTMLSpace<UChar>(input[position])) { // 6 216 output.append(input[position++]); // 6.1, 6.2 217 continue; // 6.3 218 } 219 220 // Step 7 221 StringBuilder tokenBuilder; 222 while (position < inputLength && isNotHTMLSpace<UChar>(input[position])) 223 tokenBuilder.append(input[position++]); 224 225 // Step 8 226 String token = tokenBuilder.toString(); 227 if (tokens.contains(token)) { 228 // Step 8.1 229 while (position < inputLength && isHTMLSpace<UChar>(input[position])) 230 ++position; 231 232 // Step 8.2 233 size_t j = output.length(); 234 while (j > 0 && isHTMLSpace<UChar>(output[j - 1])) 235 --j; 236 output.resize(j); 237 238 // Step 8.3 239 if (position < inputLength && !output.isEmpty()) 240 output.append(' '); 241 } else { 242 output.append(token); // Step 9 243 } 244 } 245 246 return output.toAtomicString(); 247 } 248 249 } // namespace blink 250