Commit 1e9ee02f authored by Clemens Arbesser's avatar Clemens Arbesser Committed by Commit Bot

[Autofill Assistant] Added support for model identifier subscripts.

This allows subscript access into model identifiers, both direct (e.g.,
"my_identifier[0]" as well as indirect, e.g., "my_identifier[index]",
where |index| is a model identifier that points to a single integer.
This can be nested arbitrarily deeply, i.e., "a[b[c[d[...]]]]".

Note that subscript access into UTF-8 model identifiers is NOT supported.
Specifically, identifiers must match a regular expression \w+ for
subscript support.

Bug: b/145043394
Change-Id: I03ca5c2d2c11a8c9c014a8cb3ad1768b7c13a62e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2139725
Commit-Queue: Clemens Arbesser <arbesser@google.com>
Reviewed-by: default avatarSandro Maggi <sandromaggi@google.com>
Reviewed-by: default avatarMarian Fechete <marianfe@google.com>
Cr-Commit-Position: refs/heads/master@{#758784}
parent 6e8643e2
......@@ -4,10 +4,47 @@
#include "components/autofill_assistant/browser/user_model.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "third_party/re2/src/re2/re2.h"
namespace autofill_assistant {
namespace {
// Matches an ASCII identifier (w+) which ends in [<string>], e.g.,
// test_identifier[sub_identifier[2]].
static const char* const kExtractArraySubidentifierRegex = R"(^(\w+)\[(.+)\]$)";
// Simple wrapper around value_util::GetNthValue to unwrap the base::optional
// |value|.
base::Optional<ValueProto> GetNthValue(const base::Optional<ValueProto>& value,
int index) {
if (!value.has_value()) {
return base::nullopt;
}
return GetNthValue(*value, index);
}
// Same as above, but expects |index_value| to point to a single integer value
// specifying the index to retrieve.
base::Optional<ValueProto> GetNthValue(
const base::Optional<ValueProto>& value,
const base::Optional<ValueProto>& index_value) {
if (!value.has_value() || !index_value.has_value()) {
return base::nullopt;
}
if (!AreAllValuesOfSize({*index_value}, 1) ||
!AreAllValuesOfType({*index_value}, ValueProto::kInts)) {
return base::nullopt;
}
return GetNthValue(*value, index_value->ints().values().at(0));
}
} // namespace
UserModel::Observer::Observer() = default;
UserModel::Observer::~Observer() = default;
......@@ -38,6 +75,21 @@ base::Optional<ValueProto> UserModel::GetValue(
auto it = values_.find(identifier);
if (it != values_.end()) {
return it->second;
} else if (base::EndsWith(identifier, "]", base::CompareCase::SENSITIVE)) {
std::string identifier_without_suffix;
std::string subidentifier;
if (re2::RE2::FullMatch(identifier, kExtractArraySubidentifierRegex,
&identifier_without_suffix, &subidentifier)) {
int index;
if (base::StringToInt(subidentifier, &index)) {
// The case 'identifier[n]'.
return GetNthValue(GetValue(identifier_without_suffix), index);
} else {
// The case 'identifier[subidentifier]'.
return GetNthValue(GetValue(identifier_without_suffix),
GetValue(subidentifier));
}
}
}
return base::nullopt;
}
......
......@@ -47,6 +47,8 @@ class UserModel {
bool force_notification = false);
// Returns the value for |identifier| or nullopt if there is no such value.
// Also supports the array operator to retrieve a specific element of a list,
// e.g., "identifier[0]" to get the first item.
base::Optional<ValueProto> GetValue(const std::string& identifier) const;
// Returns the value for |reference| or nullopt if there is no such value.
......
......@@ -219,4 +219,90 @@ TEST_F(UserModelTest, UpdateProto) {
Property(&ModelProto::ModelValue::value, Eq(value_c)))));
}
TEST_F(UserModelTest, SubscriptAccess) {
ValueProto value;
value.mutable_strings()->add_values("a");
value.mutable_strings()->add_values("b");
value.mutable_strings()->add_values("c");
model_.SetValue("value", value);
EXPECT_EQ(model_.GetValue("value"), value);
EXPECT_EQ(model_.GetValue("value[0]"), SimpleValue(std::string("a")));
EXPECT_EQ(model_.GetValue("value[1]"), SimpleValue(std::string("b")));
EXPECT_EQ(model_.GetValue("value[2]"), SimpleValue(std::string("c")));
EXPECT_EQ(model_.GetValue("value[001]"), SimpleValue(std::string("b")));
EXPECT_EQ(model_.GetValue("value[3]"), base::nullopt);
EXPECT_EQ(model_.GetValue("value[-1]"), base::nullopt);
model_.SetValue("index", SimpleValue(0));
EXPECT_EQ(model_.GetValue("value[index]"), SimpleValue(std::string("a")));
model_.SetValue("index", SimpleValue(1));
EXPECT_EQ(model_.GetValue("value[index]"), SimpleValue(std::string("b")));
model_.SetValue("index", SimpleValue(2));
EXPECT_EQ(model_.GetValue("value[index]"), SimpleValue(std::string("c")));
model_.SetValue("index", SimpleValue(3));
EXPECT_EQ(model_.GetValue("value[index]"), base::nullopt);
model_.SetValue("index", SimpleValue(-1));
EXPECT_EQ(model_.GetValue("value[index]"), base::nullopt);
model_.SetValue("index", SimpleValue(0));
EXPECT_EQ(model_.GetValue("value[index[0]]"), SimpleValue(std::string("a")));
model_.SetValue("index", SimpleValue(std::string("not an index")));
EXPECT_EQ(model_.GetValue("value[index]"), base::nullopt);
ValueProto indices;
indices.mutable_ints()->add_values(2);
indices.mutable_ints()->add_values(0);
indices.mutable_ints()->add_values(1);
model_.SetValue("indices", indices);
model_.SetValue("index", SimpleValue(0));
EXPECT_EQ(model_.GetValue("value[indices[index]]"),
SimpleValue(std::string("c")));
model_.SetValue("index", SimpleValue(1));
EXPECT_EQ(model_.GetValue("value[indices[index]]"),
SimpleValue(std::string("a")));
model_.SetValue("index", SimpleValue(2));
EXPECT_EQ(model_.GetValue("value[indices[index]]"),
SimpleValue(std::string("b")));
}
TEST_F(UserModelTest, IrregularModelIdentifiers) {
ValueProto value;
value.mutable_strings()->add_values("a");
value.mutable_strings()->add_values("b");
value.mutable_strings()->add_values("c");
model_.SetValue("normal_identifier", value);
model_.SetValue("utf_8_ü万𠜎", value);
model_.SetValue("ends_in_bracket]", value);
model_.SetValue("contains_[brackets]", value);
model_.SetValue("[]", value);
model_.SetValue("empty_brackets[]", value);
// Retrieving simple values works for any identifiers.
EXPECT_EQ(model_.GetValue("normal_identifier"), value);
EXPECT_EQ(model_.GetValue("utf_8_ü万𠜎"), value);
EXPECT_EQ(model_.GetValue("ends_in_bracket]"), value);
EXPECT_EQ(model_.GetValue("contains_[brackets]"), value);
EXPECT_EQ(model_.GetValue("[]"), value);
EXPECT_EQ(model_.GetValue("empty_brackets[]"), value);
// Subscript access is not supported for model identifiers containing
// irregular characters (i.e., outside of \w+).
EXPECT_EQ(model_.GetValue("normal_identifier[1]"),
SimpleValue(std::string("b")));
EXPECT_EQ(model_.GetValue("ends_in_bracket][1]"), base::nullopt);
EXPECT_EQ(model_.GetValue("contains_[brackets][1]"), base::nullopt);
EXPECT_EQ(model_.GetValue("[][0]"), base::nullopt);
EXPECT_EQ(model_.GetValue("empty_brackets[1]"), base::nullopt);
EXPECT_EQ(model_.GetValue("empty_brackets[][1]"), base::nullopt);
// Subscript access into UTF-8 identifiers is not supported.
EXPECT_EQ(model_.GetValue("utf_8_ü万𠜎[1]"), base::nullopt);
}
} // namespace autofill_assistant
......@@ -279,37 +279,63 @@ bool AreAllValuesOfSize(const std::vector<ValueProto>& values,
return false;
}
for (const auto& value : values) {
switch (value.kind_case()) {
case ValueProto::kStrings:
if (value.strings().values_size() != target_size)
return false;
break;
case ValueProto::kBooleans:
if (value.booleans().values_size() != target_size)
return false;
break;
case ValueProto::kInts:
if (value.ints().values_size() != target_size)
return false;
break;
case ValueProto::kUserActions:
if (value.user_actions().values_size() != target_size)
return false;
break;
case ValueProto::kDates:
if (value.dates().values_size() != target_size)
return false;
break;
case ValueProto::KIND_NOT_SET:
if (target_size != 0) {
return false;
}
break;
if (GetValueSize(value) != target_size) {
return false;
}
}
return true;
}
int GetValueSize(const ValueProto& value) {
switch (value.kind_case()) {
case ValueProto::kStrings:
return value.strings().values_size();
case ValueProto::kBooleans:
return value.booleans().values_size();
case ValueProto::kInts:
return value.ints().values_size();
case ValueProto::kUserActions:
return value.user_actions().values_size();
case ValueProto::kDates:
return value.dates().values_size();
case ValueProto::KIND_NOT_SET:
return 0;
}
}
base::Optional<ValueProto> GetNthValue(const ValueProto& value, int index) {
if (value == ValueProto()) {
return base::nullopt;
}
if (index < 0 || index >= GetValueSize(value)) {
return base::nullopt;
}
ValueProto nth_value;
switch (value.kind_case()) {
case ValueProto::kStrings:
nth_value.mutable_strings()->add_values(
value.strings().values().at(index));
return nth_value;
case ValueProto::kBooleans:
nth_value.mutable_booleans()->add_values(
value.booleans().values().at(index));
return nth_value;
case ValueProto::kInts:
nth_value.mutable_ints()->add_values(value.ints().values().at(index));
return nth_value;
case ValueProto::kUserActions:
*nth_value.mutable_user_actions()->add_values() =
value.user_actions().values().at(index);
return nth_value;
case ValueProto::kDates:
*nth_value.mutable_dates()->add_values() =
value.dates().values().at(index);
return nth_value;
case ValueProto::KIND_NOT_SET:
return base::nullopt;
}
}
base::Optional<ValueProto> CombineValues(
const std::vector<ValueProto>& values) {
if (values.empty()) {
......
......@@ -62,6 +62,13 @@ bool AreAllValuesOfType(const std::vector<ValueProto>& values,
// Returns true if all |values| share the specified |target_size|.
bool AreAllValuesOfSize(const std::vector<ValueProto>& values, int target_size);
// Returns the number of elements in |value|.
int GetValueSize(const ValueProto& value);
// Returns the |index|'th item of |value| or nullopt if |index| is
// out-of-bounds.
base::Optional<ValueProto> GetNthValue(const ValueProto& value, int index);
// Combines all specified |values| in a single ValueProto where the individual
// value lists are appended after each other. Returns nullopt if |values| do not
// share the same type.
......
......@@ -350,5 +350,19 @@ TEST_F(ValueUtilTest, NotEqualOperatorForValueProto) {
EXPECT_TRUE(value_a != value_b);
}
TEST_F(ValueUtilTest, TestGetNthValue) {
ValueProto value;
value.mutable_strings()->add_values("a");
value.mutable_strings()->add_values("b");
value.mutable_strings()->add_values("c");
EXPECT_EQ(GetNthValue(value, 0), SimpleValue(std::string("a")));
EXPECT_EQ(GetNthValue(value, 1), SimpleValue(std::string("b")));
EXPECT_EQ(GetNthValue(value, 2), SimpleValue(std::string("c")));
EXPECT_EQ(GetNthValue(value, -1), base::nullopt);
EXPECT_EQ(GetNthValue(value, 3), base::nullopt);
}
} // namespace value_util
} // namespace autofill_assistant
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment