[Python-modules-commits] [python-cssselect] 01/03: Import python-cssselect_1.0.0.orig.tar.gz
Wolfgang Borgert
debacle at moszumanska.debian.org
Thu Dec 8 08:44:30 UTC 2016
This is an automated email from the git hooks/post-receive script.
debacle pushed a commit to branch master
in repository python-cssselect.
commit 80923de732e6623088c6aa08052a5917c37faed8
Author: W. Martin Borgert <debacle at debian.org>
Date: Thu Dec 8 00:05:57 2016 +0000
Import python-cssselect_1.0.0.orig.tar.gz
---
.bumpversion.cfg | 7 ++
.coveragerc | 1 +
.gitignore | 7 ++
.travis.yml | 22 ++++
CHANGES | 10 ++
PKG-INFO | 46 -------
cssselect.egg-info/PKG-INFO | 46 -------
cssselect.egg-info/SOURCES.txt | 19 ---
cssselect.egg-info/dependency_links.txt | 1 -
cssselect.egg-info/top_level.txt | 1 -
cssselect/__init__.py | 2 +-
cssselect/xpath.py | 167 +++++++++++++++++++-------
docs/conf.py | 2 +-
setup.cfg | 13 +-
tests/__init__.py | 0
cssselect/tests.py => tests/test_cssselect.py | 116 ++++++++++++++----
tox.ini | 9 +-
17 files changed, 276 insertions(+), 193 deletions(-)
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
new file mode 100644
index 0000000..426ea28
--- /dev/null
+++ b/.bumpversion.cfg
@@ -0,0 +1,7 @@
+[bumpversion]
+current_version = 1.0.0
+commit = True
+tag = True
+
+[bumpversion:file:cssselect/__init__.py]
+
diff --git a/.coveragerc b/.coveragerc
index 2ee5ff3..ed1fac6 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,5 +1,6 @@
[run]
branch = True
+source = cssselect
[report]
exclude_lines =
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4c89f4c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+*.pyc
+*.egg-info
+/.tox
+/MANIFEST
+/dist
+/docs/_build
+/.coverage
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..a89d5b3
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,22 @@
+language: python
+
+python:
+ - "2.6"
+ - "2.7"
+ - "3.2"
+ - "3.3"
+ - "3.4"
+ - "3.5"
+
+install:
+ - pip install lxml -e .
+ - pip install -U codecov pytest-cov
+ - if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]];
+ then pip uninstall -y coverage pytest && pip install "coverage<4" && pip install "pytest<3";
+ fi
+
+script:
+ py.test --cov-report term --cov=cssselect
+
+after_success:
+ codecov
diff --git a/CHANGES b/CHANGES
index 5ae9a39..94abe77 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,16 @@
Changelog
=========
+Version 1.0.0
+-------------
+
+Released on 2016-10-21.
+
+* Add code coverage reports.
+* Fix ``:nth-*(an+b)`` pseudo-classes selectors.
+ (except ``*:nth-child()`` which looks untranslatable to XPath 1.0.)
+
+
Version 0.9.2
-------------
diff --git a/PKG-INFO b/PKG-INFO
deleted file mode 100644
index ecdb2a4..0000000
--- a/PKG-INFO
+++ /dev/null
@@ -1,46 +0,0 @@
-Metadata-Version: 1.1
-Name: cssselect
-Version: 0.9.2
-Summary: cssselect parses CSS3 Selectors and translates them to XPath 1.0
-Home-page: https://pythonhosted.org/cssselect/
-Author: Paul Tremberth
-Author-email: paul.tremberth at gmail.com
-License: BSD
-Description: ===================================
- cssselect: CSS Selectors for Python
- ===================================
-
- *cssselect* parses `CSS3 Selectors`_ and translate them to `XPath 1.0`_
- expressions. Such expressions can be used in lxml_ or another XPath engine
- to find the matching elements in an XML or HTML document.
-
- This module used to live inside of lxml as ``lxml.cssselect`` before it was
- extracted as a stand-alone project.
-
- .. _CSS3 Selectors: https://www.w3.org/TR/css3-selectors/
- .. _XPath 1.0: https://www.w3.org/TR/xpath/
- .. _lxml: http://lxml.de/
-
-
- Quick facts:
-
- * Free software: BSD licensed
- * Compatible with Python 2.6+ and 3.2+
- * Latest documentation `on python.org <https://pythonhosted.org/cssselect/>`_
- * Source, issues and pull requests `on Github
- <https://github.com/scrapy/cssselect>`_
- * Releases `on PyPI <http://pypi.python.org/pypi/cssselect>`_
- * Install with ``pip install cssselect``
-
-Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: BSD License
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.6
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.2
-Classifier: Programming Language :: Python :: 3.3
-Classifier: Programming Language :: Python :: 3.4
-Classifier: Programming Language :: Python :: 3.5
diff --git a/cssselect.egg-info/PKG-INFO b/cssselect.egg-info/PKG-INFO
deleted file mode 100644
index ecdb2a4..0000000
--- a/cssselect.egg-info/PKG-INFO
+++ /dev/null
@@ -1,46 +0,0 @@
-Metadata-Version: 1.1
-Name: cssselect
-Version: 0.9.2
-Summary: cssselect parses CSS3 Selectors and translates them to XPath 1.0
-Home-page: https://pythonhosted.org/cssselect/
-Author: Paul Tremberth
-Author-email: paul.tremberth at gmail.com
-License: BSD
-Description: ===================================
- cssselect: CSS Selectors for Python
- ===================================
-
- *cssselect* parses `CSS3 Selectors`_ and translate them to `XPath 1.0`_
- expressions. Such expressions can be used in lxml_ or another XPath engine
- to find the matching elements in an XML or HTML document.
-
- This module used to live inside of lxml as ``lxml.cssselect`` before it was
- extracted as a stand-alone project.
-
- .. _CSS3 Selectors: https://www.w3.org/TR/css3-selectors/
- .. _XPath 1.0: https://www.w3.org/TR/xpath/
- .. _lxml: http://lxml.de/
-
-
- Quick facts:
-
- * Free software: BSD licensed
- * Compatible with Python 2.6+ and 3.2+
- * Latest documentation `on python.org <https://pythonhosted.org/cssselect/>`_
- * Source, issues and pull requests `on Github
- <https://github.com/scrapy/cssselect>`_
- * Releases `on PyPI <http://pypi.python.org/pypi/cssselect>`_
- * Install with ``pip install cssselect``
-
-Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: BSD License
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.6
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.2
-Classifier: Programming Language :: Python :: 3.3
-Classifier: Programming Language :: Python :: 3.4
-Classifier: Programming Language :: Python :: 3.5
diff --git a/cssselect.egg-info/SOURCES.txt b/cssselect.egg-info/SOURCES.txt
deleted file mode 100644
index c1037d3..0000000
--- a/cssselect.egg-info/SOURCES.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-.coveragerc
-AUTHORS
-CHANGES
-LICENSE
-MANIFEST.in
-README.rst
-setup.cfg
-setup.py
-tox.ini
-cssselect/__init__.py
-cssselect/parser.py
-cssselect/tests.py
-cssselect/xpath.py
-cssselect.egg-info/PKG-INFO
-cssselect.egg-info/SOURCES.txt
-cssselect.egg-info/dependency_links.txt
-cssselect.egg-info/top_level.txt
-docs/conf.py
-docs/index.rst
\ No newline at end of file
diff --git a/cssselect.egg-info/dependency_links.txt b/cssselect.egg-info/dependency_links.txt
deleted file mode 100644
index 8b13789..0000000
--- a/cssselect.egg-info/dependency_links.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/cssselect.egg-info/top_level.txt b/cssselect.egg-info/top_level.txt
deleted file mode 100644
index d2a154e..0000000
--- a/cssselect.egg-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-cssselect
diff --git a/cssselect/__init__.py b/cssselect/__init__.py
index ed330ac..f46a0e4 100644
--- a/cssselect/__init__.py
+++ b/cssselect/__init__.py
@@ -18,5 +18,5 @@ from cssselect.parser import (parse, Selector, FunctionalPseudoElement,
from cssselect.xpath import GenericTranslator, HTMLTranslator, ExpressionError
-VERSION = '0.9.2'
+VERSION = '1.0.0'
__version__ = VERSION
diff --git a/cssselect/xpath.py b/cssselect/xpath.py
index b0913ab..698748a 100644
--- a/cssselect/xpath.py
+++ b/cssselect/xpath.py
@@ -376,40 +376,127 @@ class GenericTranslator(object):
a, b = parse_series(function.arguments)
except ValueError:
raise ExpressionError("Invalid series: '%r'" % function.arguments)
+
+ # From https://www.w3.org/TR/css3-selectors/#structural-pseudos:
+ #
+ # :nth-child(an+b)
+ # an+b-1 siblings before
+ #
+ # :nth-last-child(an+b)
+ # an+b-1 siblings after
+ #
+ # :nth-of-type(an+b)
+ # an+b-1 siblings with the same expanded element name before
+ #
+ # :nth-last-of-type(an+b)
+ # an+b-1 siblings with the same expanded element name after
+ #
+ # So,
+ # for :nth-child and :nth-of-type
+ #
+ # count(preceding-sibling::<nodetest>) = an+b-1
+ #
+ # for :nth-last-child and :nth-last-of-type
+ #
+ # count(following-sibling::<nodetest>) = an+b-1
+ #
+ # therefore,
+ # count(...) - (b-1) ≡ 0 (mod a)
+ #
+ # if a == 0:
+ # ~~~~~~~~~~
+ # count(...) = b-1
+ #
+ # if a < 0:
+ # ~~~~~~~~~
+ # count(...) - b +1 <= 0
+ # -> count(...) <= b-1
+ #
+ # if a > 0:
+ # ~~~~~~~~~
+ # count(...) - b +1 >= 0
+ # -> count(...) >= b-1
+
+ # work with b-1 instead
+ b_min_1 = b - 1
+
+ # early-exit condition 1:
+ # ~~~~~~~~~~~~~~~~~~~~~~~
+ # for a == 1, nth-*(an+b) means n+b-1 siblings before/after,
+ # and since n ∈ {0, 1, 2, ...}, if b-1<=0,
+ # there is always an "n" matching any number of siblings (maybe none)
+ if a == 1 and b_min_1 <=0:
+ return xpath
+
+ # early-exit condition 2:
+ # ~~~~~~~~~~~~~~~~~~~~~~~
+ # an+b-1 siblings with a<0 and (b-1)<0 is not possible
+ if a < 0 and b_min_1 < 0:
+ return xpath.add_condition('0')
+
+ # `add_name_test` boolean is inverted and somewhat counter-intuitive:
+ #
+ # nth_of_type() calls nth_child(add_name_test=False)
if add_name_test:
- xpath.add_name_test()
- xpath.add_star_prefix()
- if a == 0:
- if last:
- b = 'last() - %s' % b
- return xpath.add_condition('position() = %s' % b)
- if last:
- # FIXME: I'm not sure if this is right
- a = -a
- b = -b
- if b > 0:
- b_neg = str(-b)
+ nodetest = '*'
+ else:
+ nodetest = '%s' % xpath.element
+
+ # count siblings before or after the element
+ if not last:
+ siblings_count = 'count(preceding-sibling::%s)' % nodetest
else:
- b_neg = '+%s' % (-b)
- if a != 1:
- expr = ['(position() %s) mod %s = 0' % (b_neg, a)]
+ siblings_count = 'count(following-sibling::%s)' % nodetest
+
+ # special case of fixed position: nth-*(0n+b)
+ # if a == 0:
+ # ~~~~~~~~~~
+ # count(***-sibling::***) = b-1
+ if a == 0:
+ return xpath.add_condition('%s = %s' % (siblings_count, b_min_1))
+
+ expr = []
+
+ if a > 0:
+ # siblings count, an+b-1, is always >= 0,
+ # so if a>0, and (b-1)<=0, an "n" exists to satisfy this,
+ # therefore, the predicate is only interesting if (b-1)>0
+ if b_min_1 > 0:
+ expr.append('%s >= %s' % (siblings_count, b_min_1))
else:
- expr = []
- if b >= 0:
- expr.append('position() >= %s' % b)
- elif b < 0 and last:
- expr.append('position() < (last() %s)' % b)
- expr = ' and '.join(expr)
- if expr:
- xpath.add_condition(expr)
+ # if a<0, and (b-1)<0, no "n" satisfies this,
+ # this is tested above as an early exist condition
+ # otherwise,
+ expr.append('%s <= %s' % (siblings_count, b_min_1))
+
+ # operations modulo 1 or -1 are simpler, one only needs to verify:
+ #
+ # - either:
+ # count(***-sibling::***) - (b-1) = n = 0, 1, 2, 3, etc.,
+ # i.e. count(***-sibling::***) >= (b-1)
+ #
+ # - or:
+ # count(***-sibling::***) - (b-1) = -n = 0, -1, -2, -3, etc.,
+ # i.e. count(***-sibling::***) <= (b-1)
+ # we we just did above.
+ #
+ if abs(a) != 1:
+ # count(***-sibling::***) - (b-1) ≡ 0 (mod a)
+ left = siblings_count
+
+ # apply "modulo a" on 2nd term, -(b-1),
+ # to simplify things like "(... +6) % -3",
+ # and also make it positive with |a|
+ b_neg = (-b_min_1) % abs(a)
+
+ if b_neg != 0:
+ b_neg = '+%s' % (b_neg)
+ left = '(%s %s)' % (left, b_neg)
+
+ expr.append('%s mod %s = 0' % (left, a))
+
+ xpath.add_condition(' and '.join(expr))
return xpath
- # FIXME: handle an+b, odd, even
- # an+b means every-a, plus b, e.g., 2n+1 means odd
- # 0n+b means b
- # n+0 means a=1, i.e., all elements
- # an means every a elements, i.e., 2n means even
- # -n means -1n
- # -1n+6 means elements 6 and previous
def xpath_nth_last_child_function(self, xpath, function):
return self.xpath_nth_child_function(xpath, function, last=True)
@@ -455,39 +542,31 @@ class GenericTranslator(object):
return xpath.add_condition("not(parent::*)")
def xpath_first_child_pseudo(self, xpath):
- xpath.add_star_prefix()
- xpath.add_name_test()
- return xpath.add_condition('position() = 1')
+ return xpath.add_condition('count(preceding-sibling::*) = 0')
def xpath_last_child_pseudo(self, xpath):
- xpath.add_star_prefix()
- xpath.add_name_test()
- return xpath.add_condition('position() = last()')
+ return xpath.add_condition('count(following-sibling::*) = 0')
def xpath_first_of_type_pseudo(self, xpath):
if xpath.element == '*':
raise ExpressionError(
"*:first-of-type is not implemented")
- xpath.add_star_prefix()
- return xpath.add_condition('position() = 1')
+ return xpath.add_condition('count(preceding-sibling::%s) = 0' % xpath.element)
def xpath_last_of_type_pseudo(self, xpath):
if xpath.element == '*':
raise ExpressionError(
"*:last-of-type is not implemented")
- xpath.add_star_prefix()
- return xpath.add_condition('position() = last()')
+ return xpath.add_condition('count(following-sibling::%s) = 0' % xpath.element)
def xpath_only_child_pseudo(self, xpath):
- xpath.add_name_test()
- xpath.add_star_prefix()
- return xpath.add_condition('last() = 1')
+ return xpath.add_condition('count(parent::*/child::*) = 1')
def xpath_only_of_type_pseudo(self, xpath):
if xpath.element == '*':
raise ExpressionError(
"*:only-of-type is not implemented")
- return xpath.add_condition('last() = 1')
+ return xpath.add_condition('count(parent::*/child::%s) = 1' % xpath.element)
def xpath_empty_pseudo(self, xpath):
return xpath.add_condition("not(*) and not(string-length())")
diff --git a/docs/conf.py b/docs/conf.py
index 22e6032..b2612d0 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -95,7 +95,7 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-#html_theme = 'agogo'
+html_theme = 'classic'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
diff --git a/setup.cfg b/setup.cfg
index 0ab3578..270daee 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,18 +1,13 @@
[build_sphinx]
source-dir = docs
-build-dir = docs/_build
+build-dir = docs/_build
+#all_files = 1
-[upload_sphinx]
+[upload_sphinx] # Sphinx-PyPI-upload
upload-dir = docs/_build/html
[pytest]
-python_files = tests.py
+testpaths = tests
[bdist_wheel]
universal = 1
-
-[egg_info]
-tag_build =
-tag_date = 0
-tag_svn_revision = 0
-
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/cssselect/tests.py b/tests/test_cssselect.py
old mode 100755
new mode 100644
similarity index 92%
rename from cssselect/tests.py
rename to tests/test_cssselect.py
index 567e3c5..4a0bd39
--- a/cssselect/tests.py
+++ b/tests/test_cssselect.py
@@ -334,35 +334,73 @@ class TestCssselect(unittest.TestCase):
assert xpath('e[hreflang|="en"]') == (
"e[@hreflang and ("
"@hreflang = 'en' or starts-with(@hreflang, 'en-'))]")
+
+ # --- nth-* and nth-last-* -------------------------------------
assert xpath('e:nth-child(1)') == (
- "*/*[name() = 'e' and (position() = 1)]")
+ "e[count(preceding-sibling::*) = 0]")
+
+ # always true
+ assert xpath('e:nth-child(n)') == (
+ "e")
+ assert xpath('e:nth-child(n+1)') == (
+ "e")
+ # always true too
+ assert xpath('e:nth-child(n-10)') == (
+ "e")
+ # b=2 is the limit...
+ assert xpath('e:nth-child(n+2)') == (
+ "e[count(preceding-sibling::*) >= 1]")
+ # always false
+ assert xpath('e:nth-child(-n)') == (
+ "e[0]")
+ # equivalent to first child
+ assert xpath('e:nth-child(-n+1)') == (
+ "e[count(preceding-sibling::*) <= 0]")
+
+ assert xpath('e:nth-child(3n+2)') == (
+ "e[count(preceding-sibling::*) >= 1 and "
+ "(count(preceding-sibling::*) +2) mod 3 = 0]")
+ assert xpath('e:nth-child(3n-2)') == (
+ "e[count(preceding-sibling::*) mod 3 = 0]")
+ assert xpath('e:nth-child(-n+6)') == (
+ "e[count(preceding-sibling::*) <= 5]")
+
assert xpath('e:nth-last-child(1)') == (
- "*/*[name() = 'e' and (position() = last() - 1)]")
+ "e[count(following-sibling::*) = 0]")
+ assert xpath('e:nth-last-child(2n)') == (
+ "e[(count(following-sibling::*) +1) mod 2 = 0]")
+ assert xpath('e:nth-last-child(2n+1)') == (
+ "e[count(following-sibling::*) mod 2 = 0]")
assert xpath('e:nth-last-child(2n+2)') == (
- "*/*[name() = 'e' and ("
- "(position() +2) mod -2 = 0 and position() < (last() -2))]")
+ "e[count(following-sibling::*) >= 1 and "
+ "(count(following-sibling::*) +1) mod 2 = 0]")
+ assert xpath('e:nth-last-child(3n+1)') == (
+ "e[count(following-sibling::*) mod 3 = 0]")
+ # represents the two last e elements
+ assert xpath('e:nth-last-child(-n+2)') == (
+ "e[count(following-sibling::*) <= 1]")
+
assert xpath('e:nth-of-type(1)') == (
- "*/e[position() = 1]")
- assert xpath('e:nth-last-of-type(1)') == (
- "*/e[position() = last() - 1]")
+ "e[count(preceding-sibling::e) = 0]")
assert xpath('e:nth-last-of-type(1)') == (
- "*/e[position() = last() - 1]")
+ "e[count(following-sibling::e) = 0]")
assert xpath('div e:nth-last-of-type(1) .aclass') == (
- "div/descendant-or-self::*/e[position() = last() - 1]"
+ "div/descendant-or-self::*/e[count(following-sibling::e) = 0]"
"/descendant-or-self::*/*[@class and contains("
"concat(' ', normalize-space(@class), ' '), ' aclass ')]")
+
assert xpath('e:first-child') == (
- "*/*[name() = 'e' and (position() = 1)]")
+ "e[count(preceding-sibling::*) = 0]")
assert xpath('e:last-child') == (
- "*/*[name() = 'e' and (position() = last())]")
+ "e[count(following-sibling::*) = 0]")
assert xpath('e:first-of-type') == (
- "*/e[position() = 1]")
+ "e[count(preceding-sibling::e) = 0]")
assert xpath('e:last-of-type') == (
- "*/e[position() = last()]")
+ "e[count(following-sibling::e) = 0]")
assert xpath('e:only-child') == (
- "*/*[name() = 'e' and (last() = 1)]")
+ "e[count(parent::*/child::*) = 1]")
assert xpath('e:only-of-type') == (
- "e[last() = 1]")
+ "e[count(parent::*/child::e) = 1]")
assert xpath('e:empty') == (
"e[not(*) and not(string-length())]")
assert xpath('e:EmPTY') == (
@@ -381,7 +419,7 @@ class TestCssselect(unittest.TestCase):
assert xpath('e#myid') == (
"e[@id = 'myid']")
assert xpath('e:not(:nth-child(odd))') == (
- "e[not((position() -1) mod 2 = 0 and position() >= 1)]")
+ "e[not(count(preceding-sibling::*) mod 2 = 0)]")
assert xpath('e:nOT(*)') == (
"e[0]") # never matches
assert xpath('e f') == (
@@ -392,6 +430,8 @@ class TestCssselect(unittest.TestCase):
"e/following-sibling::*[name() = 'f' and (position() = 1)]")
assert xpath('e ~ f') == (
"e/following-sibling::f")
+ assert xpath('e ~ f:nth-child(3)') == (
+ "e/following-sibling::f[count(preceding-sibling::*) = 2]")
assert xpath('div#container p') == (
"div[@id = 'container']/descendant-or-self::*/p")
@@ -632,7 +672,18 @@ class TestCssselect(unittest.TestCase):
assert pcss(':lang("EN")', '*:lang(en-US)', html_only=True) == [
'second-li', 'li-div']
assert pcss(':lang("e")', html_only=True) == []
- assert pcss('li:nth-child(3)') == ['third-li']
+
+ # --- nth-* and nth-last-* -------------------------------------
+
+ # select nothing
+ assert pcss('li:nth-child(-n)') == []
+ # select all children
+ assert pcss('li:nth-child(n)') == [
+ 'first-li', 'second-li', 'third-li', 'fourth-li',
+ 'fifth-li', 'sixth-li', 'seventh-li']
+
+ assert pcss('li:nth-child(3)',
+ '#first-li ~ :nth-child(3)') == ['third-li']
assert pcss('li:nth-child(10)') == []
assert pcss('li:nth-child(2n)', 'li:nth-child(even)',
'li:nth-child(2n+0)') == [
@@ -640,19 +691,36 @@ class TestCssselect(unittest.TestCase):
assert pcss('li:nth-child(+2n+1)', 'li:nth-child(odd)') == [
'first-li', 'third-li', 'fifth-li', 'seventh-li']
assert pcss('li:nth-child(2n+4)') == ['fourth-li', 'sixth-li']
- # FIXME: I'm not 100% sure this is right:
assert pcss('li:nth-child(3n+1)') == [
'first-li', 'fourth-li', 'seventh-li']
- assert pcss('li:nth-last-child(0)') == [
- 'seventh-li']
+ assert pcss('li:nth-child(-n+3)') == [
+ 'first-li', 'second-li', 'third-li']
+ assert pcss('li:nth-child(-2n+4)') == ['second-li', 'fourth-li']
+ assert pcss('li:nth-last-child(0)') == []
+ assert pcss('li:nth-last-child(1)') == ['seventh-li']
assert pcss('li:nth-last-child(2n)', 'li:nth-last-child(even)') == [
'second-li', 'fourth-li', 'sixth-li']
- assert pcss('li:nth-last-child(2n+2)') == ['second-li', 'fourth-li']
+ assert pcss('li:nth-last-child(2n+1)') == [
+ 'first-li', 'third-li', 'fifth-li', 'seventh-li']
+ assert pcss('li:nth-last-child(2n+2)') == [
+ 'second-li', 'fourth-li', 'sixth-li']
+ assert pcss('li:nth-last-child(3n+1)') == [
+ 'first-li', 'fourth-li', 'seventh-li']
assert pcss('ol:first-of-type') == ['first-ol']
assert pcss('ol:nth-child(1)') == []
assert pcss('ol:nth-of-type(2)') == ['second-ol']
- # FIXME: like above', '(1) or (2)?
- assert pcss('ol:nth-last-of-type(1)') == ['first-ol']
+ assert pcss('ol:nth-last-of-type(1)') == ['second-ol']
+
+ # "+" and "~" tests
+ assert pcss('ol#first-ol li + li:nth-child(4)') == ['fourth-li']
+ assert pcss('li + li:nth-child(1)') == []
+ assert pcss('li ~ li:nth-child(2n+1)') == [
+ 'third-li', 'fifth-li', 'seventh-li'
+ ] # all but the first
+ assert pcss('li ~ li:nth-last-child(2n+1)') == [
+ 'third-li', 'fifth-li', 'seventh-li'
+ ] # all but the first
+
assert pcss('span:only-child') == ['foobar-span']
assert pcss('li div:only-child') == ['li-div']
assert pcss('div *:only-child') == ['li-div', 'foobar-span']
@@ -693,6 +761,8 @@ class TestCssselect(unittest.TestCase):
assert pcss('ol :Not(li[class])') == [
'first-li', 'second-li', 'li-div',
'fifth-li', 'sixth-li', 'seventh-li']
+ assert pcss('ol.a.b.c > li.c:nth-child(3)') == ['third-li']
+
# Invalid characters in XPath element names, should not crash
assert pcss(r'di\a0 v', r'div\[') == []
assert pcss(r'[h\a0 ref]', r'[h\]ref]') == []
diff --git a/tox.ini b/tox.ini
index ca053d8..7a3359a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,8 +2,13 @@
envlist = py25,py26,py27,py32,py33
[testenv]
-deps=lxml
-commands = python cssselect/tests.py
+deps=
+ lxml
+ pytest<3
+ pytest-cov
+
+commands =
+ py.test --cov-report term --cov=cssselect
[testenv:py25]
setenv =
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-cssselect.git
More information about the Python-modules-commits
mailing list