#include "ui/accessibility/test_ax_tree_update.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
namespace ui {
int PlusSignCount(const std::string& s) {
int count = 0;
for (auto& character : s) {
if (character == '+') {
count++;
}
}
return count;
}
bool IsSpaceOrTab(const char c) {
return c == ' ' || c == '\t';
}
void RemoveLeadingAndTrailingWhitespace(std::string& s) {
if (s.empty()) {
return;
}
auto front_it = s.begin();
auto back_it = s.end() - 1;
while (IsSpaceOrTab(*front_it) || IsSpaceOrTab(*back_it)) {
if (front_it > back_it) {
return;
}
if (front_it == back_it) {
if (IsSpaceOrTab(*front_it)) {
s.erase(front_it);
}
return;
}
if (IsSpaceOrTab(*front_it)) {
s.erase(front_it);
front_it = s.begin();
back_it = s.end() - 1;
}
if (IsSpaceOrTab(*back_it)) {
s.erase(back_it);
back_it = s.end() - 1;
front_it = s.begin();
}
}
}
bool StringToBool(const std::string& s) {
if (s == "true" || s == "True" || s == "1") {
return true;
}
if (s == "false" || s == "False" || s == "0") {
return false;
}
NOTREACHED() << "Invalid value passed to StringToBool: " << s;
return false;
}
void ParseAndAddNodeProperties(
AXNodeData& node_data,
const std::vector<std::string>& node_line) {
DCHECK(node_line.size() >= 1)
<< "Error in formatting of the tree structure. Possibly extra whiespace.";
for (auto& prop : node_line) {
std::vector<std::string> property_vector = base::SplitStringUsingSubstr(
prop, "=", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
DCHECK(property_vector.size() == 2)
<< "Properties should always be formed as "
"<SupportedProperty>=<PropertyValue/s>";
std::string property = property_vector[0];
if (property == "name" || property == "Name") {
std::string name = property_vector[1];
DCHECK(name.front() == '\"' && name.back() == '\"');
name.erase(name.begin());
name.erase(--name.end());
node_data.SetName(name);
} else if (property == "states" || property == "state") {
std::vector<std::string> state_values = base::SplitStringUsingSubstr(
property_vector[1], ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
DCHECK(state_values.size() >= 1)
<< "State values should be at least one, and they should be "
"separated by commas.";
for (auto& value : state_values) {
node_data.AddState(StringToState(value));
}
} else if (property == "intAttribute" || property == "IntAttribute" ||
property == "intattribute") {
std::vector<std::string> int_attribute_vector =
base::SplitStringUsingSubstr(property_vector[1], ",",
base::KEEP_WHITESPACE,
base::SPLIT_WANT_ALL);
DCHECK(int_attribute_vector.size() == 2)
<< "Int attribute in string must always be formed: "
"intAttribute=<intAttributeType>,<intAttributeValue>";
int int_value = 0;
base::StringToInt(int_attribute_vector[1], &int_value);
DCHECK(int_value) << "Formatting error or non integer passed as node ID.";
node_data.AddIntAttribute(StringToIntAttribute(int_attribute_vector[0]),
int_value);
} else if (property == "stringAttribute" || property == "StringAttribute" ||
property == "stringattribute") {
std::vector<std::string> string_attribute_vector =
base::SplitStringUsingSubstr(property_vector[1], ",",
base::KEEP_WHITESPACE,
base::SPLIT_WANT_ALL);
DCHECK(string_attribute_vector.size() == 2)
<< "String attribute in string must always be formed: "
"stringAttribute=<stringAttributeType>,<stringAttributeValue>";
node_data.AddStringAttribute(
StringToStringAttribute(string_attribute_vector[0]),
string_attribute_vector[1]);
} else if (property == "boolAttribute" || property == "BoolAttribute" ||
property == "boolattribute") {
std::vector<std::string> bool_attribute_vector =
base::SplitStringUsingSubstr(property_vector[1], ",",
base::KEEP_WHITESPACE,
base::SPLIT_WANT_ALL);
DCHECK(bool_attribute_vector.size() == 2)
<< "Bool attribute in string must always be formed: "
"boolAttribute=<boolAttributeType>,<boolAttributeValue>";
node_data.AddBoolAttribute(
StringToBoolAttribute(bool_attribute_vector[0]),
StringToBool(bool_attribute_vector[1]));
} else {
NOTREACHED()
<< "Either an invalid property was specified, or this function does "
"not currently support the specified property.";
}
}
}
AXNodeData ParseNodeInfo(std::vector<std::string>& node_line,
std::set<int>& found_ids) {
AXNodeData data;
DCHECK(node_line.size() >= 2) << "Error, a node must have an id and role.";
int int_value = 0;
base::StringToInt(node_line[0], &int_value);
DCHECK(int_value) << "Formatting error or non integer passed as node ID.";
data.id = int_value;
DCHECK(found_ids.find(data.id) == found_ids.end())
<< "Error, can't have duplicate IDs.";
found_ids.insert(data.id);
ax::mojom::Role role = StringToRole(node_line[1]);
data.role = role;
data.child_ids = {};
if (node_line.size() >= 3) {
node_line.erase(node_line.begin());
node_line.erase(node_line.begin());
ParseAndAddNodeProperties(data, node_line);
}
return data;
}
TestAXTreeUpdateNode::TestAXTreeUpdateNode(const TestAXTreeUpdateNode&) =
default;
TestAXTreeUpdateNode::TestAXTreeUpdateNode(TestAXTreeUpdateNode&&) = default;
TestAXTreeUpdateNode::~TestAXTreeUpdateNode() = default;
TestAXTreeUpdateNode::TestAXTreeUpdateNode(
ax::mojom::Role role,
const std::vector<TestAXTreeUpdateNode>& children)
: children(children) {
DCHECK_NE(role, ax::mojom::Role::kUnknown);
data.role = role;
}
TestAXTreeUpdateNode::TestAXTreeUpdateNode(
ax::mojom::Role role,
ax::mojom::State state,
const std::vector<TestAXTreeUpdateNode>& children)
: children(children) {
DCHECK_NE(role, ax::mojom::Role::kUnknown);
DCHECK_NE(state, ax::mojom::State::kNone);
data.role = role;
data.AddState(state);
}
TestAXTreeUpdateNode::TestAXTreeUpdateNode(const std::string& text) {
data.role = ax::mojom::Role::kStaticText;
data.SetName(text);
}
TestAXTreeUpdate::TestAXTreeUpdate(const TestAXTreeUpdateNode& root) {
root_id = SetSubtree(root);
}
AXNodeID TestAXTreeUpdate::SetSubtree(const TestAXTreeUpdateNode& node) {
size_t node_index = nodes.size();
nodes.push_back(node.data);
nodes[node_index].id = node_index + 1;
std::vector<AXNodeID> child_ids;
for (const auto& child : node.children) {
child_ids.push_back(SetSubtree(child));
}
nodes[node_index].child_ids = child_ids;
return nodes[node_index].id;
}
TestAXTreeUpdate::TestAXTreeUpdate(const std::string& tree_structure) {
std::vector<AXNodeData> node_data_vector;
std::vector<std::string> tree_structure_vector = base::SplitStringUsingSubstr(
tree_structure, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
RemoveLeadingAndTrailingWhitespace(*tree_structure_vector.begin());
RemoveLeadingAndTrailingWhitespace(*--tree_structure_vector.end());
DCHECK(tree_structure_vector.front().empty() &&
tree_structure_vector.back().empty())
<< "Error in parsing the tree structure, double check the formatting.";
tree_structure_vector.erase(tree_structure_vector.begin());
tree_structure_vector.erase(--tree_structure_vector.end());
node_data_vector.reserve(tree_structure_vector.size());
RemoveLeadingAndTrailingWhitespace(tree_structure_vector[0]);
int root_pluses = PlusSignCount(tree_structure_vector[0]);
DCHECK_EQ(root_pluses, 2)
<< "The first line of the test needs to start with 2 '+' sign, not "
<< root_pluses;
tree_structure_vector[0].erase(0, root_pluses);
std::vector<std::string> root_line =
base::SplitStringUsingSubstr(tree_structure_vector[0], " ",
base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
std::set<int> found_ids;
node_data_vector.push_back(ParseNodeInfo(root_line, found_ids));
std::unordered_map<int, int> last_index_appearance_of_plus_count;
last_index_appearance_of_plus_count[2] = 0;
int last_count = 2;
int greatest_count = 2;
for (size_t i = 1; i < tree_structure_vector.size(); i++) {
RemoveLeadingAndTrailingWhitespace(tree_structure_vector[i]);
int plus_count = PlusSignCount(tree_structure_vector[i]);
DCHECK(plus_count % 2 == 0)
<< "Error in plus sign count, can't have an odd number of plus signs";
DCHECK(plus_count <= greatest_count + 2)
<< "Error in plus signs count at tree structure line number " << i
<< ", it can't have more than two more plus signs than the previous "
"element.";
if (plus_count > greatest_count) {
greatest_count = plus_count;
}
auto elem = last_index_appearance_of_plus_count.find(plus_count);
if (elem == last_index_appearance_of_plus_count.end() ||
(int)i > elem->second) {
last_index_appearance_of_plus_count[plus_count] = i;
}
tree_structure_vector[i].erase(0, plus_count);
std::vector<std::string> node_line = base::SplitStringUsingSubstr(
tree_structure_vector[i], " ", base::KEEP_WHITESPACE,
base::SPLIT_WANT_ALL);
AXNodeData curr_node = ParseNodeInfo(node_line, found_ids);
node_data_vector.push_back(curr_node);
if (plus_count > last_count) {
node_data_vector[i - 1].child_ids.push_back(curr_node.id);
} else {
elem = last_index_appearance_of_plus_count.find(plus_count - 2);
DCHECK(elem != last_index_appearance_of_plus_count.end())
<< "Error in plus sign count.";
int parent_index = elem->second;
node_data_vector[parent_index].child_ids.push_back(curr_node.id);
}
last_count = plus_count;
}
DCHECK(node_data_vector.size() >= 1);
tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
tree_data.focused_tree_id = tree_data.tree_id;
tree_data.parent_tree_id = ui::AXTreeIDUnknown();
tree_data = tree_data;
has_tree_data = true;
root_id = node_data_vector[0].id;
for (auto& node : node_data_vector) {
nodes.push_back(node);
}
}
}