diff options
-rwxr-xr-x | bin/gla11y | 165 | ||||
-rw-r--r-- | solenv/gbuild/UIConfig.mk | 6 | ||||
-rw-r--r-- | solenv/gbuild/platform/com_GCC_class.mk | 2 | ||||
-rw-r--r-- | solenv/gbuild/platform/com_MSC_class.mk | 2 | ||||
-rw-r--r-- | solenv/sanitizers/ui/cui.suppr | 3 | ||||
-rw-r--r-- | solenv/sanitizers/ui/svt.suppr | 2 | ||||
-rw-r--r-- | solenv/sanitizers/ui/svx.suppr | 6 |
7 files changed, 168 insertions, 18 deletions
diff --git a/bin/gla11y b/bin/gla11y index 77a84840087a..9d550a6ea001 100755 --- a/bin/gla11y +++ b/bin/gla11y @@ -39,11 +39,17 @@ except ImportError: lxml = False progname = os.path.basename(sys.argv[0]) +suppressions = {} +gen_suppr = None +gen_supprfile = None outfile = None +pflag = False Werror = False Wnone = False errors = 0 +errexists = 0 warnings = 0 +warnexists = 0 def step_elm(elm): """ @@ -75,6 +81,29 @@ def find_elm(root, elm): 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 += "[@id='%s']" % oid + 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): """ @@ -99,10 +128,44 @@ def elm_name(elm): return name return "" -def err(filename, tree, elm, msg): - global errors +def elm_suppr(filename, tree, elm, msgtype): + """ + 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, pflag + + if suppressions or gen_suppr is not None or pflag: + prefix = errpath(filename, tree, elm) + + if suppressions or gen_suppr is not None: + suppr = '%s %s' % (prefix, msgtype) + + if gen_suppr is not None and msgtype is not None: + if gen_supprfile is None: + gen_supprfile = open(gen_suppr, 'w') + print(suppr, file=gen_supprfile) + else: + suppr = None + + if not pflag: + # Use user-friendly line numbers + prefix = elm_prefix(filename, elm) - prefix = elm_prefix(filename, elm) + return (prefix, suppr) + +def err(filename, tree, elm, msgtype, msg): + """ + Emit an error for an element + """ + global errors, errexists + + (prefix, suppr) = elm_suppr(filename, tree, elm, msgtype) + + if suppr in suppressions: + # Suppressed + errexists += 1 + return errors += 1 msg = "%s ERROR: %s%s" % (prefix, elm_name(elm), msg) @@ -111,13 +174,23 @@ def err(filename, tree, elm, msg): print(msg, file=outfile) -def warn(filename, elm, msg): - global Werror, Wnone, errors, warnings +def warn(filename, tree, elm, msgtype, msg): + """ + Emit a warning for an element + """ + global Werror, Wnone, errors, errexists, warnings, warnexists if Wnone: return - prefix = elm_prefix(filename, elm) + (prefix, suppr) = elm_suppr(filename, tree, elm, msgtype) + if suppr in suppressions: + # Suppressed + if Werror: + errexists += 1 + else: + warnexists += 1 + return if Werror: errors += 1 @@ -136,9 +209,9 @@ def check_objects(filename, tree, elm, objects, target): """ length = len(list(objects)) if length == 0: - err(filename, tree, elm, "uses undeclared target '%s'" % target) + err(filename, tree, elm, "undeclared-target", "uses undeclared target '%s'" % target) elif length > 1: - err(filename, tree, elm, "several targets are named '%s'" % target) + err(filename, tree, elm, "multiple-target", "several targets are named '%s'" % target) def check_props(filename, tree, root, elm, props): """ @@ -220,25 +293,53 @@ def check_a11y_relation(filename, tree): if len(labelled_by) > 0: continue + # Case 3/4: has an ID... + oid = obj.attrib.get('id') + if oid is not None: + # ...referenced by a single "label-for" <relation> + rels = root.iterfind(".//relation[@target='%s']" % oid) + labelfor = [r for r in rels if r.attrib.get('type') == 'label-for'] + if len(labelfor) == 1: + continue + if len(labelfor) > 1: + err(filename, tree, obj, "multiple-label-for", "has too many elements" + ", expected single <relation type='label-for' target='%s'>" + "%s" % (oid, elm_lines(labelfor))) + continue + + # ...referenced by a single "mnemonic_widget" + props = root.iterfind(".//property[@name='mnemonic_widget']") + props = [p for p in props if p.text == oid] + # TODO: warn when more than one. + if len(props) >= 1: + continue + # TODO: after a few more checks and false-positives filtering, warn # that this does not have a label + if obj.attrib['class'] == "GtkScale": + # GtkScale definitely needs a context + err(filename, tree, obj, "no-labelled-by", "has no accessibility label") def usage(): - print("%s [-W error|none] [-o LOG_FILE] [file ... | -L filelist]" % progname, + print("%s [-W error|none] [-p] [-g SUPPR_FILE] [-s SUPPR_FILE] [-o LOG_FILE] [file ... | -L filelist]" % progname, file=sys.stderr) + 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"); print(" -o Also prints errors and warnings to given file"); sys.exit(2) def main(): - global Werror, Wnone, errors, outfile + global pflag, Werror, Wnone, gen_suppr, gen_supprfile, suppressions, errors, outfile try: - opts, args = getopt.getopt(sys.argv[1:], "W:o:L:") + opts, args = getopt.getopt(sys.argv[1:], "W:pg:s:o:L:") except getopt.GetoptError: usage() + suppr = None out = None filelist = None for o, a in opts: @@ -247,11 +348,28 @@ def main(): Werror = True elif a == "none": Wnone = True + elif o == "-p": + pflag = True + elif o == "-g": + gen_suppr = a + elif o == "-s": + suppr = a elif o == "-o": out = a elif o == "-L": filelist = a + # Read suppression file before overwriting it + if suppr is not None: + try: + supprfile = open(suppr, 'r') + for line in supprfile.readlines(): + prefix = line.rstrip() + suppressions[prefix] = True + supprfile.close() + except IOError: + pass + if out is not None: outfile = open(out, 'w') @@ -270,10 +388,10 @@ def main(): try: tree = ET.parse(filename) except ET.ParseError: - err(filename, None, None, "malformatted xml file") + err(filename, None, None, "parse", "malformatted xml file") continue except IOError: - err(filename, None, None, "unable to read file") + err(filename, None, None, None, "unable to read file") continue try: @@ -281,11 +399,26 @@ def main(): except Exception as error: import traceback traceback.print_exc() - err(filename, None, None, "error parsing file") - + err(filename, None, None, "parse", "error parsing file") + + if errors > 0 or errexists > 0: + estr = "%s new error%s" % (errors, 's' if errors > 1 else '') + if errexists > 0: + estr += " (%s suppressed by %s)" % (errexists, suppr) + print(estr) + + if warnings > 0 or warnexists > 0: + wstr = "%s new warning%s" % (warnings, + 's' if warnings > 1 else '') + if warnexists > 0: + wstr += " (%s suppressed by %s)" % (warnexists, suppr) + print(wstr) + + if gen_supprfile is not None: + gen_supprfile.close() if outfile is not None: outfile.close() - if errors > 0: + if errors > 0 and gen_suppr is None: sys.exit(1) diff --git a/solenv/gbuild/UIConfig.mk b/solenv/gbuild/UIConfig.mk index 0de62d712009..a0d8974fffe9 100644 --- a/solenv/gbuild/UIConfig.mk +++ b/solenv/gbuild/UIConfig.mk @@ -124,6 +124,11 @@ $(call gb_UIConfig_get_clean_target,%) : $(call gb_Output_announce,$*,$(false),UIA,2) rm -f $(call gb_UIConfig_get_a11yerrors_target,$*) +# Enable this to regenerate suppression files +ifeq (1,0) +GEN_A11Y_SUPPRS = -g $(UI_A11YSUPPRS) +endif + define gb_UIConfig_a11yerrors__command $(call gb_Output_announce,$(2),$(true),UIA,1) $(call gb_UIConfig__gla11y_command) @@ -154,6 +159,7 @@ $(call gb_PackageSet_add_package,$(call gb_UIConfig_get_packagesetname,$(1)),$(c $(call gb_UIConfig_get_target,$(1)) :| $(dir $(call gb_UIConfig_get_target,$(1))).dir $(call gb_UIConfig_get_imagelist_target,$(1)) :| $(dir $(call gb_UIConfig_get_imagelist_target,$(1))).dir $(call gb_UIConfig_get_a11yerrors_target,$(1)) :| $(dir $(call gb_UIConfig_get_a11yerrors_target,$(1))).dir +$(call gb_UIConfig_get_a11yerrors_target,$(1)) : UI_A11YSUPPRS := $(SRCDIR)/solenv/sanitizers/ui/$(1).suppr $(call gb_UIConfig_get_target,$(1)) : $(call gb_PackageSet_get_target,$(call gb_UIConfig_get_packagesetname,$(1))) $(call gb_UIConfig_get_clean_target,$(1)) : $(call gb_PackageSet_get_clean_target,$(call gb_UIConfig_get_packagesetname,$(1))) diff --git a/solenv/gbuild/platform/com_GCC_class.mk b/solenv/gbuild/platform/com_GCC_class.mk index 2d4c2cf90a98..3345c7f6cca3 100644 --- a/solenv/gbuild/platform/com_GCC_class.mk +++ b/solenv/gbuild/platform/com_GCC_class.mk @@ -159,7 +159,7 @@ define gb_UIConfig__gla11y_command $(call gb_Helper_abbreviate_dirs,\ $(gb_UIConfig_LXML_PATH) $(gb_Helper_set_ld_path) \ $(call gb_ExternalExecutable_get_command,python) \ - $(gb_UIConfig_gla11y_SCRIPT) -o $@ $(UIFILES) + $(gb_UIConfig_gla11y_SCRIPT) -s $(UI_A11YSUPPRS) $(GEN_A11Y_SUPPRS) -o $@ $(UIFILES) ) endef diff --git a/solenv/gbuild/platform/com_MSC_class.mk b/solenv/gbuild/platform/com_MSC_class.mk index 271459635a3c..467d27cbc529 100644 --- a/solenv/gbuild/platform/com_MSC_class.mk +++ b/solenv/gbuild/platform/com_MSC_class.mk @@ -583,7 +583,7 @@ $(call gb_Helper_abbreviate_dirs,\ FILES=$(call var2file,$(shell $(gb_MKTEMP)),100,$(UIFILES)) && \ $(gb_UIConfig_LXML_PATH) $(gb_Helper_set_ld_path) \ $(call gb_ExternalExecutable_get_command,python) \ - $(gb_UIConfig_gla11y_SCRIPT) -o $@ -L $$FILES + $(gb_UIConfig_gla11y_SCRIPT) -s $(UI_A11YSUPPRS) $(GEN_A11Y_SUPPRS) -o $@ -L $$FILES ) endef diff --git a/solenv/sanitizers/ui/cui.suppr b/solenv/sanitizers/ui/cui.suppr new file mode 100644 index 000000000000..d9e0294f464f --- /dev/null +++ b/solenv/sanitizers/ui/cui.suppr @@ -0,0 +1,3 @@ +cui/uiconfig/ui/gradientpage.ui:GtkBox[@id='GradientPage']/GtkFrame[@id='frame1']/GtkAlignment[@id='alignment1']/GtkBox[@id='box2']/GtkGrid[@id='grid6']/GtkScale[@id='incrementslider'] no-labelled-by +cui/uiconfig/ui/gradientpage.ui:GtkBox[@id='GradientPage']/GtkFrame[@id='frame1']/GtkAlignment[@id='alignment1']/GtkBox[@id='box2']/GtkGrid[@id='grid3']/GtkScale[@id='borderslider'] no-labelled-by +cui/uiconfig/ui/hatchpage.ui:GtkBox[@id='HatchPage']/GtkFrame[@id='frame1']/GtkAlignment[@id='alignment1']/GtkBox[@id='box3']/GtkBox[@id='box1']/GtkScale[@id='angleslider'] no-labelled-by diff --git a/solenv/sanitizers/ui/svt.suppr b/solenv/sanitizers/ui/svt.suppr new file mode 100644 index 000000000000..40ba68b7b93a --- /dev/null +++ b/solenv/sanitizers/ui/svt.suppr @@ -0,0 +1,2 @@ +svtools/uiconfig/ui/graphicexport.ui:GtkDialog[@id='GraphicExportDialog']/GtkBox[@id='dialog-vbox1']/GtkBox[@id='box1']/GtkFrame[@id='jpgquality']/GtkAlignment[@id='alignment5']/GtkGrid[@id='grid2']/GtkScale[@id='compressionjpgsb'] no-labelled-by +svtools/uiconfig/ui/graphicexport.ui:GtkDialog[@id='GraphicExportDialog']/GtkBox[@id='dialog-vbox1']/GtkBox[@id='box1']/GtkFrame[@id='pngcompression']/GtkAlignment[@id='alignment13']/GtkGrid[@id='grid9']/GtkScale[@id='compressionpngsb'] no-labelled-by diff --git a/solenv/sanitizers/ui/svx.suppr b/solenv/sanitizers/ui/svx.suppr new file mode 100644 index 000000000000..9179abb2dbd5 --- /dev/null +++ b/solenv/sanitizers/ui/svx.suppr @@ -0,0 +1,6 @@ +svx/uiconfig/ui/compressgraphicdialog.ui:GtkDialog[@id='CompressGraphicDialog']/GtkBox[@id='dialog-vbox1']/GtkGrid/GtkFrame[@id='frame2']/GtkAlignment[@id='alignment1']/GtkGrid[@id='grid2']/GtkAlignment/GtkGrid/GtkScale[@id='scale-quality'] no-labelled-by +svx/uiconfig/ui/compressgraphicdialog.ui:GtkDialog[@id='CompressGraphicDialog']/GtkBox[@id='dialog-vbox1']/GtkGrid/GtkFrame[@id='frame2']/GtkAlignment[@id='alignment1']/GtkGrid[@id='grid2']/GtkAlignment/GtkGrid/GtkScale[@id='scale-compression'] no-labelled-by +svx/uiconfig/ui/mediaplayback.ui:GtkGrid[@id='MediaPlaybackPanel']/GtkGrid[@id='grid1']/GtkScale[@id='timeslider'] no-labelled-by +svx/uiconfig/ui/mediaplayback.ui:GtkGrid[@id='MediaPlaybackPanel']/GtkGrid[@id='grid1']/GtkScale[@id='volumeslider'] no-labelled-by +svx/uiconfig/ui/sidebararea.ui:GtkGrid[@id='AreaPropertyPanel']/GtkBox[@id='box1']/GtkGrid[@id='grid1']/GtkScale[@id='transparencyslider'] no-labelled-by +svx/uiconfig/ui/sidebarshadow.ui:GtkGrid[@id='ShadowPropertyPanel']/GtkGrid[@id='grid3']/GtkBox[@id='box2']/GtkBox[@id='box1']/GtkGrid[@id='grid2']/GtkScale[@id='transparency_slider'] no-labelled-by |