text: // Copyright 2018 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <memory>
  
  #include "third_party/blink/renderer/core/dom/names_map.h"
  #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
  #include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
  #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
  
  namespace blink {
  
  NamesMap::NamesMap(const AtomicString& string) {
    Set(string);
  }
  
  void NamesMap::Set(const AtomicString& source) {
    if (source.IsNull()) {
      Clear();
      return;
    }
    if (source.Is8Bit()) {
      Set(source, source.Characters8());
      return;
    }
  
    Set(source, source.Characters16());
  }
  
  void NamesMap::Add(const AtomicString& key, const AtomicString& value) {
    // AddResult
    auto add_result = data_.insert(key, base::Optional<SpaceSplitString>());
    if (add_result.is_new_entry) {
      add_result.stored_value->value =
          base::make_optional<SpaceSplitString>(SpaceSplitString());
    }
    add_result.stored_value->value.value().Add(value);
  }
  
  // Parser for HTML exportparts attribute. See
  // http://drafts.csswg.org/css-shadow-parts/.
  //
  // Summary is that we are parsing a comma-separated list of part-mappings. A
  // part mapping is a part name or 2 colon-separated part names. If any
  // part-mapping is invalid, we ignore it and continue parsing after the next
  // comma. Part names are delimited by space, comma or colon. Apart from that,
  // whitespace is not significant.
  
  // The states that can occur while parsing the part map and their transitions.
  // A "+" indicates that this transition should consume the current character.  A
  // "*" indicates that this is invalid input. In general invalid input causes us
  // to reject the current mapping and returns us to searching for a comma.
  enum State {
    kPreKey,     // Searching for the start of a key:
                 //   space, comma -> kPreKey+
                 //   colon* -> kError+
                 //   else -> kKey
    kKey,        // Searching for the end of a key:
                 //   comma -> kPreKey+
                 //   colon -> kPreValue+
                 //   space -> kPostKey+
                 //   else -> kKey+
    kPostKey,    // Searching for a delimiter:
                 //   comma -> kPreKey+
                 //   colon -> kPreValue+
                 //   space -> kPostKey+
                 //   else* -> kError+
    kPreValue,   // Searching for the start of a value:
                 //   colon* -> kPostValue+
                 //   comma* -> kPreKey+
                 //   space -> kPreValue+
                 //   else -> kValue+
    kValue,      // Searching for the end of a value:
                 //   comma -> kPreKey+
                 //   space -> kPostValue+
                 //   colon* -> kError+
                 //   else -> kValue+
    kPostValue,  // Searching for the comma after the value:
                 //   comma -> kPreKey+
                 //   colon*, else* -> kError+
    kError,      // Searching for the comma after an error:
                 //   comma -> kPreKey+
                 //   else* -> kError+
  };
  
  template <typename CharacterType>
  void NamesMap::Set(const AtomicString& source,
                     const CharacterType* characters) {
    Clear();
    unsigned length = source.length();
  
    // The character we are examining.
    unsigned cur = 0;
    // The start of the current token.
    unsigned start = 0;
    State state = kPreKey;
    // The key and value are held here until we succeed in parsing a valid
    // part-mapping.
    AtomicString key;
    AtomicString value;
    while (cur < length) {
      // All cases break, ensuring that some input is consumed and we avoid
      // an infinite loop.
      //
      // The only state which should set a value for key is kKey, as we leave the
      // state.
      switch (state) {
        case kPreKey:
          // Skip any number of spaces, commas. When we find something else, it is
          // the start of a key.
          if (IsHTMLSpaceOrComma<CharacterType>(characters[cur]))
            break;
          // Colon is invalid here.
          if (IsColon<CharacterType>(characters[cur])) {
            state = kError;
            break;
          }
          start = cur;
          state = kKey;
          break;
        case kKey:
          // At a comma this was a key without a value, the implicit value is the
          // same as the key.
          if (IsComma<CharacterType>(characters[cur])) {
            key = AtomicString(characters + start, cur - start);
            Add(key, key);
            state = kPreKey;
            // At a colon, we have found the end of the key and we expect a value.
          } else if (IsColon<CharacterType>(characters[cur])) {
            key = AtomicString(characters + start, cur - start);
            state = kPreValue;
            // At a space, we have found the end of the key.
          } else if (IsHTMLSpace<CharacterType>(characters[cur])) {
            key = AtomicString(characters + start, cur - start);
            state = kPostKey;
          }
          break;
        case kPostKey:
          // At a comma this was a key without a value, the implicit value is the
          // same as the key.
          if (IsComma<CharacterType>(characters[cur])) {
            Add(key, key);
            state = kPreKey;
            // At a colon this was a key with a value, we expect a value.
          } else if (IsColon<CharacterType>(characters[cur])) {
            state = kPreValue;
            // Anything else except space is invalid.
          } else if (!IsHTMLSpace<CharacterType>(characters[cur])) {
            key = g_null_atom;
            state = kError;
          }
          break;
        case kPreValue:
          // Colon is invalid.
          if (IsColon<CharacterType>(characters[cur])) {
            state = kError;
            // Comma is invalid.
          } else if (IsComma<CharacterType>(characters[cur])) {
            state = kPreKey;
            // Space is ignored.
          } else if (IsHTMLSpace<CharacterType>(characters[cur])) {
            break;
            // If we reach a non-space character, we have found the start of the
            // value.
          } else {
            start = cur;
            state = kValue;
          }
          break;
        case kValue:
          // At a comma, we have found the end of the value and expect
          // the next key.
          if (IsComma<CharacterType>(characters[cur])) {
            value = AtomicString(characters + start, cur - start);
            Add(key, value);
            state = kPreKey;
            // At a space, we have found the end of the value, store it.
          } else if (IsHTMLSpace<CharacterType>(characters[cur]) ||
                     IsColon<CharacterType>(characters[cur])) {
            value = AtomicString(characters + start, cur - start);
            state = kPostValue;
            // A colon is invalid.
          } else if (IsColon<CharacterType>(characters[cur])) {
            state = kError;
          }
          break;
        case kPostValue:
          // At a comma, accept what we have and start looking for the next key.
          if (IsComma<CharacterType>(characters[cur])) {
            Add(key, value);
            state = kPreKey;
            // Anything else except a space is invalid.
          } else if (!IsHTMLSpace<CharacterType>(characters[cur])) {
            state = kError;
          }
          break;
        case kError:
          // At a comma, start looking for the next key.
          if (IsComma<CharacterType>(characters[cur])) {
            state = kPreKey;
          }
          // Anything else is consumed.
          break;
      }
  
      ++cur;
    }
  
    // We have reached the end of the string, add whatever we had into the map.
    switch (state) {
      case kPreKey:
        break;
      case kKey:
        // The string ends with a key.
        key = AtomicString(characters + start, cur - start);
        FALLTHROUGH;
      case kPostKey:
        // The string ends with a key.
        Add(key, key);
        break;
      case kPreValue:
        break;
      case kValue:
        // The string ends with a value.
        value = AtomicString(characters + start, cur - start);
        FALLTHROUGH;
      case kPostValue:
        Add(key, value);
        break;
      case kError:
        break;
    }
  }
  
  base::Optional<SpaceSplitString> NamesMap::Get(const AtomicString& key) const {
    auto it = data_.find(key);
    return it != data_.end() ? it->value : base::nullopt;
  }
  
  }  // namespace blink
  