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'})
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")
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())
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" })
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
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
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
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
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
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
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)
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' )
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
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
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"], )
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
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")
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"])
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)
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)
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
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
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'))
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
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'))
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
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
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 []
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'])
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)