Skip to content

Commit

Permalink
Initial
Browse files Browse the repository at this point in the history
  • Loading branch information
ydm committed May 26, 2013
1 parent 95a6ff1 commit d8ee1db
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 0 deletions.
66 changes: 66 additions & 0 deletions css2xpath.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env python3

from io import StringIO
import re
import sys


def tok2nodetest(tok, write):
m = re.match('\\w+', tok)
if m is None:
write('*')
return tok
write(m.group())
return tok[len(m.group()):]


def tok2predicate(tok, write):
predicates = (
(r'\.([\-\w_]+)', "[contains(@class, '{}')]"),
(r'#([\-\w_]+)', "[@id='{}']"),
(r':nth-child\((\d+)\)', '[{}]'),
(r'():first', '{}[1]')
)
for regexp, fmt in predicates:
m = re.match(regexp, tok)
if m:
write(fmt.format(m.group(1)))
return tok[len(m.group()):]
raise Exception('Unknown predicate token: {}'.format(tok))


def transform(sel):
'Transform a css selector to xpath query string'
buf = StringIO()
tokens = sel.split()
if not tokens:
return ''
direct = False
for token in tokens:
if token == '>':
direct = True
else:
axis, direct = '/' if direct else '//', False
buf.write(axis)
token = tok2nodetest(token, buf.write)
while token:
token = tok2predicate(token, buf.write)
return buf.getvalue()


def main():
if len(sys.argv) > 1:
for sel in sys.argv[1:]:
print(transform(sel))
else:
while 1:
try:
sel = input()
except EOFError:
break
else:
print(transform(sel))


if __name__ == '__main__':
main()
52 changes: 52 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env python3

import io
import unittest
import css2xpath


class Css2XpathTest(unittest.TestCase):

def runfunc(self, func, expwrite, expreturn, *args):
f = io.StringIO()
actreturn = func(*(args + (f.write,)))
actwrited = f.getvalue()
msg = lambda e, a: 'Tested: {}{},\n : Expected={}, Actual={}'.format(
func.__name__, args, e, a)
self.assertEqual(expwrite, actwrited, msg(expwrite, actwrited))
self.assertEqual(expreturn, actreturn, msg(expreturn, actreturn))

def batch(self, func, expwrite, expreturn, args):
for w, r, a in zip(expwrite, expreturn, args):
self.runfunc(func, w, r, a)

def test_node_star(self):
i = ['#egg', '.egg', '.spam:nth-child(1)',]
w = ['*', '*', '*']
r = i.copy()
self.batch(css2xpath.tok2nodetest, w, r, i)

def test_node_el(self):
i = ['h1#egg', 'h2.egg', 'h3.spam:nth-child(1)',]
w = ['h1', 'h2', 'h3']
r = ['#egg', '.egg', '.spam:nth-child(1)']
self.batch(css2xpath.tok2nodetest, w, r, i)

def test_predicate(self):
i = ['.eggs', '#egg', '.eggs:nth-child(1)', ':nth-child(1)']
w = ["[contains(@class, 'eggs')]", "[@id='egg']",
"[contains(@class, 'eggs')]", '[1]']
r = ['', '', ':nth-child(1)', '']
self.batch(css2xpath.tok2predicate, w, r, i)

def test_transform(self):
inp = '.spam > p.and-eggs:nth-child(3) a.something.otherthing'
exp = "//*[contains(@class, 'spam')]"\
"/p[contains(@class, 'and-eggs')][3]"\
"//a[contains(@class, 'something')]"\
"[contains(@class, 'otherthing')]"
self.assertEqual(exp, css2xpath.transform(inp))


if __name__ == '__main__':
unittest.main()

0 comments on commit d8ee1db

Please sign in to comment.