quizcomp.parser.renderer.html

  1import os
  2import re
  3import typing
  4
  5import markdown_it.renderer
  6import markdown_it.token
  7import markdown_it.utils
  8
  9import quizcomp.model.constants
 10import quizcomp.parser.common
 11import quizcomp.parser.math
 12import quizcomp.parser.renderer.base
 13import quizcomp.parser.style
 14import quizcomp.util.image
 15
 16HTML_BORDER_SPEC = '1px solid black'
 17
 18# pylint: disable=abstract-method
 19class QuizComposerRendererHTML(markdown_it.renderer.RendererHTML, quizcomp.parser.renderer.base.QuizComposerRendererBase):
 20    """
 21    A renderer for HTML.
 22    This builds of of the existing markdown_it.renderer.RendererHTML renderer.
 23    """
 24
 25    __output__ = quizcomp.model.constants.Format.HTML.value
 26
 27    def image(self,  # type: ignore[override] # pylint: disable=arguments-renamed
 28            tokens: typing.List[markdown_it.token.Token],
 29            token_index: int,
 30            options: markdown_it.utils.OptionsDict,
 31            env: typing.Dict[str, typing.Any],
 32            force_raw_image_src: bool = False,
 33            **kwargs: typing.Any) -> str:
 34        """ Render an image. """
 35
 36        # Do custom rendering and then pass onto super.
 37
 38        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
 39
 40        # Set width.
 41        width_float = quizcomp.parser.style.get_image_width(context.style)
 42        tokens[token_index].attrSet('width', f"{(width_float * 100.0):0.2f}%")
 43
 44        original_src = str(tokens[token_index].attrGet('src'))
 45
 46        # Check the env to see if we need to force raw images.
 47        force_raw_image_src = (force_raw_image_src or context.force_raw_image_src)
 48
 49        if (force_raw_image_src or re.match(r'^http(s)?://', original_src) or original_src.startswith('data:image')):
 50            # Do not further modify the src if we are explicitly directed not to
 51            # or if it is an http or data URL.
 52            pass
 53        else:
 54            # Otherwise, do a base64 encoding of the image and embed it.
 55            path = original_src
 56            if (not os.path.isabs(path)):
 57                path = os.path.join(context.base_dir, path)
 58
 59            path = os.path.abspath(path)
 60
 61            mime, content = quizcomp.util.image.b64_encode(path)
 62            tokens[token_index].attrSet('src', f"data:{mime};base64,{content}")
 63
 64        result = super().image(tokens, token_index, options, env)
 65
 66        # Reset the src so that future callback hits have the proper cache key.
 67        tokens[token_index].attrSet('src', original_src)
 68
 69        return result
 70
 71    def container_block_open(self,
 72            tokens: typing.List[markdown_it.token.Token],
 73            token_index: int,
 74            options: markdown_it.utils.OptionsDict,
 75            env: typing.Dict[str, typing.Any],
 76            ) -> str:
 77        """ Render the opening of a block. """
 78
 79        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
 80
 81        # Add on a specific class.
 82        tokens[token_index].attrJoin('class', 'qg-block')
 83
 84        # Pull any style attached to this block and put it in a copy of the context.
 85        context, block_style = quizcomp.parser.common.handle_block_style(tokens[token_index].meta, context)
 86        env[quizcomp.parser.common.ENV_KEY_CONTEXT] = context
 87
 88        # Attach style based on if we are the root block.
 89        # If root use all style, otherwise just use the style for this block.
 90        active_style = block_style
 91        if (tokens[token_index].meta.get(quizcomp.parser.common.TOKEN_META_KEY_ROOT, False)):
 92            active_style = context.style
 93
 94        style_string = quizcomp.parser.style.compute_html_style_string(active_style)
 95        if (style_string != ''):
 96            tokens[token_index].attrSet('style', style_string)
 97
 98        # Send to super for further rendering.
 99        return super().renderToken(tokens, token_index, options, env)
