bool CFilterElementHide::IsMatchFilterElementHide(IHTMLElement* pEl) const
{
  HRESULT hr;

  if (!m_tagId.IsEmpty())
  {
    CComBSTR id;
    hr = pEl->get_id(&id);
    if ((hr != S_OK) || (id != CComBSTR(m_tagId)))
    {
      return false;
    }
  }
  if (!m_tagClassName.IsEmpty())
  {
    CComBSTR classNameBSTR;
    hr = pEl->get_className(&classNameBSTR);
    if (hr == S_OK)
    {
      CString className = classNameBSTR;
      int start = 0;
      CString specificClass;
      bool foundMatch = false;
      while ((specificClass = className.Tokenize(L" ", start)) != L"")
      {
        // TODO: Consider case of multiple classes. (m_tagClassName can be something like "foo.bar")
        if (specificClass == m_tagClassName)
        {
          foundMatch = true;
        }
      }
      if (!foundMatch)
      {
        return false;
      }
    }
  }
  if (!m_tag.IsEmpty())
  {
    CComBSTR tagName;
    hr = pEl->get_tagName(&tagName);
    tagName.ToLower();
    if ((hr != S_OK) || (tagName != CComBSTR(m_tag)))
    {
      return false;
    }
  }

  // Check attributes
  for (std::vector<CFilterElementHideAttrSelector>::const_iterator attrIt = m_attributeSelectors.begin(); 
        attrIt != m_attributeSelectors.end(); ++ attrIt)
  {
    ATL::CString value;
    bool attrFound = false;
    if (attrIt->m_type == CFilterElementHideAttrType::STYLE)
    {
      CComPtr<IHTMLStyle> pStyle;
      if (SUCCEEDED(pEl->get_style(&pStyle)) && pStyle)
      {
        CComBSTR bstrStyle;

        if (SUCCEEDED(pStyle->get_cssText(&bstrStyle)) && bstrStyle)
        {
          value = bstrStyle;
          value.MakeLower();
          attrFound = true;
        }
      }
    }
    else if (attrIt->m_type == CFilterElementHideAttrType::CLASS)
    {
      CComBSTR bstrClassNames;
      if (SUCCEEDED(pEl->get_className(&bstrClassNames)) && bstrClassNames)
      {
        value = bstrClassNames;
        attrFound = true;
      }
    }
    else if (attrIt->m_type == CFilterElementHideAttrType::ID)
    {
      CComBSTR bstrId;
      if (SUCCEEDED(pEl->get_id(&bstrId)) && bstrId)
      {
        value = bstrId;
        attrFound = true;
      }
    }
    else
    {
      auto attributeValue = GetHtmlElementAttribute(*pEl, attrIt->m_bstrAttr);
      if (attrFound = attributeValue.isAttributeFound)
      {
        value = ToCString(attributeValue.attributeValue);
      }
    }

    if (attrFound)
    {
      if (attrIt->m_pos == CFilterElementHideAttrPos::EXACT)
      {
        // TODO: IE rearranges the style attribute completely. Figure out if anything can be done about it.
        if (value != attrIt->m_value)
          return false;
      }
      else if (attrIt->m_pos == CFilterElementHideAttrPos::STARTING)
      {
        if (value.Left(attrIt->m_value.GetLength()) != attrIt->m_value)
          return false;
      }
      else if (attrIt->m_pos == CFilterElementHideAttrPos::ENDING)
      {
        if (value.Right(attrIt->m_value.GetLength()) != attrIt->m_value)
          return false;
      }
      else if (attrIt->m_pos == CFilterElementHideAttrPos::ANYWHERE)
      {
        if (value.Find(attrIt->m_value) < 0)
          return false;
      }
      else if (attrIt->m_value.IsEmpty())
      {
        return true;
      }
    }
    else
    {
      return false;
    }
  }

  if (m_predecessor)
  {
    CComPtr<IHTMLElement> pDomPredecessor;
    HRESULT hr = S_FALSE;
    switch (m_predecessor->m_type)
    {
    case ETraverserComplexType::TRAVERSER_TYPE_PARENT:
      hr = pEl->get_parentElement(&pDomPredecessor);
      break;
    case ETraverserComplexType::TRAVERSER_TYPE_IMMEDIATE:
      hr = S_FALSE;
      CComQIPtr<IHTMLDOMNode> pPrevSiblingNode = pEl;
      long type = 0;
      while (pPrevSiblingNode && type != 1)
      {
        IHTMLDOMNode* tmpNode;
        pPrevSiblingNode->get_previousSibling(&tmpNode);
        pPrevSiblingNode.Attach(tmpNode);
        if (pPrevSiblingNode)
        {
          hr = pPrevSiblingNode->get_nodeType(&type);
          if (hr != S_OK)
            pPrevSiblingNode.Release();
        }
      }

      if (pPrevSiblingNode)
        hr = pPrevSiblingNode.QueryInterface(&pDomPredecessor);
      else
        return false;
      break;
    }
    if (hr != S_OK)
      return false;
    return m_predecessor->IsMatchFilterElementHide(pDomPredecessor);
  }

  return true;
}
bool CFilterElementHide::IsMatchFilterElementHide(IHTMLElement* pEl) const
{
  HRESULT hr;
  /*
   * If a tag id is specified, it must match
   */
  if (!m_tagId.empty())
  {
    CComBSTR idBstr;
    if (FAILED(pEl->get_id(&idBstr)) || !idBstr || m_tagId != ToWstring(idBstr))
    {
      return false;
    }
  }
  /*
   * If a class name is specified, it must match
   */
  if (!m_tagClassName.empty())
  {
    CComBSTR classNameListBstr;
    hr = pEl->get_className(&classNameListBstr);
    if (FAILED(hr) || !classNameListBstr)
    {
      return false; // We can't match a class name if there's no class name
    }
    std::wstring classNameList(ToWstring(classNameListBstr));
    if (classNameList.empty())
    {
      return false;
    }
    // TODO: Consider case of multiple classes. (m_tagClassName can be something like "foo.bar")
    /*
     * Match when 'm_tagClassName' appears as a token within classNameList
     */
    bool foundMatch = false;
    wchar_t* nextToken = nullptr;
    const wchar_t* token = wcstok_s(&classNameList[0], L" ", &nextToken);
    while (token != nullptr)
    {
      if (std::wstring(token) == m_tagClassName)
      {
        foundMatch = true;
        break;
      }
      token = wcstok_s(nullptr, L" ", &nextToken);
    }
    if (!foundMatch)
    {
      return false;
    }
  }
  /*
   * If a tag name is specified, it must match
   */
  if (!m_tag.empty())
  {
    CComBSTR tagNameBstr;
    if (FAILED(pEl->get_tagName(&tagNameBstr)) || !tagNameBstr)
    {
      return false;
    }
    if (m_tag != ToLowerString(ToWstring(tagNameBstr)))
    {
      return false;
    }
  }
  /*
   * Match each attribute
   */
  for (auto attrIt = m_attributeSelectors.begin(); attrIt != m_attributeSelectors.end(); ++attrIt)
  {
    std::wstring value;
    bool attrFound = false;
    if (attrIt->m_type == CFilterElementHideAttrType::STYLE)
    {
      CComPtr<IHTMLStyle> pStyle;
      if (SUCCEEDED(pEl->get_style(&pStyle)) && pStyle)
      {
        CComBSTR styleBstr;
        if (SUCCEEDED(pStyle->get_cssText(&styleBstr)) && styleBstr)
        {
          value = ToLowerString(ToWstring(styleBstr));
          attrFound = true;
        }
      }
    }
    else if (attrIt->m_type == CFilterElementHideAttrType::CLASS)
    {
      CComBSTR classNamesBstr;
      if (SUCCEEDED(pEl->get_className(&classNamesBstr)) && classNamesBstr)
      {
        value = ToWstring(classNamesBstr);
        attrFound = true;
      }
    }
    else if (attrIt->m_type == CFilterElementHideAttrType::ID)
    {
      CComBSTR idBstr;
      if (SUCCEEDED(pEl->get_id(&idBstr)) && idBstr)
      {
        value = ToWstring(idBstr);
        attrFound = true;
      }
    }
    else
    {
      CComBSTR attrArgument(attrIt->m_attr.length(), attrIt->m_attr.c_str());
      auto x = GetHtmlElementAttribute(*pEl, attrArgument);
      attrFound = x.isAttributeFound;
      if (attrFound)
      {
        value = x.attributeValue;
      }
    }

    if (attrFound)
    {
      if (attrIt->m_pos == CFilterElementHideAttrPos::EXACT)
      {
        // TODO: IE rearranges the style attribute completely. Figure out if anything can be done about it.
        if (value != attrIt->m_value)
          return false;
      }
      else if (attrIt->m_pos == CFilterElementHideAttrPos::STARTING)
      {
        if (value.compare(0, attrIt->m_value.length(), attrIt->m_value) != 0)
          return false;
      }
      else if (attrIt->m_pos == CFilterElementHideAttrPos::ENDING)
      {
        size_t valueLength = value.length();
        size_t attrLength = attrIt->m_value.length();
        if (valueLength < attrLength)
          return false;
        if (value.compare(valueLength - attrLength, attrLength, attrIt->m_value) != 0)
          return false;
      }
      else if (attrIt->m_pos == CFilterElementHideAttrPos::ANYWHERE)
      {
        if (value.find(attrIt->m_value) == std::wstring::npos)
          return false;
      }
      else if (attrIt->m_value.empty())
      {
        return true;
      }
    }
    else
    {
      return false;
    }
  }

  if (m_predecessor)
  {
    CComPtr<IHTMLElement> pDomPredecessor;
    HRESULT hr = S_FALSE;
    switch (m_predecessor->m_type)
    {
    case ETraverserComplexType::TRAVERSER_TYPE_PARENT:
      hr = pEl->get_parentElement(&pDomPredecessor);
      break;
    case ETraverserComplexType::TRAVERSER_TYPE_IMMEDIATE:
      hr = S_FALSE;
      CComQIPtr<IHTMLDOMNode> pPrevSiblingNode = pEl;
      long type = 0;
      while (pPrevSiblingNode && type != 1)
      {
        IHTMLDOMNode* tmpNode;
        pPrevSiblingNode->get_previousSibling(&tmpNode);
        pPrevSiblingNode.Attach(tmpNode);
        if (pPrevSiblingNode)
        {
          hr = pPrevSiblingNode->get_nodeType(&type);
          if (hr != S_OK)
            pPrevSiblingNode.Release();
        }
      }

      if (pPrevSiblingNode)
        hr = pPrevSiblingNode.QueryInterface(&pDomPredecessor);
      else
        return false;
      break;
    }
    if (hr != S_OK)
      return false;
    return m_predecessor->IsMatchFilterElementHide(pDomPredecessor);
  }

  return true;
}