Skip to content

Commit 83941be

Browse files
committed
Merge remote-tracking branch 'origin/master' into bandit
2 parents 05c0e76 + ffb931c commit 83941be

File tree

9 files changed

+158
-41
lines changed

9 files changed

+158
-41
lines changed

.editorconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
indent_style = space
6+
indent_size = 4
7+
insert_final_newline = true
8+
end_of_line = lf
9+
10+
[*.{yml,yaml}]
11+
indent_size = 2

.github/workflows/checks.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Checks
2+
on: [push, pull_request]
3+
4+
jobs:
5+
checks:
6+
runs-on: ubuntu-latest
7+
strategy:
8+
matrix:
9+
include:
10+
- python-version: 3
11+
env:
12+
TOXENV: security
13+
14+
steps:
15+
- uses: actions/checkout@v2
16+
17+
- name: Set up Python ${{ matrix.python-version }}
18+
uses: actions/setup-python@v2
19+
with:
20+
python-version: ${{ matrix.python-version }}
21+
22+
- name: Run check
23+
env: ${{ matrix.env }}
24+
run: |
25+
pip install -U pip
26+
pip install -U tox
27+
tox

.github/workflows/publish.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Publish
2+
on: [push]
3+
4+
jobs:
5+
publish:
6+
runs-on: ubuntu-latest
7+
if: startsWith(github.event.ref, 'refs/tags/')
8+
9+
steps:
10+
- uses: actions/checkout@v2
11+
12+
- name: Set up Python 3.8
13+
uses: actions/setup-python@v2
14+
with:
15+
python-version: 3
16+
17+
- name: Check Tag
18+
id: check-release-tag
19+
run: |
20+
if [[ ${{ github.event.ref }} =~ ^refs/tags/[0-9]+[.][0-9]+[.][0-9]+(rc[0-9]+|[.]dev[0-9]+)?$ ]]; then
21+
echo ::set-output name=release_tag::true
22+
fi
23+
24+
- name: Publish to PyPI
25+
if: steps.check-release-tag.outputs.release_tag == 'true'
26+
run: |
27+
pip install --upgrade setuptools wheel twine
28+
python setup.py sdist bdist_wheel
29+
export TWINE_USERNAME=__token__
30+
export TWINE_PASSWORD=${{ secrets.PYPI_TOKEN }}
31+
twine upload dist/*

.github/workflows/tests.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Tests
2+
on: [push, pull_request]
3+
4+
jobs:
5+
tests:
6+
runs-on: ubuntu-latest
7+
strategy:
8+
matrix:
9+
include:
10+
- python-version: 2.7
11+
env:
12+
TOXENV: py
13+
- python-version: 3.5
14+
env:
15+
TOXENV: py
16+
- python-version: 3.6
17+
env:
18+
TOXENV: py
19+
- python-version: 3.7
20+
env:
21+
TOXENV: py
22+
23+
steps:
24+
- uses: actions/checkout@v2
25+
26+
- name: Set up Python ${{ matrix.python-version }}
27+
uses: actions/setup-python@v2
28+
with:
29+
python-version: ${{ matrix.python-version }}
30+
31+
- name: Run tests
32+
env: ${{ matrix.env }}
33+
run: |
34+
pip install -U tox
35+
tox
36+
37+
- name: Upload coverage report
38+
run: bash <(curl -s https://codecov.io/bash)

.travis.yml

Lines changed: 0 additions & 27 deletions
This file was deleted.

cssselect/xpath.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def __repr__(self):
5656

5757
def add_condition(self, condition):
5858
if self.condition:
59-
self.condition = '%s and (%s)' % (self.condition, condition)
59+
self.condition = '(%s) and (%s)' % (self.condition, condition)
6060
else:
6161
self.condition = condition
6262
return self
@@ -180,7 +180,7 @@ def css_to_xpath(self, css, prefix='descendant-or-self::'):
180180
This string is prepended to the XPath expression for each selector.
181181
The default makes selectors scoped to the context node’s subtree.
182182
:raises:
183-
:class:`SelectorSyntaxError` on invalid selectors,
183+
:class:`~cssselect.SelectorSyntaxError` on invalid selectors,
184184
:class:`ExpressionError` on unknown/unsupported selectors,
185185
including pseudo-elements.
186186
:returns:
@@ -457,19 +457,19 @@ def xpath_nth_child_function(self, xpath, function, last=False,
457457
if a == 0:
458458
return xpath.add_condition('%s = %s' % (siblings_count, b_min_1))
459459

460-
expr = []
460+
expressions = []
461461

462462
if a > 0:
463463
# siblings count, an+b-1, is always >= 0,
464464
# so if a>0, and (b-1)<=0, an "n" exists to satisfy this,
465465
# therefore, the predicate is only interesting if (b-1)>0
466466
if b_min_1 > 0:
467-
expr.append('%s >= %s' % (siblings_count, b_min_1))
467+
expressions.append('%s >= %s' % (siblings_count, b_min_1))
468468
else:
469469
# if a<0, and (b-1)<0, no "n" satisfies this,
470470
# this is tested above as an early exist condition
471471
# otherwise,
472-
expr.append('%s <= %s' % (siblings_count, b_min_1))
472+
expressions.append('%s <= %s' % (siblings_count, b_min_1))
473473

474474
# operations modulo 1 or -1 are simpler, one only needs to verify:
475475
#
@@ -495,9 +495,14 @@ def xpath_nth_child_function(self, xpath, function, last=False,
495495
b_neg = '+%s' % b_neg
496496
left = '(%s %s)' % (left, b_neg)
497497

498-
expr.append('%s mod %s = 0' % (left, a))
498+
expressions.append('%s mod %s = 0' % (left, a))
499499

500-
xpath.add_condition(' and '.join(expr))
500+
if len(expressions) > 1:
501+
template = '(%s)'
502+
else:
503+
template = '%s'
504+
xpath.add_condition(' and '.join(template % expression
505+
for expression in expressions))
501506
return xpath
502507

503508
def xpath_nth_last_child_function(self, xpath, function):

tests/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
codecov
22
lxml;python_version!="3.4"
33
lxml<=4.3.5;python_version=="3.4"
4-
pytest
4+
pytest >=4.6, <4.7 # 4.7 drops support for Python 2.7 and 3.4
55
pytest-cov

tests/test_cssselect.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -428,8 +428,8 @@ def xpath(css):
428428
"e[count(preceding-sibling::*) <= 0]")
429429

430430
assert xpath('e:nth-child(3n+2)') == (
431-
"e[count(preceding-sibling::*) >= 1 and "
432-
"(count(preceding-sibling::*) +2) mod 3 = 0]")
431+
"e[(count(preceding-sibling::*) >= 1) and "
432+
"((count(preceding-sibling::*) +2) mod 3 = 0)]")
433433
assert xpath('e:nth-child(3n-2)') == (
434434
"e[count(preceding-sibling::*) mod 3 = 0]")
435435
assert xpath('e:nth-child(-n+6)') == (
@@ -442,8 +442,8 @@ def xpath(css):
442442
assert xpath('e:nth-last-child(2n+1)') == (
443443
"e[count(following-sibling::*) mod 2 = 0]")
444444
assert xpath('e:nth-last-child(2n+2)') == (
445-
"e[count(following-sibling::*) >= 1 and "
446-
"(count(following-sibling::*) +1) mod 2 = 0]")
445+
"e[(count(following-sibling::*) >= 1) and "
446+
"((count(following-sibling::*) +1) mod 2 = 0)]")
447447
assert xpath('e:nth-last-child(3n+1)') == (
448448
"e[count(following-sibling::*) mod 3 = 0]")
449449
# represents the two last e elements
@@ -497,7 +497,7 @@ def xpath(css):
497497
assert xpath('e > f') == (
498498
"e/f")
499499
assert xpath('e + f') == (
500-
"e/following-sibling::*[name() = 'f' and (position() = 1)]")
500+
"e/following-sibling::*[(name() = 'f') and (position() = 1)]")
501501
assert xpath('e ~ f') == (
502502
"e/following-sibling::f")
503503
assert xpath('e ~ f:nth-child(3)') == (
@@ -622,6 +622,11 @@ def xpath_attr_href_simple_pseudo_element(self, xpath):
622622
other = XPathExpr('@href', '', )
623623
return xpath.join('/', other)
624624

625+
# pseudo-element:
626+
# used to demonstrate operator precedence
627+
def xpath_first_or_second_pseudo(self, xpath):
628+
return xpath.add_condition("@id = 'first' or @id = 'second'")
629+
625630
def xpath(css):
626631
return _unicode(CustomTranslator().css_to_xpath(css))
627632

@@ -633,6 +638,25 @@ def xpath(css):
633638
assert xpath('p img::attr(src)') == (
634639
"descendant-or-self::p/descendant-or-self::*/img/@src")
635640
assert xpath(':scope') == "descendant-or-self::*[1]"
641+
assert xpath(':first-or-second[href]') == (
642+
"descendant-or-self::*[(@id = 'first' or @id = 'second') "
643+
"and (@href)]")
644+
645+
assert str(XPathExpr('', '', condition='@href')) == "[@href]"
646+
647+
document = etree.fromstring(OPERATOR_PRECEDENCE_IDS)
648+
sort_key = dict(
649+
(el, count) for count, el in enumerate(document.getiterator())
650+
).__getitem__
651+
def operator_id(selector):
652+
xpath = CustomTranslator().css_to_xpath(selector)
653+
items = document.xpath(xpath)
654+
items.sort(key=sort_key)
655+
return [element.get('id', 'nil') for element in items]
656+
657+
assert operator_id(':first-or-second') == ['first', 'second']
658+
assert operator_id(':first-or-second[href]') == ['second']
659+
assert operator_id('[href]:first-or-second') == ['second']
636660

637661
def test_series(self):
638662
def series(css):
@@ -935,6 +959,14 @@ def count(selector):
935959
assert count(':scope > div > div[class=dialog]') == 1
936960
assert count(':scope > div div') == 242
937961

962+
OPERATOR_PRECEDENCE_IDS = '''
963+
<html>
964+
<a id="first"></a>
965+
<a id="second" href="#"></a>
966+
<a id="third" href="#"></a>
967+
</html>
968+
'''
969+
938970
XMLLANG_IDS = '''
939971
<test>
940972
<a id="first" xml:lang="en">a</a>

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist = py27, py34, py35, py36, py37
2+
envlist = security,py
33

44
[testenv]
55
deps=

0 commit comments

Comments
 (0)