Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Accessibility] some accessibility improvements #2021

Merged
merged 8 commits into from
Jul 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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