100
101    def math_inline(self,
102            tokens: typing.List[markdown_it.token.Token],
103            token_index: int,
104            options: markdown_it.utils.OptionsDict,
105            env: typing.Dict[str, typing.Any],
106            ) -> str:
107        """ Render inline math. """
108
109        return quizcomp.parser.math.render(quizcomp.model.constants.Format.HTML, True, tokens, token_index, options, env)
110
111    def math_block(self,
112            tokens: typing.List[markdown_it.token.Token],
113            token_index: int,
114            options: markdown_it.utils.OptionsDict,
115            env: typing.Dict[str, typing.Any],
116            ) -> str:
117        """ Render a math block. """
118
119        return quizcomp.parser.math.render(quizcomp.model.constants.Format.HTML, False, tokens, token_index, options, env)
120
121    def placeholder(self,
122            tokens: typing.List[markdown_it.token.Token],
123            token_index: int,
124            options: markdown_it.utils.OptionsDict,
125            env: typing.Dict[str, typing.Any],
126            ) -> str:
127        """ Render an answer placeholder. """
128
129        return f"<placeholder>{tokens[token_index].content}</placeholder>"
130
131    def table_open(self,
132            tokens: typing.List[markdown_it.token.Token],
133            token_index: int,
134            options: markdown_it.utils.OptionsDict,
135            env: typing.Dict[str, typing.Any],
136            ) -> str:
137        """ Render the opening of a table. """
138
139        token = tokens[token_index]
140        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
141
142        table_style = [
143            'border-collapse: collapse',
144        ]
145
146        has_border = quizcomp.parser.style.get_boolean_style_key(
147            context.style,
148            quizcomp.parser.style.KEY_TABLE_BORDER_TABLE,
149            quizcomp.parser.style.DEFAULT_TABLE_BORDER_TABLE,
150        )
151
152        if (has_border):
153            table_style.append(f"border: {HTML_BORDER_SPEC}")
154        else:
155            table_style.append('border-style: hidden')
156
157        # HTML tables require extra encouragement to align.
158        text_align = quizcomp.parser.style.get_alignment(context.style, quizcomp.parser.style.KEY_TEXT_ALIGN)
159        if (text_align is not None):
160            table_style.append(f"text-align: {text_align}")
161
162        _join_html_style(token, table_style)
163
164        return super().renderToken(tokens, token_index, options, env)
165
166    def thead_open(self,
167            tokens: typing.List[markdown_it.token.Token],
168            token_index: int,
169            options: markdown_it.utils.OptionsDict,
170            env: typing.Dict[str, typing.Any],
171            ) -> str:
172        """ Render the opening of a table header. """
173
174        token = tokens[token_index]
175
176        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
177
178        has_head = quizcomp.parser.style.get_boolean_style_key(
179            context.style,
180            quizcomp.parser.style.KEY_TABLE_HEAD_RULE,
181            quizcomp.parser.style.DEFAULT_TABLE_HEAD_RULE,
182        )
183
184        if (has_head):
185            _join_html_style(token, [f"border-bottom: {HTML_BORDER_SPEC}"])
186
187        return super().renderToken(tokens, token_index, options, env)
188
189    def th_open(self,
190            tokens: typing.List[markdown_it.token.Token],
191            token_index: int,
192            options: markdown_it.utils.OptionsDict,
193            env: typing.Dict[str, typing.Any],
194            ) -> str:
195        """ Render the opening of a th. """
196
197        token = tokens[token_index]
198
199        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
200
201        self._cell_html(token, context.style)
202
203        weight = 'normal'
204        use_bold = quizcomp.parser.style.get_boolean_style_key(
205            context.style,
206            quizcomp.parser.style.KEY_TABLE_HEAD_BOLD,
207            quizcomp.parser.style.DEFAULT_TABLE_HEAD_BOLD,
208        )
209
210        if (use_bold):
211            weight = 'bold'
212
213        _join_html_style(token, [f"font-weight: {weight}"])
214
215        return super().renderToken(tokens, token_index, options, env)
216
217    def td_open(self,
218            tokens: typing.List[markdown_it.token.Token],
219            token_index: int,
220            options: markdown_it.utils.OptionsDict,
221            env: typing.Dict[str, typing.Any],
222            ) -> str:
223        """ Render the opening of a td. """
224
225        token = tokens[token_index]
226
227        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
228
229        self._cell_html(token, context.style)
230
231        return super().renderToken(tokens, token_index, options, env)
232
233    def _cell_html(self, token: markdown_it.token.Token, style: typing.Dict[str, typing.Any]) -> None:
234        """
235        Common cell rendering.
236        """
237
238        height = max(1.0, float(style.get(quizcomp.parser.style.KEY_TABLE_CELL_HEIGHT, quizcomp.parser.style.DEFAULT_TABLE_CELL_HEIGHT)))
239        vertical_padding = height - 1.0
240
241        width = max(1.0, float(style.get(quizcomp.parser.style.KEY_TABLE_CELL_WIDTH, quizcomp.parser.style.DEFAULT_TABLE_CELL_WIDTH)))
242        horizontal_padding = width - 1.0
243
244        cell_style = {
245            'padding-top': f"{(vertical_padding / 2):0.2f}em",
246            'padding-bottom': f"{(vertical_padding / 2):0.2f}em",
247            'padding-left': f"{(horizontal_padding / 2):0.2f}em",
248            'padding-right': f"{(horizontal_padding / 2):0.2f}em",
249        }
250
251        if (quizcomp.parser.style.get_boolean_style_key(
252                style, quizcomp.parser.style.KEY_TABLE_BORDER_CELLS, quizcomp.parser.style.DEFAULT_TABLE_BORDER_CELLS)):
253            cell_style['border'] = HTML_BORDER_SPEC
254
255        _join_html_style(token, [': '.join(item) for item in cell_style.items()])
256
257def _join_html_style(token: markdown_it.token.Token, rules: typing.List[str]) -> None:
258    """
259    Take all style rules to apply, add in any existing style, and set the style attribute.
260    """
261
262    if (len(rules) == 0):
263        return
264
265    existing_style = token.attrGet('style')
266    if (existing_style is not None):
267        rules = [str(existing_style)] + rules
268
269    style_string = '; '.join(rules)
270    token.attrSet('style', style_string)
271
272def get_renderer(options: markdown_it.utils.OptionsDict) -> QuizComposerRendererHTML:
273    """ Get this renderer and options. """
274
275    return QuizComposerRendererHTML()
HTML_BORDER_SPEC = '1px solid black'
class QuizComposerRendererHTML(markdown_it.renderer.RendererHTML, quizcomp.parser.renderer.base.QuizComposerRendererBase):
 20class QuizComposerRendererHTML(markdown_it.renderer.RendererHTML, quizcomp.parser.renderer.base.QuizComposerRendererBase):
 21    """
 22    A renderer for HTML.
 23    This builds of of the existing markdown_it.renderer.RendererHTML renderer.
 24    """
 25
 26    __output__ = quizcomp.model.constants.Format.HTML.value
 27
 28    def image(self,  # type: ignore[override] # pylint: disable=arguments-renamed
 29            tokens: typing.List[markdown_it.token.Token],
 30            token_index: int,
 31            options: markdown_it.utils.OptionsDict,
 32            env: typing.Dict[str, typing.Any],
 33            force_raw_image_src: bool = False,
 34            **kwargs: typing.Any) -> str:
 35        """ Render an image. """
 36
 37        # Do custom rendering and then pass onto super.
 38
 39        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
 40
 41        # Set width.
 42        width_float = quizcomp.parser.style.get_image_width(context.style)
 43        tokens[token_index].attrSet('width', f"{(width_float * 100.0):0.2f}%")
 44
 45        original_src = str(tokens[token_index].attrGet('src'))
 46
 47        # Check the env to see if we need to force raw images.
 48        force_raw_image_src = (force_raw_image_src or context.force_raw_image_src)
 49
 50        if (force_raw_image_src or re.match(r'^http(s)?://', original_src) or original_src.startswith('data:image')):
 51            # Do not further modify the src if we are explicitly directed not to
 52            # or if it is an http or data URL.
 53            pass
 54        else:
 55            # Otherwise, do a base64 encoding of the image and embed it.
 56            path = original_src
 57            if (not os.path.isabs(path)):
 58                path = os.path.join(context.base_dir, path)
 59
 60            path = os.path.abspath(path)
 61
 62            mime, content = quizcomp.util.image.b64_encode(path)
 63            tokens[token_index].attrSet('src', f"data:{mime};base64,{content}")
 64
 65        result = super().image(tokens, token_index, options, env)
 66
 67        # Reset the src so that future callback hits have the proper cache key.
 68        tokens[token_index].attrSet('src', original_src)
 69
 70        return result
 71
 72    def container_block_open(self,
 73            tokens: typing.List[markdown_it.token.Token],
 74            token_index: int,
 75            options: markdown_it.utils.OptionsDict,
 76            env: typing.Dict[str, typing.Any],
 77            ) -> str:
 78        """ Render the opening of a block. """
 79
 80        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
 81
 82        # Add on a specific class.
 83        tokens[token_index].attrJoin('class', 'qg-block')
 84
 85        # Pull any style attached to this block and put it in a copy of the context.
 86        context, block_style = quizcomp.parser.common.handle_block_style(tokens[token_index].meta, context)
 87        env[quizcomp.parser.common.ENV_KEY_CONTEXT] = context
 88
 89        # Attach style based on if we are the root block.
 90        # If root use all style, otherwise just use the style for this block.
 91        active_style = block_style
 92        if (tokens[token_index].meta.get(quizcomp.parser.common.TOKEN_META_KEY_ROOT, False)):
 93            active_style = context.style
 94
 95        style_string = quizcomp.parser.style.compute_html_style_string(active_style)
 96        if (style_string != ''):
 97            tokens[token_index].attrSet('style', style_string)
 98
 99        # Send to super for further rendering.
