Ejemplo n.º 1
0
def exercise_filter(elem, doc):
    if isinstance(elem, pf.Para) and len(elem.content) == 0:
        return []  ## Remove empty paragraphs ...
    elif isinstance(elem, pf.Header) and (
            "exercise"
            in elem.classes):  # No need to use level 3 and elem.level==3:
        if "reset" in elem.classes:
            doc.exercisecount = 1
        else:
            doc.exercisecount += 1
        #print(sys.stderr,"Exercise detected",file=sys.stderr)
        doc.inside_exercise = True
        return []
    elif isinstance(elem, pf.Header) and (
            "question"
            in elem.classes):  # No need to use level 3 and elem.level==3:
        if "reset" in elem.classes:
            doc.questioncount = 1
        else:
            doc.questioncount += 1
        #print(sys.stderr,"Exercise detected",file=sys.stderr)
        doc.inside_question = True
        return []
    elif doc.inside_exercise:
        if isinstance(elem, pf.Para) and len(elem.content) > 0:
            cogollo = pf.Str(str(doc.exercisecount) + ".-")
            elem.content = [pf.Strong(cogollo), pf.Space] + list(elem.content)
            doc.inside_exercise = False
            return elem
    elif doc.inside_question:
        if isinstance(elem, pf.Para) and len(elem.content) > 0:
            cogollo = pf.Str(str(doc.questioncount) + ".-")
            elem.content = [pf.Strong(cogollo), pf.Space] + list(elem.content)
            doc.inside_question = False
            return elem
Ejemplo n.º 2
0
def action(elem, doc):
    """The panflute filter main method, called once per element."""
    global shift, workaround_level_overflow, workaround_level_underflow
    if isinstance(elem, pf.Header):
        level_old = elem.level
        level_new = level_old + shift
        if level_new > MAX_LEVEL:
            eprint(
                "After shifting header levels by %d, '%s' would be on level %d, "
                "which is above the max level %d." %
                (shift, elem.identifier, level_new, MAX_LEVEL))
            if workaround_level_overflow:
                eprint(
                    "Thus we convert it to an emphazised text paragraph instead."
                )
                if level_new == (MAX_LEVEL + 1):
                    elem = pf.Para(pf.Strong(*elem.content))
                else:
                    elem = pf.Para(pf.Emph(*elem.content))
            else:
                raise OverflowError()
        elif level_new < MIN_LEVEL:
            eprint(
                "After shifting header levels by %d, '%s' would be on level %d, "
                "which is below the min level %d." %
                (shift, elem.identifier, level_new, MIN_LEVEL))
            if workaround_level_underflow:
                eprint("Thus we leave it at the min level.")
            else:
                raise OverflowError()
        else:
            elem.level = level_new
    return elem
Ejemplo n.º 3
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'})
Ejemplo n.º 4
0
def build_header(elem):
    # We use a `pf.RawInline` here because setting the `align`
    # attribute on `pf.Div` does not work for some reason.
    header = pf.Plain(pf.RawInline('<div align="center">', 'html'),
                      pf.Strong(*elem.content), pf.RawInline('</div>', 'html'))
    width = float(
        elem.attributes['width']) if 'width' in elem.attributes else 0
    return header, width
Ejemplo n.º 5
0
def action(elem, doc):
    if isinstance(elem, pf.Image) and doc.format == 'html':
        global counter

        strong = pf.Strong(pf.Str(f'Obr. {counter}: '))
        elem.content.insert(0, strong)
        counter += 1

        elem.classes.append('img-responsive')
Ejemplo n.º 6
0
def action(elem, doc):
    if type(elem) == pf.Str:
        if len(match_key.findall(elem.text)) > 0:
            var_full = match_full.findall(elem.text)[0]
            var_key = match_key.findall(elem.text)[0]

            if var_key in doc.variables:
                new_string = elem.text.replace(var_full,
                                               str(doc.variables[var_key]))
                if doc.boldface_variables:
                    var_value = pf.Strong(pf.Str(new_string))
                else:
                    var_value = pf.Str(new_string)
            else:
                var_value = pf.Strong(
                    pf.Str('{{Missing variable: {}}}'.format(var_key)))
                doc.missing_variables.add(var_key)

            return (var_value)
Ejemplo n.º 7
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
Ejemplo n.º 8
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"])
Ejemplo n.º 9
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
Ejemplo n.º 10
0
    def handle_mentry(self, cmd_args, elem):
        r"""Handle ``\MEntry`` command.

        This command creates an entry for the index.
        """

        if isinstance(elem, pf.Block):
            log("Warning: Expected Inline for MEntry: {}".format(cmd_args))

        text = cmd_args[0]
        concept = cmd_args[1]
        for repl in MATH_SUBSTITUTIONS:  # can contain LaTeX!
            concept = re.sub(repl[0], repl[1], concept)
        strong = pf.Strong()
        strong.content.extend(
            parse_fragment(text, elem.doc.metadata["lang"].text)[0].content)
        span = pf.Span()
        span.attributes = {INDEX_ATTRIBUTE: concept}
        span.content = [strong]
        return block_wrap(span, elem)
