From 84cfe690472749a4db567355bac4827924120175 Mon Sep 17 00:00:00 2001
From: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com>
Date: Sat, 25 Jul 2026 12:30:15 -0700
Subject: [PATCH] Support negative indexes in the :eq() selector
`:eq(-1)` silently selected nothing instead of the last element:
`xpath_eq_function` always emitted `position() = value + 1`, so a
negative argument produced a position that can never match. jQuery
counts a negative :eq() index back from the end, and pyquery's own
`.eq(-1)` method already does.
Negative values now translate to `position() = last()-n`, leaving the
non-negative path byte-identical.
---
pyquery/cssselectpatch.py | 12 +++++++++++-
tests/test_pyquery.py | 7 +++++++
2 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/pyquery/cssselectpatch.py b/pyquery/cssselectpatch.py
index 1af5716..dd5c102 100644
--- a/pyquery/cssselectpatch.py
+++ b/pyquery/cssselectpatch.py
@@ -375,6 +375,11 @@ def xpath_eq_function(self, xpath, function):
>>> d('h1:eq(1)')
[
]
+ A negative index counts back from the last element::
+
+ >>> d('h1:eq(-1)')
+ []
+
..
"""
if function.argument_types() != ['NUMBER']:
@@ -382,7 +387,12 @@ def xpath_eq_function(self, xpath, function):
"Expected a single integer for :eq(), got %r" % (
function.arguments,))
value = int(function.arguments[0].value)
- xpath.add_post_condition('position() = %s' % (value + 1))
+ if value < 0:
+ xpath.add_post_condition(
+ 'position() = last()%s' % (
+ '%+d' % (value + 1) if value != -1 else ''))
+ else:
+ xpath.add_post_condition('position() = %s' % (value + 1))
return xpath
def xpath_gt_function(self, xpath, function):
diff --git a/tests/test_pyquery.py b/tests/test_pyquery.py
index 7adf243..1405f06 100644
--- a/tests/test_pyquery.py
+++ b/tests/test_pyquery.py
@@ -219,6 +219,13 @@ def test_pseudo_classes(self):
assert len(e(":empty")) == 1
assert len(e(":contains('Heading')")) == 8
+ def test_eq_negative_index(self):
+ e = self.klass(self.html)
+ self.assertEqual(e('div:eq(-1)').text(), 'node3')
+ self.assertEqual(e('div:eq(-2)').text(), 'node2')
+ self.assertEqual(e('div:eq(-3)').text(), 'node1')
+ self.assertEqual(e('div:eq(-4)').text(), '')
+
def test_on_the_fly_dom_creation(self):
e = self.klass(self.html)
assert e('Hello world
').text() == 'Hello world'