100        return super().renderToken(tokens, token_index, options, env)
101
102    def math_inline(self,
103            tokens: typing.List[markdown_it.token.Token],
104            token_index: int,
105            options: markdown_it.utils.OptionsDict,
106            env: typing.Dict[str, typing.Any],
107            ) -> str:
108        """ Render inline math. """
109
110        return quizcomp.parser.math.render(quizcomp.model.constants.Format.HTML, True, tokens, token_index, options, env)
111
112    def math_block(self,
113            tokens: typing.List[markdown_it.token.Token],
114            token_index: int,
115            options: markdown_it.utils.OptionsDict,
116            env: typing.Dict[str, typing.Any],
117            ) -> str:
118        """ Render a math block. """
119
120        return quizcomp.parser.math.render(quizcomp.model.constants.Format.HTML, False, tokens, token_index, options, env)
121
122    def placeholder(self,
123            tokens: typing.List[markdown_it.token.Token],
124            token_index: int,
125            options: markdown_it.utils.OptionsDict,
126            env: typing.Dict[str, typing.Any],
127            ) -> str:
128        """ Render an answer placeholder. """
129
130        return f"<placeholder>{tokens[token_index].content}</placeholder>"
131
132    def table_open(self,
133            tokens: typing.List[markdown_it.token.Token],
134            token_index: int,
135            options: markdown_it.utils.OptionsDict,
136            env: typing.Dict[str, typing.Any],
137            ) -> str:
138        """ Render the opening of a table. """
139
140        token = tokens[token_index]
141        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
142
143        table_style = [
144            'border-collapse: collapse',
145        ]
146
147        has_border = quizcomp.parser.style.get_boolean_style_key(
148            context.style,
149            quizcomp.parser.style.KEY_TABLE_BORDER_TABLE,
150            quizcomp.parser.style.DEFAULT_TABLE_BORDER_TABLE,
151        )
152
153        if (has_border):
154            table_style.append(f"border: {HTML_BORDER_SPEC}")
155        else:
156            table_style.append('border-style: hidden')
157
158        # HTML tables require extra encouragement to align.
159        text_align = quizcomp.parser.style.get_alignment(context.style, quizcomp.parser.style.KEY_TEXT_ALIGN)
160        if (text_align is not None):
161            table_style.append(f"text-align: {text_align}")
162
163        _join_html_style(token, table_style)
164
165        return super().renderToken(tokens, token_index, options, env)
166
167    def thead_open(self,
168            tokens: typing.List[markdown_it.token.Token],
169            token_index: int,
170            options: markdown_it.utils.OptionsDict,
171            env: typing.Dict[str, typing.Any],
172            ) -> str:
173        """ Render the opening of a table header. """
174
175        token = tokens[token_index]
176
177        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
178
179        has_head = quizcomp.parser.style.get_boolean_style_key(
180            context.style,
181            quizcomp.parser.style.KEY_TABLE_HEAD_RULE,
182            quizcomp.parser.style.DEFAULT_TABLE_HEAD_RULE,
183        )
184
185        if (has_head):
186            _join_html_style(token, [f"border-bottom: {HTML_BORDER_SPEC}"])
187
188        return super().renderToken(tokens, token_index, options, env)
189
190    def th_open(self,
191            tokens: typing.List[markdown_it.token.Token],
192            token_index: int,
193            options: markdown_it.utils.OptionsDict,
194            env: typing.Dict[str, typing.Any],
195            ) -> str:
196        """ Render the opening of a th. """
197
198        token = tokens[token_index]
199
200        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
201
202        self._cell_html(token, context.style)
203
204        weight = 'normal'
205        use_bold = quizcomp.parser.style.get_boolean_style_key(
206            context.style,
207            quizcomp.parser.style.KEY_TABLE_HEAD_BOLD,
208            quizcomp.parser.style.DEFAULT_TABLE_HEAD_BOLD,
209        )
210
211        if (use_bold):
212            weight = 'bold'
213
214        _join_html_style(token, [f"font-weight: {weight}"])
215
216        return super().renderToken(tokens, token_index, options, env)
217
218    def td_open(self,
219            tokens: typing.List[markdown_it.token.Token],
220            token_index: int,
221            options: markdown_it.utils.OptionsDict,
222            env: typing.Dict[str, typing.Any],
223            ) -> str:
224        """ Render the opening of a td. """
225
226        token = tokens[token_index]
227
228        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
229
230        self._cell_html(token, context.style)
231
232        return super().renderToken(tokens, token_index, options, env)
233
234    def _cell_html(self, token: markdown_it.token.Token, style: typing.Dict[str, typing.Any]) -> None:
235        """
236        Common cell rendering.
237        """
238
239        height = max(1.0, float(style.get(quizcomp.parser.style.KEY_TABLE_CELL_HEIGHT, quizcomp.parser.style.DEFAULT_TABLE_CELL_HEIGHT)))
240        vertical_padding = height - 1.0
241
242        width = max(1.0, float(style.get(quizcomp.parser.style.KEY_TABLE_CELL_WIDTH, quizcomp.parser.style.DEFAULT_TABLE_CELL_WIDTH)))
243        horizontal_padding = width - 1.0
244
245        cell_style = {
246            'padding-top': f"{(vertical_padding / 2):0.2f}em",
247            'padding-bottom': f"{(vertical_padding / 2):0.2f}em",
248            'padding-left': f"{(horizontal_padding / 2):0.2f}em",
249            'padding-right': f"{(horizontal_padding / 2):0.2f}em",
250        }
251
252        if (quizcomp.parser.style.get_boolean_style_key(
253                style, quizcomp.parser.style.KEY_TABLE_BORDER_CELLS, quizcomp.parser.style.DEFAULT_TABLE_BORDER_CELLS)):
254            cell_style['border'] = HTML_BORDER_SPEC
255
256        _join_html_style(token, [': '.join(item) for item in cell_style.items()])

