Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/nbconvert/exporters/html.py: 32%

144 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-01 06:54 +0000

1"""HTML Exporter class""" 

2 

3# Copyright (c) Jupyter Development Team. 

4# Distributed under the terms of the Modified BSD License. 

5 

6import base64 

7import json 

8import mimetypes 

9import os 

10from pathlib import Path 

11from typing import Any, Dict, Optional, Tuple 

12 

13import jinja2 

14import markupsafe 

15from jupyter_core.paths import jupyter_path 

16from traitlets import Bool, Unicode, default 

17from traitlets.config import Config 

18 

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 

23 

24from jinja2.loaders import split_template_path 

25from nbformat import NotebookNode 

26 

27from nbconvert.filters.highlight import Highlight2HTML 

28from nbconvert.filters.markdown_mistune import IPythonRenderer, MarkdownWithMath 

29from nbconvert.filters.widgetsdatatypefilter import WidgetsDataTypeFilter 

30 

31from .templateexporter import TemplateExporter 

32 

33 

34def find_lab_theme(theme_name): 

35 """ 

36 Find a JupyterLab theme location by name. 

37 

38 Parameters 

39 ---------- 

40 theme_name : str 

41 The name of the labextension theme you want to find. 

42 

43 Raises 

44 ------ 

45 ValueError 

46 If the theme was not found, or if it was not specific enough. 

47 

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") 

56 

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"] 

67 

68 if labext_name == theme_name or theme_name in labext_name.split("/"): 

69 matching_themes.append(labext_name) 

70 

71 full_theme_name = labext_name 

72 theme_path = Path(dirpath) / "themes" / labext_name 

73 

74 if len(matching_themes) == 0: 

75 msg = f'Could not find lab theme "{theme_name}"' 

76 raise ValueError(msg) 

77 

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) 

84 

85 return full_theme_name, theme_path 

86 

87 

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 """ 

95 

96 export_from_notebook = "HTML" 

97 

98 anchor_link_text = Unicode("¶", help="The text used as the text for anchor links.").tag( 

99 config=True 

100 ) 

101 

102 exclude_anchor_links = Bool(False, help="If anchor links should be included or not.").tag( 

103 config=True 

104 ) 

105 

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. 

110 

111 Defaults to loading from cdnjs. 

112 """, 

113 ).tag(config=True) 

114 

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. 

119 

120 Defaults to loading from cdnjs. 

121 """, 

122 ).tag(config=True) 

123 

124 mermaid_js_url = Unicode( 

125 "https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.2.3/mermaid.esm.min.mjs", 

126 help=""" 

127 URL to load MermaidJS from. 

128 

129 Defaults to loading from cdnjs. 

130 """, 

131 ) 

132 

133 jquery_url = Unicode( 

134 "https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js", 

135 help=""" 

136 URL to load jQuery from. 

137 

138 Defaults to loading from cdnjs. 

139 """, 

140 ).tag(config=True) 

141 

142 jupyter_widgets_base_url = Unicode( 

143 "https://unpkg.com/", help="URL base for Jupyter widgets" 

144 ).tag(config=True) 

145 

146 widget_renderer_url = Unicode("", help="Full URL for Jupyter widgets").tag(config=True) 

147 

148 html_manager_semver_range = Unicode( 

149 "*", help="Semver range for Jupyter widgets HTML manager" 

150 ).tag(config=True) 

151 

152 @default("file_extension") 

153 def _file_extension_default(self): 

154 return ".html" 

155 

156 @default("template_name") 

157 def _template_name_default(self): 

158 return "lab" 

159 

160 theme = Unicode( 

161 "light", 

162 help="Template specific theme(e.g. the name of a JupyterLab CSS theme distributed as prebuilt extension for the lab template)", 

163 ).tag(config=True) 

164 

165 sanitize_html = Bool( 

166 False, 

167 help=( 

168 "Whether the HTML in Markdown cells and cell outputs should be sanitized." 

169 "This should be set to True by nbviewer or similar tools." 

170 ), 

171 ).tag(config=True) 

172 

173 embed_images = Bool( 

174 False, help="Whether or not to embed images as base64 in markdown cells." 

175 ).tag(config=True) 

176 

177 output_mimetype = "text/html" 

178 

179 @property 

180 def default_config(self): 

181 c = Config( 

182 { 

183 "NbConvertBase": { 

184 "display_data_priority": [ 

185 "application/vnd.jupyter.widget-view+json", 

186 "application/javascript", 

187 "text/html", 

188 "text/markdown", 

189 "image/svg+xml", 

190 "text/latex", 

191 "image/png", 

192 "image/jpeg", 

193 "text/plain", 

194 ] 

195 }, 

196 "HighlightMagicsPreprocessor": {"enabled": True}, 

197 } 

198 ) 

199 if super().default_config: 

200 c2 = super().default_config.copy() 

201 c2.merge(c) 

202 c = c2 

203 return c 

204 

205 @contextfilter 

206 def markdown2html(self, context, source): 

207 """Markdown to HTML filter respecting the anchor_link_text setting""" 

