Make the XPath relative to the current context.

The returned XPath will match elements matching the XPath inside the current crawler
when running in the context of a node of the crawler.
7074
private function relativize(string $xpath): string
    {
        $expressions = [];

        // An expression which will never match to replace expressions which cannot match in the crawler
        // We cannot drop
        $nonMatchingExpression = 'a[name() = "b"]';

        $xpathLen = \strlen($xpath);
        $openedBrackets = 0;
        $startPosition = strspn($xpath, " \t\n\r\0\x0B");

        for ($i = $startPosition; $i <= $xpathLen; ++$i) {
            $i += strcspn($xpath, '"\'[]|', $i);

            if ($i < $xpathLen) {
                switch ($xpath[$i]) {
                    case '"':
                    case "'":
                        if (false === $i = strpos($xpath, $xpath[$i], $i + 1)) {
                            return $xpath; // The XPath expression is invalid
                        }
                        continue 2;
                    case '[':
                        ++$openedBrackets;
                        continue 2;
                    case ']':
                        --$openedBrackets;
                        continue 2;
                }
            }
            if ($openedBrackets) {
                continue;
            }

            if ($startPosition < $xpathLen && '(' === $xpath[$startPosition]) {
                // If the union is inside some braces, we need to preserve the opening braces and apply
                // the change only inside it.
                $j = 1 + strspn($xpath, "( \t\n\r\0\x0B", $startPosition + 1);
                $parenthesis = substr($xpath, $startPosition, $j);
                $startPosition += $j;
            } else {
                $parenthesis = '';
            }
            $expression = rtrim(substr($xpath, $startPosition, $i - $startPosition));

            if (0 === strpos($expression, 'self::*/')) {
                $expression = './'.substr($expression, 8);
            }

            // add prefix before absolute element selector
            if ('' === $expression) {
                $expression = $nonMatchingExpression;
            } elseif (0 === strpos($expression, '//')) {
                $expression = 'descendant-or-self::'.substr($expression, 2);
            } elseif (0 === strpos($expression, './/')) {
                $expression = 'descendant-or-self::'.substr($expression, 3);
            } elseif (0 === strpos($expression, './')) {
                $expression = 'self::'.substr($expression, 2);
            } elseif (0 === strpos($expression, 'child::')) {
                $expression = 'self::'.substr($expression, 7);
            } elseif ('/' === $expression[0] || '.' === $expression[0] || 0 === strpos($expression, 'self::')) {
                $expression = $nonMatchingExpression;
            } elseif (0 === strpos($expression, 'descendant::')) {
                $expression = 'descendant-or-self::'.substr($expression, 12);
            } elseif (preg_match('/^(ancestor|ancestor-or-self|attribute|following|following-sibling|namespace|parent|preceding|preceding-sibling)::/', $expression)) {
                // the fake root has no parent, preceding or following nodes and also no attributes (even no namespace attributes)
                $expression = $nonMatchingExpression;
            } elseif (0 !== strpos($expression, 'descendant-or-self::')) {
                $expression = 'self::'.$expression;
            }
            $expressions[] = $parenthesis.$expression;

            if ($i === $xpathLen) {
                return implode(' | ', $expressions);
            }

            $i += strspn($xpath, " \t\n\r\0\x0B", $i + 1);
            $startPosition = $i + 1;
        }

        return $xpath; // The XPath expression is invalid
    }
314786
def xpathNextParent(self, cur):
        
        if cur is None: cur__o = None
        else: cur__o = cur._o
        ret = libxml2mod.xmlXPathNextParent(self._o, cur__o)
        if ret is None:raise xpathError('xmlXPathNextParent() failed')
        __tmp = xmlNode(_obj=ret)
        return __tmp
688223
def xpathNewValueTree(self):
        
        ret = libxml2mod.xmlXPathNewValueTree(self._o)
        if ret is None:raise xpathError('xmlXPathNewValueTree() failed')
        return xpathObjectRet(ret)
954993
def elXpath(self, xpath, dom=None):
        """"""
        if dom is None:
            dom = self.browser
        return expect(dom.is_element_present_by_xpath, args=[xpath])
726230
def xpathNewNodeSet(self):
        
        ret = libxml2mod.xmlXPathNewNodeSet(self._o)
        if ret is None:raise xpathError('xmlXPathNewNodeSet() failed')
        return xpathObjectRet(ret)