A renderer for HTML. This builds of of the existing markdown_it.renderer.RendererHTML renderer.

def image( self, tokens: List[markdown_it.token.Token], token_index: int, options: markdown_it.utils.OptionsDict, env: Dict[str, Any], force_raw_image_src: bool = False, **kwargs: Any) -> str:
28    def image(self,  # type: ignore[override] # pylint: disable=arguments-renamed
29            tokens: typing.List[markdown_it.token.Token],
30            token_index: int,
31            options: markdown_it.utils.OptionsDict,
32            env: typing.Dict[str, typing.Any],
33            force_raw_image_src: bool = False,
34            **kwargs: typing.Any) -> str:
35        """ Render an image. """
36
37        # Do custom rendering and then pass onto super.
38
39        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
40
41        # Set width.
42        width_float = quizcomp.parser.style.get_image_width(context.style)
43        tokens[token_index].attrSet('width', f"{(width_float * 100.0):0.2f}%")
44
45        original_src = str(tokens[token_index].attrGet('src'))
46
47        # Check the env to see if we need to force raw images.
48        force_raw_image_src = (force_raw_image_src or context.force_raw_image_src)
49
50        if (force_raw_image_src or re.match(r'^http(s)?://', original_src) or original_src.startswith('data:image')):
51            # Do not further modify the src if we are explicitly directed not to
52            # or if it is an http or data URL.
53            pass
54        else:
55            # Otherwise, do a base64 encoding of the image and embed it.
56            path = original_src
57            if (not os.path.isabs(path)):
58                path = os.path.join(context.base_dir, path)
59
60            path = os.path.abspath(path)
61
62            mime, content = quizcomp.util.image.b64_encode(path)
63            tokens[token_index].attrSet('src', f"data:{mime};base64,{content}")
64
65        result = super().image(tokens, token_index, options, env)
66
67        # Reset the src so that future callback hits have the proper cache key.
68        tokens[token_index].attrSet('src', original_src)
69
70        return result

