[python] Rekurzivní generátor v Pythonu 3?

Petr Messner petr.messner na gmail.com
Neděle Srpen 28 12:54:20 CEST 2016


> 
> V mém případě může část dokumentu vypadat třeba takto:
> 
>    text text
>    <verse/>
>    textB textB <czap> textC textC <verse/> textD textD </czap>


Na tohle se vykašli. Jakmile dokument nemá stromovou strukturu, tak je vše tisíckrát složitější. Dikybohu toto XML neumí. 

Co je špatného na tom příkladě se sID/eID? Jestli je to v tom, že to neoznačuje i část obsahu/verše, co takhle tohle?

<verse>a b <mark sID="x"> c d</verse><verse>e f<mark eID="x"> g h</verse>

Na pochopení toho kódu bych si musel sednout někam v klidu, ale zatím jen taková drobnost - nedělej skládání řetězců stylem txt += part, je to strašně pomalé (O(n^2) místo O(n)).. Z txt si udělej list a používej append: txt.append(part) a na konci "".join(txt).

Petr Messner

27. 8. 2016 v 23:09, Matěj Cepl <mcepl na cepl.eu>:

> Dobrý den,
> 
> zkouším napsat v Pythonu 3.4+ nástroj pracující s milestonovanými XML soubory (zdrojové texty překladu Bible).  Podrobně jsem to popsal v blogpostu https://matej.ceplovi.cz/blog/parsing-milestoned-xml-in-python.html, zde jenom velice stručně. Milestones (milníky?) jsou metoda jak obejít neschopnost XML pracovat s několika překrývajícími se hierarchiemi v jednom souboru. Tak třeba právě v biblických textech (TEI se potýká s podobnými problémy) je základní struktura kniha-kapitola-verš, ale přes to jsou další elementy které se překrývají. Několik veršů (nebo jejich částí) jsou sdruženy do logických oddílů (ale některé začínají v půli verše a často přesahují hranice kapitol), nebo třeba zejména anglické biblické překlady mají v oblibě značit výroky Pána Ježíše zvlášť (což pochopitelně často začíná a končí v půli verše).
> 
> Jedna z metod (používaná husta právě v biblických textech, TEI a podobných složitě strukturovaných dokumentech) jsou právě milníky. Takže místo aby byl text značen nějak takto:
> 
>     <book>
>     <chapter>
>     <verse>text</verse>
>     ...
>     </chapter>
>     ...
>     </book>
> 
> použijí se na hranici knihy, kapitoly i verše milníky takto (buď se zvláštním označením konce veršů nebo taky ne):
> 
>     <book>
>     <chapter n="1" />
>     <verse sID="ID1.1" />text of verse 1.1
>     <verse eID="ID1.1" /> ....
>     </book>
> 
> V mém případě může část dokumentu vypadat třeba takto:
> 
>    text text
>    <verse/>
>    textB textB <czap> textC textC <verse/> textD textD </czap>
> 
> A chtěl bych aby moje knihovna naparsovala toto XML a vyprodukovala takovýto seznam:
> 
>    [(1, 1, "text text", ['text text']),
>     (1, 2, "textB textB textC textC",
>      ['<verse/>', 'textB textB', '<czap>', 'textC textC']),
>     (1, 3, "textD textD", ['<verse/>', 'textD textD', '</czap>'])]
> 
> (první dvě čísla jsou číslo kapitoly a verše, poslední položka tuplu je seznam kousků XML v podobě přijatelné pro ElementTree.fromstringlist). Takže představoval bych si generátor, který by byl schopen takovéto API:
> 
>    if __name__ == '__main__':
>        xml_file = ET.parse('tests/data/Mat-old.xml')
>        parser = ET.XMLParser(target=ET.TreeBuilder())
> 
>        with open('test.txt', 'w', newline='\r\n') as out_txt, \
>                open('test.xml', 'w', newline='\r\n') as out_xml:
>            for ch, v, verse_txt, verse_xml in recursive_parse(xml_file):
>                print(verse_txt, file=out_txt)
>                # or directly parser.feed(verse_xml)
>                # if verse_xml is not a list
>                parser.feed(''.join(verse_xml))
> 
>            print(ET.tostring(parser.close(), encoding='unicode'),
>                  file=out_xml)
> 
> Po různých peripetiích (popsaných v tom zmiňovaném blogpostu) jsem v současném okamžiku u tohoto (zatím bez shromažďování XML kousků):
> 
>    def __iter__(self) -> Tuple[CollectedInfo, str]:
>        """
>        iterate through the first level elements
>        """
>        cur_chapter = 0
>        cur_verse = 0
>        collected_txt = ''
>        # collected XML is NOT directly convertable into Element objects,
>        # it should be treated more like a list of SAX-like events.
>        #
>        # xml.etree.ElementTree.fromstringlist(sequence, parser=None)
>        # Parses an XML document from a sequence of string fragments.
>        # sequence is a list or other sequence containing XML data fragments.
>        # parser is an optional parser instance. If not given, the standard
>        # XMLParser parser is used. Returns an Element instance.
>        #
>        # sequence = ["<html><body>", "text</bo", "dy></html>"]
>        # element = ET.fromstringlist(sequence)
>        # self.assertEqual(ET.tostring(element),
>        #         b'<html><body>text</body></html>')
> 
>        for child in self.root.iter():
>            if child.tag in ['titulek']:
>                collected_txt += '\n{}\n'.format(child.text)
>                collected_txt += child.tail or ''
>            if child.tag in ['kap', 'vers']:
>                if collected_txt and collected_txt.strip():
>                    yield CollectedInfo(cur_chapter, cur_verse,
>                                        re.sub(r'[\s\n]+', ' ', collected_txt,
>                                               flags=re.DOTALL).strip()), \
>                        child.tail or ''
> 
>                if child.tag == 'kap':
>                    cur_chapter = int(child.get('n'))
>                elif child.tag == 'vers':
>                    cur_verse = int(child.get('n'))
>            else:
>                collected_txt += child.text or ''
> 
>                for sub_child in child:
>                    for sub_info, sub_tail in MilestonedElement(sub_child):
>                        if sub_info.verse == 0 or sub_info.chap == 0:
>                            collected_txt += sub_info.text + sub_tail
>                        else:
>                            # FIXME what happens if sub_element contains
>                            # multiple <verse/> elements?
>                            yield CollectedInfo(
>                                sub_info.chap, sub_info.verse,
>                                collected_txt + sub_info.text), ''
>                            collected_txt = sub_tail
> 
>                collected_txt += child.tail or ''
> 
>        yield CollectedInfo(0, 0, collected_txt), ''
> 
> Připadá Vám to jako způsobilá metoda jak dělat rekurzivní generátor nebo jsem něco nepochopil?
> 
> Děkuji za jakoukoli pomoc s tímto velmi zmateným úkolem.
> 
> Matěj
> 
> -- 
> https://matej.ceplovi.cz/blog/, Jabber: mcepl na ceplovi.cz
> GPG Finger: 3C76 A027 CA45 AD70 98B5  BC1D 7920 5802 880B C9D8
> 
> He uses statistics as a drunken man uses lamp-posts... for
> support, rather than illumination.
>      -- Andrew Lang
> _______________________________________________
> Python mailing list
> python na py.cz
> http://www.py.cz/mailman/listinfo/python
> 
> Visit: http://www.py.cz
------------- další část ---------------
HTML příloha byla odstraněna...
URL: <http://www.py.cz/pipermail/python/attachments/20160828/57c98bec/attachment.html>


Další informace o konferenci Python