From 0c5314df36fa67c7e368ba1e300461d4fe170c72 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Mon, 10 Jul 2023 11:58:39 +0200 Subject: [PATCH 1/8] Add main landmark --- share/templates/classic/index.html.j2 | 8 +++++--- share/templates/lab/index.html.j2 | 2 ++ share/templates/reveal/index.html.j2 | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/share/templates/classic/index.html.j2 b/share/templates/classic/index.html.j2 index 87a8578ac..a4caaef69 100644 --- a/share/templates/classic/index.html.j2 +++ b/share/templates/classic/index.html.j2 @@ -92,13 +92,15 @@ div#notebook-container{ {% block body_header %} -
-
+
+
+
{% endblock body_header %} {% block body_footer %} +
-
+ {% endblock body_footer %} diff --git a/share/templates/lab/index.html.j2 b/share/templates/lab/index.html.j2 index 7549a4538..4b63d7384 100644 --- a/share/templates/lab/index.html.j2 +++ b/share/templates/lab/index.html.j2 @@ -125,9 +125,11 @@ a.anchor-link { {% else %} {% endif %} +
{%- endblock body_header -%} {% block body_footer %} +
{% endblock body_footer %} diff --git a/share/templates/reveal/index.html.j2 b/share/templates/reveal/index.html.j2 index 84cb65280..af1cd95c8 100644 --- a/share/templates/reveal/index.html.j2 +++ b/share/templates/reveal/index.html.j2 @@ -118,6 +118,7 @@ a.anchor-link { {% else %} {% endif %} +
{%- endblock body_header -%} @@ -125,6 +126,7 @@ a.anchor-link { {% block body_footer %}
+
{% endblock body_footer %} From 52b8d6da4d97398d6dbca5d95973b118ae0b71b6 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Mon, 10 Jul 2023 14:07:28 +0200 Subject: [PATCH 2/8] Add html lang attribute (default to 'en') --- nbconvert/exporters/html.py | 16 ++- nbconvert/utils/iso639_1.py | 191 ++++++++++++++++++++++++++ share/templates/classic/index.html.j2 | 2 +- share/templates/lab/index.html.j2 | 2 +- share/templates/reveal/index.html.j2 | 2 +- 5 files changed, 209 insertions(+), 4 deletions(-) create mode 100644 nbconvert/utils/iso639_1.py diff --git a/nbconvert/exporters/html.py b/nbconvert/exporters/html.py index 30c10b7f0..aacc851af 100644 --- a/nbconvert/exporters/html.py +++ b/nbconvert/exporters/html.py @@ -13,7 +13,7 @@ import jinja2 import markupsafe 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): @@ -27,6 +27,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 @@ -202,6 +203,18 @@ 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""" @@ -318,4 +331,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 diff --git a/nbconvert/utils/iso639_1.py b/nbconvert/utils/iso639_1.py new file mode 100644 index 000000000..ce5c2c664 --- /dev/null +++ b/nbconvert/utils/iso639_1.py @@ -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", +] diff --git a/share/templates/classic/index.html.j2 b/share/templates/classic/index.html.j2 index a4caaef69..30cd50264 100644 --- a/share/templates/classic/index.html.j2 +++ b/share/templates/classic/index.html.j2 @@ -4,7 +4,7 @@ {%- block header -%} - + {%- block html_head -%} diff --git a/share/templates/lab/index.html.j2 b/share/templates/lab/index.html.j2 index 4b63d7384..ad184ca7c 100644 --- a/share/templates/lab/index.html.j2 +++ b/share/templates/lab/index.html.j2 @@ -5,7 +5,7 @@ {%- block header -%} - + {%- block html_head -%} diff --git a/share/templates/reveal/index.html.j2 b/share/templates/reveal/index.html.j2 index af1cd95c8..26fed48c4 100644 --- a/share/templates/reveal/index.html.j2 +++ b/share/templates/reveal/index.html.j2 @@ -10,7 +10,7 @@ {%- block header -%} - + {%- block html_head -%} From 4b3cb5adcf26ca71033fd9cdc2a7f388a8808d9c Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Mon, 10 Jul 2023 15:53:07 +0200 Subject: [PATCH 3/8] Add missing alternative text to img (matplotlib) --- nbconvert/exporters/html.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nbconvert/exporters/html.py b/nbconvert/exporters/html.py index aacc851af..c3f1df5a2 100644 --- a/nbconvert/exporters/html.py +++ b/nbconvert/exporters/html.py @@ -12,6 +12,7 @@ import jinja2 import markupsafe +from bs4 import BeautifulSoup from jupyter_core.paths import jupyter_path from traitlets import Bool, Unicode, default, validate from traitlets.config import Config @@ -253,7 +254,12 @@ 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") + for elem in soup.select("img:not([alt])"): + elem.attrs["alt"] = "Image" + return str(soup), resources + def _init_resources(self, resources): # noqa def resources_include_css(name): From 112365e1a951e16f2c297cd56d4aef74692b2d8b Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Mon, 10 Jul 2023 16:28:50 +0200 Subject: [PATCH 4/8] Set the input and output element focusable for navigation with tab --- nbconvert/exporters/html.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nbconvert/exporters/html.py b/nbconvert/exporters/html.py index c3f1df5a2..910640b51 100644 --- a/nbconvert/exporters/html.py +++ b/nbconvert/exporters/html.py @@ -256,8 +256,15 @@ def from_notebook_node( # type:ignore self.register_filter("filter_data_type", filter_data_type) 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 From 9ea5c20c53c7eb1cba0528dab213915aa10db439 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 14:42:50 +0000 Subject: [PATCH 5/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- nbconvert/exporters/html.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nbconvert/exporters/html.py b/nbconvert/exporters/html.py index 910640b51..055e69915 100644 --- a/nbconvert/exporters/html.py +++ b/nbconvert/exporters/html.py @@ -211,8 +211,10 @@ def default_config(self): @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\".") + 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"] @@ -267,7 +269,6 @@ def from_notebook_node( # type:ignore return str(soup), resources - def _init_resources(self, resources): # noqa def resources_include_css(name): env = self.environment From 7dedd6fbcfa7b825c62fe232a0285b2a9905b93b Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Mon, 10 Jul 2023 18:09:00 +0200 Subject: [PATCH 6/8] Fix tests --- nbconvert/exporters/tests/test_html.py | 13 ++++++------- nbconvert/tests/test_nbconvertapp.py | 8 ++++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/nbconvert/exporters/tests/test_html.py b/nbconvert/exporters/tests/test_html.py index 6d5db7c3c..04c2a74f8 100644 --- a/nbconvert/exporters/tests/test_html.py +++ b/nbconvert/exporters/tests/test_html.py @@ -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']*?)>') + check_for_png = re.compile(r'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( @@ -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']*?)>') + check_for_png = re.compile(r'image.png') result = check_for_png.search(output) assert result - self.assertTrue(result.group(0).strip().startswith('image.png= ' 'symbols' '(' - ''x y z'' + '\'x y z' ')' ) for no_input_flag in (False, True): @@ -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 [" not in text) + assert no_input_flag == ("In [" not in text) assert no_input_flag == ("Out[6]" not in text) assert no_input_flag == (input_content_html not in text) @@ -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): From fdd77b1a4022066251f17197cb29cd572a258746 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 16:09:16 +0000 Subject: [PATCH 7/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- nbconvert/tests/test_nbconvertapp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nbconvert/tests/test_nbconvertapp.py b/nbconvert/tests/test_nbconvertapp.py index c2f05ea0e..657841b0d 100644 --- a/nbconvert/tests/test_nbconvertapp.py +++ b/nbconvert/tests/test_nbconvertapp.py @@ -341,7 +341,7 @@ def test_no_prompt(self): assert os.path.isfile("notebook1.html") with open("notebook1.html", encoding="utf8") as f: text2 = f.read() - assert "In [" in text2 + assert "In [" in text2 assert "Out[6]" in text2 def test_cell_tag_output(self): @@ -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 [" not in text) + assert no_input_flag == ("In [" not in text) assert no_input_flag == ("Out[6]" not in text) assert no_input_flag == (input_content_html not in text) @@ -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): From 53ad886a2873ac726da08cac61107ea70a43cb4e Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Tue, 11 Jul 2023 10:57:27 +0200 Subject: [PATCH 8/8] Fix not-breaking space character --- nbconvert/tests/test_nbconvertapp.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nbconvert/tests/test_nbconvertapp.py b/nbconvert/tests/test_nbconvertapp.py index 657841b0d..590f8a8fd 100644 --- a/nbconvert/tests/test_nbconvertapp.py +++ b/nbconvert/tests/test_nbconvertapp.py @@ -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 [" 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 [" in text2 + assert "In\xa0[" in text2 assert "Out[6]" in text2 def test_cell_tag_output(self): @@ -369,7 +369,7 @@ def test_no_input(self): '= ' 'symbols' '(' - '\'x y z' + '\'x y z\'' ')' ) for no_input_flag in (False, True): @@ -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 [" 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)