Skip to content

Commit

Permalink
pagelems: pe-not, a negative-match element
Browse files Browse the repository at this point in the history
As name and docs suggest, it inverts the logic to specify that
some element should NOT be contained in its parent.
  • Loading branch information
xrg committed May 21, 2019
1 parent 560b670 commit 1483cc4
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 2 deletions.
4 changes: 4 additions & 0 deletions behave_manners/pagelems/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ def pretty_parent(self):
return repr(self.parent)


class UnwantedElement(NoSuchElementException):
pass


class PageNotReady(AssertionError):
"""Raised when browser has not finished rendering/settled the page
"""
Expand Down
78 changes: 76 additions & 2 deletions behave_manners/pagelems/page_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
from copy import deepcopy
from collections import defaultdict

from .helpers import textescape, prepend_xpath, word_re, to_bool
from .helpers import textescape, prepend_xpath, word_re, to_bool, Integer
from .base_parsers import DPageElement, DataElement, BaseDPOParser, \
HTMLParseError, DOMScope
from .site_collection import DSiteCollection
from .exceptions import ElementNotFound, CAttributeError, CKeyError
from .exceptions import ElementNotFound, CAttributeError, CKeyError, \
UnwantedElement
from selenium.webdriver.remote.webdriver import WebElement
from selenium.common.exceptions import NoSuchElementException
import six
Expand Down Expand Up @@ -297,6 +298,77 @@ def consume(self, element):
raise TypeError('%s cannot consume %r' % (self._name, element))


class PeNotElement(DPageElement):
"""Negative-match element: check that element does NOT contain others
Negative-match will invert the meaning of contained matches, thus
not selecting parents that contain specified patterns.
Example::
<div class="eggs">
<pe-not><div class="spam"></div>
</pe-not>
</div>
Meaning that it will match a `div@class=eggs` that does NOT contain
a spam `div` .
When multiple children elements are specified inside pe-not, then
*all* of them should be present for parent to mis-match. Having any
of the children but not all, will allow the parent to match.
The other logic, failing the parent if any children exist, is possible
by using multiple `<pe-not>` elements.
Do not use named elements (with `this`) or logic of `pe-choice`,
`pe-repeat` or pe-optional elements inside a `pe-not`. As it will never
create components, such logic is pointless.
"""
_name = 'tag.pe-not'
_inherit = '.domContainer'

def reduce(self, site=None):
n = super(PeNotElement, self).reduce(site)
if n is self and not self._children:
raise ValueError("<pe-not> must have children")
return n

def xpath_locator(self, score, top=False):
"""Force that this will not match a narrow locator of its contents
"""
score2 = Integer(1000)
child_locs = []
for c in self._children:
cloc = c.xpath_locator(score2)
if cloc and cloc != '*':
child_locs.append(cloc)

locator = ' and '.join(child_locs)
if top:
return '*[' + locator + ']'
else:
return 'not(%s)' % locator

def _locate_in(self, remote, scope, xpath_prefix, match):
if (not xpath_prefix):
xpath2 = 'self::' + self.xpath
else:
xpath2 = prepend_xpath(xpath_prefix, self.xpath)
if remote.find_elements_by_xpath(xpath2):
raise UnwantedElement(parent=remote, selector=xpath2)
return ()

def iter_items(self, remote, scope, xpath_prefix='', match=None):
"""This element should never participate as a component
"""
raise RuntimeError("<pe-not> found materialized")

def iter_attrs(self, webelem=None, scope=None, xpath_prefix=''):
raise RuntimeError("<pe-not> found materialized")


class TextAttrGetter(AttrGetter):
"""Descriptor that returns the text of some DOM element
"""
Expand Down Expand Up @@ -879,6 +951,8 @@ def _locate_in(self, remote, scope, xpath_prefix, match):
seen.add(welem)
yield n, welem, p, scp
nfound += 1
except UnwantedElement:
pass
except ElementNotFound as e:
if enofound is None:
enofound = e
Expand Down

0 comments on commit 1483cc4

Please sign in to comment.