summaryrefslogtreecommitdiff
path: root/bin/gla11y
diff options
context:
space:
mode:
Diffstat (limited to 'bin/gla11y')
-rwxr-xr-xbin/gla11y167
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)