Home | History | Annotate | Download | only in dom
      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