[Libreoffice-commits] core.git: bin/gla11y solenv/gbuild solenv/sanitizers
Samuel Thibault
sthibault at hypra.fr
Wed Feb 28 21:57:42 UTC 2018
bin/gla11y | 165 ++++++++++++++++++++++++++++----
solenv/gbuild/UIConfig.mk | 6 +
solenv/gbuild/platform/com_GCC_class.mk | 2
solenv/gbuild/platform/com_MSC_class.mk | 2
solenv/sanitizers/ui/cui.suppr | 3
solenv/sanitizers/ui/svt.suppr | 2
solenv/sanitizers/ui/svx.suppr | 6 +
7 files changed, 168 insertions(+), 18 deletions(-)
New commits:
commit d09cc5fe73fc1de27e92dae38bc58ea0aadb4f27
Author: Samuel Thibault <sthibault at hypra.fr>
Date: Fri Feb 23 14:49:01 2018 +0100
gla11y: add warning/error suppression machinery
Also add an accessibility test which does have a few existing issues, and
the corresponding suppression lines.
Change-Id: I7095cdc13e40501bbdf6e635c1e4f93f70bc1316
Reviewed-on: https://gerrit.libreoffice.org/50251
Tested-by: Jenkins <ci at libreoffice.org>
Reviewed-by: Stephan Bergmann <sbergman at redhat.com>
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
More information about the Libreoffice-commits
mailing list