Render an image.

def container_block_open( self, tokens: List[markdown_it.token.Token], token_index: int, options: markdown_it.utils.OptionsDict, env: Dict[str, Any]) -> str:
 72    def container_block_open(self,
 73            tokens: typing.List[markdown_it.token.Token],
 74            token_index: int,
 75            options: markdown_it.utils.OptionsDict,
 76            env: typing.Dict[str, typing.Any],
 77            ) -> str:
 78        """ Render the opening of a block. """
 79
 80        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
 81
 82        # Add on a specific class.
 83        tokens[token_index].attrJoin('class', 'qg-block')
 84
 85        # Pull any style attached to this block and put it in a copy of the context.
 86        context, block_style = quizcomp.parser.common.handle_block_style(tokens[token_index].meta, context)
 87        env[quizcomp.parser.common.ENV_KEY_CONTEXT] = context
 88
 89        # Attach style based on if we are the root block.
 90        # If root use all style, otherwise just use the style for this block.
 91        active_style = block_style
 92        if (tokens[token_index].meta.get(quizcomp.parser.common.TOKEN_META_KEY_ROOT, False)):
 93            active_style = context.style
 94
 95        style_string = quizcomp.parser.style.compute_html_style_string(active_style)
 96        if (style_string != ''):
 97            tokens[token_index].attrSet('style', style_string)
 98
 99        # Send to super for further rendering.
