diff options
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/gla11y | 167 |
1 files changed, 126 insertions, 41 deletions
diff --git a/bin/gla11y b/bin/gla11y index 04d5d83ccd1a..77a84840087a 100755 --- a/bin/gla11y +++ b/bin/gla11y @@ -31,33 +31,84 @@ from __future__ import print_function import os import sys import getopt -import lxml.etree as ET +try: + import lxml.etree as ET + lxml = True +except ImportError: + import xml.etree.ElementTree as ET + lxml = False progname = os.path.basename(sys.argv[0]) +outfile = None Werror = False Wnone = False errors = 0 warnings = 0 - -def errstr(elm): +def step_elm(elm): + """ + Return the XML class path step corresponding to elm. + This can be empty if the elm does not have any class or id. + """ + step = elm.attrib.get('class') + if step is None: + step = "" + oid = elm.attrib.get('id') + if oid is not None: + oid = oid.encode('ascii','ignore').decode('ascii') + step += "[@id='%s']" % oid + if len(step) > 0: + step += '/' + return step + +def find_elm(root, elm): """ - Print the line number of the element + Return the XML class path of the element from the given root. + This is the slow version used when getparent is not available. """ + if root == elm: + return "" + for o in root: + path = find_elm(o, elm) + if path is not None: + step = step_elm(o) + return step + path + return None - return str(elm.sourceline) -def err(filename, elm, msg): +def elm_prefix(filename, elm): + """ + Return the display prefix of the element + """ + if elm == None or not lxml: + return "%s:" % filename + else: + return "%s:%u" % (filename, elm.sourceline) + +def elm_name(elm): + """ + Return a display name of the element + """ + if elm is not None: + name = "" + if 'class' in elm.attrib: + name = "'%s' " % elm.attrib['class'] + if 'id' in elm.attrib: + id = elm.attrib['id'].encode('ascii','ignore').decode('ascii') + name += "'%s' " % id + return name + return "" + +def err(filename, tree, elm, msg): global errors - if elm == None: - prefix = "%s:" % filename - else: - prefix = "%s:%s" % (filename, errstr(elm)) + prefix = elm_prefix(filename, elm) errors += 1 - msg = "%s ERROR: %s" % (prefix, msg) - print(msg.encode('ascii', 'ignore')) + msg = "%s ERROR: %s%s" % (prefix, elm_name(elm), msg) + print(msg) + if outfile is not None: + print(msg, file=outfile) def warn(filename, elm, msg): @@ -66,61 +117,73 @@ def warn(filename, elm, msg): if Wnone: return - prefix = "%s:%s" % (filename, errstr(elm)) + prefix = elm_prefix(filename, elm) if Werror: errors += 1 else: warnings += 1 - msg = "%s WARNING: %s" % (prefix, msg) - print(msg.encode('ascii', 'ignore')) + msg = "%s WARNING: %s%s" % (prefix, elm_name(elm), msg) + print(msg) + if outfile is not None: + print(msg, file=outfile) -def check_objects(filename, elm, objects, target): +def check_objects(filename, tree, elm, objects, target): """ Check that objects contains exactly one object """ length = len(list(objects)) if length == 0: - err(filename, elm, "use of undeclared target '%s'" % target) + err(filename, tree, elm, "uses undeclared target '%s'" % target) elif length > 1: - err(filename, elm, "several targets are named '%s'" % target) + err(filename, tree, elm, "several targets are named '%s'" % target) -def check_props(filename, root, props): +def check_props(filename, tree, root, elm, props): """ Check the given list of relation properties """ for prop in props: objects = root.iterfind(".//object[@id='%s']" % prop.text) - check_objects(filename, prop, objects, prop.text) + check_objects(filename, tree, elm, objects, prop.text) -def check_rels(filename, root, rels): +def check_rels(filename, tree, root, elm, rels): """ Check the given list of relations """ for rel in rels: target = rel.attrib['target'] targets = root.iterfind(".//object[@id='%s']" % target) - check_objects(filename, rel, targets, target) + check_objects(filename, tree, elm, targets, target) -def check_a11y_relation(filename, root): +def elms_lines(elms): + """ + Return the list of lines for the given elements. + """ + if lxml: + return ": lines " + ', '.join([str(l.sourceline) for l in elms]) + else: + return "" + +def check_a11y_relation(filename, tree): """ Emit an error message if any of the 'object' elements of the XML document represented by `root' doesn't comply with Accessibility rules. """ + root = tree.getroot() for obj in root.iter('object'): label_for = obj.findall("accessibility/relation[@type='label-for']") - check_rels(filename, root, label_for) + check_rels(filename, tree, root, obj, label_for) labelled_by = obj.findall("accessibility/relation[@type='labelled-by']") - check_rels(filename, root, labelled_by) + check_rels(filename, tree, root, obj, labelled_by) member_of = obj.findall("accessibility/relation[@type='member-of']") - check_rels(filename, root, member_of) + check_rels(filename, tree, root, obj, member_of) if obj.attrib['class'] == 'GtkLabel': # Case 0: A 'GtkLabel' must contain one or more "label-for" @@ -130,14 +193,13 @@ def check_a11y_relation(filename, root): # ...a single "mnemonic_widget" properties = obj.findall("property[@name='mnemonic_widget']") - check_props(filename, root, properties) + check_props(filename, tree, root, obj, properties) if len(properties) > 1: # It does not make sense for a label to be a mnemonic for # several actions. - lines = ', '.join([str(p.sourceline) for p in properties]) - err(filename, obj, "too many sub-elements" + err(filename, tree, obj, "multiple-mnemonic", "has too many sub-elements" ", expected single <property name='mnemonic_widgets'>" - ": lines %s" % lines) + "%s" % elms_lines(properties)) continue if len(properties) == 1: continue @@ -148,10 +210,9 @@ def check_a11y_relation(filename, root): children = obj.findall("child[@internal-child='accessible']") if children: if len(children) > 1: - lines = ', '.join([str(c.sourceline) for c in children]) - err(filename, obj, "too many sub-elements" + err(filename, tree, obj, "multiple-accessible", "has too many sub-elements" ", expected single <child internal-child='accessible'>" - ": lines %s" % lines) + "%s" % elms_lines(children)) continue # Case 2: has an <accessibility> sub-element with a "labelled-by" @@ -164,42 +225,66 @@ def check_a11y_relation(filename, root): def usage(): - print("%s [-W error|none] [file ...]" % progname, + print("%s [-W error|none] [-o LOG_FILE] [file ... | -L filelist]" % progname, file=sys.stderr) + print(" -o Also prints errors and warnings to given file"); sys.exit(2) def main(): - global Werror, Wnone, errors + global Werror, Wnone, errors, outfile try: - opts, args = getopt.getopt(sys.argv[1:], "pW:") + opts, args = getopt.getopt(sys.argv[1:], "W:o:L:") except getopt.GetoptError: usage() + out = None + filelist = None for o, a in opts: if o == "-W": if a == "error": Werror = True elif a == "none": Wnone = True + elif o == "-o": + out = a + elif o == "-L": + filelist = a + + if out is not None: + outfile = open(out, 'w') + + if filelist is not None: + try: + filelistfile = open(filelist, 'r') + for line in filelistfile.readlines(): + line = line.strip() + if line: + args += line.split(' ') + filelistfile.close() + except IOError: + err(filelist, None, None, "unable to read file list file") for filename in args: try: tree = ET.parse(filename) except ET.ParseError: - err(filename, None, "malformatted xml file") + err(filename, None, None, "malformatted xml file") + continue except IOError: - err(filename, None, "unable to read file") + err(filename, None, None, "unable to read file") + continue try: - check_a11y_relation(filename, tree.getroot()) + check_a11y_relation(filename, tree) except Exception as error: import traceback traceback.print_exc() - err(filename, None, "error parsing file") - + err(filename, None, None, "error parsing file") + if outfile is not None: + outfile.close() if errors > 0: sys.exit(1) |