autoslides 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- autoslides-0.1.0/LICENSE +19 -0
- autoslides-0.1.0/PKG-INFO +11 -0
- autoslides-0.1.0/pyproject.toml +22 -0
- autoslides-0.1.0/setup.cfg +4 -0
- autoslides-0.1.0/src/autoslides/ASTcollapser.py +597 -0
- autoslides-0.1.0/src/autoslides/__init__.py +0 -0
- autoslides-0.1.0/src/autoslides/args.py +2 -0
- autoslides-0.1.0/src/autoslides/block.py +437 -0
- autoslides-0.1.0/src/autoslides/component.py +176 -0
- autoslides-0.1.0/src/autoslides/head.py +25 -0
- autoslides-0.1.0/src/autoslides/main.py +39 -0
- autoslides-0.1.0/src/autoslides/mdPreprocess.py +35 -0
- autoslides-0.1.0/src/autoslides/mistletoeExtension.py +45 -0
- autoslides-0.1.0/src/autoslides/utils.py +58 -0
- autoslides-0.1.0/src/autoslides.egg-info/PKG-INFO +11 -0
- autoslides-0.1.0/src/autoslides.egg-info/SOURCES.txt +17 -0
- autoslides-0.1.0/src/autoslides.egg-info/dependency_links.txt +1 -0
- autoslides-0.1.0/src/autoslides.egg-info/entry_points.txt +2 -0
- autoslides-0.1.0/src/autoslides.egg-info/top_level.txt +1 -0
autoslides-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2018 The Python Packaging Authority
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: autoslides
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Convert markdown documents into summarized slides
|
|
5
|
+
Author-email: Rubelito Abella <bbadoodles@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/HowDoIGitHelp/resource_builder.git
|
|
8
|
+
Requires-Python: >=3.14
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "autoslides"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
authors = [
|
|
5
|
+
{ name = "Rubelito Abella", email = "bbadoodles@gmail.com" },
|
|
6
|
+
]
|
|
7
|
+
description = "Convert markdown documents into summarized slides"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.14"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
|
|
13
|
+
[project.urls]
|
|
14
|
+
Homepage = "https://github.com/HowDoIGitHelp/resource_builder.git"
|
|
15
|
+
|
|
16
|
+
[project.scripts]
|
|
17
|
+
autoslides = "autoslides.main:main"
|
|
18
|
+
|
|
19
|
+
[build-system]
|
|
20
|
+
requires = ["setuptools"]
|
|
21
|
+
build-backend = "setuptools.build_meta"
|
|
22
|
+
|
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from string import Template
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from mistletoe import Document
|
|
5
|
+
from mistletoe.markdown_renderer import MarkdownRenderer, LinkReferenceDefinition, BlankLine
|
|
6
|
+
from mistletoe.block_token import Paragraph, Heading, List, ListItem, BlockToken, CodeFence, Quote, Table, TableRow, TableCell
|
|
7
|
+
from mistletoe.span_token import LineBreak, RawText, Strong, Emphasis, Image, EscapeSequence, SpanToken
|
|
8
|
+
from mistletoe.token import Token
|
|
9
|
+
import math
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
LINES = 6
|
|
14
|
+
LINEWIDTH = 50
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Component(ABC):
|
|
18
|
+
'''
|
|
19
|
+
CompositeBlocks are made up of Components
|
|
20
|
+
'''
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def height(self, lineWidth=LINEWIDTH):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Sentence(Component):
|
|
28
|
+
'''
|
|
29
|
+
base class that can be decorated by StrongSentence and EmphasizedSentence
|
|
30
|
+
'''
|
|
31
|
+
|
|
32
|
+
def __init__(self,sentence:str):
|
|
33
|
+
self.__sentence = sentence
|
|
34
|
+
|
|
35
|
+
def __str__(self) -> str:
|
|
36
|
+
return str(self.__sentence)
|
|
37
|
+
|
|
38
|
+
def importantParts(self) -> list:
|
|
39
|
+
return []
|
|
40
|
+
|
|
41
|
+
def size(self) -> int:
|
|
42
|
+
return len(self.__sentence)
|
|
43
|
+
|
|
44
|
+
def height(self, lineWidth=LINEWIDTH):
|
|
45
|
+
return math.ceil(self.size() / lineWidth)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Head:
|
|
49
|
+
|
|
50
|
+
def __init__(self, mdHeading:Heading):
|
|
51
|
+
self.__level = mdHeading.level
|
|
52
|
+
self.__content = collapse(mdHeading.children)
|
|
53
|
+
|
|
54
|
+
def height(self, lineWidth=LINEWIDTH):
|
|
55
|
+
return 0
|
|
56
|
+
|
|
57
|
+
def headText(self) -> str:
|
|
58
|
+
return str(self.__content)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Block(ABC):
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def height(self) -> int:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
def items(self, level=0, indentSize=0, leader=''):
|
|
68
|
+
return [IndentedListItem(self, level=level, indentSize=indentSize, leader=leader)]
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def slideContent(self, head:Head):
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
def mdSlides(self, head:Head, lines=LINES):
|
|
75
|
+
return f'{self.slideContent(head)}\n---\n\n'
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class CompositeBlock(Block):
|
|
79
|
+
'''
|
|
80
|
+
composite blocks can be split into multiple slides, split is based on number of lines
|
|
81
|
+
'''
|
|
82
|
+
|
|
83
|
+
def slides(self, head:Head, lines=LINES, lineWidth=LINEWIDTH) -> list:
|
|
84
|
+
slides = []
|
|
85
|
+
currentHeight = 0
|
|
86
|
+
currentSlide = []
|
|
87
|
+
for component in self.components():
|
|
88
|
+
if currentHeight + component.height(lineWidth) > lines:
|
|
89
|
+
slides.append(self.slideContent(currentSlide, head))
|
|
90
|
+
currentSlide = [component]
|
|
91
|
+
currentHeight = component.height(lineWidth)
|
|
92
|
+
else:
|
|
93
|
+
currentSlide.append(component)
|
|
94
|
+
currentHeight += component.height(lineWidth)
|
|
95
|
+
slides.append(self.slideContent(currentSlide, head))
|
|
96
|
+
return slides
|
|
97
|
+
|
|
98
|
+
def mdSlides(self, head:Head, lines=LINES) -> str:
|
|
99
|
+
subDeck = ''
|
|
100
|
+
for slide in self.slides(head, lines):
|
|
101
|
+
subDeck += f'{slide}\n'
|
|
102
|
+
subDeck += '\n---\n\n'
|
|
103
|
+
return subDeck
|
|
104
|
+
|
|
105
|
+
@abstractmethod
|
|
106
|
+
def components(self) -> list:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
def height(self, lineWidth=LINEWIDTH):
|
|
110
|
+
cumulativeHeight = 0
|
|
111
|
+
for component in self.components():
|
|
112
|
+
cumulativeHeight += component.height(lineWidth)
|
|
113
|
+
return cumulativeHeight
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class StrongSentence(Sentence):
|
|
117
|
+
|
|
118
|
+
def __init__(self,sentence:Sentence, strongParts:list):
|
|
119
|
+
self.__sentence = sentence
|
|
120
|
+
self.__strongParts = strongParts
|
|
121
|
+
|
|
122
|
+
def __str__(self) -> str:
|
|
123
|
+
return str(self.__sentence)
|
|
124
|
+
|
|
125
|
+
def importantParts(self) -> list:
|
|
126
|
+
innerImportantParts = []
|
|
127
|
+
for part in self.__strongParts:
|
|
128
|
+
innerImportantParts += part.importantParts()
|
|
129
|
+
return self.__sentence.importantParts() + self.__strongParts + innerImportantParts
|
|
130
|
+
|
|
131
|
+
def size(self) -> int:
|
|
132
|
+
return self.__sentence.size()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class EmphasizedSentence(Sentence):
|
|
136
|
+
|
|
137
|
+
def __init__(self,sentence:Sentence,emphasizedParts:list):
|
|
138
|
+
self.__sentence = sentence
|
|
139
|
+
self.__emphasizedParts = emphasizedParts
|
|
140
|
+
|
|
141
|
+
def __str__(self) -> str:
|
|
142
|
+
return str(self.__sentence)
|
|
143
|
+
|
|
144
|
+
def importantParts(self) -> list:
|
|
145
|
+
innerImportantParts = []
|
|
146
|
+
for part in self.__emphasizedParts:
|
|
147
|
+
innerImportantParts += part.importantParts()
|
|
148
|
+
return self.__sentence.importantParts() + self.__emphasizedParts + innerImportantParts
|
|
149
|
+
|
|
150
|
+
def size(self) -> int:
|
|
151
|
+
return self.__sentence.size()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class UnsupportedTokenException(Exception):
|
|
155
|
+
|
|
156
|
+
def __init__(self, token:BlockToken):
|
|
157
|
+
self.unsupportedToken = token
|
|
158
|
+
|
|
159
|
+
def isMathBlock(paragraph:Paragraph, mathFence = '$$') -> bool:
|
|
160
|
+
'''
|
|
161
|
+
mathblocks are not native to commonmark or mistletoe, this function checks if a paragraph is surrounded by the math fence token
|
|
162
|
+
'''
|
|
163
|
+
startsWithDollars = isinstance(paragraph.children[0], RawText) and paragraph.children[0].content.startswith(mathFence)
|
|
164
|
+
endsWithDollars = isinstance(paragraph.children[-1], RawText) and paragraph.children[-1].content.endswith(mathFence)
|
|
165
|
+
return startsWithDollars and endsWithDollars
|
|
166
|
+
|
|
167
|
+
def isInvisible(token:Token):
|
|
168
|
+
if isinstance(token, BlankLine):
|
|
169
|
+
return True
|
|
170
|
+
if isinstance(token, LinkReferenceDefinition):
|
|
171
|
+
return True
|
|
172
|
+
else:
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
def asBlock(token:BlockToken) -> Block:
|
|
176
|
+
'''
|
|
177
|
+
convert mistletoe.block_token.BlockToken into Blocks
|
|
178
|
+
'''
|
|
179
|
+
if isinstance(token, Paragraph) and isMathBlock(token):
|
|
180
|
+
return MathBlock(token)
|
|
181
|
+
elif isinstance(token, Paragraph) and isImageBlock(token):
|
|
182
|
+
return ImageBlock(token)
|
|
183
|
+
elif isinstance(token, Paragraph):
|
|
184
|
+
return ParagraphBlock(token)
|
|
185
|
+
elif isinstance(token, Heading):
|
|
186
|
+
return Head(token)
|
|
187
|
+
elif isinstance(token, List):
|
|
188
|
+
return ListBlock(token)
|
|
189
|
+
elif isinstance(token, ListItem):
|
|
190
|
+
return Item(token)
|
|
191
|
+
elif isinstance(token, CodeFence):
|
|
192
|
+
return CodeBlock(token)
|
|
193
|
+
elif isinstance(token, Quote):
|
|
194
|
+
return QuoteBlock(token)
|
|
195
|
+
elif isinstance(token, Table):
|
|
196
|
+
return TableBlock(token)
|
|
197
|
+
else:
|
|
198
|
+
raise UnsupportedTokenException(token)
|
|
199
|
+
|
|
200
|
+
class SentenceDelimiter(SpanToken):
|
|
201
|
+
|
|
202
|
+
def __init__(self):
|
|
203
|
+
self.content = ''
|
|
204
|
+
|
|
205
|
+
def delimitedTextToken(textToken:RawText) -> list:
|
|
206
|
+
'''
|
|
207
|
+
this function takes a RawText span token and splits it into multiple RawText tokens with SentenceDelimiters in between
|
|
208
|
+
the delimiter is the string '. '
|
|
209
|
+
this function will not split inline math pharases
|
|
210
|
+
'''
|
|
211
|
+
pattern = re.compile(r'\$.*?\$')
|
|
212
|
+
inlineMathParts = re.findall(pattern, textToken.content)
|
|
213
|
+
rawTextContent = pattern.sub('$inlineMath$', textToken.content)
|
|
214
|
+
cumulativeTokenList = []
|
|
215
|
+
tokens = rawTextContent.split('. ')
|
|
216
|
+
for token in tokens[:-1]:
|
|
217
|
+
cumulativeTokenList += [RawText(f'{token}.'), SentenceDelimiter()]
|
|
218
|
+
if tokens[-1] != '':
|
|
219
|
+
cumulativeTokenList.append(RawText(tokens[-1]))
|
|
220
|
+
i = 0
|
|
221
|
+
while i < len(inlineMathParts):
|
|
222
|
+
for token in cumulativeTokenList:
|
|
223
|
+
for j in range(token.content.count('$inlineMath$')):
|
|
224
|
+
token.content = token.content.replace('$inlineMath$', inlineMathParts[i] ,1)
|
|
225
|
+
i += 1
|
|
226
|
+
return cumulativeTokenList
|
|
227
|
+
|
|
228
|
+
class ParagraphBlock(CompositeBlock):
|
|
229
|
+
|
|
230
|
+
def __init__(self, mdParagraph:Paragraph):
|
|
231
|
+
self.__mdParagraph = mdParagraph
|
|
232
|
+
self.__sentences = [collapse(spanList) for spanList in self.decompose()]
|
|
233
|
+
|
|
234
|
+
def decompose(self) -> list:
|
|
235
|
+
'''
|
|
236
|
+
Decompose a paragraph into list of span tokens based on SoftBreaks (single line breaks)
|
|
237
|
+
'''
|
|
238
|
+
spanTokenList = []
|
|
239
|
+
for child in self.__mdParagraph.children:
|
|
240
|
+
if isinstance(child, RawText):
|
|
241
|
+
spanTokenList += delimitedTextToken(child)
|
|
242
|
+
else:
|
|
243
|
+
spanTokenList.append(child)
|
|
244
|
+
sentenceComposites = []
|
|
245
|
+
rawSentences = []
|
|
246
|
+
for spanToken in spanTokenList:
|
|
247
|
+
if isinstance(spanToken, LineBreak) or isinstance(spanToken, SentenceDelimiter):
|
|
248
|
+
rawSentences.append(sentenceComposites)
|
|
249
|
+
sentenceComposites = []
|
|
250
|
+
else:
|
|
251
|
+
sentenceComposites.append(spanToken)
|
|
252
|
+
rawSentences.append(sentenceComposites)
|
|
253
|
+
return rawSentences
|
|
254
|
+
|
|
255
|
+
def components(self):
|
|
256
|
+
return self.__sentences
|
|
257
|
+
|
|
258
|
+
def slideContent(self, components:list, head:Head) -> str:
|
|
259
|
+
md = ''
|
|
260
|
+
md += f'# {head.headText()}\n'
|
|
261
|
+
md += '\n'
|
|
262
|
+
for sentence in components:
|
|
263
|
+
md += f'- {sentence}\n'
|
|
264
|
+
return md
|
|
265
|
+
|
|
266
|
+
def __str__(self) -> str:
|
|
267
|
+
cumulativeString = ''
|
|
268
|
+
for sentence in self.__sentences:
|
|
269
|
+
cumulativeString += f'{sentence} '
|
|
270
|
+
return cumulativeString[:-1]
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class IndentedListItem(Component):
|
|
274
|
+
|
|
275
|
+
def __init__(self, content:Block, indentSize=0, level=0, leader=''):
|
|
276
|
+
self.__level = level
|
|
277
|
+
self.__content = content
|
|
278
|
+
self.__leader = leader
|
|
279
|
+
self.__indentSize = indentSize
|
|
280
|
+
self.__prefix = ' ' * ((indentSize * level) - len(leader))
|
|
281
|
+
self.__prefix += leader
|
|
282
|
+
|
|
283
|
+
def __str__(self) -> str:
|
|
284
|
+
return f'{self.__prefix}{self.__content}'
|
|
285
|
+
|
|
286
|
+
def height(self, lineWidth=LINEWIDTH) -> int:
|
|
287
|
+
return math.ceil(len(str(self)) / lineWidth)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class ListBlock(CompositeBlock):
|
|
291
|
+
|
|
292
|
+
def __init__(self, mdList:List):
|
|
293
|
+
self.__mdList = mdList
|
|
294
|
+
self.__items = []
|
|
295
|
+
for item in self.__mdList.children:
|
|
296
|
+
self.__items.append(asBlock(item))
|
|
297
|
+
|
|
298
|
+
def height(self, lineWidth=LINEWIDTH):
|
|
299
|
+
cumulativeHeight = 0
|
|
300
|
+
for item in self.__items:
|
|
301
|
+
cumulativeHeight += item.height(lineWidth)
|
|
302
|
+
return cumulativeHeight
|
|
303
|
+
|
|
304
|
+
def nthItem(self, n:int) -> Block:
|
|
305
|
+
return self.__items[n]
|
|
306
|
+
|
|
307
|
+
def items(self, level=0, indentSize=0, leader='') -> list:
|
|
308
|
+
itemsDFS = []
|
|
309
|
+
for item in self.__items:
|
|
310
|
+
itemsDFS += item.items(level=level, indentSize=indentSize, leader=leader)
|
|
311
|
+
return itemsDFS
|
|
312
|
+
|
|
313
|
+
def components(self) -> list:
|
|
314
|
+
return self.items()
|
|
315
|
+
|
|
316
|
+
def __str__(self) -> str:
|
|
317
|
+
cumulativeString = ''
|
|
318
|
+
for items in self.items():
|
|
319
|
+
cumulativeString += f'{items}\n'
|
|
320
|
+
return cumulativeString[:-1]
|
|
321
|
+
|
|
322
|
+
def slideContent(self, components:list, head:Head) -> str:
|
|
323
|
+
md = ''
|
|
324
|
+
md += f'# {head.headText()}\n'
|
|
325
|
+
md += '\n'
|
|
326
|
+
for line in components:
|
|
327
|
+
md += f'{line}\n'
|
|
328
|
+
return md
|
|
329
|
+
#override CompositeBlock.slides() to prevent orphaned list item children
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class CodeLine(Component):
|
|
333
|
+
|
|
334
|
+
def __init__(self, content):
|
|
335
|
+
self.__content = content
|
|
336
|
+
|
|
337
|
+
def height(self, lineWidth=LINEWIDTH):
|
|
338
|
+
return math.ceil(len(self.__content) / lineWidth)
|
|
339
|
+
|
|
340
|
+
def __str__(self) -> str:
|
|
341
|
+
return self.__content
|
|
342
|
+
|
|
343
|
+
class CodeBlock(CompositeBlock):
|
|
344
|
+
|
|
345
|
+
def __init__(self, mdCodeFence:CodeFence):
|
|
346
|
+
self.__language = mdCodeFence.language
|
|
347
|
+
self.__lines = []
|
|
348
|
+
for lineContent in mdCodeFence.children[0].content.split('\n'):
|
|
349
|
+
self.__lines.append(CodeLine(lineContent))
|
|
350
|
+
|
|
351
|
+
def components(self):
|
|
352
|
+
return self.__lines
|
|
353
|
+
|
|
354
|
+
def slideContent(self, components:list, head:Head) -> str:
|
|
355
|
+
md = ''
|
|
356
|
+
md += f'# {head.headText()}\n'
|
|
357
|
+
md += '\n'
|
|
358
|
+
md += f'```{self.__language}\n'
|
|
359
|
+
for line in components:
|
|
360
|
+
md += f'{line}\n'
|
|
361
|
+
md += '```'
|
|
362
|
+
return md
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class QuoteBlock(CompositeBlock):
|
|
366
|
+
|
|
367
|
+
def __init__(self, mdContent):
|
|
368
|
+
self.__mdQuote = mdContent
|
|
369
|
+
self.__children = []
|
|
370
|
+
for child in self.__mdQuote.children:
|
|
371
|
+
self.__children.append(asBlock(child))
|
|
372
|
+
|
|
373
|
+
def components(self):
|
|
374
|
+
return self.__children
|
|
375
|
+
|
|
376
|
+
def slideContent(self, components:list, head:Head) -> str:
|
|
377
|
+
md = ''
|
|
378
|
+
md += f'# {head.headText()}\n'
|
|
379
|
+
md += '\n'
|
|
380
|
+
for line in components:
|
|
381
|
+
md += f'> {line}\n'
|
|
382
|
+
return md
|
|
383
|
+
|
|
384
|
+
def __str__(self) -> str:
|
|
385
|
+
cumulativeString = ''
|
|
386
|
+
for child in self.__children:
|
|
387
|
+
cumulativeString += f'> {child} '
|
|
388
|
+
return cumulativeString[:-1]
|
|
389
|
+
|
|
390
|
+
def height(self, lineWidth=LINEWIDTH) -> int:
|
|
391
|
+
cumulativeHeight = 0
|
|
392
|
+
for child in self.__children:
|
|
393
|
+
cumulativeHeight += math.ceil(len(str(child)) / lineWidth)#change height calculation
|
|
394
|
+
return cumulativeHeight
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def extractedMathEnvironments(mathBlock, environment) -> dict:
|
|
398
|
+
'''
|
|
399
|
+
this function extracts multiline math environments (e.g. matrix, bmatrix) so that the MathBlock.__init__() can safely split math blocks by newlines
|
|
400
|
+
it does not support nested multiline environments e.g. matrix inside a matrix
|
|
401
|
+
'''
|
|
402
|
+
pattern = Template(r'\\begin{$env}.*?\\end{$env}')
|
|
403
|
+
multilinePattern = re.compile(pattern.substitute(env=environment))
|
|
404
|
+
multilineBlocks = multilinePattern.findall(mathBlock)
|
|
405
|
+
replacedMathBlock = re.sub(multilinePattern, f'$${environment}$$', mathBlock)
|
|
406
|
+
return {'extractedBlocks':multilineBlocks, 'replacedMathBlock':replacedMathBlock}
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
class MathLine(Component):
|
|
410
|
+
|
|
411
|
+
def __init__(self, mathTeX:str):
|
|
412
|
+
self.__mathTeX = mathTeX
|
|
413
|
+
|
|
414
|
+
def height(self, lineWidth=LINEWIDTH):
|
|
415
|
+
return self.__mathTeX.count('\\\\') + 1
|
|
416
|
+
|
|
417
|
+
def __str__(self) -> str:
|
|
418
|
+
return self.__mathTeX
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def rawTex(mathParagraph):
|
|
422
|
+
rawLines = [line for line in mathParagraph.children]
|
|
423
|
+
cumulativeString = ''
|
|
424
|
+
for span in mathParagraph.children:
|
|
425
|
+
if isinstance(span, EscapeSequence):
|
|
426
|
+
cumulativeString += f'\\{span.children[0].content}'
|
|
427
|
+
else:
|
|
428
|
+
cumulativeString += span.content
|
|
429
|
+
return cumulativeString[2:-2]
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
class MathBlock(CompositeBlock):
|
|
434
|
+
|
|
435
|
+
def __init__(self, mdParagraph:Paragraph):
|
|
436
|
+
self.__isAligned = False
|
|
437
|
+
jointLines = rawTex(mdParagraph)
|
|
438
|
+
if jointLines.startswith('\\begin{aligned}') and jointLines.endswith('\\end{aligned}'):
|
|
439
|
+
self.__isAligned = True
|
|
440
|
+
jointLines = jointLines[15:-13]
|
|
441
|
+
multilineEnvironments = ['bmatrix','matrix']
|
|
442
|
+
extractedBlocks = {}
|
|
443
|
+
#remove all multiline blocks (e.g. matrices) while saving removed blocks to extractedBlocks
|
|
444
|
+
for env in multilineEnvironments:
|
|
445
|
+
exME = extractedMathEnvironments(jointLines, env)
|
|
446
|
+
jointLines = exME['replacedMathBlock']
|
|
447
|
+
extractedBlocks[env] = exME['extractedBlocks']
|
|
448
|
+
#now that all multiline blocks are gone, replace newline separators with $$nl$$ token
|
|
449
|
+
#this replacement is now safe since there are no multiline environments in the string
|
|
450
|
+
jointLines = jointLines.replace('\\\\', '$$nl$$')
|
|
451
|
+
#place all extracted blocks back
|
|
452
|
+
for env in multilineEnvironments:
|
|
453
|
+
for block in extractedBlocks[env]:
|
|
454
|
+
jointLines = jointLines.replace(f'$${env}$$', block, 1)
|
|
455
|
+
#safely split the math block using the $$nl$$ token
|
|
456
|
+
self.__lines = [MathLine(line) for line in jointLines.split('$$nl$$')]
|
|
457
|
+
|
|
458
|
+
def components(self):
|
|
459
|
+
return self.__lines
|
|
460
|
+
|
|
461
|
+
def slideContent(self, components:list, head:Head) -> str:
|
|
462
|
+
md = ''
|
|
463
|
+
md += f'# {head.headText()}\n'
|
|
464
|
+
md += '\n'
|
|
465
|
+
md += f'<div>\n$$\n'
|
|
466
|
+
if self.__isAligned:
|
|
467
|
+
md += '\\begin{aligned}\n'
|
|
468
|
+
for line in components:
|
|
469
|
+
md += f'{line}\\\\\n'
|
|
470
|
+
if len(components) > 0:
|
|
471
|
+
md = f'{md[:-3]}\n'#remove the latex newline '\\' on the last line
|
|
472
|
+
if self.__isAligned:
|
|
473
|
+
md += '\\end{aligned}\n'
|
|
474
|
+
md += '$$\n</div>'
|
|
475
|
+
return md
|
|
476
|
+
|
|
477
|
+
def __str__(self) -> str:
|
|
478
|
+
cumulativeString = ''
|
|
479
|
+
for component in self.__lines:
|
|
480
|
+
cumulativeString += f'{str(component)} \\\\'
|
|
481
|
+
|
|
482
|
+
if self.__isAligned:
|
|
483
|
+
return '<div> $$ \\begin{aligned} ' + cumulativeString + ' \\end{aligned} $$ </div>'
|
|
484
|
+
else:
|
|
485
|
+
return '<div> $$ ' + cumulativeString + ' $$ </div>'
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
class Cell:
|
|
489
|
+
|
|
490
|
+
def __init__(self, content:TableCell):
|
|
491
|
+
self.__content = collapse(content.children)
|
|
492
|
+
self.__align = content.align
|
|
493
|
+
|
|
494
|
+
def height(self, lineWidth=LINEWIDTH) -> int:
|
|
495
|
+
return self.__content.height(lineWidth)
|
|
496
|
+
|
|
497
|
+
def __str__(self) -> str:
|
|
498
|
+
return str(self.__content)
|
|
499
|
+
|
|
500
|
+
class Row(Component):
|
|
501
|
+
|
|
502
|
+
def __init__(self, content:TableRow):
|
|
503
|
+
self.__cells = [Cell(child) for child in content.children]
|
|
504
|
+
|
|
505
|
+
def height(self, lineWidth=LINEWIDTH):
|
|
506
|
+
tallestCell = self.__cells[0]
|
|
507
|
+
for cell in self.__cells[1:]:
|
|
508
|
+
if cell.height(lineWidth) > tallestCell.height(lineWidth):
|
|
509
|
+
tallestCell = cell
|
|
510
|
+
return tallestCell.height(lineWidth)
|
|
511
|
+
|
|
512
|
+
def __str__(self) -> str:
|
|
513
|
+
cumulativeString = '|' if len(self.__cells) > 0 else ''
|
|
514
|
+
for cell in self.__cells:
|
|
515
|
+
cumulativeString += f' {str(cell)} |'
|
|
516
|
+
return cumulativeString
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
class TableBlock(CompositeBlock):
|
|
520
|
+
|
|
521
|
+
def __init__(self, content:Table):
|
|
522
|
+
self.__header = Row(content.header)
|
|
523
|
+
self.__rows = [Row(child) for child in content.children]
|
|
524
|
+
self.__alignmentRow = '|' if len(content.header.children) > 0 else ''
|
|
525
|
+
for alignCode in content.column_align:
|
|
526
|
+
if alignCode is None:
|
|
527
|
+
self.__alignmentRow += '-----|'
|
|
528
|
+
elif alignCode == 0:
|
|
529
|
+
self.__alignmentRow += ':---:|'
|
|
530
|
+
else:
|
|
531
|
+
self.__alignmentRow += '----:|'
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def height(self, lineWidth=LINEWIDTH):
|
|
535
|
+
cumulativeHeight = self.__header.height(lineWidth)
|
|
536
|
+
for row in self.__rows:
|
|
537
|
+
cumulativeHeight += row.height(lineWidth)
|
|
538
|
+
return cumulativeHeight
|
|
539
|
+
|
|
540
|
+
def components(self):
|
|
541
|
+
return self.__rows
|
|
542
|
+
|
|
543
|
+
def slideContent(self, components:list, head:Head):
|
|
544
|
+
md = ''
|
|
545
|
+
md += f'# {head.headText()}\n'
|
|
546
|
+
md += '\n'
|
|
547
|
+
md += f'{str(self.__header)}\n'
|
|
548
|
+
md += f'{str(self.__alignmentRow)}\n'
|
|
549
|
+
for row in components:
|
|
550
|
+
md += f'{str(row)}\n'
|
|
551
|
+
return md
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def isImageBlock(paragraph:Paragraph):
|
|
555
|
+
return len(paragraph.children) == 1 and isinstance(paragraph.children[0], Image)
|
|
556
|
+
|
|
557
|
+
class ImageBlock(Block):
|
|
558
|
+
|
|
559
|
+
def __init__(self, paragraph:Paragraph):
|
|
560
|
+
self.__mdImage = paragraph.children[0]
|
|
561
|
+
self.__altText:Sentence = collapse(self.__mdImage.children)
|
|
562
|
+
self.__title = self.__mdImage.title
|
|
563
|
+
self.__src = self.__mdImage.src
|
|
564
|
+
|
|
565
|
+
def height(self, lineWidth=LINEWIDTH):
|
|
566
|
+
return 1
|
|
567
|
+
|
|
568
|
+
def slideContent(self, head:Head):
|
|
569
|
+
md = ''
|
|
570
|
+
md += f'# {head.headText()}\n'
|
|
571
|
+
md += '\n'
|
|
572
|
+
md += f''
|
|
573
|
+
return md
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def collapse(spanList:list) -> Sentence:
|
|
577
|
+
'''
|
|
578
|
+
Collapses a list of span tokens and returns Sentence
|
|
579
|
+
'''
|
|
580
|
+
emphasizedParts = []
|
|
581
|
+
strongParts = []
|
|
582
|
+
mdSpanList = []
|
|
583
|
+
for token in spanList:
|
|
584
|
+
if isinstance(token, Emphasis):
|
|
585
|
+
emphasizedParts.append(collapse(token.children))
|
|
586
|
+
elif isinstance(token, Strong):
|
|
587
|
+
strongParts.append(collapse(token.children))
|
|
588
|
+
with MarkdownRenderer() as renderer:
|
|
589
|
+
mdSpanList.append(renderer.render(token)[:-1])
|
|
590
|
+
|
|
591
|
+
s = Sentence(''.join(mdSpanList))
|
|
592
|
+
if len(emphasizedParts) > 0:
|
|
593
|
+
s = EmphasizedSentence(s,emphasizedParts)
|
|
594
|
+
if len(strongParts) > 0:
|
|
595
|
+
s = StrongSentence(s,strongParts)
|
|
596
|
+
return s
|
|
597
|
+
|
|
File without changes
|