1"""Module that pre-processes the notebook for export to HTML."""
2
3# Copyright (c) Jupyter Development Team.
4# Distributed under the terms of the Modified BSD License.
5
6import hashlib
7import os
8
9from jupyterlab_pygments import JupyterStyle # type:ignore[import-untyped]
10from pygments.style import Style
11from traitlets import Type, Unicode, Union
12
13from .base import Preprocessor
14
15try:
16 from notebook import DEFAULT_STATIC_FILES_PATH # type:ignore[import-not-found]
17except ImportError:
18 DEFAULT_STATIC_FILES_PATH = None
19
20
21class CSSHTMLHeaderPreprocessor(Preprocessor):
22 """
23 Preprocessor used to pre-process notebook for HTML output. Adds IPython notebook
24 front-end CSS and Pygments CSS to HTML output.
25 """
26
27 highlight_class = Unicode(".highlight", help="CSS highlight class identifier").tag(config=True)
28
29 style = Union(
30 [Unicode("default"), Type(klass=Style)],
31 help="Name of the pygments style to use",
32 default_value=JupyterStyle,
33 ).tag(config=True)
34
35 def __init__(self, *pargs, **kwargs):
36 """Initialize the preprocessor."""
37 Preprocessor.__init__(self, *pargs, **kwargs)
38 self._default_css_hash = None
39
40 def preprocess(self, nb, resources):
41 """Fetch and add CSS to the resource dictionary
42
43 Fetch CSS from IPython and Pygments to add at the beginning
44 of the html files. Add this css in resources in the
45 "inlining.css" key
46
47 Parameters
48 ----------
49 nb : NotebookNode
50 Notebook being converted
51 resources : dictionary
52 Additional resources used in the conversion process. Allows
53 preprocessors to pass variables into the Jinja engine.
54 """
55 resources["inlining"] = {}
56 resources["inlining"]["css"] = self._generate_header(resources)
57 return nb, resources
58
59 def _generate_header(self, resources):
60 """
61 Fills self.header with lines of CSS extracted from IPython
62 and Pygments.
63 """
64 from pygments.formatters import HtmlFormatter
65
66 header = []
67
68 formatter = HtmlFormatter(style=self.style)
69 pygments_css = formatter.get_style_defs(self.highlight_class)
70 header.append(pygments_css)
71
72 # Load the user's custom CSS and IPython's default custom CSS. If they
73 # differ, assume the user has made modifications to his/her custom CSS
74 # and that we should inline it in the nbconvert output.
75 config_dir = resources["config_dir"]
76 custom_css_filename = os.path.join(config_dir, "custom", "custom.css")
77 if os.path.isfile(custom_css_filename):
78 if DEFAULT_STATIC_FILES_PATH and self._default_css_hash is None:
79 self._default_css_hash = self._hash(
80 os.path.join(DEFAULT_STATIC_FILES_PATH, "custom", "custom.css")
81 )
82 if self._hash(custom_css_filename) != self._default_css_hash:
83 with open(custom_css_filename, encoding="utf-8") as f:
84 header.append(f.read())
85 return header
86
87 def _hash(self, filename):
88 """Compute the hash of a file."""
89 md5 = hashlib.md5() # noqa: S324
90 with open(filename, "rb") as f:
91 md5.update(f.read())
92 return md5.digest()