Skip to content

Commit 4e90061

Browse files
committed
add a method on the Selector class, to export back the selector to css
1 parent cb7a7e2 commit 4e90061

File tree

3 files changed

+101
-7
lines changed

3 files changed

+101
-7
lines changed

cssselect/parser.py

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def __init__(self, tree, pseudo_element=None):
7676
#: +-------------------------+----------------+--------------------------------+
7777
#: | Invalid pseudo-class | ``li:marker`` | ``None`` |
7878
#: +-------------------------+----------------+--------------------------------+
79-
#: | Functinal | ``a::foo(2)`` | ``FunctionalPseudoElement(…)`` |
79+
#: | Functional | ``a::foo(2)`` | ``FunctionalPseudoElement(…)`` |
8080
#: +-------------------------+----------------+--------------------------------+
8181
#:
8282
#: .. _Lists3: http://www.w3.org/TR/2011/WD-css3-lists-20110524/#marker-pseudoelement
@@ -92,6 +92,20 @@ def __repr__(self):
9292
return '%s[%r%s]' % (
9393
self.__class__.__name__, self.parsed_tree, pseudo_element)
9494

95+
def css(self):
96+
"""Return a CSS representation for this selector (a string)
97+
"""
98+
if isinstance(self.pseudo_element, FunctionalPseudoElement):
99+
pseudo_element = '::%s' % self.pseudo_element.css()
100+
elif self.pseudo_element:
101+
pseudo_element = '::%s' % self.pseudo_element
102+
else:
103+
pseudo_element = ''
104+
res = '%s%s' % (self.parsed_tree.css(), pseudo_element)
105+
if len(res) > 1:
106+
res = res.lstrip('*')
107+
return res
108+
95109
def specificity(self):
96110
"""Return the specificity_ of this selector as a tuple of 3 integers.
97111
@@ -116,6 +130,9 @@ def __repr__(self):
116130
return '%s[%r.%s]' % (
117131
self.__class__.__name__, self.selector, self.class_name)
118132

133+
def css(self):
134+
return '%s.%s' % (self.selector.css(), self.class_name)
135+
119136
def specificity(self):
120137
a, b, c = self.selector.specificity()
121138
b += 1
@@ -151,6 +168,10 @@ def __repr__(self):
151168
def argument_types(self):
152169
return [token.type for token in self.arguments]
153170

171+
def css(self):
172+
args = ''.join(token.css() for token in self.arguments)
173+
return '%s(%s)' % (self.name, args)
174+
154175
def specificity(self):
155176
a, b, c = self.selector.specificity()
156177
b += 1
@@ -174,6 +195,10 @@ def __repr__(self):
174195
def argument_types(self):
175196
return [token.type for token in self.arguments]
176197

198+
def css(self):
199+
args = ''.join(token.css() for token in self.arguments)
200+
return '%s:%s(%s)' % (self.selector.css(), self.name, args)
201+
177202
def specificity(self):
178203
a, b, c = self.selector.specificity()
179204
b += 1
@@ -192,6 +217,9 @@ def __repr__(self):
192217
return '%s[%r:%s]' % (
193218
self.__class__.__name__, self.selector, self.ident)
194219

220+
def css(self):
221+
return '%s:%s' % (self.selector.css(), self.ident)
222+
195223
def specificity(self):
196224
a, b, c = self.selector.specificity()
197225
b += 1
@@ -210,6 +238,10 @@ def __repr__(self):
210238
return '%s[%r:not(%r)]' % (
211239
self.__class__.__name__, self.selector, self.subselector)
212240

241+
def css(self):
242+
return '%s:not(%s)' % (self.selector.css(),
243+
self.subselector.css())
244+
213245
def specificity(self):
214246
a1, b1, c1 = self.selector.specificity()
215247
a2, b2, c2 = self.subselector.specificity()
@@ -238,7 +270,20 @@ def __repr__(self):
238270
else:
239271
return '%s[%r[%s %s %r]]' % (
240272
self.__class__.__name__, self.selector, attrib,
241-
self.operator, self.value)
273+
self.operator, self.value.value)
274+
275+
def css(self):
276+
if self.namespace:
277+
attrib = '%s|%s' % (self.namespace, self.attrib)
278+
else:
279+
attrib = self.attrib
280+
281+
if self.operator == 'exists':
282+
op = attrib
283+
else:
284+
op = '%s%s%s' % (attrib, self.operator, self.value.css())
285+
286+
return '%s[%s]' % (self.selector.css(), op)
242287

243288
def specificity(self):
244289
a, b, c = self.selector.specificity()
@@ -258,10 +303,13 @@ def __init__(self, namespace=None, element=None):
258303
self.element = element
259304

260305
def __repr__(self):
306+
return '%s[%s]' % (self.__class__.__name__, self.css())
307+
308+
def css(self):
261309
element = self.element or '*'
262310
if self.namespace:
263311
element = '%s|%s' % (self.namespace, element)
264-
return '%s[%s]' % (self.__class__.__name__, element)
312+
return element
265313

266314
def specificity(self):
267315
if self.element:
@@ -282,6 +330,9 @@ def __repr__(self):
282330
return '%s[%r#%s]' % (
283331
self.__class__.__name__, self.selector, self.id)
284332

333+
def css(self):
334+
return '%s#%s' % (self.selector.css(), self.id)
335+
285336
def specificity(self):
286337
a, b, c = self.selector.specificity()
287338
a += 1
@@ -303,6 +354,10 @@ def __repr__(self):
303354
return '%s[%r %s %r]' % (
304355
self.__class__.__name__, self.selector, comb, self.subselector)
305356

357+
def css(self):
358+
return '%s %s %s' % (self.selector.css(),
359+
self.combinator, self.subselector.css())
360+
306361
def specificity(self):
307362
a1, b1, c1 = self.selector.specificity()
308363
a2, b2, c2 = self.subselector.specificity()
@@ -536,7 +591,7 @@ def parse_attrib(selector, stream):
536591
if next != ('DELIM', ']'):
537592
raise SelectorSyntaxError(
538593
"Expected ']', got %s" % (next,))
539-
return Attrib(selector, namespace, attrib, op, value.value)
594+
return Attrib(selector, namespace, attrib, op, value)
540595

541596

542597
def parse_series(tokens):
@@ -591,6 +646,12 @@ def is_delim(self, *values):
591646
type = property(operator.itemgetter(0))
592647
value = property(operator.itemgetter(1))
593648

649+
def css(self):
650+
if self.type == 'STRING':
651+
return repr(self.value)
652+
else:
653+
return self.value
654+
594655

595656
class EOFToken(Token):
596657
def __new__(cls, pos):

cssselect/xpath.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -308,10 +308,12 @@ def xpath_attrib(self, selector):
308308
attrib = '@' + name
309309
else:
310310
attrib = 'attribute::*[name() = %s]' % self.xpath_literal(name)
311-
if self.lower_case_attribute_values:
312-
value = selector.value.lower()
311+
if selector.value is None:
312+
value = None
313+
elif self.lower_case_attribute_values:
314+
value = selector.value.value.lower()
313315
else:
314-
value = selector.value
316+
value = selector.value.value
315317
return method(self.xpath(selector.selector), attrib, value)
316318

317319
def xpath_class(self, class_selector):

tests/test_cssselect.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,37 @@ def specificity(css):
244244
assert specificity('#lorem + foo#ipsum:first-child > bar:first-line'
245245
) == (2, 1, 3)
246246

247+
def test_css_export(self):
248+
def css2css(css, res=None):
249+
selectors = parse(css)
250+
assert len(selectors) == 1
251+
assert selectors[0].css() == (res or css)
252+
253+
css2css('*')
254+
css2css(' foo', 'foo')
255+
css2css('Foo', 'Foo')
256+
css2css(':empty ', ':empty')
257+
css2css(':before', '::before')
258+
css2css(':beFOre', '::before')
259+
css2css('*:before', '::before')
260+
css2css(':nth-child(2)')
261+
css2css('.bar')
262+
css2css('[baz]')
263+
css2css('[baz="4"]', "[baz='4']")
264+
css2css('[baz^="4"]', "[baz^='4']")
265+
css2css('#lipsum')
266+
css2css(':not(*)')
267+
css2css(':not(foo)')
268+
css2css(':not(*.foo)')
269+
css2css(':not(*[foo])')
270+
css2css(':not(*:empty)')
271+
css2css(':not(*#foo)')
272+
css2css('foo:empty')
273+
css2css('foo::before')
274+
css2css('foo:empty::before')
275+
css2css('::name(arg + "val" - 3)', "::name(arg+'val'-3)")
276+
css2css('#lorem + foo#ipsum:first-child > bar::first-line')
277+
247278
def test_parse_errors(self):
248279
def get_error(css):
249280
try:

0 commit comments

Comments
 (0)