Beispiel #1
0
    def header(elem, doc):
        if not isinstance(elem, pf.Plain):
            return None

        return pf.Div(
            pf.Plain(pf.RawInline('\\centering', 'latex'),
                     pf.Strong(*elem.content)),
            attributes={'style': 'text-align:center'})
Beispiel #2
0
 def test_uxid_para(self):
     annot = pf.Div(
         identifier="{}-foo".format(SITE_UXID_PREFIX),
         classes=(SITE_UXID_PREFIX, ),
     )
     para = pf.Para(pf.Str("bar"))
     identifier = extract_identifier([annot, para])
     self.assertEqual(identifier, "foo")
Beispiel #3
0
def finalize(doc):
    if doc.backmatter:
        doc.replacement_ok = False
        backmatter = pf.Div(*doc.backmatter, identifier='backmatter')
        pf.replace_keyword(doc, '$backmatter', backmatter)
        assert doc.replacement_ok, "Couldn't replace '$backmatter'"
    else:
        pf.replace_keyword(doc, '$backmatter', pf.Null())
Beispiel #4
0
def fenced_action(options, data, element, doc):
	modalid  = options.get('id', 'modal1')
	title    = options.get('title')
	closebtn = options.get('closebtn', True)
	size     = options.get('size', 'default')
	size2class = {
		'default': None,
		'small'  : 'modal-sm',
		'sm'     : 'modal-sm',
		'large'  : 'modal-lg',
		'lg'     : 'modal-lg',
		'xlarge' : 'modal-xl',
		'xl'     : 'modal-xl',
	}

	components = []
	if title:
		modal_header1 = pf.Header(pf.Str(title), classes=['modal-title'], level=5, identifier=modalid + 'Title')
		modal_header2 = pf.Div(
			pf.Div(pf.Para(pf.Str('x')), attributes = {'aria-hidden': "true"}),
			classes = ['close', 'button'],
			attributes = {
				'type': 'button',
				'data-dismiss': 'modal',
				'aria-label': 'Close'
			})
		components.append(pf.Div(modal_header1, modal_header2, classes = ['modal-header']))
	components.append(pf.Div(*data, classes = ['modal-body']))
	if closebtn:
		components.append(pf.Div(
			pf.Div(pf.Para(pf.Str('Close')), classes = ['button', 'btn', 'btn-secondary'],
			attributes = {
				'type': 'button',
				'data-dismiss': 'modal',
			}),
			classes = ['modal-footer']
		))
	modal_content = pf.Div(*components, classes = ['modal-content'])
	mainclasses = ['modal-dialog', 'modal-dialog-centered', 'modal-dialog-scrollable']
	sizeclass = size2class.get(size)
	if sizeclass:
		mainclasses.append(sizeclass)
	model_dialog = pf.Div(modal_content, classes = mainclasses, attributes = {'role': 'document'})

	return pf.Div(model_dialog, classes = ['modal', 'fade'], identifier = modalid, attributes = {
		'tabindex'       : '-1',
		'role'           : 'dialog',
		'aria-labelledby': modalid + 'Title',
		'aria-hidden'    : "true"
	})
Beispiel #5
0
def action(elem, doc):
    if isinstance(elem, pf.Div) and "Question" in elem.classes:
        question = elem
        parent = question.parent
        header = pf.Div(pf.Para(pf.Str("Question {0}".format(doc.latest_question))))
        doc.latest_question += 1
        parent.content[question.index] = header
        header.classes.append("QNumber")
        header.content.append(question)
        return header
Beispiel #6
0
 def handle_minputhint(self, cmd_args, elem):
     r"""Handle ``\MInputHint`` command."""
     content = parse_fragment(cmd_args[0], elem.doc.metadata["lang"].text)
     if isinstance(elem, pf.Block):
         div = pf.Div(classes=ELEMENT_CLASSES["MINPUTHINT"])
         div.content.extend(content)
         return div
     span = pf.Span(classes=ELEMENT_CLASSES["MINPUTHINT"])
     if content and isinstance(content[0], pf.Para):
         span.content.extend(content[0].content)
     return span
Beispiel #7
0
 def action(self, elem, doc):
     if (doc.format == "docx"):
         default_style = doc.get_metadata("image-div-style", "Image Caption")
         if isinstance(elem, pf.Para) and len(elem.content) == 1:
             for subelem in elem.content:
                 if isinstance(subelem, pf.Image):
                     style = subelem.attributes.get("custom-style", default_style)
                     subelem.attributes["custom-style"] = style
                     subelem.attributes.pop("custom-style")
                     d = pf.Div(elem, attributes={"custom-style": style})
                     return d
