#!/usr/bin/env python # -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
java.lang.NullPointerException # This file is part of the LibreOffice project.
java.lang.NullPointerException # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
java.lang.NullPointerException # This file incorporates work covered by the following license notice:
java.lang.NullPointerException # Copyright (c) 2018 Martin Pieuchot # Copyright (c) 2018-2020 Samuel Thibault <sthibault@hypra.fr>
java.lang.NullPointerException # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice andthis permission notice appear in all copies.
java.lang.NullPointerException # THE SOFTWARE IS PROVIDED "AS IS"AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# Take LibreOffice (glade) .ui files and check for non accessible widgets
# A white paper documents the rationale of the implementation:
java.lang.NullPointerException # https://inria.hal.science/hal-02957129
from __future__ import print_function
import os
import sys
import getopt try:
import lxml.etree as ET
lxml = True
except ImportError: if sys.version_info < (2,7):
print("gla11y needs lxml or python >= 2.7") exit()
import xml.etree.ElementTree as ET
lxml = False
# This dictionary contains the set of suppression lines as read from the # suppression file(s). It is merely indexed by the text of the suppression line # and contains whether the suppressions was unused.
suppressions = {}
# This dictionary is indexed like suppressions and returns a "file:line" string # to report where in the suppression file the suppression was read
suppressions_to_line = {}
# This dictionary is similar to the suppressions dictionary, but forfalse # positives rather than suppressions
false_positives = {}
# This dictionary is indexed by the xml id and returns the element object.
ids = {} # This dictionary is indexed by the xml id and returns whether several objects # have the same id.
ids_dup = {}
# This dictionary is indexed by the xml id of an element A and returns the list # of objects which are labelled-by A.
labelled_by_elm = {}
# This dictionary is indexed by the xml id of an element A and returns the list # of objects which are label-for A.
label_for_elm = {}
# This dictionary is indexed by the xml id of an element A and returns the list # of objects which have a mnemonic-for A.
mnemonic_for_elm = {}
# Possibly a file name to put generated suppression lines in
gen_suppr = None # The corresponding opened file
gen_supprfile = None # A prefix to remove from file names in the generated suppression lines
suppr_prefix = ""
# Possibly an opened file in which our output should also be written to.
outfile = None
# Whether -p option was set, i.e. print XML class path instead of line number in # the output
pflag = False
# Whether we should warn about labels which are orphan
warn_orphan_labels = True
# Number of errors
errors = 0 # Number of suppressed errors
errexists = 0 # Number of warnings
warnings = 0 # Number of suppressed warnings
warnexists = 0 # Number of fatal errors
fatals = 0 # Number of suppressed fatal errors
fatalexists = 0
# List of warnings and errors which are fatal
java.lang.NullPointerException # Format of each element: (enabled, type, class) # See the is_enabled function: the list is traversed completely, each element # can specify whether it enables or disables the warning, possibly the type of # warning to be enabled/disabled, possibly the class of XML element for which it # should be enabled.
java.lang.NullPointerException # This mechanism matches the semantic of the parameters on the command line, # each of which refining the semantic set by the previous parameters
dofatals = [ ]
# List of warnings and errors which are enabled # Same format as dofatals
enables = [ ]
# buffers all printed output, so it isn't split in parallel builds
output_buffer = ""
java.lang.NullPointerException # XML browsing and printing functions
java.lang.NullPointerException
def elm_parent(root, elm): """ Return the parent of the element. """ if lxml: return elm.getparent() else:
def find_parent(cur, elm): for o in cur: if o == elm: return cur
parent = find_parent(o, elm) if parent is not None: return parent return None return find_parent(root, 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 classor 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): """ 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
def errpath(filename, tree, elm): """ Return the XML class path of the element """ if elm is None: return""
path = "" if'class' in elm.attrib:
path += elm.attrib['class']
oid = elm.attrib.get('id') if oid is not None:
oid = oid.encode('ascii','ignore').decode('ascii')
path = "//" + path + "[@id='%s']" % oid else: if lxml:
elm = elm.getparent() while elm is not None:
step = step_elm(elm)
path = step + path
elm = elm.getparent() else:
path = find_elm(tree.getroot(), elm)[:-1]
path = filename + ':' + path return path
def elm_prefix(filename, elm): """ Return the display prefix of the element """ if elm == None ornot 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 ifnot name:
name = "'" + elm.tag + "'" if lxml:
name += " line " + str(elm.sourceline) return name return""
def elm_name_line(elm): """ Return a display name of the element with line number """ if elm is not None:
name = elm_name(elm) if lxml and" line "not in name:
name += "line " + str(elm.sourceline) + " " return name return""
def elm_line(elm): """ Return the line for the given element. """ if lxml: return" line " + str(elm.sourceline) else: return""
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 elms_names_lines(elms): """ Return the list of names and lines for the given elements. """ return', '.join([elm_name_line(elm) for elm in elms])
def elm_suppr(filename, tree, elm, msgtype, dogen): """ Return the prefix to be displayed to the user and the suppression line for
the warning type "msgtype"for element "elm" """
global gen_suppr, gen_supprfile, suppr_prefix, pflag
if suppressions or false_positives or gen_suppr is not None or pflag:
prefix = errpath(filename, tree, elm) if prefix[0:len(suppr_prefix)] == suppr_prefix:
prefix = prefix[len(suppr_prefix):]
if suppressions or false_positives or gen_suppr is not None:
suppr = '%s %s' % (prefix, msgtype)
if gen_suppr is not None and msgtype is not None and dogen: if gen_supprfile is None:
gen_supprfile = open(gen_suppr, 'w')
print(suppr, file=gen_supprfile) else:
suppr = None
ifnot pflag: # Use user-friendly line numbers
prefix = elm_prefix(filename, elm) if prefix[0:len(suppr_prefix)] == suppr_prefix:
prefix = prefix[len(suppr_prefix):]
return (prefix, suppr)
def is_enabled(elm, msgtype, l, default): """
Test whether warning type msgtype is enabled for elm in l """
enabled = default for (enable, thetype, klass) in l: # Match warning type if thetype is not None: if thetype != msgtype: continue # Match elm class if klass is not None and elm is not None: if klass != elm.attrib.get('class'): continue
enabled = enable return enabled
def err(filename, tree, elm, msgtype, msg, error = True): """
Emit a warning or error for an element """
global errors, errexists, warnings, warnexists, fatals, fatalexists, output_buffer
# Let user tune whether a warning or error
fatal = is_enabled(elm, msgtype, dofatals, error)
# By default warnings and errors are enabled, but let user tune it ifnot is_enabled(elm, msgtype, enables, True): return
(prefix, suppr) = elm_suppr(filename, tree, elm, msgtype, True) if suppr in false_positives: # That was actually expected return if suppr in suppressions: # Suppressed
suppressions[suppr] = False if fatal:
fatalexists += 1 if error:
errexists += 1 else:
warnexists += 1 return
if error:
errors += 1 else:
warnings += 1 if fatal:
fatals += 1
msg = "%s %s%s: %s%s" % (prefix, "FATAL "if fatal else"", "ERROR"if error else"WARNING",
elm_name(elm), msg)
output_buffer += msg + "\n" if outfile is not None:
print(msg, file=outfile)
def warn(filename, tree, elm, msgtype, msg): """
Emit a warning for an element """
err(filename, tree, elm, msgtype, msg, False)
def find_button_parent(root, elm): """
Find a parent which is a button """ if lxml:
parent = elm.getparent() if parent is not None: if parent.attrib.get('class') in widgets_buttons: return parent return find_button_parent(root, parent) else:
def find_parent(cur, elm): for o in cur: if o == elm: if cur.attrib.get('class') in widgets_buttons: # we are the button, immediately above the target return cur else: # we aren't the button, but target is over there returnTrue
parent = find_parent(o, elm) if parent == True: # It is over there, but didn't find a button yet if cur.attrib.get('class') in widgets_buttons: # we are the button return cur else: returnTrue if parent is not None: # we have the button parent over there return parent return None
parent = find_parent(root, elm) if parent == True:
parent = None return parent
def is_labelled_parent(elm): """ Return whether this element is a labelled parent """
klass = elm.attrib.get('class') if klass in widgets_toplevel: returnTrue if klass == 'GtkShortcutsGroup':
children = elm.findall("property[@name='title']") if len(children) >= 1: returnTrue if klass == 'GtkFrame'or klass == 'GtkNotebook':
children = elm.findall("child[@type='tab']") + elm.findall("child[@type='label']") if len(children) >= 1: returnTrue returnFalse
def elm_labelled_parent(root, elm): """ Return the first labelled parent of the element, which can thus be used as
the root of widgets with common labelled context """
if lxml:
def find_labelled_parent(elm): if is_labelled_parent(elm): return elm
parent = elm.getparent() if parent is None: return None return find_labelled_parent(parent)
parent = elm.getparent() if parent is None: return None return find_labelled_parent(elm.getparent()) else:
def find_labelled_parent(cur, elm): if cur == elm: # the target element is over there returnTrue for o in cur:
parent = find_labelled_parent(o, elm) if parent == True: # target element is over there, check ourself if is_labelled_parent(cur): # yes, and we are the first ancestor of the target element return cur else: # no, but target element is over there. returnTrue if parent != None: # the first ancestor of the target element was over there return parent return None
parent = find_labelled_parent(root, elm) if parent == True:
parent = None return parent
def is_orphan_label(filename, tree, root, obj, orphan_root, doprint = False): """
Check whether this label has no accessibility relation, or doubtful relation
because another label labels the same target """
global label_for_elm, labelled_by_elm, mnemonic_for_elm, warnexists
# label-for
label_for = obj.findall("accessibility/relation[@type='label-for']") for rel in label_for:
target = rel.attrib['target']
l = label_for_elm[target] if len(l) > 1: returnTrue
# mnemonic_widget
mnemonic_for = obj.findall("property[@name='mnemonic_widget']") + \
obj.findall("property[@name='mnemonic-widget']") for rel in mnemonic_for:
target = rel.text
l = mnemonic_for_elm[target] if len(l) > 1: returnTrue
if len(label_for) > 0: # At least one label-for, we are not orphan. returnFalse
if len(mnemonic_for) > 0: # At least one mnemonic_widget, we are not orphan. returnFalse
labelled_by = obj.findall("accessibility/relation[@type='labelled-by']") if len(labelled_by) > 0: # Oh, a labelled label, probably not to be labelling anything returnFalse
# explicit role?
roles = [x.text for x in obj.findall("child[@internal-child='accessible']/object[@class='AtkObject']/property[@name='AtkObject::accessible-role']")]
roles += [x.attrib.get("type") for x in obj.findall("accessibility/role")] if len(roles) > 1 and doprint:
err(filename, tree, obj, "multiple-role", "has multiple "%s" % elms_lines(children)) for role in roles: if role == 'static'or role == 'ATK_ROLE_STATIC': # This is static text, not meant to label anything returnFalse
parent = elm_parent(root, obj) if parent is not None:
childtype = parent.attrib.get('type') if childtype is None:
childtype = parent.attrib.get('internal-child') if parent.tag == 'child'and childtype == 'label' \ or childtype == 'tab': # This is a frame or a notebook label, not orphan. returnFalse
if find_button_parent(root, obj) is not None: # This label is part of a button returnFalse
oid = obj.attrib.get('id') if oid is not None: if oid in labelled_by_elm: # Some widget is labelled by us, we are not orphan. # We should have had a label-for, will warn about it later. returnFalse
# No label-for, no mnemonic-for, no labelled-by, we are orphan.
(_, suppr) = elm_suppr(filename, tree, obj, "orphan-label", False) if suppr in false_positives: # That was actually expected returnFalse if suppr in suppressions: # Warning suppressed forthis label if suppressions[suppr]:
warnexists += 1
suppressions[suppr] = False returnFalse
if doprint:
context = elm_name(orphan_root) if context:
context = " within " + context
warn(filename, tree, obj, "orphan-label", "does not specify what it labels" + context) returnTrue
def is_orphan_widget(filename, tree, root, obj, orphan, orphan_root, doprint = False): """
Check whether this widget has no accessibility relation. """
global warnexists if obj.tag != 'object': returnFalse
oid = obj.attrib.get('id')
klass = obj.attrib.get('class')
# "Don't care" special case if klass in widgets_ignored: returnFalse for suffix in widgets_suffixignored: if klass[-len(suffix):] == suffix: returnFalse
# Widgets usual donot strictly require a label, i.e. a labelled parent # is enough for context, but some do always need one.
requires_label = klass in widgets_needlabel
# Labels special case if klass in widgets_labels: returnFalse
# Case 1: has an explicit <child internal-child="accessible"> sub-element
children = obj.findall("child[@internal-child='accessible']") if len(children) > 1 and doprint:
err(filename, tree, obj, "multiple-accessible", "has multiple " "%s" % elms_lines(children)) if len(children) >= 1: returnFalse
# Case 2: has an <accessibility> sub-element with a "labelled-by" # <relation> pointing to an existing element. if len(labelled_by) > 0: returnFalse
# Case 3: has a label-for if oid in label_for_elm: returnFalse
# Case 4: has a mnemonic if oid in mnemonic_for_elm: returnFalse
# Case 5: Has a <property name="tooltip_text">
tooltips = obj.findall("property[@name='tooltip_text']") + \
obj.findall("property[@name='tooltip-text']") if len(tooltips) > 1 and doprint:
err(filename, tree, obj, "multiple-tooltip", "has multiple tooltip_text properties") if len(tooltips) >= 1 and klass != 'GtkCheckButton': returnFalse
# Case 6: Has a <property name="placeholder_text">
placeholders = obj.findall("property[@name='placeholder_text']") + \
obj.findall("property[@name='placeholder-text']") if len(placeholders) > 1 and doprint:
err(filename, tree, obj, "multiple-placeholder", "has multiple placeholder_text properties") if len(placeholders) >= 1: returnFalse
# Buttons usually don't need an external label, their own is enough, (but they do need one) if klass in widgets_buttons:
labels = obj.findall("property[@name='label']") if len(labels) > 1 and doprint:
err(filename, tree, obj, "multiple-label", "has multiple label properties") if len(labels) >= 1: # Has a <property name="label"> returnFalse
actions = obj.findall("property[@name='action_name']") if len(actions) > 1 and doprint:
err(filename, tree, obj, "multiple-action_name", "has multiple action_name properties") if len(actions) >= 1: # Has a <property name="action_name"> returnFalse
# Uses id as an action_name if'id' in obj.attrib: if obj.attrib['id'].startswith(".uno:"): returnFalse
gtklabels = obj.findall(".//object[@class='GtkLabel']") + obj.findall(".//object[@class='GtkAccelLabel']") if len(gtklabels) >= 1: # Has a custom label returnFalse
# no label for a button, warn if doprint:
warn(filename, tree, obj, "button-no-label", "does not have its own label") ifnot is_enabled(obj, "button-no-label", enables, True): # Warnings disabled returnFalse
(_, suppr) = elm_suppr(filename, tree, obj, "button-no-label", False) if suppr in false_positives: # That was actually expected returnFalse if suppr in suppressions: # Warning suppressed forthis widget if suppressions[suppr]:
warnexists += 1
suppressions[suppr] = False returnFalse returnTrue
# GtkImages special case if klass == "GtkImage":
uses = [u for u in tree.iterfind(".//object/property[@name='image']") if u.text == oid] if len(uses) > 0: # This image is just used by another element, don't warn # about the image itself, we probably want the warning on # the element instead. returnFalse
if find_button_parent(root, obj) is not None: # This image is part of a button, we want the warning on the button # instead, if any. returnFalse
# GtkEntry special case if klass == 'GtkEntry'or klass == 'GtkSearchEntry':
parent = elm_parent(root, obj) if parent is not None: if parent.tag == 'child'and \
parent.attrib.get('internal-child') == "entry": # This is an internal entry of another widget. Relations # will be handled by that widget. returnFalse
# GtkShortcutsShortcut special case if klass == 'GtkShortcutsShortcut':
children = obj.findall("property[@name='title']") if len(children) >= 1: returnFalse
# Really no label, perhaps emit a warning ifnot is_enabled(obj, "no-labelled-by", enables, True): # Warnings disabled forthisclass of widgets returnFalse
(_, suppr) = elm_suppr(filename, tree, obj, "no-labelled-by", False) if suppr in false_positives: # That was actually expected returnFalse if suppr in suppressions: # Warning suppressed forthis widget if suppressions[suppr]:
warnexists += 1
suppressions[suppr] = False returnFalse
ifnot orphan: # No orphan label, so probably the labelled parent provides enough # context. if requires_label: # But these always need a label. if doprint:
warn(filename, tree, obj, "no-labelled-by", "has no accessibility label") returnTrue returnFalse
if doprint:
context = elm_name(orphan_root) if context:
context = " within " + context
warn(filename, tree, obj, "no-labelled-by", "has no accessibility label while there are orphan labels" + context) returnTrue
def orphan_items(filename, tree, root, elm): """
Check whether from some element there exists orphan labels and orphan widgets """
orphan_labels = False
orphan_widgets = False if elm.attrib.get('class') in widgets_labels:
orphan_labels = is_orphan_label(filename, tree, root, elm, None) else:
orphan_widgets = is_orphan_widget(filename, tree, root, elm, True, None) for obj in elm: # We are not interested in orphan labels under another labelled # parent. This also allows to keep linear complexity. ifnot is_labelled_parent(obj):
label, widget = orphan_items(filename, tree, root, obj) if label:
orphan_labels = True if widget:
orphan_widgets = True if orphan_labels and orphan_widgets: # No need to look up more break return orphan_labels, orphan_widgets
def check_props(filename, tree, root, elm, forward): """
Check the given list of relation properties """
props = elm.findall("property[@name='" + forward + "']") for prop in props: if prop.text not in ids:
err(filename, tree, elm, "undeclared-target", forward + " uses undeclared target '%s'"% prop.text) return props
def check_rels(filename, tree, root, elm, forward, backward = None): """
Check the relations given by forward """
oid = elm.attrib.get('id')
rels = elm.findall("accessibility/relation[@type='" + forward + "']") for rel in rels:
target = rel.attrib['target'] if target not in ids:
err(filename, tree, elm, "undeclared-target", forward + " uses undeclared target '%s'"% target)
elif backward is not None:
widget = ids[target]
backrels = widget.findall("accessibility/relation[@type='" + backward + "']") if len([x for x in backrels if x.attrib['target'] == oid]) == 0:
err(filename, tree, elm, "missing-" + backward, "has " + forward + \ ", but is not " + backward + " by " + elm_name_line(widget)) return rels
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. """
global widgets_ignored, ids, label_for_elm, labelled_by_elm, mnemonic_for_elm
def check_elm(orphan_root, obj, orphan_labels, orphan_widgets): """
Check one element, knowing that orphan_labels/widgets tell whether
there are orphan labels and widgets within orphan_root """
oid = obj.attrib.get('id')
klass = obj.attrib.get('class')
# "Don't care" special case if klass in widgets_ignored: return for suffix in widgets_suffixignored: if klass[-len(suffix):] == suffix: return
# Widgets usual donot strictly require a label, i.e. a labelled parent # is enough for context, but some do always need one.
requires_label = klass in widgets_needlabel
if oid is not None: # Check that ids are unique if oid in ids_dup: if ids[oid] == obj: # We are the first, warn
duplicates = tree.findall(".//object[@id='" + oid + "']")
err(filename, tree, obj, "duplicate-id", "has the same id as other elements " + elms_names_lines(duplicates))
# Check label-for and their dual labelled-by
label_for = check_rels(filename, tree, root, obj, "label-for", "labelled-by")
# Check labelled-by and its dual label-for
labelled_by = check_rels(filename, tree, root, obj, "labelled-by", "label-for")
visible = is_visible(obj)
# warning message type "syntax" used:
java.lang.NullPointerException # multiple-* => 2+ XML tags of the inspected element itself # duplicate-* => 2+ XML tags of other elements referencing this element
# Should have only one label if len(labelled_by) >= 1: if oid in mnemonic_for_elm:
warn(filename, tree, obj, "labelled-by-and-mnemonic", "has both a mnemonic " + elm_name_line(mnemonic_for_elm[oid][0]) + "and labelled-by relation") if len(labelled_by) > 1:
warn(filename, tree, obj, "multiple-labelled-by", "has multiple labelled-by relations")
if oid in labelled_by_elm: if len(labelled_by_elm[oid]) == 1:
paired = labelled_by_elm[oid][0] if paired != None and visible != is_visible(paired):
warn(filename, tree, obj, "visibility-conflict", "visibility conflicts with paired "+ elm_name_line(paired))
if oid in label_for_elm: if len(label_for_elm[oid]) > 1:
warn(filename, tree, obj, "duplicate-label-for", "is referenced by multiple label-for " + elms_names_lines(label_for_elm[oid]))
elif len(label_for_elm[oid]) == 1:
paired = label_for_elm[oid][0] if visible != is_visible(paired):
warn(filename, tree, obj, "visibility-conflict", "visibility conflicts with paired "+ elm_name_line(paired))
if oid in mnemonic_for_elm: if len(mnemonic_for_elm[oid]) > 1:
warn(filename, tree, obj, "duplicate-mnemonic", "is referenced by multiple mnemonic_widget " + elms_names_lines(mnemonic_for_elm[oid]))
# First pass to get links into hash tables, no warning, just record duplicates for obj in root.iter('object'):
oid = obj.attrib.get('id') if oid is not None: if oid not in ids:
ids[oid] = obj else:
ids_dup[oid] = True
labelled_by = obj.findall("accessibility/relation[@type='labelled-by']") for rel in labelled_by:
target = rel.attrib.get('target') if target is not None: if target not in labelled_by_elm:
labelled_by_elm[target] = [ obj ] else:
labelled_by_elm[target].append(obj)
label_for = obj.findall("accessibility/relation[@type='label-for']") for rel in label_for:
target = rel.attrib.get('target') if target is not None: if target not in label_for_elm:
label_for_elm[target] = [ obj ] else:
label_for_elm[target].append(obj)
mnemonic_for = obj.findall("property[@name='mnemonic_widget']") + \
obj.findall("property[@name='mnemonic-widget']") for rel in mnemonic_for:
target = rel.text if target is not None: if target not in mnemonic_for_elm:
mnemonic_for_elm[target] = [ obj ] else:
mnemonic_for_elm[target].append(obj)
# Second pass, recursive depth-first, to be able to efficiently know whether # there are orphan labels within a part of the tree.
def recurse(orphan_root, obj, orphan_labels, orphan_widgets): if obj == root or is_labelled_parent(obj):
orphan_root = obj
orphan_labels, orphan_widgets = orphan_items(filename, tree, root, obj)
if obj.tag == 'object':
check_elm(orphan_root, obj, orphan_labels, orphan_widgets)
for o in obj:
recurse(orphan_root, o, orphan_labels, orphan_widgets)
recurse(root, root, False, False)
java.lang.NullPointerException # Main
java.lang.NullPointerException
def usage(fatal = True):
print("`%s' checks accessibility of glade .ui files" % progname)
print("")
print("Usage: %s [-p] [-g SUPPR_FILE] [-s SUPPR_FILE] [-f SUPPR_FILE] [-P PREFIX] [-o LOG_FILE] [file ...]" % progname)
print("")
print(" -p Print XML class path instead of line number")
print(" -g Generate suppression file SUPPR_FILE")
print(" -s Suppress warnings given by file SUPPR_FILE, but count them")
print(" -f Suppress warnings given by file SUPPR_FILE completely")
print(" -P Remove PREFIX from file names in warnings")
print(" -o Also prints errors and warnings to given file")
print("")
print(" --widgets-FOO [+][CLASS1[,CLASS2[,...]]]")
print(" Give or extend one of the lists of widget classes, where FOO can be:")
print(" - toplevel : widgets to be considered toplevel windows")
print(" - ignored : widgets which do not need labelling (e.g. GtkBox)")
print(" - suffixignored : suffixes of widget classes which do not need labelling")
print(" - needlabel : widgets which always need labelling (e.g. GtkEntry)")
print(" - buttons : widgets which need their own label but not more")
print(" (e.g. GtkButton)")
print(" - labels : widgets which provide labels (e.g. GtkLabel)")
print(" --widgets-print print default widgets lists")
print("")
print(" --enable-all enable all warnings/dofatals (default)")
print(" --disable-all disable all warnings/dofatals")
print(" --fatal-all make all warnings dofatals")
print(" --not-fatal-all do not make all warnings dofatals (default)")
print("")
print(" --enable-type=TYPE enable warning/fatal type TYPE")
print(" --disable-type=TYPE disable warning/fatal type TYPE")
print(" --fatal-type=TYPE make warning type TYPE a fatal")
print(" --not-fatal-type=TYPE make warning type TYPE not a fatal")
print("")
print(" --enable-widgets=CLASS enable warning/fatal type CLASS")
print(" --disable-widgets=CLASS disable warning/fatal type CLASS")
print(" --fatal-widgets=CLASS make warning type CLASS a fatal")
print(" --not-fatal-widgets=CLASS make warning type CLASS not a fatal")
print("")
print(" --enable-specific=TYPE.CLASS enable warning/fatal type TYPE for widget")
print(" class CLASS")
print(" --disable-specific=TYPE.CLASS disable warning/fatal type TYPE for widget")
print(" class CLASS")
print(" --fatal-specific=TYPE.CLASS make warning type TYPE a fatal for widget")
print(" class CLASS")
print(" --not-fatal-specific=TYPE.CLASS make warning type TYPE not a fatal for widget")
print(" class CLASS")
print("")
print(" --disable-orphan-labels only warn about orphan labels when there are")
print(" orphan widgets in the same context")
print("")
print("Report bugs to ")
sys.exit(2 if fatal else 0)
def widgets_opt(widgets_list, arg): """
Replace or extend `widgets_list' with the list of classes contained in `arg' """
append = arg and arg[0] == '+' if append:
arg = arg[1:]
if arg:
widgets = arg.split(',') else:
widgets = []
ifnot append:
del widgets_list[:]
widgets_list.extend(widgets)
def main():
global pflag, gen_suppr, gen_supprfile, suppressions, suppr_prefix, false_positives, dofatals, enables, dofatals, warn_orphan_labels
global widgets_toplevel, widgets_ignored, widgets_suffixignored, widgets_needlabel, widgets_buttons, widgets_labels
global outfile, output_buffer
for o, a in opts: if o == "--help"or o == "-h":
usage(False) if o == "--version":
print("0.1")
sys.exit(0)
elif o == "-p":
pflag = True
elif o == "-g":
gen_suppr = a
elif o == "-s":
suppr = a
elif o == "-f": false = a
elif o == "-P":
suppr_prefix = a
elif o == "-o":
out = a
elif o == "-L":
filelist = a
elif o == "--widgets-toplevel":
widgets_opt(widgets_toplevel, a)
elif o == "--widgets-ignored":
widgets_opt(widgets_ignored, a)
elif o == "--widgets-suffixignored":
widgets_opt(widgets_suffixignored, a)
elif o == "--widgets-needlabel":
widgets_opt(widgets_needlabel, a)
elif o == "--widgets-buttons":
widgets_opt(widgets_buttons, a)
elif o == "--widgets-labels":
widgets_opt(widgets_labels, a)
elif o == "--widgets-print":
print("--widgets-toplevel '" + ','.join(widgets_toplevel) + "'")
print("--widgets-ignored '" + ','.join(widgets_ignored) + "'")
print("--widgets-suffixignored '" + ','.join(widgets_suffixignored) + "'")
print("--widgets-needlabel '" + ','.join(widgets_needlabel) + "'")
print("--widgets-buttons '" + ','.join(widgets_buttons) + "'")
print("--widgets-labels '" + ','.join(widgets_labels) + "'")
sys.exit(0)
elif o == '--enable-all':
enables.append( (True, None, None) )
elif o == '--disable-all':
enables.append( (False, None, None) )
elif o == '--fatal-all':
dofatals.append( (True, None, None) )
elif o == '--not-fatal-all':
dofatals.append( (False, None, None) )
elif o == '--enable-type':
enables.append( (True, a, None) )
elif o == '--disable-type':
enables.append( (False, a, None) )
elif o == '--fatal-type':
dofatals.append( (True, a, None) )
elif o == '--not-fatal-type':
dofatals.append( (False, a, None) )
elif o == '--enable-widgets':
enables.append( (True, None, a) )
elif o == '--disable-widgets':
enables.append( (False, None, a) )
elif o == '--fatal-widgets':
dofatals.append( (True, None, a) )
elif o == '--not-fatal-widgets':
dofatals.append( (False, None, a) )
elif o == '--disable-orphan-labels':
warn_orphan_labels = False
output_header = ""
# Read suppression file before overwriting it if suppr is not None: try:
output_header += "Suppression file: " + suppr + "\n"
supprfile = open(suppr, 'r')
line_no = 0 for line in supprfile.readlines():
line_no = line_no + 1 if line.startswith('#'): continue
prefix = line.rstrip()
suppressions[prefix] = True
suppressions_to_line[prefix] = "%s:%u" % (suppr, line_no)
supprfile.close()
except IOError:
pass
# Read false positives file iffalse is not None: try:
output_header += "False positive file: " + false + "\n"
falsefile = open(false, 'r') for line in falsefile.readlines(): if line.startswith('#'): continue
prefix = line.rstrip()
false_positives[prefix] = True
falsefile.close()
except IOError:
pass
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, None, "parse", "malformatted xml file") continue
except IOError:
err(filename, None, None, None, "unable to read file") continue
if errors > 0 or errexists > 0:
output_buffer += "%s new error%s" % (errors, 's'if errors != 1 else'') if errexists > 0:
output_buffer += " (%s suppressed by %s, please fix %s)" % (errexists, suppr, 'them'iferrexists > 1 else'it')
output_buffer += "\n"
if warnings > 0 or warnexists > 0:
output_buffer += "%s new warning%s" % (warnings, 's'if warnings != 1 else'') if warnexists > 0:
output_buffer += " (%s suppressed by %s, please fix %s)" % (warnexists, suppr, 'them'if warnexists > 1 else'it')
output_buffer += "\n"
if fatals > 0 or fatalexists > 0:
output_buffer += "%s new fatal%s" % (fatals, 's'if fatals != 1 else'') if fatalexists > 0:
output_buffer += " (%s suppressed by %s, please fix %s)" % (fatalexists, suppr, 'them'if fatalexists > 1 else'it')
output_buffer += "\n"
n = 0 for (suppr,unused) in suppressions.items(): if unused:
n += 1
if n > 0:
output_buffer += "%s suppression%s unused:\n" % (n, 's'if n != 1 else'') for (suppr,unused) in suppressions.items(): if unused:
output_buffer += " %s:%s\n" % (suppressions_to_line[suppr], suppr)
if gen_supprfile is not None:
gen_supprfile.close() if outfile is not None:
outfile.close()
if gen_suppr is None: if output_buffer != "":
output_buffer += "Explanations are available on " + howto_url + "\n"
if fatals > 0:
print(output_header.rstrip() + "\n" + output_buffer)
sys.exit(1)
if len(output_buffer) > 0:
print(output_header.rstrip() + "\n" + output_buffer)
if __name__ == "__main__": try:
main()
except KeyboardInterrupt:
pass
# vim: set shiftwidth=4 softtabstop=4 expandtab:
Messung V0.5
[ Dauer der Verarbeitung: 0.39 Sekunden
(vorverarbeitet)
]