100        return super().renderToken(tokens, token_index, options, env)

Render the opening of a block.

def math_inline( self, tokens: List[markdown_it.token.Token], token_index: int, options: markdown_it.utils.OptionsDict, env: Dict[str, Any]) -> str:
102    def math_inline(self,
103            tokens: typing.List[markdown_it.token.Token],
104            token_index: int,
105            options: markdown_it.utils.OptionsDict,
106            env: typing.Dict[str, typing.Any],
107            ) -> str:
108        """ Render inline math. """
109
110        return quizcomp.parser.math.render(quizcomp.model.constants.Format.HTML, True, tokens, token_index, options, env)

Render inline math.

def math_block( self, tokens: List[markdown_it.token.Token], token_index: int, options: markdown_it.utils.OptionsDict, env: Dict[str, Any]) -> str:
112    def math_block(self,
113            tokens: typing.List[markdown_it.token.Token],
114            token_index: int,
115            options: markdown_it.utils.OptionsDict,
116            env: typing.Dict[str, typing.Any],
117            ) -> str:
118        """ Render a math block. """
119
120        return quizcomp.parser.math.render(quizcomp.model.constants.Format.HTML, False, tokens, token_index, options, env)

Render a math block.

def placeholder( self, tokens: List[markdown_it.token.Token], token_index: int, options: markdown_it.utils.OptionsDict, env: Dict[str, Any]) -> str:
122    def placeholder(self,
123            tokens: typing.List[markdown_it.token.Token],
124            token_index: int,
125            options: markdown_it.utils.OptionsDict,
126            env: typing.Dict[str, typing.Any],
127            ) -> str:
128        """ Render an answer placeholder. """
129
130        return f"<placeholder>{tokens[token_index].content}</placeholder>"

Render an answer placeholder.