Beispiel #8
0
def action(elem, doc):
    if isinstance(elem, pf.BulletList) and doc.format == 'latex':
        div = pf.Div()
        for item in elem.content.list:
            plain = item.content.list[0]
            key = plain.content.pop(0)
            cv_line_tex = "\cvitem{%s}{%s}{}" % (pf.stringify(key),
                                                 pf.stringify(plain))
            raw_inline = pf.RawInline(cv_line_tex, format='latex')
            div.content.extend([pf.Plain(raw_inline)])
        return div
Beispiel #9
0
def figure(options, data, element, doc):

    # Get options
    fn = os.path.abspath(options['source']).replace('\\', '/')
    title = options.get('title', 'Untitled')
    notes = data
    label = options.get('label', os.path.splitext(os.path.basename(fn))[0])

    if doc.format == 'latex':
        subs = {'fn': fn, 'label': label}
        subs['title'] = pf.convert_markdown(title, format='latex')
        subs['notes'] = pf.convert_markdown(notes, format='latex')
        backmatter = doc.get_metadata('format.backmatter', False)
        pagebreak = doc.get_metadata('format.media-pagebreak', False)

        w = options.get('width', 1.0)
        subs['width'] = w
        subs['innerwidth'] = options.get('innerwidth', w) / w
        subs['notesize'] = options.get('notesize', 'small')
        subs['pagebreak'] = '\\clearpage\n' if pagebreak else ''

        text = LATEX_TEMPLATE.safe_substitute(subs)
        ans = pf.RawBlock(text=text, format='latex')

        if backmatter:
            doc.backmatter.append(ans)
            msg = '\hyperref[fig:{}]{{[\Cref{{fig:{}}} Goes Here]}}'
            msg = msg.format(label, label)
            return pf.Plain(pf.Str(msg))
        else:
            return ans
    else:
        title = pf.convert_markdown(title)
        assert len(title)==1, title
        title = (title[0]).items

        notes = pf.Div(*pf.convert_markdown(notes), classes=['note'])
        title_text = pf.stringify(title)
        img = pf.Image(*title, url=fn, title=title_text, identifier=label)
        ans = pf.Div(pf.Plain(img), pf.Plain(pf.LineBreak), notes, classes=['figure'])
        return ans
Beispiel #10
0
    def _unknown_environment_debug(env_name, elem):
        """Handle unknown latex environment.

        Output visual feedback about the unknown environment.
        """
        classes = ELEMENT_CLASSES["DEBUG_UNKNOWN_ENV"] + [slugify(env_name)]
        div = pf.Div(classes=classes)
        div.content.extend([
            pf.Para(pf.Strong(*destringify("Unhandled environment:"))),
            pf.CodeBlock(elem.text),
        ])
        return div
Beispiel #11
0
def codeblock(elem, doc):
    if not isinstance(elem, pf.CodeBlock):
        return None

    if not elem.classes:
        elem.classes.append('default')

    result = elem

    if any(cls in elem.classes
           for cls in ['cpp', 'default', 'diff']) and escape_char in elem.text:
        datadir = doc.get_metadata('datadir')
        syntaxdir = os.path.join(datadir, 'syntax')

        text = pf.convert_text(elem,
                               input_format='panflute',
                               output_format=doc.format,
                               extra_args=[
                                   '--syntax-definition',
                                   os.path.join(syntaxdir, 'isocpp.xml')
                               ])

        def repl(match_obj):
            match = match_obj.group(1)
            if not match:  # @@
                return match_obj.group(0)
            if match.isspace():  # @  @
                return match

            if doc.format == 'latex':
                # Undo `escapeLaTeX` from https://github.com/jgm/skylighting
                match = match.replace('\\textbackslash{}', '\\') \
                             .replace('\\{', '{') \
                             .replace('\\}', '}')

            plain = pf.Plain(*pf.convert_text(match)[0].content)
            return pf.convert_text(plain.walk(divspan, doc),
                                   input_format='panflute',
                                   output_format=doc.format)

        result = pf.RawBlock(escape_span.sub(repl, text), doc.format)

    if 'diff' not in elem.classes:
        return result

    # For HTML, this is handled via CSS in `data/template/wg21.html`.
    command = '\\renewcommand{{\\{}}}[1]{{\\textcolor[HTML]{{{}}}{{#1}}}}'
    return pf.Div(
        pf.RawBlock(
            command.format('VariableTok', doc.get_metadata('addcolor')),
            'latex'),
        pf.RawBlock(command.format('StringTok', doc.get_metadata('rmcolor')),
                    'latex'), result)
