Извлечь список полей из reStructuredText

Скажем, у меня есть следующий ввод reST:

Some text ...

:foo: bar

Some text ...

То, что я хотел бы получить, это такой дикт:

{"foo": "bar"}

Я пытался использовать это:

tree = docutils.core.publish_parts(text)

Он анализирует список полей, но я получаю псевдо XML в tree["whole"]?:

<document source="<string>">
    <docinfo>
        <field>
            <field_name>
                foo
            <field_body>
                <paragraph>
                    bar

Поскольку tree dict не содержит никакой другой полезной информации и представляет собой просто строку, я не знаю, как разобрать список полей из документа reST. Как бы я это сделал?


person Martin Ueding    schedule 26.05.2012    source источник


Ответы (3)


Вы можете попробовать использовать что-то вроде следующего кода. Вместо использования метода publish_parts я использовал publish_doctree, чтобы получить псевдо-XML Представление вашего документа. Затем я преобразовал в XML DOM, чтобы извлечь все элементы field. Затем я получаю первые элементы field_name и field_body каждого элемента field.

from docutils.core import publish_doctree

source = """Some text ...

:foo: bar

Some text ...
"""

# Parse reStructuredText input, returning the Docutils doctree as
# an `xml.dom.minidom.Document` instance.
doctree = publish_doctree(source).asdom()

# Get all field lists in the document.
fields = doctree.getElementsByTagName('field')

d = {}

for field in fields:
    # I am assuming that `getElementsByTagName` only returns one element.
    field_name = field.getElementsByTagName('field_name')[0]
    field_body = field.getElementsByTagName('field_body')[0]

    d[field_name.firstChild.nodeValue] = \
        " ".join(c.firstChild.nodeValue for c in field_body.childNodes)

print d # Prints {u'foo': u'bar'}

Модуль xml.dom не самый простой в работе (почему я нужно использовать .firstChild.nodeValue, а не только .nodeValue, например), поэтому вы можете использовать xml.etree.ElementTree, с которым мне намного проще работать. Если вы используете lxml, вы также можете использовать нотацию XPATH, чтобы найти все элементы field, field_name и field_body.

person Chris    schedule 28.05.2012
comment
Спасибо, это похоже на то, что я ищу! - person Martin Ueding; 28.05.2012

У меня есть альтернативное решение, которое я считаю менее обременительным, но, возможно, более хрупким. После проверки реализации класса узла https://sourceforge.net/p/docutils/code/HEAD/tree/trunk/docutils/docutils/nodes.py вы увидите, что он поддерживает метод обхода, который можно использовать для извлечения нужных данных без необходимости создайте два разных xml-представления ваших данных. Вот что я использую сейчас в своем коде прототипа:

https://github.com/h4ck3rm1k3/gcc-introspector/blob/master/peewee_adaptor.py#L33

from docutils.core import publish_doctree
import docutils.nodes

а потом

def walk_docstring(prop):
    doc = prop.__doc__
    doctree = publish_doctree(doc)
    class Walker:
        def __init__(self, doc):
            self.document = doc
            self.fields = {}
        def dispatch_visit(self,x):
            if isinstance(x, docutils.nodes.field):
                field_name = x.children[0].rawsource
                field_value = x.children[1].rawsource
                self.fields[field_name]=field_value
    w = Walker(doctree)
    doctree.walk(w)
    # the collected fields I wanted
    pprint.pprint(w.fields)
person h4ck3rm1k3    schedule 08.02.2015

Вот моя реализация ElementTree:

from docutils.core import publish_doctree
from xml.etree.ElementTree import fromstring

source = """Some text ...

:foo: bar

Some text ...
"""


def gen_fields(source):
    dom = publish_doctree(source).asdom()
    tree = fromstring(dom.toxml())

    for field in tree.iter(tag='field'):
        name = next(field.iter(tag='field_name'))
        body = next(field.iter(tag='field_body'))
        yield {name.text: ''.join(body.itertext())}

Применение

>>> next(gen_fields(source))
{'foo': 'bar'}
person reubano    schedule 08.06.2017