Exemple #1
0
    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()
Exemple #2
0
    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>"
Exemple #3
0
    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'>&#8718;</span>")
        return doc.getvalue() + "\n<p>"
Exemple #4
0
    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)
Exemple #5
0
 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()
Exemple #6
0
    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)
Exemple #7
0
 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 ""
Exemple #8
0
 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]
Exemple #9
0
 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))
Exemple #10
0
    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()
Exemple #11
0
    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()
Exemple #12
0
    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)
Exemple #13
0
    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()
Exemple #14
0
    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()
Exemple #15
0
 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
Exemple #16
0
    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()
Exemple #17
0
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
Exemple #18
0
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
Exemple #19
0
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
Exemple #20
0
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
Exemple #21
0
    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)
Exemple #22
0
    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()