Beispiel #12
0
 def test_single_line(self):
     doc = common.MockDoc('html')
     filename = os.path.join('linguafilter', 'test_lexicon.csv')
     attributes = {'file': filename}
     div = pf.Div(pf.Para(pf.Str('{field1}, {field2}, and {field3}')),
                  attributes=attributes,
                  classes=['lexicon'])
     lexicon.parse(div, doc)
     self.assertEqual(
         pf.stringify(div),
         'r1f1, r1f2, and r1f3\n\nr2f1, r2f2, a field, and r2f3\n\nr3f1, r3f2, and r3f3\n\n'
     )
Beispiel #13
0
    def handle_mdirectrouletteexercises(self, cmd_args, elem):
        r"""Handle ``\MDirectRouletteExercises`` command.

        Remember points for next question.
        """
        filepath = join(environ["INNOCONV_MINTMOD_CURRENT_DIR"], cmd_args[0])
        with open(filepath, "r") as input_file:
            input_content = input_file.read()
        content = parse_fragment(input_content, elem.doc.metadata["lang"].text)
        div = pf.Div(classes=ELEMENT_CLASSES["MDIRECTROULETTEEXERCISES"])
        div.content.extend(content)
        return div
Beispiel #14
0
def collapse(elem, doc):
    if isinstance(elem, pf.CodeBlock) and "html" in doc.format:
        content = [
            pf.RawBlock("<details>", format="html"),
            pf.RawBlock("""
                <summary onclick="this.innerHTML == ' Show code ' ? this.innerHTML = ' Hide code ': this.innerHTML = ' Show code '"> Show code </summary>
                """),
            elem,
            pf.RawBlock("</details>", format="html"),
        ]
        div = pf.Div(*content, classes=["collapsible_code"])
        return div
Beispiel #15
0
def table_matrix_to_pf(matrix, doc):
    table = matrix[0]
    footnotes = [i for i in list_to_elems([matrix[1]])]

    row_cnt = len(table)

    rows = []
    new_col_cnt = 0
    old_col_cnt = None

    for r, row in enumerate(table):
        cells = []
        r_kwargs = row[1]

        for c, cell in enumerate(row[0]):
            new_col_cnt += 1

            if isinstance(cell, tuple):
                c_args = cell[0]
                c_kwargs = cell[1]

                col_span = check_type(c_kwargs.get("col_span", 1), int)

                cells.append(TableCell(*list_to_elems(c_args), **c_kwargs))

                for i in range(1, col_span):
                    new_col_cnt += 1
                    cells.append(TableCell(pf.Null(), covered=True))
            else:
                cells.append(TableCell(*list_to_elems([cell])))

        if old_col_cnt is None:
            old_col_cnt = new_col_cnt

        if new_col_cnt != old_col_cnt:
            raise IndexError(
                f"Expected {old_col_cnt} columns " f"but got {new_col_cnt} in {row}"
            )

        new_col_cnt = 0

        rows.append(TableRow(*cells, **r_kwargs))

    t_kwargs = {}

    if doc.current_caption:
        t_kwargs["caption"] = [pf.Span(pf.Str(doc.current_caption))]

    return pf.Div(
        Table(*rows, col_cnt=old_col_cnt, row_cnt=row_cnt, **t_kwargs),
        *footnotes,
        classes=["custom_table"],
    )
Beispiel #16
0
def h_block_header(e, doc):
    """Change block starting with header to a Div"""
    if not isinstance(e, pf.BlockQuote): return None
    if not isinstance(e.content[0], pf.Header) or not e.content[0].classes:
        return None
    h = e.content[0]
    f = pf.Div(*e.content[1:],
               identifier=h.identifier,
               classes=h.classes,
               attributes=h.attributes)
    e = action(f, doc)
    return e if e else f