Ejemplo n.º 11
0
def test_all():
    md = 'Some *markdown* **text** ~xyz~'
    c_md = pf.convert_text(md)
    b_md = [
        pf.Para(pf.Str("Some"), pf.Space, pf.Emph(pf.Str("markdown")),
                pf.Space, pf.Strong(pf.Str("text")), pf.Space,
                pf.Subscript(pf.Str("xyz")))
    ]

    print("Benchmark MD:")
    print(b_md)
    print("Converted MD:")
    print(c_md)
    assert repr(c_md) == repr(b_md)

    with io.StringIO() as f:
        doc = pf.Doc(*c_md)
        pf.dump(doc, f)
        c_md_dump = f.getvalue()

    with io.StringIO() as f:
        doc = pf.Doc(*b_md)
        pf.dump(doc, f)
        b_md_dump = f.getvalue()

    assert c_md_dump == b_md_dump

    # ----------------------
    print()

    tex = r'Some $x^y$ or $x_n = \sqrt{a + b}$ \textit{a}'
    c_tex = pf.convert_text(tex)
    b_tex = [
        pf.Para(pf.Str("Some"), pf.Space, pf.Math("x^y", format='InlineMath'),
                pf.Space, pf.Str("or"), pf.Space,
                pf.Math(r"x_n = \sqrt{a + b}", format='InlineMath'), pf.Space,
                pf.RawInline(r"\textit{a}", format='tex'))
    ]

    print("Benchmark TEX:")
    print(b_tex)
    print("Converted TEX:")
    print(c_tex)
    assert repr(c_tex) == repr(b_tex)

    with io.StringIO() as f:
        doc = pf.Doc(*c_tex)
        pf.dump(doc, f)
        c_tex_dump = f.getvalue()

    with io.StringIO() as f:
        doc = pf.Doc(*b_tex)
        pf.dump(doc, f)
        b_tex_dump = f.getvalue()

    assert c_tex_dump == b_tex_dump

    print("\nBack and forth conversions... md->json->md")
    md = 'Some *markdown* **text** ~xyz~'
    print("[MD]", md)
    md2json = pf.convert_text(md,
                              input_format='markdown',
                              output_format='json')
    print("[JSON]", md2json)
    md2json2md = pf.convert_text(md2json,
                                 input_format='json',
                                 output_format='markdown')
    print("[MD]", md2json2md)
    assert md == md2json2md

    print("\nBack and forth conversions... md->panflute->md")
    md = 'Some *markdown* **text** ~xyz~'
    print("[MD]", md)
    md2panflute = pf.convert_text(md,
                                  input_format='markdown',
                                  output_format='panflute')
    print("[PANFLUTE]", md2panflute)
    md2panflute2md = pf.convert_text(md2panflute,
                                     input_format='panflute',
                                     output_format='markdown')
    print("[MD]", md2panflute2md)
    assert md == md2panflute2md

    print("\nBack and forth conversions... md->panflute(standalone)->md")
    md = 'Some *markdown* **text** ~xyz~'
    print("[MD]", md)
    md2panflute = pf.convert_text(md,
                                  input_format='markdown',
                                  output_format='panflute',
                                  standalone=True)
    print("[PANFLUTE]", md2panflute)
    md2panflute2md = pf.convert_text(md2panflute,
                                     input_format='panflute',
                                     output_format='markdown')
    print("[MD]", md2panflute2md)
    assert md == md2panflute2md

    print(
        "\nBack and forth conversions... md table -> json(standalone) -> md table"
    )
    md = """lorem

  --- ---
  x   y
  --- ---

ipsum"""
    print("[MD]", repr(md))
    md2json = pf.convert_text(md,
                              input_format='markdown',
                              output_format='json',
                              standalone=True)
    print("[json]", md2json)
    md2json2md = pf.convert_text(md2json,
                                 input_format='json',
                                 output_format='markdown')
    print("[MD]", repr(md2json2md))
    assert md == md2json2md

    print(
        "\nBack and forth conversions... md table -> panflute(standalone) -> md table"
    )
    print("[MD]", repr(md))
    md2panflute = pf.convert_text(md,
                                  input_format='markdown',
                                  output_format='panflute',
                                  standalone=True)
    print("[PANFLUTE]", md2panflute)
    md2panflute2md = pf.convert_text(md2panflute,
                                     input_format='panflute',
                                     output_format='markdown')
    print("[MD]", repr(md2panflute2md))
    assert md == md2panflute2md

    print(
        "\nBack and forth conversions... gfm table (empty) -> json(standalone) -> gfm table (empty)"
    )
    md = """lorem

| x | y |
| - | - |

ipsum"""
    print("[MD]", repr(md))
    md2json = pf.convert_text(md,
                              input_format='gfm',
                              output_format='json',
                              standalone=True)
    print("[json]", md2json)
    md2json2md = pf.convert_text(md2json,
                                 input_format='json',
                                 output_format='gfm')
    print("[MD]", repr(md2json2md))
    assert md == md2json2md

    print(
        "\nBack and forth conversions... gfm table (empty) -> panflute(standalone) -> gfm table (empty)"
    )
    print("[MD]", repr(md))
    md2panflute = pf.convert_text(md,
                                  input_format='gfm',
                                  output_format='panflute',
                                  standalone=True)
    print("[PANFLUTE]", md2panflute)
    md2panflute2md = pf.convert_text(md2panflute,
                                     input_format='panflute',
                                     output_format='gfm')
    print("[MD]", repr(md2panflute2md))
    assert md == md2panflute2md
