| // Copyright 2018 The Chromium Authors |
| // 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, std::optional<SpaceSplitString>()); |
| if (add_result.is_new_entry) { |
| add_result.stored_value->value = |
| std::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; |
| } |
| } |
| |
| std::optional<SpaceSplitString> NamesMap::Get(const AtomicString& key) const { |
| auto it = data_.find(key); |
| return it != data_.end() ? it->value : std::nullopt; |
| } |
| |
| } // namespace blink |