Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/nbconvert/exporters/html.py: 32%
142 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-03 06:10 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-03 06:10 +0000
1"""HTML Exporter class"""
3# Copyright (c) Jupyter Development Team.
4# Distributed under the terms of the Modified BSD License.
6import base64
7import json
8import mimetypes
9import os
10from pathlib import Path
11from typing import Any, Dict, Optional, Tuple
13import jinja2
14import markupsafe
15from jupyter_core.paths import jupyter_path
16from traitlets import Bool, Unicode, default
17from traitlets.config import Config
19if tuple(int(x) for x in jinja2.__version__.split(".")[:3]) < (3, 0, 0):
20 from jinja2 import contextfilter # type:ignore
21else:
22 from jinja2 import pass_context as contextfilter
24from jinja2.loaders import split_template_path
25from nbformat import NotebookNode
27from nbconvert.filters.highlight import Highlight2HTML
28from nbconvert.filters.markdown_mistune import IPythonRenderer, MarkdownWithMath
29from nbconvert.filters.widgetsdatatypefilter import WidgetsDataTypeFilter
31from .templateexporter import TemplateExporter
34def find_lab_theme(theme_name):
35 """
36 Find a JupyterLab theme location by name.
38 Parameters
39 ----------
40 theme_name : str
41 The name of the labextension theme you want to find.
43 Raises
44 ------
45 ValueError
46 If the theme was not found, or if it was not specific enough.
48 Returns
49 -------
50 theme_name: str
51 Full theme name (with scope, if any)
52 labextension_path : Path
53 The path to the found labextension on the system.
54 """
55 paths = jupyter_path("labextensions")
57 matching_themes = []
58 theme_path = None
59 for path in paths:
60 for dirpath, dirnames, filenames in os.walk(path):
61 # If it's a federated labextension that contains themes
62 if "package.json" in filenames and "themes" in dirnames:
63 # TODO Find the theme name in the JS code instead?
64 # TODO Find if it's a light or dark theme?
65 with open(Path(dirpath) / "package.json", encoding="utf-8") as fobj:
66 labext_name = json.loads(fobj.read())["name"]
68 if labext_name == theme_name or theme_name in labext_name.split("/"):
69 matching_themes.append(labext_name)
71 full_theme_name = labext_name
72 theme_path = Path(dirpath) / "themes" / labext_name
74 if len(matching_themes) == 0:
75 msg = f'Could not find lab theme "{theme_name}"'
76 raise ValueError(msg)
78 if len(matching_themes) > 1:
79 msg = (
80 f'Found multiple themes matching "{theme_name}": {matching_themes}. '
81 "Please be more specific about which theme you want to use."
82 )
83 raise ValueError(msg)
85 return full_theme_name, theme_path
88class HTMLExporter(TemplateExporter):
89 """
90 Exports a basic HTML document. This exporter assists with the export of
91 HTML. Inherit from it if you are writing your own HTML template and need
92 custom preprocessors/filters. If you don't need custom preprocessors/
93 filters, just change the 'template_file' config option.
94 """
96 export_from_notebook = "HTML"
98 anchor_link_text = Unicode("¶", help="The text used as the text for anchor links.").tag(
99 config=True
100 )
102 exclude_anchor_links = Bool(False, help="If anchor links should be included or not.").tag(
103 config=True
104 )
106 require_js_url = Unicode(
107 "https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.10/require.min.js",
108 help="""
109 URL to load require.js from.
111 Defaults to loading from cdnjs.
112 """,
113 ).tag(config=True)
115 mathjax_url = Unicode(
116 "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?config=TeX-AMS_CHTML-full,Safe",
117 help="""
118 URL to load Mathjax from.
120 Defaults to loading from cdnjs.
121 """,
122 ).tag(config=True)
124 jquery_url = Unicode(
125 "https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js",
126 help="""
127 URL to load jQuery from.
129 Defaults to loading from cdnjs.
130 """,
131 ).tag(config=True)
133 jupyter_widgets_base_url = Unicode(
134 "https://unpkg.com/", help="URL base for Jupyter widgets"
135 ).tag(config=True)
137 widget_renderer_url = Unicode("", help="Full URL for Jupyter widgets").tag(config=True)
139 html_manager_semver_range = Unicode(
140 "*", help="Semver range for Jupyter widgets HTML manager"
141 ).tag(config=True)
143 @default("file_extension")
144 def _file_extension_default(self):
145 return ".html"
147 @default("template_name")
148 def _template_name_default(self):
149 return "lab"
151 theme = Unicode(
152 "light",
153 help="Template specific theme(e.g. the name of a JupyterLab CSS theme distributed as prebuilt extension for the lab template)",
154 ).tag(config=True)
156 sanitize_html = Bool(
157 False,
158 help=(
159 "Whether the HTML in Markdown cells and cell outputs should be sanitized."
160 "This should be set to True by nbviewer or similar tools."
161 ),
162 ).tag(config=True)
164 embed_images = Bool(
165 False, help="Whether or not to embed images as base64 in markdown cells."
166 ).tag(config=True)
168 output_mimetype = "text/html"
170 @property
171 def default_config(self):
172 c = Config(
173 {
174 "NbConvertBase": {
175 "display_data_priority": [
176 "application/vnd.jupyter.widget-view+json",
177 "application/javascript",
178 "text/html",
179 "text/markdown",
180 "image/svg+xml",
181 "text/latex",
182 "image/png",
183 "image/jpeg",
184 "text/plain",
185 ]
186 },
187 "HighlightMagicsPreprocessor": {"enabled": True},
188 }
189 )
190 if super().default_config:
191 c2 = super().default_config.copy()
192 c2.merge(c)
193 c = c2
194 return c
196 @contextfilter
197 def markdown2html(self, context, source):
198 """Markdown to HTML filter respecting the anchor_link_text setting"""
199 cell = context.get("cell", {})
200 attachments = cell.get("attachments", {})
201 path = context.get("resources", {}).get("metadata", {}).get("path", "")
203 renderer = IPythonRenderer(
204 escape=False,
205 attachments=attachments,
206 embed_images=self.embed_images,
207 path=path,
208 anchor_link_text=self.anchor_link_text,
209 exclude_anchor_links=self.exclude_anchor_links,
210 )
211 return MarkdownWithMath(renderer=renderer).render(source)
213 def default_filters(self):
214 """Get the default filters."""
215 yield from super().default_filters()
216 yield ("markdown2html", self.markdown2html)
218 def from_notebook_node( # type:ignore
219 self, nb: NotebookNode, resources: Optional[Dict] = None, **kw: Any
220 ) -> Tuple[str, Dict]:
221 """Convert from notebook node."""
222 langinfo = nb.metadata.get("language_info", {})
223 lexer = langinfo.get("pygments_lexer", langinfo.get("name", None))
224 highlight_code = self.filters.get(
225 "highlight_code", Highlight2HTML(pygments_lexer=lexer, parent=self)
226 )
228 filter_data_type = WidgetsDataTypeFilter(
229 notebook_metadata=self._nb_metadata, parent=self, resources=resources
230 )
232 self.register_filter("highlight_code", highlight_code)
233 self.register_filter("filter_data_type", filter_data_type)
234 return super().from_notebook_node(nb, resources, **kw)
236 def _init_resources(self, resources): # noqa
237 def resources_include_css(name):
238 env = self.environment
239 code = """<style type="text/css">\n%s</style>""" % (env.loader.get_source(env, name)[0])
240 return markupsafe.Markup(code)
242 def resources_include_lab_theme(name):
243 # Try to find the theme with the given name, looking through the labextensions
244 _, theme_path = find_lab_theme(name)
246 with open(theme_path / "index.css") as file:
247 data = file.read()
249 # Embed assets (fonts, images...)
250 for asset in os.listdir(theme_path):
251 local_url = f"url({Path(asset).as_posix()})"
253 if local_url in data:
254 mime_type = mimetypes.guess_type(asset)[0]
256 # Replace asset url by a base64 dataurl
257 with open(theme_path / asset, "rb") as assetfile:
258 base64_data = base64.b64encode(assetfile.read())
259 base64_str = base64_data.replace(b"\n", b"").decode("ascii")
261 data = data.replace(local_url, f"url(data:{mime_type};base64,{base64_str})")
263 code = """<style type="text/css">\n%s</style>""" % data
264 return markupsafe.Markup(code)
266 def resources_include_js(name):
267 """Get the resources include JS for a name."""
268 env = self.environment
269 code = """<script>\n%s</script>""" % (env.loader.get_source(env, name)[0])
270 return markupsafe.Markup(code)
272 def resources_include_url(name):
273 """Get the resources include url for a name."""
274 env = self.environment
275 mime_type, encoding = mimetypes.guess_type(name)
276 try:
277 # we try to load via the jinja loader, but that tries to load
278 # as (encoded) text
279 data = env.loader.get_source(env, name)[0].encode("utf8")
280 except UnicodeDecodeError:
281 # if that fails (for instance a binary file, png or ttf)
282 # we mimic jinja2
283 pieces = split_template_path(name)
284 for searchpath in self.template_paths:
285 filename = os.path.join(searchpath, *pieces)
286 if os.path.exists(filename):
287 with open(filename, "rb") as f:
288 data = f.read()
289 break
290 else:
291 msg = f"No file {name!r} found in {searchpath!r}"
292 raise ValueError(msg)
293 data = base64.b64encode(data)
294 data = data.replace(b"\n", b"").decode("ascii")
295 src = f"data:{mime_type};base64,{data}"
296 return markupsafe.Markup(src)
298 resources = super()._init_resources(resources)
299 resources["theme"] = self.theme
300 resources["include_css"] = resources_include_css
301 resources["include_lab_theme"] = resources_include_lab_theme
302 resources["include_js"] = resources_include_js
303 resources["include_url"] = resources_include_url
304 resources["require_js_url"] = self.require_js_url
305 resources["mathjax_url"] = self.mathjax_url
306 resources["jquery_url"] = self.jquery_url
307 resources["jupyter_widgets_base_url"] = self.jupyter_widgets_base_url
308 resources["widget_renderer_url"] = self.widget_renderer_url
309 resources["html_manager_semver_range"] = self.html_manager_semver_range
310 resources["should_sanitize_html"] = self.sanitize_html
311 return resources