#!/usr/bin/env python
try:
import lxml.etree as et
except ImportError, e:
import sys
print >> sys.stderr, "ERR: %s." % e
sys.exit(5)
import sys, os.path, optparse, itertools
SHORT_DESCRIPTION = "An XPath file finder for XML files."
__doc__ = SHORT_DESCRIPTION + '''
Evaluates an XPath expression against a series of files and prints the
matching subtrees to stdout.
Examples::
$ cat test.xml
# find all leaf elements:
$ SCRIPT '//*[not(*)]' test.xml
# find all elements with attribute values containing "abc" ignoring case:
$ SCRIPT '//*[@*[contains(py:lower(.), "abc")]]' test.xml
# find all numeric attribute values:
$ SCRIPT '//@*[re:match(., "^[0-9]+$")]' test.xml
1234
* find all elements with numeric attribute values:
$ SCRIPT '//*[@*[re:match(., "^[0-9]+$")]]' test.xml
* find all elements with numeric attribute values in more than one file:
$ SCRIPT '//*[@*[re:match(., "^[0-9]+$")]]' test.xml test.xml test.xml
>> test.xml
>> test.xml
>> test.xml
* find XML files that have non-empty root nodes:
$ SCRIPT -q '*' test.xml test.xml test.xml
>> test.xml
>> test.xml
>> test.xml
* find out if an XML file has at most depth three:
$ SCRIPT 'not(/*/*/*)' test.xml
True
* find all elements that belong to a specific namespace and have @num=2
$ SCRIPT --ns e=http://www.example.org/ns/example '//e:*[@num="2"]' test.xml
By default, all Python builtins and string methods are available as
XPath functions through the ``py`` prefix. There is also a string
comparison function ``py:within(x, a, b)`` that tests the string x for
being lexicographically within the interval ``a <= x <= b``.
'''.replace('SCRIPT', os.path.basename(sys.argv[0]))
REGEXP_NS = "http://exslt.org/regular-expressions"
PYTHON_BUILTINS_NS = "PYTHON-BUILTINS"
parser = et.XMLParser(remove_blank_text=True)
def print_result(result, pretty_print):
if et.iselement(result):
result = et.tostring(result, xml_declaration=False,
pretty_print=pretty_print)
if pretty_print:
result = result[:-1] # strip newline at the end
print result
def print_results(results, pretty_print):
if isinstance(results, list):
for result in results:
print_result(result, pretty_print)
else:
print_result(results, pretty_print)
def find_in_file(f, xpath, print_name=True, xinclude=False, pretty_print=True):
if hasattr(f, 'name'):
filename = f.name
else:
filename = f
try:
try:
tree = et.parse(f, parser)
except IOError, e:
print >> sys.stderr, "ERR: parsing %r failed: %s: %s" % (
filename, e.__class__.__name__, e)
return False
try:
if xinclude:
tree.xinclude()
except IOError, e:
print >> sys.stderr, "ERR: XInclude for %r failed: %s: %s" % (
filename, e.__class__.__name__, e)
return False
if not callable(xpath):
xpath = et.XPath(xpath)
results = xpath(tree)
if results == []:
return False
if print_name:
print ">> %s" % f
if options.verbose:
print_results(results, pretty_print)
return True
except Exception, e:
print >> sys.stderr, "ERR: %r: %s: %s" % (
filename, e.__class__.__name__, e)
return False
def register_builtins():
ns = et.FunctionNamespace(PYTHON_BUILTINS_NS)
tostring = et.tostring
str_xpath = et.XPath("string()")
def make_string(s):
if isinstance(s, list):
if not s:
return u''
s = s[0]
if not isinstance(s, unicode):
if et.iselement(s):
s = tostring(s, method="text", encoding=unicode)
else:
s = unicode(s)
return s
def wrap_builtin(b):
def wrapped_builtin(_, *args):
return b(*args)
return wrapped_builtin
for (name, builtin) in vars(__builtins__).iteritems():
if callable(builtin):
if not name.startswith('_') and name == name.lower():
ns[name] = wrap_builtin(builtin)
def wrap_str_method(b):
def wrapped_method(_, *args):
args = tuple(map(make_string, args))
return b(*args)
return wrapped_method
for (name, method) in vars(unicode).iteritems():
if callable(method):
if not name.startswith('_'):
ns[name] = wrap_str_method(method)
def within(_, s, a, b):
return make_string(a) <= make_string(s) <= make_string(b)
ns["within"] = within
def parse_options():
from optparse import OptionParser
usage = "usage: %prog [options] XPATH [FILE ...]"
parser = OptionParser(
usage = usage,
version = "%prog using lxml.etree " + et.__version__,
description = SHORT_DESCRIPTION)
parser.add_option("-H", "--long-help",
action="store_true", dest="long_help", default=False,
help="a longer help text including usage examples")
parser.add_option("-i", "--xinclude",
action="store_true", dest="xinclude", default=False,
help="run XInclude on the file before XPath")
parser.add_option("--no-python",
action="store_false", dest="python", default=True,
help="disable Python builtins and functions (prefix 'py')")
parser.add_option("--no-regexp",
action="store_false", dest="regexp", default=True,
help="disable regular expressions (prefix 're')")
parser.add_option("-q", "--quiet",
action="store_false", dest="verbose", default=True,
help="don't print status messages to stdout")
parser.add_option("-p", "--plain",
action="store_false", dest="pretty_print", default=True,
help="do not pretty-print the output")
parser.add_option("-N", "--ns",
action="append", default=[],
dest="namespaces",
help="add a namespace declaration: --ns PREFIX=NS",)
options, args = parser.parse_args()
if options.long_help:
parser.print_help()
print __doc__[__doc__.find('\n\n')+1:]
sys.exit(0)
if len(args) < 1:
parser.error("first argument must be an XPath expression")
return options, args
def main(options, args):
namespaces = {}
if options.regexp:
namespaces["re"] = REGEXP_NS
if options.python:
register_builtins()
namespaces["py"] = PYTHON_BUILTINS_NS
for ns in options.namespaces:
prefix, NS = ns.split("=", 1)
namespaces[prefix.strip()] = NS.strip()
xpath = et.XPath(args[0], namespaces=namespaces)
found = False
if len(args) == 1:
found = find_in_file(
sys.stdin, xpath, False, options.xinclude,
options.pretty_print)
else:
print_name = len(args) > 2
for filename in itertools.islice(args, 1, None):
found |= find_in_file(
filename, xpath, print_name, options.xinclude,
options.pretty_print)
return found
if __name__ == "__main__":
try:
options, args = parse_options()
found = main(options, args)
if found:
sys.exit(0)
else:
sys.exit(1)
except et.XPathSyntaxError, e:
print >> sys.stderr, "Err: %s" % e
sys.exit(4)
except KeyboardInterrupt:
pass