sergiocorreia / panflute

An Pythonic alternative to John MacFarlane's pandocfilters, with extra helper functions
http://scorreia.com/software/panflute/
BSD 3-Clause "New" or "Revised" License
500 stars 59 forks source link

Stringifying the terms of a definition list results in AttributeError #218

Closed amine-aboufirass closed 2 years ago

amine-aboufirass commented 2 years ago

I have the following markdown:

---
title: my title
author: bob marley
---

# Header {#identifier .class key=value}

Some text

::: {#special .glossary}
Term 1
: Definition 1

Term 2
: Definition 2
:::

I try to parse and stringify the individual terms and definitons of the definition lists with panflute as follows:

import panflute as pf

def action(elem, doc):
    if isinstance(elem, pf.Div):
        for definition_item in elem.content[0].content:
            # print(pf.stringify(definition_item.term))
            print(pf.stringify(definition_item.definitions[0]))

def debug():
    with open("definition-lists.md") as fs:
        markdown = fs.read()

    doc = pf.convert_text(markdown, standalone=True)
    doc.walk(action)

if __name__ == "__main__":
    debug()

It seems that the definition print out fine, but uncommenting line 6 in the python code results in the following error:

AttributeError: 'ListContainer' object has no attribute 'walk'

So I suspect it is not possible to stringify ListContainers like that.

How can I obtain the terms of my definition list using panflute? Thanks.

amine-aboufirass commented 2 years ago

I came up with this solution:

import panflute as pf

def action(elem, doc):
    if isinstance(elem, pf.Div):
        for definition_item in elem.content[0].content:
            term = "".join(list(map(pf.stringify, definition_item.term)))
            print(term)
            print(pf.stringify(definition_item.definitions[0]))

def debug():
    with open("definition-lists.md") as fs:
        markdown = fs.read()

    doc = pf.convert_text(markdown, standalone=True)
    doc.walk(action)

if __name__ == "__main__":
    debug()

But I don't know if it's very good or robust. I feel like there should be a built-in way to "walk" through a ListContainer type....

sergiocorreia commented 2 years ago

I feel like there should be a built-in way to "walk" through a ListContainer type....

That makes sense, and might simplify some of the code

Still, I wonder if that will bring unexpected problems, so I need to think a bit more on this. I also think some things might be easier if we allow walk() to work top-to-bottom as needed.

BTW, unrelated but note that you are acting on a Div element and not on a DefinitionList object which makes the code a bit harder to read. For instance, the action function could be written as

    def action(elem, doc):
        if isinstance(elem, pf.DefinitionList):
            for item in elem.content:
                term = ''.join(pf.stringify(part) for part in item.term)
                definitions = '; '.join(pf.stringify(defn) for defn in  item.definitions)
                print(f'{term}: {definitions}')
sergiocorreia commented 2 years ago

Hi Amine,

Just added a new version https://github.com/sergiocorreia/panflute/releases/tag/v2.2.0 that should help you do what you want.

Besides adding walk() to lists as you suggested, walk() also supports an optional argument stop_if that takes a function that can be used to stop the tree traversal as needed.

This helps quite a bit when stringifying. I added a custom stringify code for definition lists which is hopefully close to what you want. Feel free to modify it (or to further improve stringify() and send a PR)

amine-aboufirass commented 2 years ago

Hi @sergiocorreia that's awesome! I'll be sure to test it out as soon as possible.

With regards to the Div element, this is in fact intentional because I am writing code to parse through something like this:

:::{ .glossary }
function
: An operation which has an input and output

variable
: A symbol which can take on any value in a defined domain
:::

So I basically check whether the Div has a certain class before implementing what we discussed here. Not that that's super relevant, but now you know the context.

I'll repost once I give stop_if a shot...

amine-aboufirass commented 2 years ago

Hi @sergiocorreia is there any way to install v2.2.0 using pip ? Or should I download the source code and install it locally?

sergiocorreia commented 2 years ago

Thanks for catching that. The new version should have been automatically published to pypi but was not due to a weird error (it was supposed to be published with Python 3.10 but the Github workflow understands 3.10=3.1 and there's no available Python 3.1 of course).

Should work not; let me know if not.

amine-aboufirass commented 2 years ago

I was able to test this and it seems to work. Closing now.