208 cell = context.get("cell", {}) 

209 attachments = cell.get("attachments", {}) 

210 path = context.get("resources", {}).get("metadata", {}).get("path", "") 

211 

212 renderer = IPythonRenderer( 

213 escape=False, 

214 attachments=attachments, 

215 embed_images=self.embed_images, 

216 path=path, 

217 anchor_link_text=self.anchor_link_text, 

218 exclude_anchor_links=self.exclude_anchor_links, 

219 ) 

220 return MarkdownWithMath(renderer=renderer).render(source) 

221 

222 def default_filters(self): 

223 """Get the default filters.""" 

224 yield from super().default_filters() 

225 yield ("markdown2html", self.markdown2html) 

226 

227 def from_notebook_node( # type:ignore 

228 self, nb: NotebookNode, resources: Optional[Dict] = None, **kw: Any 

229 ) -> Tuple[str, Dict]: 

230 """Convert from notebook node.""" 

231 langinfo = nb.metadata.get("language_info", {}) 

232 lexer = langinfo.get("pygments_lexer", langinfo.get("name", None)) 

233 highlight_code = self.filters.get( 

234 "highlight_code", Highlight2HTML(pygments_lexer=lexer, parent=self) 

235 ) 

236 

237 filter_data_type = WidgetsDataTypeFilter( 

238 notebook_metadata=self._nb_metadata, parent=self, resources=resources 

239 ) 

240 

241 self.register_filter("highlight_code", highlight_code) 

242 self.register_filter("filter_data_type", filter_data_type) 

243 return super().from_notebook_node(nb, resources, **kw) 

244 

245 def _init_resources(self, resources): # noqa 

246 def resources_include_css(name): 

247 env = self.environment 

248 code = """<style type="text/css">\n%s</style>""" % (env.loader.get_source(env, name)[0]) 

249 return markupsafe.Markup(code) 

250 

251 def resources_include_lab_theme(name): 

252 # Try to find the theme with the given name, looking through the labextensions 

253 _, theme_path = find_lab_theme(name) 

254 

255 with open(theme_path / "index.css") as file: 

256 data = file.read() 

257 

258 # Embed assets (fonts, images...) 

259 for asset in os.listdir(theme_path): 

260 local_url = f"url({Path(asset).as_posix()})" 

261 

262 if local_url in data: 

263 mime_type = mimetypes.guess_type(asset)[0] 

264 

265 # Replace asset url by a base64 dataurl 

266 with open(theme_path / asset, "rb") as assetfile: 

267 base64_data = base64.b64encode(assetfile.read()) 

268 base64_str = base64_data.replace(b"\n", b"").decode("ascii") 

269 

270 data = data.replace(local_url, f"url(data:{mime_type};base64,{base64_str})") 

271 

272 code = """<style type="text/css">\n%s</style>""" % data 

273 return markupsafe.Markup(code) 

274 

275 def resources_include_js(name): 

276 """Get the resources include JS for a name.""" 

277 env = self.environment 

278 code = """<script>\n%s</script>""" % (env.loader.get_source(env, name)[0]) 

279 return markupsafe.Markup(code) 

280 

281 def resources_include_url(name): 

282 """Get the resources include url for a name.""" 

283 env = self.environment 

284 mime_type, encoding = mimetypes.guess_type(name) 

285 try: 

286 # we try to load via the jinja loader, but that tries to load 

287 # as (encoded) text 

288 data = env.loader.get_source(env, name)[0].encode("utf8") 

289 except UnicodeDecodeError: 

290 # if that fails (for instance a binary file, png or ttf) 

291 # we mimic jinja2 

292 pieces = split_template_path(name) 

293 for searchpath in self.template_paths: 

294 filename = os.path.join(searchpath, *pieces) 

295 if os.path.exists(filename): 

296 with open(filename, "rb") as f: 

297 data = f.read() 

298 break 

299 else: 

300 msg = f"No file {name!r} found in {searchpath!r}" 

301 raise ValueError(msg) 

302 data = base64.b64encode(data) 

303 data = data.replace(b"\n", b"").decode("ascii") 

304 src = f"data:{mime_type};base64,{data}" 

305 return markupsafe.Markup(src) 

306 

307 resources = super()._init_resources(resources) 

308 resources["theme"] = self.theme 

309 resources["include_css"] = resources_include_css 

310 resources["include_lab_theme"] = resources_include_lab_theme 

311 resources["include_js"] = resources_include_js 

312 resources["include_url"] = resources_include_url 

313 resources["require_js_url"] = self.require_js_url 

314 resources["mathjax_url"] = self.mathjax_url 

315 resources["mermaid_js_url"] = self.mermaid_js_url 

316 resources["jquery_url"] = self.jquery_url 

317 resources["jupyter_widgets_base_url"] = self.jupyter_widgets_base_url 

318 resources["widget_renderer_url"] = self.widget_renderer_url 

319 resources["html_manager_semver_range"] = self.html_manager_semver_range 

320 resources["should_sanitize_html"] = self.sanitize_html 

321 return resources