Beispiel #17
0
def h_html_footnote(e, doc):
    """Handle footnotes with bigfoot"""
    if not isinstance(e, pf.Note) or doc.format != "html": return None
    htmlref = rf'<sup id="fnref:{doc.footnotecounter}"><a href="#fn:{doc.footnotecounter}" rel="footnote">{doc.footnotecounter}</a></sup>'
    htmlcontent_before = rf'<li class="footnote" id="fn:{doc.footnotecounter}"><p>'
    htmlcontent_after = rf'<a href="#fnref:{doc.footnotecounter}" title="return to article"> ↩</a><p></li>'
    doc.footnotecounter += 1
    conts = pf.Div(*e.content)
    doc.footnotecontents += [
        pf.RawBlock(htmlcontent_before, format="html")
    ] + [conts] + [pf.RawBlock(htmlcontent_after, format="html")]
    return pf.RawInline(htmlref, format="html")
Beispiel #18
0
    def test_to_inline(self):
        """It should convert different elements correctly to inline"""

        content1 = pf.Para(pf.Strong(pf.Str("just some text")))
        transformed1 = content1.content[0]

        content2 = pf.Div(
            pf.Para(pf.Strong(pf.Str("again")), pf.Space,
                    pf.Emph(pf.Str("normal"))))

        content3 = pf.Div(
            pf.Para(
                pf.Span(pf.Str("foo"), classes=["1st-span-class"]),
                pf.Span(
                    pf.Strong(pf.Str("Unhandled"), pf.Space,
                              pf.Str("command:")),
                    classes=["2nd-span-class"],
                ),
            ),
            pf.CodeBlock(r"\MLFunctionQuestion{10}{sin(x)}{5}{x}{5}{DS2}"),
            classes=["div-class"],
        )

        self.assertEqual(to_inline(content1), transformed1)

        # test if nested inlining works
        il_content2 = to_inline(content2)
        self.assertIsInstance(il_content2.content[0], pf.Strong)
        self.assertEqual(il_content2.content[0].content[0].text, "again")
        self.assertIsInstance(il_content2.content[2], pf.Emph)

        # test if class conservation works and advanced nesting
        il_content3 = to_inline(content3)
        self.assertEqual(len(il_content3.content), 2)
        self.assertEqual(len(il_content3.content[0].content), 2)
        self.assertEqual(il_content3.classes, ["div-class"])
        self.assertEqual(il_content3.content[0].content[0].classes,
                         ["1st-span-class"])
        self.assertEqual(il_content3.content[0].content[1].classes,
                         ["2nd-span-class"])
Beispiel #19
0
 def test_merge_root_multiple_nodes(self):
     doc = common.MockDoc('html')
     filename = os.path.join('linguafilter', 'test_lexicon.csv')
     attributes = {'file': filename, 'merge_root': 'foo'}
     div = pf.Div(pf.Para(pf.Str('{field1}')),
                  pf.Para(pf.Str('{field2}')),
                  attributes=attributes,
                  classes=['lexicon'])
     with self.assertRaisesRegexp(
             Exception,
             'if merge_root is specified, there can be only one node under the lexicon div'
     ):
         lexicon.parse(div, doc)
Beispiel #20
0
    def test_uxid_label_para(self):
        mlabel = pf.Div(
            identifier="{}-foo".format(INDEX_LABEL_PREFIX),
            classes=(INDEX_LABEL_PREFIX, ),
        )
        uxid = pf.Div(
            identifier="{}-bar".format(SITE_UXID_PREFIX),
            classes=(SITE_UXID_PREFIX, ),
        )
        para = pf.Para(pf.Str("bar"))

        tests = (
            ("(mlabel,uxid,para)", "foo", [mlabel, uxid, para]),
            ("(uxid,mlabel,para)", "foo", [uxid, mlabel, para]),
            ("(uxid,para)", "bar", [uxid, para]),
            ("(para)", None, [para]),
        )

        for name, exp_id, test in tests:
            with self.subTest("{} expected: {}".format(name, exp_id)):
                identifier = extract_identifier(test)
                self.assertEqual(identifier, exp_id)
Beispiel #21
0
def action(elem, doc):
    if isinstance(elem, pf.Para) and is_include_line(elem):
        
        fn = get_filename(elem)
        if not os.path.isfile(fn):
            return
        
        with open(fn) as f:
            raw = f.read()

        new_elems = pf.convert_text(raw)
        div = pf.Div(*new_elems, attributes={'source': fn})
        return div
