def handle_heading(self, tag: QqTag) -> str: """ Uses tags: chapter, section, subsection, subsubsection Uses tags: label, number Example: \chapter This is first heading \section This is the second heading \label{sec:second} :param tag: :return: """ tag_to_hx = { 'chapter': 'h1', 'section': 'h2', 'subsection': 'h3', 'subsubsection': 'h4' } doc, html, text = Doc().tagtext() with html(tag_to_hx[tag.name]): doc.attr(id=self.tag_id(tag)) if tag.find("number"): with html("span", klass="section__number"): with html("a", href="#" + self.tag_id(tag), klass="section__number"): text(tag.number_.value) doc.asis(self.format(tag, blanks_to_pars=False)) ret = doc.getvalue() if tag.next() and isinstance(tag.next(), str): ret += "<p>" return doc.getvalue()
def handle_enumerateable(self, tag: QqTag) -> str: """ Uses tags: label, number, name Add tags used manually from enumerateable_envs :param tag: :return: """ doc, html, text = Doc().tagtext() name = tag.name env_localname = self.localize(self.enumerateable_envs[name]) with html("div", klass="env env__" + name): if tag.find("label"): doc.attr(id=self.label2id(tag.label_.value)) number = tag.get("number", "") with html("span", klass="env-title env-title__" + name): if tag.find("label"): with html("a", klass="env-title env-title__" + name, href="#" + self.label2id(tag.label_.value)): text(join_nonempty(env_localname, number) + ".") else: text(join_nonempty(env_localname, number) + ".") doc.asis(" " + self.format(tag, blanks_to_pars=True)) return "<p>" + doc.getvalue() + "</p>\n<p>"
def handle_proof(self, tag: QqTag) -> str: """ Uses tags: proof, label, outline, of Examples: \proof Here is the proof \proof \of theorem \ref{thm:1} Now we pass to proof of theorem \ref{thm:1} :param tag: :return: HTML of proof """ doc, html, text = Doc().tagtext() with html("div", klass="env env__proof"): if tag.find("label"): doc.attr(id=self.label2id(tag.label_.value)) with html("span", klass="env-title env-title__proof"): if tag.exists("outline"): proofline = 'Proof outline' else: proofline = 'Proof' doc.asis( join_nonempty( self.localize(proofline), self.format(tag.find("of"), blanks_to_pars=False)).rstrip() + ".") doc.asis(rstrip_p(" " + self.format(tag, blanks_to_pars=True))) doc.asis("<span class='end-of-proof'>∎</span>") return doc.getvalue() + "\n<p>"
def make_numbers(self, root: QqTag) -> None: """ Uses tags: number, label, nonumber, flabel :return: """ for tag in root.children_tags(): name = tag.name if ((name in self.counters or name in self.enumerateable_envs) and not (tag.find('number') or tag.exists('nonumber'))): counter = self.get_counter_for_tag(tag) if counter is not None: counter.increase() tag.append_child(QqTag({'number': str(counter)})) if tag.find('label'): label = tag.label_.value self.label_to_number[label] = str(counter) # self.label_to_title[label] = tag.text_content if tag.find('label') and tag.find('number'): self.label_to_number[tag.label_.value] = tag.number_.value if tag.find('label'): self.label_to_tag[tag.label_.value] = tag if tag.find('flabel'): self.flabel_to_tag[tag.flabel_.value.lower()] = tag self.make_numbers(tag)
def handle_link(self, tag: QqTag) -> str: doc, html, text = Doc().tagtext() with html("p", klass="meta meta-link"): if tag.exists("role"): doc.add_class("meta-link-" + tag.role_.value) if tag.exists("url"): with html("a", href=tag.url_.value): doc.asis(self.format(tag, blanks_to_pars=False)) else: doc.asis(self.format(tag, blanks_to_pars=False)) return doc.getvalue()
def handle_snippet(self, tag: QqTag) -> str: """ Uses tags: hidden, backref, label :param tag: :return: """ anchor = "" if not tag.exists("backref") and tag.exists("label"): anchor = "<span id='{}'></span>".format( self.label2id(tag.label_.value)) if tag.exists("hidden"): return anchor return anchor + self.format(tag, blanks_to_pars=True)
def tag_id(self, tag: QqTag) -> str: """ Returns id of tag: - If it has label, it is label-based - If it does not have label, but have number, it is number-based :param tag: :return: str id """ if tag.find("label"): return self.label2id(tag.label_.value) elif tag.find("number"): return (self.label2id(tag.name + "_number_" + str(tag.number_.value))) else: return ""
def tag_hash_id(tag: QqTag) -> str: """ Returns autogenerated tag id based on tag's contents. It's first 5 characters of MD5-hashsum of tag's content :return: """ return hashlib.md5(repr(tag.as_list()).encode('utf-8')).hexdigest()[:5]
def make_chapters(self): for heading, *contents in split_by_predicate( self.root, predicate=lambda tag: (isinstance(tag, QqTag) and tag.name == "chapter"), zero_delim=QqTag("_zero_chapter")): self.add_chapter(Chapter(heading, [heading] + contents))
def handle_snref(self, tag: QqTag) -> str: """ Makes snippet ref. Example: Consider \snref[Initial Value Problem|sn:IVP]. Here sn:IVP -- label of snippet. If no separator present, fuzzy search will be performed over flabels Example: \snippet \label sn:IVP \flabel Initial Value Problem Initial Value Problem is a problem with initial value Consider \snref[initial value problem]. :param tag: :return: """ doc, html, text = Doc().tagtext() if len(tag) == 1: tag = tag.unitemized() if tag.is_simple: title = tag.value.replace("\n", " ") target = self.find_tag_by_flabel(title) label = target.label_.value else: if len(tag) != 2: raise Exception("Incorrect number of arguments in " + str(tag) + (": one or two arguments " "expected")) title, label = tag.children_values(not_simple='keep') # TODO: testme data_url = self.url_for_snippet(label) with html("a", ('data-url', data_url), klass="snippet-ref"): doc.asis(self.format(title, blanks_to_pars=True)) return doc.getvalue()
def handle_equation(self, tag: QqTag) -> str: """ Uses tags: equation, number, label Example: \equation \label eq:first x^2 + y^2 = z^2 :param tag: :return: """ doc, html, text = Doc().tagtext() with html("div", klass="latex_equation"): text("\\[\n") text("\\begin{equation}\n") if tag.find('number'): text("\\tag{{{}}}\n".format(tag.number_.value)) if tag.find('label'): doc.attr(id=self.label2id(tag.label_.value)) doc.asis(self.format(tag, blanks_to_pars=False)) text("\\end{equation}\n") text("\\]\n") return doc.getvalue()
def handle_quiz(self, tag: QqTag) -> str: """ Uses tags: choice, correct, comment Example: \question Do you like qqmbr? \quiz \choice No. \comment You didn't even try! \choice \correct Yes, i like it very much! \comment And so do I! :param tag: :return: """ if not tag.exists('md5id'): tag.append_child(QqTag('md5id', [self.tag_hash_id(tag)])) template = Template( filename=os.path.join(self.templates_dir, "quiz.html")) return template.render(formatter=self, tag=tag)
def handle_figure(self, tag: QqTag) -> str: """ Currently, only python-generated figures and plotly figures are supported. Also one can use \rawhtml to embed arbitrary HTML code (e.g. use D3.js). Example: \figure \label fig:figure \pythonfigure plt.plot([1, 2, 3], [1, 4, 9]) \caption Some figure Uses tags: figure, label, caption, number, showcode, collapsed :param tag: QqTag :return: HTML of figure """ doc, html, text = Doc().tagtext() subtags = ['pythonfigure', 'plotly', 'rawhtml'] langs = { 'pythonfigure': 'python', 'plotly': 'python', 'rawhtml': 'html' } with html("div", klass="figure"): if tag.find("label"): doc.attr(id=self.label2id(tag.label_.value)) label = tag.label_.value else: label = None for child in tag.children_tags(): if child.name in subtags: if tag.exists("showcode"): doc.asis( self.showcode(child, collapsed=tag.exists("collapsed"), lang=langs.get(child.name))) doc.asis(self.handle(child)) elif child.name == 'caption': with html("div", klass="figure_caption"): if label is not None: with html("a", klass="figure_caption_anchor", href="#" + self.label2id(label)): text( join_nonempty(self.localize("Fig."), tag.get("number"))) text(": ") else: text( join_nonempty(self.localize("Fig."), tag.get("number")) + ": ") doc.asis(self.format(child, blanks_to_pars=True)) return doc.getvalue()
def handle_href(self, tag: QqTag) -> str: """ Example: Content from \href[Wikipedia|http://wikipedia.org] Uses tags: href :param tag: tag to proceed :return: """ a, url = tag.children_values(not_simple='keep') doc, html, text = Doc().tagtext() with html("a", klass="href", href=url.strip()): doc.asis(self.format(a.strip(), blanks_to_pars=False)) return doc.getvalue()
def get_counter_for_tag(self, tag: QqTag) -> Optional[Counter]: name = tag.name counters = self.counters while True: if tag.exists('nonumber'): return None current = counters.get(name) if current is None: return None if isinstance(current, Counter): return current if isinstance(current, dict): counters = current tag = tag.parent name = tag.name continue return None
def handle_pythonfigure(self, tag: QqTag) -> str: """ Uses tags: pythonfigure, style :param tag: :return: """ path = self.make_python_fig(tag.text_content, exts=("svg", )) doc, html, text = Doc().tagtext() with html("img", klass="figure img-responsive", src=self.url_for_figure(path + "/" + self.default_figname + ".svg")): if tag.exists("style"): doc.attr(style=tag.style_.value) return doc.getvalue()
def process_python(tag: QqTag): """ Output: \_codeblock \_code ...(str)... \_output ...(str)... \_code ...(str)... \_output ...(str)... \_code ...(str)... .... \ref ... \seealso ... \require ... :param tag: :return: """ loc = {} glob = {} codeblock = QqTag("_codeblock") chunk = [] for child in tag: if isinstance(child, str): with stdout_io() as s: try: exec(child, loc, glob) except Exception as e: print("Exception: {}\n{}".format(e.__class__.__name__, e)) chunk.append(strip_blank_lines(child) + "\n"), if s.getvalue(): codeblock.append(QqTag("_code", ["".join(chunk)])) codeblock.append(QqTag("_output", [s.getvalue()])) chunk.clear() elif child.name == 'out': chunk.append(strip_blank_lines(child.text_content)) codeblock.append(QqTag("_code", ["".join(chunk)])) chunk.clear() res = eval(child.text_content, loc, glob) codeblock.append(QqTag("_output", [repr(res)])) elif child.name != 'flush': codeblock.append(child) if chunk: codeblock.append(QqTag("_code", ["".join(chunk)])) return codeblock
def process_python(tag: QqTag): """ Output: \_codeblock \_code ...(str)... \_output ...(str)... \_code ...(str)... \_output ...(str)... \_code ...(str)... .... \ref ... \seealso ... \require ... :param tag: :return: """ loc = {} glob = {} codeblock = QqTag("_codeblock") chunk = [] for child in tag: if isinstance(child, str): with stdout_io() as s: try: exec(child, loc, glob) except Exception as e: print("Exception: {}\n{}". format(e.__class__.__name__, e)) chunk.append(strip_blank_lines(child)+"\n"), if s.getvalue(): codeblock.append(QqTag("_code", ["".join(chunk)])) codeblock.append(QqTag("_output", [s.getvalue()])) chunk.clear() elif child.name == 'out': chunk.append(strip_blank_lines(child.text_content)) codeblock.append(QqTag("_code", ["".join(chunk)])) chunk.clear() res = eval(child.text_content, loc, glob) codeblock.append(QqTag("_output", [repr(res)])) elif child.name != 'flush': codeblock.append(child) if chunk: codeblock.append(QqTag("_code", ["".join(chunk)])) return codeblock
def process_js(tag: QqTag): """ Output: \_codeblock \_item \_code ...(str)... \_output ...(str)... \_item \_code ...(str)... \_output ...(str)... \ref ... \seealso ... \require ... :param tag: :return: """ codeblock = QqTag("_codeblock") cumulative_code = [] current_chunk = [] for child in itertools.chain(tag, [None]): if isinstance(child, str) and child.strip(): cumulative_code.append(child) current_chunk.append(child) elif child is None or child.name == 'out': code = "".join(cumulative_code) if current_chunk: res = node_exec(code) if res or child is None: code_tag = QqTag("_code", [strip_blank_lines("".join( current_chunk))]) codeblock.append_child(code_tag) if res: codeblock.append_child(QqTag("_output", [res])) current_chunk.clear() if child is not None: logger = "console.log({})".format(child.text_content) res = node_exec(code + ";\n" + logger) code_tag = QqTag("_code", [strip_blank_lines( "".join(current_chunk) + child.text_content.rstrip() + ";")]) current_chunk.clear() codeblock.append_child(code_tag) codeblock.append_child(QqTag("_output", [res])) else: codeblock.append_child(child) return codeblock
def process_js(tag: QqTag): """ Output: \_codeblock \_item \_code ...(str)... \_output ...(str)... \_item \_code ...(str)... \_output ...(str)... \ref ... \seealso ... \require ... :param tag: :return: """ codeblock = QqTag("_codeblock") cumulative_code = [] current_chunk = [] for child in itertools.chain(tag, [None]): if isinstance(child, str) and child.strip(): cumulative_code.append(child) current_chunk.append(child) elif child is None or child.name == 'out': code = "".join(cumulative_code) if current_chunk: res = node_exec(code) if res or child is None: code_tag = QqTag( "_code", [strip_blank_lines("".join(current_chunk))]) codeblock.append_child(code_tag) if res: codeblock.append_child(QqTag("_output", [res])) current_chunk.clear() if child is not None: logger = "console.log({})".format(child.text_content) res = node_exec(code + ";\n" + logger) code_tag = QqTag("_code", [ strip_blank_lines("".join(current_chunk) + child.text_content.rstrip() + ";") ]) current_chunk.clear() codeblock.append_child(code_tag) codeblock.append_child(QqTag("_output", [res])) else: codeblock.append_child(child) return codeblock
def __init__(self, root: QqTag = QqTag("_root"), with_chapters=True, eq_preview_by_labels=False) -> None: self.templates_dir = os.path.join( os.path.dirname(os.path.realpath(__file__)), "templates") self.with_chapters = with_chapters self.eq_preview_by_labels = eq_preview_by_labels self.label_to_number: Dict[str, str] = {} self.label_to_title: Dict[str, str] = {} self.label_to_tag: Dict[str, QqTag] = {} self.label_to_chapter: Dict[str, int] = {} self.flabel_to_tag: Dict[str, QqTag] = {} self.root: QqTag = root self.counters = {} self.chapters: List[Chapter] = [] self.heading_to_level = { 'chapter': 1, 'section': 2, 'subsection': 3, 'subsubsection': 4 } self.mode = 'wholedoc' #: how to render the doc? the following options are available: #: - 'wholedoc' - the whole document on one page #: - 'bychapters' - every chapter on its own page chapters_counter = None if with_chapters: chapters_counter = Counter() self.counters['chapter'] = chapters_counter self.counters['section'] = spawn_or_create_counter(chapters_counter) self.counters['subsection'] = (self.counters['section'].spawn_child()) self.counters['subsubsection'] = ( self.counters['subsection'].spawn_child()) self.counters['equation'] = spawn_or_create_counter(chapters_counter) self.counters['equation'].showparents = True self.counters['item'] = {'align': self.counters['equation']} self.counters['figure'] = spawn_or_create_counter(chapters_counter) self.enumerateable_envs = { name: name.capitalize() for name in [ 'remark', 'theorem', 'example', 'exercise', 'definition', 'proposition', 'lemma', 'question', 'corollary' ] } self.metatags = { 'meta', 'author', 'affiliation', 'link', 'license', 'title', 'url', 'lang', 'role' } # You can make self.localnames = {} to use # plain English localization self.localizations = { 'ru': { 'Remark': 'Замечание', 'Theorem': 'Теорема', 'Example': 'Пример', 'Exercise': 'Упражнение', 'Definition': 'Определение', 'Proposition': 'Утверждение', 'Lemma': 'Лемма', 'Proof': 'Доказательство', 'Proof outline': 'Набросок доказательства', 'Figure': 'Рисунок', 'Fig.': "Рис.", 'Question': 'Вопрос', 'Corollary': 'Следствие', } } self.localnames: Dict[str, str] = None self.formulaenvs = {'eq', 'equation', 'align'} for env in self.enumerateable_envs: self.counters[env] = spawn_or_create_counter(chapters_counter) self.counters[env].showparents = False self.figures_dir = None self.default_figname = "fig" plt.rcParams['figure.figsize'] = (6, 4) self.pythonfigure_globals = {'plt': plt} self.code_prefixes = dict( pythonfigure='import matplotlib.pyplot as plt\n', plotly=("import plotly\n" "import plotly.graph_objs as go\n" "from plotly.offline import iplot " "as plot\n" "from plotly.offline import " "init_notebook_mode\n\n" "init_notebook_mode()\n\n"), rawhtml='') self.plotly_plotter = PlotlyPlotter() self.plotly_globals: Dict[str, Any] = {} self.css: Dict[str, str] = {} self.js_top: Dict[str, str] = {} self.js_bottom: Dict[str, str] = {} self.js_onload: Dict[str, str] = {} self.safe_tags = (set(self.enumerateable_envs) | set( self.formulaenvs) | { 'item', 'figure', 'label', 'number', 'ref', 'nonumber', 'snref', 'snippet', 'flabel', 'name', 'proof', 'outline', 'of', 'caption', 'showcode', 'collapsed', 'hidden', 'backref', 'label', 'em', 'emph', 'quiz', 'choice', 'correct', 'comment' } | set(self.heading_to_level) | self.metatags)
def handle_ref(self, tag: QqTag): """ Examples: See Theorem \ref{thm:existence} Other way: See \ref[Theorem|thm:existence] In this case word ``Theorem'' will be part of a reference: e.g. in HTML it will look like See <a href="#label_thm:existence">Theorem 1</a> If you want to omit number, just use \nonumber tag like so: See \ref[Theorem\nonumber|thm:existence] This will produce HTML like See <a href="#label_thm:existence">Theorem</a> Uses tags: ref, nonumber :param tag: :return: """ doc, html, text = Doc().tagtext() if len(tag) == 1: tag = tag.unitemized() if tag.is_simple: prefix = None label = tag.value else: if len(tag) != 2: raise Exception("Incorrect number of arguments in " + str(tag) + ": 2 arguments expected") prefix, label = tag.children_values(not_simple='keep') number = self.label_to_number.get(label, "???") target = self.label_to_tag.get(label) href = "" if self.mode == 'bychapters': if 'snippet' not in [t.name for t in tag.ancestor_path()]: # check that we're not inside snippet now fromindex = self.tag2chapter(tag) else: fromindex = None href = (self.url_for_chapter(self.tag2chapter(target), fromindex=fromindex) if target else "") eqref = (target and (target.name in self.formulaenvs or target.name == 'item' and target.parent.name in self.formulaenvs)) if eqref: href += "#mjx-eqn-" + str(number) else: href += "#" + self.label2id(label) with html("span", klass="ref"): with html("a", klass="a-ref", href=href, title=self.label_to_title.get(label, "")): if prefix: doc.asis(self.format(prefix, blanks_to_pars=False)) if eqref: eq_id = label if self.eq_preview_by_labels else number try: doc.attr(('data-url', self.url_for_eq_snippet(eq_id))) except NotImplementedError: pass if (not isinstance(prefix, QqTag) or not prefix.exists("nonumber")): if prefix: doc.asis(" ") if eqref: text("(" + number + ")") else: text(number) return doc.getvalue()