134466
function( elem, context, xml ) {
			while ( (elem = elem[ dir ]) ) {
				if ( checkNonElements || elem.nodeType === 1  ) {
					return matcher( elem, context, xml );
				}
			}
		}
349615
def update_xml_element(self):
        

        if not hasattr(self, 'xml_element'):
            self.xml_element = etree.Element(self.name, nsmap=NSMAP)

        if hasattr(self, 'idref'):
            self.xml_element.set('idref', self.idref)

        return self.xml_element
314476
def format_xpaths(xpath_map, *args, **kwargs):
    

    formatted = {}.fromkeys(xpath_map)

    for key, xpath in iteritems(xpath_map):
        formatted[key] = xpath.format(*args, **kwargs)

    return formatted
723378
def xpath(node, query, namespaces={}):
    """"""
    if namespaces and 'None' not in namespaces:
        return node.xpath(query, namespaces=namespaces)
    return node.xpath(query)
1262885
def at_xpath(self, xpath, timeout = DEFAULT_AT_TIMEOUT, **kw):
    
    return self.wait_for_safe(lambda: super(WaitMixin, self).at_xpath(xpath),
                              timeout = timeout,
                              **kw)
498137
def _grab_xpath(root, xpath, converter=lambda x: x):
    
    elements = root.xpath(xpath)
    if elements:
        return converter(str(elements[0]))
    else:
        return None
578270
def get_element_by_xpath(self, xpath_str, namespaces=None):
        
        try:
            return self._root_el.findall('.' + xpath_str, namespaces)
        except (ValueError, xml.etree.ElementTree.ParseError) as e:
            raise SimpleXMLWrapperException(
                'XPath select raised exception. xpath_str="{}" error="{}"'.format(
                    xpath_str, str(e)
                )
            )
641215
def context(self):
        
        ret = libxml2mod.xmlXPathParserGetContext(self._o)
        if ret is None:raise xpathError('xmlXPathParserGetContext() failed')
        __tmp = xpathContext(_obj=ret)
        return __tmp
867917
def xpathNextSelf(self, cur):
        
        if cur is None: cur__o = None
        else: cur__o = cur._o
        ret = libxml2mod.xmlXPathNextSelf(self._o, cur__o)
        if ret is None:raise xpathError('xmlXPathNextSelf() failed')
        __tmp = xmlNode(_obj=ret)
        return __tmp
1163373
def xpath_selector(selector, html, select_all):
    
    from defusedxml import lxml as dlxml
    from lxml import etree
    import re

    # lxml requires argument to be bytes
    # see https://github.com/kibitzr/kibitzr/issues/47
    encoded = html.encode('utf-8')
    root = dlxml.fromstring(encoded, parser=etree.HTMLParser())
    xpath_results = root.xpath(selector)
    if not xpath_results:
        logger.warning('XPath selector not found: %r', selector)
        return False, html

    if isinstance(xpath_results, list):
        if select_all is False:
            xpath_results = xpath_results[0:1]
    else:
        xpath_results = [xpath_results]

    # Serialize xpath_results
    # see https://lxml.de/xpathxslt.html#xpath-return-values
    results = []
    for r in xpath_results:
        # namespace declarations
        if isinstance(r, tuple):
            results.append("%s=\"%s\"" % (r[0], r[1]))
        # an element
        elif hasattr(r, 'tag'):
            results.append(
                re.sub(r'\s+', ' ',
                       dlxml.tostring(r, method='html', encoding='unicode'))
            )
        else:
            results.append(r)

    return True, u"\n".join(six.text_type(x).strip() for x in results)
935220
def xpathNextDescendant(self, ctxt):
        
        if ctxt is None: ctxt__o = None
        else: ctxt__o = ctxt._o
        ret = libxml2mod.xmlXPathNextDescendant(ctxt__o, self._o)
        if ret is None:raise xpathError('xmlXPathNextDescendant() failed')
        __tmp = xmlNode(_obj=ret)
        return __tmp
967117
def xpath(self, xpath, dom=None):
        """"""
        if dom is None:
            dom = self.browser
        return expect(dom.find_by_xpath, args=[xpath])