Ejemplo n.º 12
0
    def convert(self):
        doc = panflute.Doc(
            api_version=(1, 17, 5),
            metadata={
                'pagetitle': self.title,
            },
        )

        doc.content.append(panflute.Header(panflute.Str(self.title)))

        lists = {}
        tables = {}
        table_rows = {}
        table_cells = {}

        for chunk in self._attr_chunks():
            self.logger.debug(chunk)
            container = panflute.Para()
            cdiv = panflute.Div(container)

            # Handle lists
            if 'list_class' in chunk[0]['attrs']:
                lc = chunk[0]['attrs']['list_class']
                check_state = None
                if lc in ['checked', 'unchecked']:
                    check_state = lc
                    lc = 'checklist'
                ld = chunk[0]['attrs']['list_depth']

                # prune any lists that are lower than us, they're finished
                for i in list(lists.keys()):
                    if i > ld:
                        lists.pop(i)

                # non-homogenous list types can be immediately adjacent without
                # ending up merged
                if ld in lists and lists[ld]['class'] != lc:
                    lists.pop(ld)

                # checklists are a special case, they can't contain other lists
                if lc != 'checklist' and lists and lists[1][
                        'class'] == 'checklist':
                    lists = {}

                # make sure any intermediate lists were created, including
                # the top level because boxnotes
                for i in range(1, ld + 1):
                    if i not in lists:
                        lists[i] = self._list(lc, i)
                        if i != ld:
                            lists[i]['pf'].content.append(panflute.ListItem())
                        lp = lists[i]['pf']
                        if lc == 'checklist':
                            lp = panflute.Div(lp, classes=['checklist'])
                        if i == 1:
                            doc.content.append(lp)
                        else:
                            lists[i - 1]['pf'].content[-1].content.append(lp)

                # set the container for the other subchunks
                container = panflute.Plain()
                cdiv.content = [container]
                cdiv.classes.append(lc)
                if check_state:
                    cdiv.classes.append(check_state)
                lists[ld]['pf'].content.append(panflute.ListItem(cdiv))

                if check_state == 'checked':
                    container.content.append(panflute.Str(CHECKED))
                elif check_state == 'unchecked':
                    container.content.append(panflute.Str(UNCHECKED))

            elif 'table_id' in chunk[-1]['attrs']:
                table_id = chunk[-1]['attrs']['table_id']
                row_id = chunk[-1]['attrs']['table_row']
                cell_id = row_id + chunk[-1]['attrs']['table_col']

                if table_id not in tables:
                    # There's some magic in the constructor for panflute tables
                    # that isn't exposed in any other way, so we can't create
                    # the table until we've finished populating the rows.
                    # Instead, use a placeholder div to locate it within the
                    # document.
                    tables[table_id] = {
                        'div': panflute.Div(),
                        'rows': [],
                    }
                    doc.content.append(tables[table_id]['div'])
                if row_id not in table_rows:
                    table_rows[row_id] = panflute.TableRow()
                    tables[table_id]['rows'].append(table_rows[row_id])
                if cell_id not in table_cells:
                    cdiv = panflute.Div(panflute.Plain())
                    table_cells[cell_id] = panflute.TableCell(cdiv)
                    table_rows[row_id].content.append(table_cells[cell_id])
                container = table_cells[cell_id].content[0].content[0]

            else:
                lists = {}
                doc.content.append(cdiv)

            if 'align' in chunk[0]['attrs']:
                cdiv.attributes['style'] = 'text-align: ' + chunk[0]['attrs'][
                    'align'] + ';'

            for subchunk in chunk:
                if subchunk['newlines'] > 1:
                    # we've had an extra linebreak, no more adding on to lists
                    lists = {}

                # don't do anything with markers
                if subchunk['text'] == '*' and 'lmkr' in subchunk['attrs']:
                    continue

                scont = container
                if 'href' in subchunk['attrs']:
                    scont = panflute.Link(url=subchunk['attrs']['href'])
                    container.content.append(scont)

                if 'image' in subchunk['attrs']:
                    scont.content.append(
                        panflute.Image(
                            url=self._image(subchunk['attrs']['author'],
                                            subchunk['attrs']['image'])))
                    continue

                span = panflute.Span()
                lines = subchunk['text'].splitlines()
                while lines:
                    subtext = lines.pop(0)
                    span.content.append(panflute.Str(subtext))
                    if lines:
                        span.content.append(panflute.LineBreak())

                if 'font' in subchunk['attrs']:
                    color = subchunk['attrs']['font'].get('color', '000000')
                    size = subchunk['attrs']['font'].get('size', 'medium')
                    span.classes.append('font-size-' + size)
                    span.classes.append('font-color-' + color)

                    # I don't actually know what the possible colors are and I
                    # don't feel like finding out, so just inject it as an
                    # inline style.
                    if color != '000000':
                        span.attributes['style'] = 'color: #' + color + ';'

                if subchunk['attrs'].get('underline'):
                    span.classes.append('underline')
                if subchunk['attrs'].get('bold'):
                    span = panflute.Strong(span)
                if subchunk['attrs'].get('italic'):
                    span = panflute.Emph(span)
                if subchunk['attrs'].get('strikethrough'):
                    span = panflute.Strikeout(span)
                scont.content.append(span)

        # Actually create the tables
        for x in tables:
            tables[x]['div'].content.append(panflute.Table(*tables[x]['rows']))

        with io.StringIO() as f:
            panflute.dump(doc, f)
            return f.getvalue()
