AccessibleList APath::executePath()

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;
}