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

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

128 

129 Defaults to loading from cdnjs. 

130 """, 

131 ).tag(config=True) 

132 

133 jupyter_widgets_base_url = Unicode( 

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

135 ).tag(config=True) 

136 

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

138 

139 html_manager_semver_range = Unicode( 

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

141 ).tag(config=True) 

142 

143 @default("file_extension") 

144 def _file_extension_default(self): 

145 return ".html" 

146 

147 @default("template_name") 

148 def _template_name_default(self): 

149 return "lab" 

150 

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) 

155 

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) 

163 

164 embed_images = Bool( 

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

166 ).tag(config=True) 

167 

168 output_mimetype = "text/html" 

169 

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 

195 

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

202 

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) 

212 

213 def default_filters(self): 

214 """Get the default filters.""" 

215 yield from super().default_filters() 

216 yield ("markdown2html", self.markdown2html) 

217 

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 ) 

227 

228 filter_data_type = WidgetsDataTypeFilter( 

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

230 ) 

231 

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) 

235 

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) 

241 

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) 

245 

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

247 data = file.read() 

248 

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

250 for asset in os.listdir(theme_path): 

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

252 

253 if local_url in data: 

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

255 

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

260 

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

262 

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

264 return markupsafe.Markup(code) 

265 

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) 

271 

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) 

297 

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