Ejemplo n.º 13
0
 def handle_modstextbf(self, cmd_args, elem):
     r"""Handle \modstextbf command."""
     return pf.Strong(*parse_fragment(
         cmd_args[0], elem.doc.metadata["lang"].text)[0].content)
Ejemplo n.º 14
0
def action(elem, doc):
    if isinstance(elem, pf.CodeBlock):
        import pdb
        pdb.set_trace()
    if isinstance(elem, pf.Emph):
        return pf.Strong(*elem.content)
Ejemplo n.º 15
0
def plain(elem: Union[panflute.Div, panflute.Header],
          doc: panflute.Doc) -> Optional[panflute.Div]:
    """Assemble the plain text version of the acronym list

    The base plain text output is a bulleted list of acronyms in the
    following format::

        -   {short}: {long}

    in a new :class:`panflute.Div` with the identifier “acronym-list”.
    If the given element is a :class:`panflute.Div`, the list is placed
    under a level 1 header with the text “Acronyms” unless the ``name``
    or ``level`` attributes are set in which case, the request is
    honored.  The list is sorted by the short version of the acronyms by
    default unless the ``sort`` attribute is set to “false” (case
    insensitive) in which case the order is unspecified.  If an
    attribute cannot be interpreted, it is omitted and a warning is
    logged.

    Parameters
    ----------

    elem: :class:`panflute.Div` or :class:`panflute.Header`
        The element to replace
    doc: :class:`panflute.Doc`
        The document under consideration.

    Returns
    -------

    :class:`panflute.Div`, optional:
        The replacement for the block.

    """
    logger = logging.getLogger(__name__ + ".plain_text")
    if "acronyms" not in doc.metadata:
        return None

    if isinstance(elem, panflute.Header):
        header = elem
    elif isinstance(elem, panflute.Div):
        header = panflute.Header(panflute.Str(
            elem.attributes.get("name", "Acronyms")),
                                 level=elem.attributes.get("level", 1))
    else:
        cls = type(elem)
        logger.warning(f"Unknown element type {cls}")
        return None

    if "sort" in elem.attributes:
        sort = elem.attributes["sort"].lower()
        if sort not in ("true", "false"):
            sort = "true"
            logger.warning(f"Unknown 'sort' option '{sort}'")
    else:
        sort = "true"

    if sort == "true":
        acronyms = sorted(doc.acronyms.values(), key=lambda x: x["short"])
    else:
        acronyms = doc.acronyms.values()

    acrolist = [
        panflute.ListItem(
            panflute.Plain(panflute.Strong(panflute.Str(acro["short"])),
                           panflute.Str(":"), panflute.Space,
                           *panflute.convert_text(acro["long"])[0].content))
        for acro in acronyms if acro["list"]
    ]
    return panflute.Div(header,
                        panflute.BulletList(*acrolist),
                        identifier="acronym-list")