Beispiel #22
0
    def _unknown_command_debug(cmd_name, elem):
        """Handle unknown latex commands.

        Output visual feedback about the unknown command.
        """
        classes = ELEMENT_CLASSES["DEBUG_UNKNOWN_CMD"] + [slugify(cmd_name)]
        msg_prefix = pf.Strong(*destringify("Unhandled command:"))
        if isinstance(elem, pf.Block):
            div = pf.Div(classes=classes)
            div.content.extend([pf.Para(msg_prefix), pf.CodeBlock(elem.text)])
            return div
        # RawInline
        span = pf.Span(classes=classes)
        span.content.extend([msg_prefix, pf.Space(), pf.Code(elem.text)])
        return span
Beispiel #23
0
def h_html_code_block(e, doc):
    """HTML code block"""
    if not isinstance(e, pf.CodeBlock) or doc.format != "html":
        return None
    if "algorithm" in e.classes:
        return None
    if not "full" in e.classes:
        return None
    label = labelref(e, doc)
    name, number, file = get_full_label(label, doc)
    title = e.attributes.get("title", "")
    before = rf"<p><i>Figure {number}: {title}</i></p>" + "\n"
    after = r"<p></p>"
    return pf.Div(pf.RawBlock(before, format='html'), e,
                  pf.RawBlock(after, format='html'))
Beispiel #24
0
 def action(self, elem, doc):
     if (doc.format == "docx"):
         default_style = [doc.get_metadata("heading-unnumbered.1", "Heading Unnumbered 1"),
                          doc.get_metadata("heading-unnumbered.2", "Heading Unnumbered 2"),
                          doc.get_metadata("heading-unnumbered.3", "Heading Unnumbered 3"),
                          doc.get_metadata("heading-unnumbered.4", "Heading Unnumbered 4"),
                          ]
         # pf.debug(default_style)
         if isinstance(elem, pf.Header) and "unnumbered" in elem.classes:
             if elem.level < 5:
                 style = elem.attributes.get("custom-style", default_style[elem.level - 1])
                 elem.attributes.update({"custom-style": style})
                 elem = pf.Div(pf.Para(*elem.content), attributes=elem.attributes,
                               identifier=elem.identifier, classes=elem.classes)
                 # pf.debug(elem)
     return elem
Beispiel #25
0
    def convert(elem, text):
        if not any(cls in elem.classes for cls in ['cpp', 'default', 'diff']):
            return elem

        def repl(match_obj):
            match = match_obj.group(1)
            if not match:  # @@
                return match_obj.group(0)
            if match.isspace():  # @  @
                return match

            result = convert.cache.get(match)
            if result is not None:
                return result

            if doc.format == 'latex':
                # Undo `escapeLaTeX` from https://github.com/jgm/skylighting
                match = match.replace('\\textbackslash{}', '\\') \
                             .replace('\\{', '{') \
                             .replace('\\}', '}')

            result = pf.convert_text(pf.Plain(
                *pf.convert_text(match)[0].content).walk(divspan, doc),
                                     input_format='panflute',
                                     output_format=doc.format)

            convert.cache[match] = result
            return result

        result = pf.RawBlock(embedded.sub(repl, text), doc.format)

        if 'diff' not in elem.classes:
            return result

        # For HTML, this is handled via CSS in `data/template/wg21.html`.
        command = '\\renewcommand{{\\{}}}[1]{{\\textcolor[HTML]{{{}}}{{#1}}}}'
        return pf.Div(
            pf.RawBlock('{', 'latex'),
            pf.RawBlock(
                command.format('NormalTok', doc.get_metadata('uccolor')),
                'latex'),
            pf.RawBlock(
                command.format('VariableTok', doc.get_metadata('addcolor')),
                'latex'),
            pf.RawBlock(
                command.format('StringTok', doc.get_metadata('rmcolor')),
                'latex'), result, pf.RawBlock('}', 'latex'))
