Drop lots of legacy code. Support Python 3.7-3.11.
[librarian.git] / src / librarian / epub.py
1 # This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Wolne Lektury. See NOTICE for more information.
3 #
4 import re
5 from copy import deepcopy
6 from lxml import etree
7
8 from librarian import WLNS
9
10
11 class Stanza:
12     """
13     Converts / verse endings into verse elements in a stanza.
14
15     Slashes may only occur directly in the stanza. Any slashes in subelements
16     will be ignored, and the subelements will be put inside verse elements.
17
18     >>> s = etree.fromstring(
19     ...         "<strofa>a <b>c</b> <b>c</b>/\\nb<x>x/\\ny</x>c/ \\nd</strofa>"
20     ...     )
21     >>> Stanza(s).versify()
22     >>> print(etree.tostring(s, encoding='unicode', pretty_print=True).strip())
23     <strofa>
24       <wers_normalny>a <b>c</b><b>c</b></wers_normalny>
25       <wers_normalny>b<x>x/
26     y</x>c</wers_normalny>
27       <wers_normalny>d</wers_normalny>
28     </strofa>
29
30     """
31     def __init__(self, stanza_elem):
32         self.stanza = stanza_elem
33         self.verses = []
34         self.open_verse = None
35
36     def versify(self):
37         self.push_text(self.stanza.text)
38         for elem in self.stanza:
39             self.push_elem(elem)
40             self.push_text(elem.tail)
41         tail = self.stanza.tail
42         self.stanza.clear()
43         self.stanza.tail = tail
44         self.stanza.extend(
45             verse for verse in self.verses
46             if verse.text or len(verse) > 0
47         )
48
49     def open_normal_verse(self):
50         self.open_verse = self.stanza.makeelement("wers_normalny")
51         self.verses.append(self.open_verse)
52
53     def get_open_verse(self):
54         if self.open_verse is None:
55             self.open_normal_verse()
56         return self.open_verse
57
58     def push_text(self, text):
59         if not text:
60             return
61         for i, verse_text in enumerate(re.split(r"/\s*\n", text)):
62             if i:
63                 self.open_normal_verse()
64             if not verse_text.strip():
65                 continue
66             verse = self.get_open_verse()
67             if len(verse):
68                 verse[-1].tail = (verse[-1].tail or "") + verse_text
69             else:
70                 verse.text = (verse.text or "") + verse_text
71
72     def push_elem(self, elem):
73         if elem.tag.startswith("wers"):
74             verse = deepcopy(elem)
75             verse.tail = None
76             self.verses.append(verse)
77             self.open_verse = verse
78         else:
79             appended = deepcopy(elem)
80             appended.tail = None
81             self.get_open_verse().append(appended)
82
83
84 def replace_by_verse(tree):
85     """ Find stanzas and create new verses in place of a '/' character """
86
87     stanzas = tree.findall('.//' + WLNS('strofa'))
88     for stanza in stanzas:
89         Stanza(stanza).versify()