Skip to content

Commit

Permalink
[Accessibility] some accessibility improvements (#2021)
Browse files Browse the repository at this point in the history
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
brichet and pre-commit-ci[bot] authored Jul 15, 2023
1 parent 8e1eae4 commit ef007a8
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 20 deletions.
32 changes: 30 additions & 2 deletions nbconvert/exporters/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@

import jinja2
import markupsafe
from bs4 import BeautifulSoup
from jupyter_core.paths import jupyter_path
from traitlets import Bool, Unicode, default
from traitlets import Bool, Unicode, default, validate
from traitlets.config import Config

if tuple(int(x) for x in jinja2.__version__.split(".")[:3]) < (3, 0, 0):
Expand All @@ -27,6 +28,7 @@
from nbconvert.filters.highlight import Highlight2HTML
from nbconvert.filters.markdown_mistune import IPythonRenderer, MarkdownWithMath
from nbconvert.filters.widgetsdatatypefilter import WidgetsDataTypeFilter
from nbconvert.utils.iso639_1 import iso639_1

from .templateexporter import TemplateExporter

Expand Down Expand Up @@ -202,6 +204,20 @@ def default_config(self):
c = c2
return c

language_code = Unicode(
"en", help="Language code of the content, should be one of the ISO639-1"
).tag(config=True)

@validate("language_code")
def _valid_language_code(self, proposal):
if self.language_code not in iso639_1:
self.log.warn(
f'"{self.language_code}" is not an ISO 639-1 language code. '
'It has been replaced by the default value "en".'
)
return proposal["trait"].default_value
return proposal["value"]

@contextfilter
def markdown2html(self, context, source):
"""Markdown to HTML filter respecting the anchor_link_text setting"""
Expand Down Expand Up @@ -240,7 +256,18 @@ def from_notebook_node( # type:ignore

self.register_filter("highlight_code", highlight_code)
self.register_filter("filter_data_type", filter_data_type)
return super().from_notebook_node(nb, resources, **kw)
html, resources = super().from_notebook_node(nb, resources, **kw)
soup = BeautifulSoup(html, features="html.parser")
# Add image's alternative text
for elem in soup.select("img:not([alt])"):
elem.attrs["alt"] = "Image"
# Set input and output focusable
for elem in soup.select(".jp-Notebook div.jp-Cell-inputWrapper"):
elem.attrs["tabindex"] = "0"
for elem in soup.select(".jp-Notebook div.jp-OutputArea-output"):
elem.attrs["tabindex"] = "0"

return str(soup), resources

def _init_resources(self, resources): # noqa
def resources_include_css(name):
Expand Down Expand Up @@ -318,4 +345,5 @@ def resources_include_url(name):
resources["widget_renderer_url"] = self.widget_renderer_url
resources["html_manager_semver_range"] = self.html_manager_semver_range
resources["should_sanitize_html"] = self.sanitize_html
resources["language_code"] = self.language_code
return resources
13 changes: 6 additions & 7 deletions nbconvert/exporters/tests/test_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ def test_png_metadata(self):
(output, resources) = HTMLExporter(template_name="classic").from_filename(
self._get_notebook(nb_name="pngmetadata.ipynb")
)
check_for_png = re.compile(r'<img src="[^"]*?"([^>]*?)>')
check_for_png = re.compile(r'<img alt="Image"([^>]*?)>')
result = check_for_png.search(output)
assert result
attr_string = result.group(1)
assert "width" in attr_string
assert "height" in attr_string
assert "width=" in attr_string
assert "height=" in attr_string

def test_javascript_output(self):
nb = v4.new_notebook(
Expand All @@ -103,13 +103,12 @@ def test_attachments(self):
(output, resources) = HTMLExporter(template_name="classic").from_file(
self._get_notebook(nb_name="attachment.ipynb")
)
check_for_png = re.compile(r'<img src="[^"]*?"([^>]*?)>')
check_for_png = re.compile(r'<img alt="image.png" src="([^"]*?)"/>')
result = check_for_png.search(output)
assert result
self.assertTrue(result.group(0).strip().startswith('<img src="data:image/png;base64,iVBOR'))
self.assertTrue(result.group(1).strip().startswith('alt="image.png"'))
self.assertTrue(result.group(1).strip().startswith('data:image/png;base64,iVBOR'))

check_for_data = re.compile(r'<img src="(?P<url>[^"]*?)"')
check_for_data = re.compile(r'<img alt="image.png" src="(?P<url>[^"]*?)"')
results = check_for_data.findall(output)
assert results[0] != results[1], "attachments only need to be unique within a cell"
assert "image/svg" in results[1], "second image should use svg"
Expand Down
10 changes: 5 additions & 5 deletions nbconvert/tests/test_nbconvertapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,13 +335,13 @@ def test_no_prompt(self):
assert os.path.isfile("notebook1.html")
with open("notebook1.html", encoding="utf8") as f:
text = f.read()
assert "In&nbsp;[" not in text
assert "In\xa0[" not in text
assert "Out[6]" not in text
self.nbconvert("notebook1.ipynb --log-level 0 --to html")
assert os.path.isfile("notebook1.html")
with open("notebook1.html", encoding="utf8") as f:
text2 = f.read()
assert "In&nbsp;[" in text2
assert "In\xa0[" in text2
assert "Out[6]" in text2

def test_cell_tag_output(self):
Expand Down Expand Up @@ -369,7 +369,7 @@ def test_no_input(self):
'<span class="o">=</span> '
'<span class="n">symbols</span>'
'<span class="p">(</span>'
'<span class="s1">&#39;x y z&#39;</span>'
'<span class="s1">\'x y z\'</span>'
'<span class="p">)</span>'
)
for no_input_flag in (False, True):
Expand All @@ -382,7 +382,7 @@ def test_no_input(self):

with open("notebook1.html", encoding="utf8") as f:
text = f.read()
assert no_input_flag == ("In&nbsp;[" not in text)
assert no_input_flag == ("In\xa0[" not in text)
assert no_input_flag == ("Out[6]" not in text)
assert no_input_flag == (input_content_html not in text)

Expand Down Expand Up @@ -580,7 +580,7 @@ def test_not_embedding_images_htmlexporter(self):
with open("notebook5_embed_images.html", encoding="utf8") as f:
text = f.read()
assert "./containerized_deployments.jpeg" in text
assert "src='./containerized_deployments.jpeg'" in text
assert 'src="./containerized_deployments.jpeg"' in text
assert text.count("data:image/jpeg;base64") == 0

def test_embedding_images_htmlexporter(self):
Expand Down
191 changes: 191 additions & 0 deletions nbconvert/utils/iso639_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
""" List of ISO639-1 language code"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

iso639_1 = [
"aa",
"ab",
"ae",
"af",
"ak",
"am",
"an",
"ar",
"as",
"av",
"ay",
"az",
"ba",
"be",
"bg",
"bh",
"bi",
"bm",
"bn",
"bo",
"br",
"bs",
"ca",
"ce",
"ch",
"co",
"cr",
"cs",
"cu",
"cv",
"cy",
"da",
"de",
"dv",
"dz",
"ee",
"el",
"en",
"eo",
"es",
"et",
"eu",
"fa",
"ff",
"fi",
"fj",
"fo",
"fr",
"fy",
"ga",
"gd",
"gl",
"gn",
"gu",
"gv",
"ha",
"he",
"hi",
"ho",
"hr",
"ht",
"hu",
"hy",
"hz",
"ia",
"id",
"ie",
"ig",
"ii",
"ik",
"io",
"is",
"it",
"iu",
"ja",
"jv",
"ka",
"kg",
"ki",
"kj",
"kk",
"kl",
"km",
"kn",
"ko",
"kr",
"ks",
"ku",
"kv",
"kw",
"ky",
"la",
"lb",
"lg",
"li",
"ln",
"lo",
"lt",
"lu",
"lv",
"mg",
"mh",
"mi",
"mk",
"ml",
"mn",
"mr",
"ms",
"mt",
"my",
"na",
"nb",
"nd",
"ne",
"ng",
"nl",
"nn",
"no",
"nr",
"nv",
"ny",
"oc",
"oj",
"om",
"or",
"os",
"pa",
"pi",
"pl",
"ps",
"pt",
"qu",
"rm",
"rn",
"ro",
"ru",
"rw",
"sa",
"sc",
"sd",
"se",
"sg",
"si",
"sk",
"sl",
"sm",
"sn",
"so",
"sq",
"sr",
"ss",
"st",
"su",
"sv",
"sw",
"ta",
"te",
"tg",
"th",
"ti",
"tk",
"tl",
"tn",
"to",
"tr",
"ts",
"tt",
"tw",
"ty",
"ug",
"uk",
"ur",
"uz",
"ve",
"vi",
"vo",
"wa",
"wo",
"xh",
"yi",
"yo",
"za",
"zh",
"zu",
]
10 changes: 6 additions & 4 deletions share/templates/classic/index.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

{%- block header -%}
<!DOCTYPE html>
<html>
<html lang="{{ resources.language_code }}">
<head>
{%- block html_head -%}
<meta charset="utf-8" />
Expand Down Expand Up @@ -92,13 +92,15 @@ div#notebook-container{

{% block body_header %}
<body>
<div tabindex="-1" id="notebook" class="border-box-sizing">
<div class="container" id="notebook-container">
<main>
<div tabindex="-1" id="notebook" class="border-box-sizing">
<div class="container" id="notebook-container">
{% endblock body_header %}

{% block body_footer %}
</div>
</div>
</div>
</main>
</body>
{% endblock body_footer %}

Expand Down
4 changes: 3 additions & 1 deletion share/templates/lab/index.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

{%- block header -%}
<!DOCTYPE html>
<html>
<html lang="{{ resources.language_code }}">
<head>
{%- block html_head -%}
<meta charset="utf-8" />
Expand Down Expand Up @@ -125,9 +125,11 @@ a.anchor-link {
{% else %}
<body class="jp-Notebook" data-jp-theme-light="true" data-jp-theme-name="JupyterLab Light">
{% endif %}
<main>
{%- endblock body_header -%}

{% block body_footer %}
</main>
</body>
{% endblock body_footer %}

Expand Down
Loading

0 comments on commit ef007a8

Please sign in to comment.