Beispiel #26
0
def div2env(elem, doc, debug=False):
    if type(elem) == panflute.Div and doc.format in ["latex", "beamer"]:
        attr = getattr(elem, "attributes", None)
        if not attr:
            return

        env = attr.get("data-environment", "").split(",")
        if not env[0]:
            # This is not marked as an environment
            return

        # Convert the positional arguments to the proper \LaTeX format
        args = attr.get("data-environment-args", "").split(",")
        args = "{{{0}}}".format("}{".join(args)) if args[0] else ""

        # Enclose the keyword arguments in '[...]'
        opt = attr.get("data-environment-keyword", "")
        opt = "[{0}]".format(opt) if opt else ""

        begin = panflute.RawInline(
                "\\begin{{{0}}}{1}{2}%\n".format(env[0], opt, args),
                format="latex"
            )
        end = panflute.RawInline(
                "%\n\\end{{{0}}}".format(env[0]),
                format="latex"
            )
        if debug:
            panflute.debug("begin = '{0!s}'".format(begin))
            panflute.debug("end = '{0!s}'".format(end))

        if not getattr(elem.content[0], "content", False):
            begin = panflute.RawBlock(begin.text, format="latex")
            end = panflute.RawBlock(end.text, format="latex")
            elem = panflute.Div(begin, *elem.content, end)
        else:
            elem.content[0] = panflute.Para(begin,
                                            *elem.content[0].content)
            elem.content[-1] = panflute.Para(*elem.content[-1].content,
                                             end)

        if debug:
            panflute.debug("content = '{0!s}'".format(elem.content))

        return elem

    return
Beispiel #27
0
    def handle_mtikzauto(self, cmd_args, elem):
        r"""Handle ``\MTikzAuto`` command.

        Create a ``CodeBlock`` with TikZ code.
        """
        if isinstance(elem, pf.Inline):
            raise ValueError(
                r"\MTikzAuto should be block element!: {}".format(cmd_args))
        tikz_code = REGEX_PATTERNS["STRIP_HASH_LINE"].sub("", cmd_args[0])
        for repl in TIKZ_SUBSTITUTIONS:
            tikz_code = re.sub(repl[0], repl[1], tikz_code)
        # remove empty lines
        tikz_code = linesep.join([s for s in tikz_code.splitlines() if s])
        codeblock = pf.CodeBlock(tikz_code)
        codeblock.classes = ELEMENT_CLASSES["MTIKZAUTO"]
        ret = pf.Div(codeblock, classes=["figure"])
        return ret
Beispiel #28
0
def bibliography(elem, doc):
    if not isinstance(elem, pf.Div) or elem.identifier != 'bibliography':
        return None

    def references(elem, doc):
        if isinstance(elem, pf.Div) and elem.identifier == 'refs':
            nonlocal refs
            refs = elem

    refs = pf.Div()
    elem.walk(references)
    if refs.content:
        # This should just be `return None`, but the HTML output
        # for headers seem to break with `<h1>` inside a `<div>`.
        elem.parent.content.extend(elem.content)

    return []
Beispiel #29
0
def fenced_html(options, data, element, doc):
    identifier = options.get('identifier', '')
    element.identifier = identifier
    caption = options.get('caption', '')
    caption = pf.convert_text(caption,
                              extra_args=['--biblatex'],
                              input_format='markdown',
                              output_format='html')

    caption_span = pf.Plain(
        pf.Span(pf.RawInline(caption), classes=['fencedSourceCodeCaption']))
    code_block = pf.CodeBlock(data,
                              classes=element.classes,
                              attributes=element.attributes)

    return pf.Div(code_block,
                  caption_span,
                  identifier=identifier,
                  classes=['fencedSourceCode'])
Beispiel #30
0
def action(elem, doc):
    if isinstance(
            elem,
            pf.RawInline) and elem.format == 'tex' and '\pnum{' in elem.text:
        repl = re.sub(r"\\pnum{([^}]+)}", "<small>(\\1)</small>", elem.text)
        return pf.RawInline(repl, 'html')
    if not isinstance(elem, pf.Div) and not isinstance(elem, pf.Span):
        return None

    color_name = None
    tag_name = None
    for cls in elem.classes:
        color_name = cls + 'color'
        if cls == 'add':
            tag_name = 'ins'
        elif cls == 'rm':
            tag_name = 'del'

    if tag_name is None:
        return None

    open_tag = pf.RawInline('<{}>'.format(tag_name), 'html')
    open_color = pf.RawInline('{{\\color{{{}}}'.format(color_name), 'tex')
    close_color = pf.RawInline('}', 'tex')
    close_tag = pf.RawInline('</{}>'.format(tag_name), 'html')

    color = doc.get_metadata(color_name)
    attributes = {} if color is None else {'style': 'color: #{}'.format(color)}

    if isinstance(elem, pf.Div):
        return pf.Div(pf.Plain(open_tag),
                      pf.Plain(open_color),
                      elem,
                      pf.Plain(close_color),
                      pf.Plain(close_tag),
                      attributes=attributes)
    elif isinstance(elem, pf.Span):
        return pf.Span(open_tag,
                       open_color,
                       elem,
                       close_color,
                       close_tag,
                       attributes=attributes)