def table_open( self, tokens: List[markdown_it.token.Token], token_index: int, options: markdown_it.utils.OptionsDict, env: Dict[str, Any]) -> str:
132    def table_open(self,
133            tokens: typing.List[markdown_it.token.Token],
134            token_index: int,
135            options: markdown_it.utils.OptionsDict,
136            env: typing.Dict[str, typing.Any],
137            ) -> str:
138        """ Render the opening of a table. """
139
140        token = tokens[token_index]
141        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
142
143        table_style = [
144            'border-collapse: collapse',
145        ]
146
147        has_border = quizcomp.parser.style.get_boolean_style_key(
148            context.style,
149            quizcomp.parser.style.KEY_TABLE_BORDER_TABLE,
150            quizcomp.parser.style.DEFAULT_TABLE_BORDER_TABLE,
151        )
152
153        if (has_border):
154            table_style.append(f"border: {HTML_BORDER_SPEC}")
155        else:
156            table_style.append('border-style: hidden')
157
158        # HTML tables require extra encouragement to align.
159        text_align = quizcomp.parser.style.get_alignment(context.style, quizcomp.parser.style.KEY_TEXT_ALIGN)
160        if (text_align is not None):
161            table_style.append(f"text-align: {text_align}")
162
163        _join_html_style(token, table_style)
164
165        return super().renderToken(tokens, token_index, options, env)

Render the opening of a table.

def thead_open( self, tokens: List[markdown_it.token.Token], token_index: int, options: markdown_it.utils.OptionsDict, env: Dict[str, Any]) -> str:
167    def thead_open(self,
168            tokens: typing.List[markdown_it.token.Token],
169            token_index: int,
170            options: markdown_it.utils.OptionsDict,
171            env: typing.Dict[str, typing.Any],
172            ) -> str:
173        """ Render the opening of a table header. """
174
175        token = tokens[token_index]
176
177        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
178
179        has_head = quizcomp.parser.style.get_boolean_style_key(
180            context.style,
181            quizcomp.parser.style.KEY_TABLE_HEAD_RULE,
182            quizcomp.parser.style.DEFAULT_TABLE_HEAD_RULE,
183        )
184
185        if (has_head):
186            _join_html_style(token, [f"border-bottom: {HTML_BORDER_SPEC}"])
187
188        return super().renderToken(tokens, token_index, options, env)

Render the opening of a table header.

def th_open( self, tokens: List[markdown_it.token.Token], token_index: int, options: markdown_it.utils.OptionsDict, env: Dict[str, Any]) -> str:
190    def th_open(self,
191            tokens: typing.List[markdown_it.token.Token],
192            token_index: int,
193            options: markdown_it.utils.OptionsDict,
194            env: typing.Dict[str, typing.Any],
195            ) -> str:
196        """ Render the opening of a th. """
197
198        token = tokens[token_index]
199
200        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
201
202        self._cell_html(token, context.style)
203
204        weight = 'normal'
205        use_bold = quizcomp.parser.style.get_boolean_style_key(
206            context.style,
207            quizcomp.parser.style.KEY_TABLE_HEAD_BOLD,
208            quizcomp.parser.style.DEFAULT_TABLE_HEAD_BOLD,
209        )
210
211        if (use_bold):
212            weight = 'bold'
213
214        _join_html_style(token, [f"font-weight: {weight}"])
215
216        return super().renderToken(tokens, token_index, options, env)

Render the opening of a th.

def td_open( self, tokens: List[markdown_it.token.Token], token_index: int, options: markdown_it.utils.OptionsDict, env: Dict[str, Any]) -> str:
218    def td_open(self,
219            tokens: typing.List[markdown_it.token.Token],
220            token_index: int,
221            options: markdown_it.utils.OptionsDict,
222            env: typing.Dict[str, typing.Any],
223            ) -> str:
224        """ Render the opening of a td. """
225
226        token = tokens[token_index]
227
228        context = typing.cast(quizcomp.parser.common.RenderContext, env[quizcomp.parser.common.ENV_KEY_CONTEXT])
229
230        self._cell_html(token, context.style)
231
232        return super().renderToken(tokens, token_index, options, env)

Render the opening of a td.

def get_renderer( options: markdown_it.utils.OptionsDict) -> QuizComposerRendererHTML:
273def get_renderer(options: markdown_it.utils.OptionsDict) -> QuizComposerRendererHTML:
274    """ Get this renderer and options. """
275
276    return QuizComposerRendererHTML()

Get this renderer and options.