blob: d2276271093511f09a9de59a1b54bc6d6a8153a7 [file] [log] [blame]
// 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