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