in tools/mga/src/library/apath.cpp [568:909]
AccessibleList APath::executePath(Accessible *anchor, StringListIterator &begin, StringListIterator const& end,
bool includeEmptyNames, bool includeEmptyIDs, bool includeInternal) {
AccessibleList result;
if (*begin == "/" && (end - begin == 1)) {
++begin;
result.push_back(_root->clone());
return result;
}
Accessible *start = anchor;
if (start == nullptr || (*begin)[0] == '/') // Absolute path or no anchor given?
start = _root;
// Loop over subranges, delimited by a slash.
// Bail out when we reach the end of the path.
while (begin != end && *begin != "|") {
if (*begin == "/" || *begin == "./")
++begin;
std::string axis = Utilities::toLower(*begin);
if (axis == "::") // No axis, use default.
axis = "child";
else {
if (axis == ".//") {
axis = "descendant";
} else if (axis == "//") {
axis = "descendant-or-self";
}
++begin;
}
Role role = Role::Any;
if (begin != end && *begin == "::") {
++begin;
std::string roleName = Utilities::toLower(*begin++);
if (roleMap.find(roleName) == roleMap.end())
throw std::runtime_error("Invalid role specified: " + roleName);
role = roleMap[roleName];
}
if (axisMap.find(axis) == axisMap.end())
throw std::runtime_error("Invalid path axis specified: " + axis);
size_t axisIndex = axisMap[axis];
switch (axisIndex) {
case 0: { // child
AccessibleList children;
if (result.empty())
children = getChildren(start, false);
else {
for (auto &entry : result) {
auto temp = getChildren(entry.get(), false);
Utilities::appendVector(children, temp);
}
}
result = std::move(children);
break;
}
case 1: // parent
if (result.empty()) {
if (start->isRoot())
throw std::runtime_error("There is no parent for the root node");
else
result.push_back(start->getParent());
} else {
for (auto &entry : result) {
if (entry->isRoot())
throw std::runtime_error("There is no parent for the root node");
else
entry = entry->getParent();
}
}
break;
case 2: // self
if (result.empty())
result.push_back(start->clone());
break;
case 3: // ancestor
case 4: { // ancestor-or-self
if (result.empty()) {
if (start->isRoot())
throw std::runtime_error("There is no parent for the root node");
result = getParents(start);
if (axisIndex == 4)
result.insert(result.begin(), start->clone());
break;
}
AccessibleList parents;
for (auto &element : result) {
Accessible *raw = element.get();
if (axisIndex == 4)
parents.push_back(std::move(element));
auto temp = getParents(raw);
Utilities::appendVector(parents, temp);
}
result = std::move(parents);
break;
}
case 5: // descendant
case 6: { // descendant-or-self
if (result.empty()) {
result = getChildren(start, true);
if (axisIndex == 6)
result.insert(result.begin(), start->clone());
break;
}
AccessibleList children;
for (auto &element : result) {
Accessible *raw = element.get();
if (axisIndex == 6)
children.push_back(std::move(element));
auto temp = getChildren(raw, true);
Utilities::appendVector(children, temp);
}
result = std::move(children);
break;
}
case 7: // following
case 8: // following-sibling
case 9: // preceding
case 10: { // preceding-sibling
bool preceding = axisIndex == 9 || axisIndex == 10;
bool siblingsOnly = axisIndex == 8 || axisIndex == 10;
AccessibleList collected;
std::vector<Accessible *> elements;
if (result.empty())
elements.push_back(start);
else {
for (auto &element : result)
elements.push_back(element.get());
}
for (Accessible *element : elements) {
auto siblings = getSiblings(element, preceding, !preceding);
if (siblingsOnly) {
Utilities::appendVector(collected, siblings);
} else {
for (auto &sibling : siblings) {
result.push_back(std::move(sibling));
auto children = getChildren(sibling.get(), true);
Utilities::appendVector(collected, children);
}
}
}
result = std::move(collected);
break;
}
}
// Remove all elements which don't match the specified role.
if (role != Role::Any)
result.erase(std::remove_if(result.begin(), result.end(), [role] (AccessibleRef const& element) {
return element->getRole() != role;
}), result.end());
// Remove elements that have no name and/or id assigned (if specified). Same for elements that are
// considered to be internal (like those added by the OS).
if (!includeEmptyNames) {
result.erase(std::remove_if(result.begin(), result.end(), [] (AccessibleRef const& element) {
return element->getName().empty();
}), result.end());
}
if (!includeEmptyIDs) {
result.erase(std::remove_if(result.begin(), result.end(), [] (AccessibleRef const& element) {
return element->getID().empty();
}), result.end());
}
// TODO: decide if we even can support that internal flag or remove it.
std::ignore = includeInternal;
/*if (!includeInternal) {
result.erase(std::remove_if(result.begin(), result.end(), [role] (AccessibleRef const& element) {
return element->isInternal();
}), result.end());
}*/
// Last step: filter by predicate. In XPath predicates can also contain XPath and some math expressions.
// We don't go that far atm. and support only these expressions:
// - self: (or .) to refer to an element's text (same as @text).
// - properties: @id, @name, @text, @valid, @enabled, @focused, @childCount, @title (not case sensitive).
// - A number as index into the child list.
// Except for the index the expression must be formulated as a relation (e.g. [@enabled=true]) to be valid.
while (begin != end && (*begin)[0] == '[') {
auto predicate = begin->substr(1, begin->size() - 2);
auto expression = tokenizeExpression(predicate);
++begin;
if (expression.empty())
return result;
// Is it an index?
if (expression.size() == 1 && expression[0].type == PredicateValueType::Number) {
// Remove everything but the value indexed by the given number.
// Keep in mind the index is one-based.
long index = static_cast<long>(expression[0].numberValue);
if (index < 1 || index > static_cast<long>(result.size()))
throw std::runtime_error("Index out of range: " + std::to_string(index));
--index;
AccessibleRef temp = std::move(result[static_cast<size_t>(index)]);
result.clear();
result.push_back(std::move(temp));
continue;
}
// Something more complex. Validate and act according to the type.
if (expression.size() != 3)
throw std::runtime_error("Invalid predicate specified: " + predicate);
if (expression[1].type != PredicateValueType::Operator)
throw std::runtime_error("Invalid relation specified in predicate: " + predicate);
std::string op = expression[1].stringValue;
switch (expression[0].type) {
case PredicateValueType::Self:
case PredicateValueType::Text: {
std::string value = getStringValue(expression[2]);
result.erase(std::remove_if(result.begin(), result.end(), [&](AccessibleRef const& element) {
try {
//std::cout << element->dump() << std::endl;
return !compareText(element->getText(), op, value);
} catch (...) {
return true;
}
}), result.end());
break;
}
case PredicateValueType::ID: {
std::string value = getStringValue(expression[2]);
result.erase(std::remove_if(result.begin(), result.end(), [&](AccessibleRef const& element) {
return !compareText(element->getID(), op, value);
}), result.end());
break;
}
case PredicateValueType::Name: {
std::string value = getStringValue(expression[2]);
result.erase(std::remove_if(result.begin(), result.end(), [&](AccessibleRef const& element) {
return !compareText(element->getName(), op, value);
}), result.end());
break;
}
case PredicateValueType::Description: {
std::string value = getStringValue(expression[2]);
result.erase(std::remove_if(result.begin(), result.end(), [&](AccessibleRef const& element) {
return !compareText(element->getDescription(), op, value);
}), result.end());
break;
}
case PredicateValueType::Valid: {
bool value = getBoolValue(expression[2]);
result.erase(std::remove_if(result.begin(), result.end(), [op, value](AccessibleRef const& element) {
return !compareBool(element->isValid(), op, value);
}), result.end());
break;
}
case PredicateValueType::Enabled: {
bool value = getBoolValue(expression[2]);
result.erase(std::remove_if(result.begin(), result.end(), [&](AccessibleRef const& element) {
try {
return !compareBool(element->isEnabled(), op, value);
} catch (...) {
return true;
}
}), result.end());
break;
}
case PredicateValueType::Focused: {
bool value = getBoolValue(expression[2]);
result.erase(std::remove_if(result.begin(), result.end(), [&](AccessibleRef const& element) {
bool canFocus = element->canFocus();
try {
return !compareBool(canFocus && element->isFocused(), op, value);
} catch (...) {
return true;
}
}), result.end());
break;
}
case PredicateValueType::ChildCount: {
long count = static_cast<long>(getNumberValue(expression[2]));
if (count < 0)
throw std::runtime_error("The value must be greater than zero (" + std::to_string(count) + ")");
result.erase(std::remove_if(result.begin(), result.end(), [&](AccessibleRef const& element) {
return !compareNumber(static_cast<long>(element->children().size()), op, count);
}), result.end());
break;
}
case PredicateValueType::Title: {
std::string value = getStringValue(expression[2]);
result.erase(std::remove_if(result.begin(), result.end(), [&](AccessibleRef const& element) {
try {
return !compareText(element->getTitle(), op, value);
} catch (...) {
return true;
}
}), result.end());
break;
}
default: // Something invalid here.
return result;
}
}
}
return result;
}