html5lib-to-markdown 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,36 @@
1
+ from .transformer import Transformer
2
+
3
+ # ==============================================================================
4
+
5
+ __VERSION__ = "1.0.0"
6
+
7
+ # ------------------------------------------------------------------------------
8
+
9
+
10
+ def transform(text: str, *, escape_text: bool = True) -> str:
11
+ """Convert HTML to Markdown, escaping literal text by default."""
12
+ transformer = Transformer(
13
+ a_as_tag=False,
14
+ img_as_tag=False,
15
+ strip_comments=False,
16
+ reference_style_link=False,
17
+ reference_style_img=False,
18
+ div_as_block=True,
19
+ escape_text=escape_text,
20
+ )
21
+ return transformer.transform(text)
22
+
23
+
24
+ # ------------------------------------------------------------------------------
25
+
26
+
27
+ if __name__ == "__main__":
28
+ import sys
29
+
30
+ if len(sys.argv) == 2:
31
+ fname = sys.argv[1]
32
+ with open(fname, "r") as fp:
33
+ input = fp.read()
34
+ print("=" * 80)
35
+ print(transform(input))
36
+ print("=" * 80)
@@ -0,0 +1,53 @@
1
+ # ==============================================================================
2
+
3
+
4
+ MARKDOWN_TAGS_CORE = [
5
+ "div", # not core, but we treat it as such
6
+ "p",
7
+ "b",
8
+ "i",
9
+ "em",
10
+ "strong",
11
+ # lists
12
+ "ul",
13
+ "ol",
14
+ "li",
15
+ # quotes
16
+ "blockquote",
17
+ # headers
18
+ "h1",
19
+ "h2",
20
+ "h3",
21
+ "h4",
22
+ "h5",
23
+ "h6",
24
+ # misc
25
+ "code",
26
+ "hr",
27
+ "a",
28
+ "img",
29
+ "pre",
30
+ "code",
31
+ ]
32
+
33
+
34
+ MARKDOWN_TAGS_PASSTHROUGH = ["table", "tr", "td", "th", "thead", "tbody"]
35
+
36
+ MARKDOWN_TAGS_PASSTHROUGH_BLOCKS = ["table"]
37
+
38
+
39
+ MARKDOWN_TAGS_ATTRIBUTES = {
40
+ "a": ["href", "title"],
41
+ "img": ["src", "title", "alt", "height", "width"],
42
+ }
43
+
44
+
45
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
46
+
47
+
48
+ __all__ = (
49
+ "MARKDOWN_TAGS_CORE",
50
+ "MARKDOWN_TAGS_PASSTHROUGH",
51
+ "MARKDOWN_TAGS_PASSTHROUGH_BLOCKS",
52
+ "MARKDOWN_TAGS_ATTRIBUTES",
53
+ )
@@ -0,0 +1,440 @@
1
+ """
2
+ This file contains replacement html5lib Tokens.
3
+ Tokens are just Python dicts.
4
+
5
+ Note:
6
+ 1. they are implemented by functions, because we may need to alter their contents
7
+ 2. the private attribute `_md_type` stores their type for post-processing
8
+
9
+ Several custom keys are in each token:
10
+
11
+
12
+ * _md_type
13
+ - the Token type
14
+ - instead of storing as STR, store as INT via lookup table `mdTokenTypes`
15
+
16
+ * _md_prefix
17
+ - if present, the token is in a blockquote and should be prefixed with this string
18
+
19
+ * _md_bq
20
+ - if present, the token is in a blockquote of this level
21
+
22
+ * _md_cb
23
+ - if present, the token is in a codeblock
24
+
25
+
26
+ "Block Elements" should be wrapped in:
27
+
28
+ TokenStartBlockElement
29
+ BlockElement
30
+ TokenEndBlockElement
31
+
32
+ What is a BlockElement?
33
+
34
+ A Block Element is something that should be surrounded by two newlines in markdown
35
+ currently these tags are considered block elements:
36
+
37
+ p
38
+ div
39
+ hn(h1-h6)
40
+ hr
41
+ pre
42
+ code
43
+ script
44
+ blockquote
45
+
46
+ """
47
+
48
+ # stdlib
49
+ from collections import OrderedDict
50
+ import os
51
+
52
+ # local
53
+ from .utils import safe_title
54
+
55
+
56
+ # ==============================================================================
57
+
58
+
59
+ DEBUG_TOKENS = bool(int(os.getenv("MD_DEBUG_TOKENS", 0)))
60
+
61
+
62
+ # ------------------------------------------------------------------------------
63
+
64
+
65
+ # use INTs for faster comparison of our '_md_type'
66
+ # unless `export MD_DEBUG_TOKENS=1` is set
67
+ mdTokenTypes = {
68
+ "TokenAMarkdown": 1,
69
+ "TokenAMarkdownReference": 2,
70
+ "TokenAMarkdownSimple": 3,
71
+ "TokenBoldItalic": 4,
72
+ "TokenCharactersAdded": 5,
73
+ "TokenCharactersSplit": 6,
74
+ "TokenDebug": 7,
75
+ "TokenEmphasis": 8,
76
+ "TokenEndBlockElement": 9,
77
+ "TokenEndBlockquote": 10,
78
+ "TokenEndBlockNative": 11,
79
+ "TokenEndCode": 12,
80
+ "TokenHNStart": 13,
81
+ "TokenHR": 14,
82
+ "TokenImgMarkdown": 15,
83
+ "TokenLiStart": 16,
84
+ "TokenNewline": 17,
85
+ "TokenNewlineBR": 18,
86
+ "TokenNewlines": 19,
87
+ "TokenSpace": 20,
88
+ "TokenStartBlockElement": 21,
89
+ "TokenStartBlockquote": 22,
90
+ "TokenStartBlockNative": 23,
91
+ "TokenStartCode": 24,
92
+ "TokenStrong": 25,
93
+ "TokenTab": 26,
94
+ }
95
+ if DEBUG_TOKENS:
96
+ for k in list(mdTokenTypes.keys()):
97
+ mdTokenTypes[k] = k
98
+
99
+ # ==============================================================================
100
+
101
+
102
+ def TokenAEndTag(href):
103
+ return {"type": "EndTag", "name": "a"}
104
+
105
+
106
+ _LABEL_ESCAPES = str.maketrans({char: "\\" + char for char in "\\[]*_`"})
107
+ _DESTINATION_ESCAPES = str.maketrans(
108
+ {char: "\\" + char for char in "\\()"}
109
+ | {chr(code): f"%{code:02X}" for code in (*range(33), ord("<"), ord(">"), 127)}
110
+ )
111
+
112
+
113
+ def TokenAMarkdown(
114
+ href: str, _link_text: str, title: str | None = None, reference: int | None = None
115
+ ) -> dict[str, str | int]:
116
+ """Render an inline or reference-style link."""
117
+ label = _link_text.translate(_LABEL_ESCAPES)
118
+ if reference is not None:
119
+ data = f"[{label}][{reference}]"
120
+ else:
121
+ suffix = f' "{safe_title(title)}"' if title else ""
122
+ data = f"[{label}]({href.translate(_DESTINATION_ESCAPES)}{suffix})"
123
+ token = {
124
+ "type": "Characters",
125
+ "data": data,
126
+ "_md_type": mdTokenTypes["TokenAMarkdown"],
127
+ }
128
+ return token
129
+
130
+
131
+ def TokenAMarkdownReference(
132
+ href: str, reference: int, title: str | None = None
133
+ ) -> dict[str, str | int]:
134
+ """
135
+ [link text][1]
136
+ [1]: <https://example.com> "Title"
137
+
138
+ <a href="https://example.com" title="Title">link text</a>
139
+ """
140
+ suffix = f' "{safe_title(title)}"' if title else ""
141
+ data = f"[{reference}]: {href.translate(_DESTINATION_ESCAPES)}{suffix}"
142
+
143
+ token = {
144
+ "type": "Characters",
145
+ "data": data,
146
+ "_md_type": mdTokenTypes["TokenAMarkdownReference"],
147
+ }
148
+ return token
149
+
150
+
151
+ def TokenAMarkdownSimple(href):
152
+ """
153
+ <https://example.com>
154
+ """
155
+ token = {
156
+ "type": "EmptyTag",
157
+ "name": href,
158
+ "data": {},
159
+ "_md_type": mdTokenTypes["TokenAMarkdownSimple"],
160
+ }
161
+ return token
162
+
163
+
164
+ def TokenAStartTag(href):
165
+ return {
166
+ "type": "StartTag",
167
+ "name": "a",
168
+ "data": OrderedDict([((None, "href"), href)]),
169
+ }
170
+
171
+
172
+ def TokenBoldItalic(character="_"):
173
+ """
174
+ Bold & Italic (`<i><b>`, `<em><strong>`) text is rendered with three underscores or asterisks
175
+ """
176
+ assert character in ("_", "*")
177
+ return {
178
+ "type": "Characters",
179
+ "data": character * 3,
180
+ "_md_type": mdTokenTypes["TokenBoldItalic"],
181
+ }
182
+
183
+
184
+ def TokenCharactersAdded(data=""):
185
+ return {
186
+ "type": "Characters",
187
+ "data": data,
188
+ "_md_type": mdTokenTypes["TokenCharactersAdded"],
189
+ }
190
+
191
+
192
+ def TokenCharactersSplit(data=""):
193
+ return {
194
+ "type": "Characters",
195
+ "data": data,
196
+ "_md_type": mdTokenTypes["TokenCharactersSplit"],
197
+ }
198
+
199
+
200
+ def TokenDebug(_debug=None):
201
+ """used to place debug info; this will not render"""
202
+ return {
203
+ "type": "SpaceCharacters",
204
+ "data": "",
205
+ "_md_type": mdTokenTypes["TokenDebug"],
206
+ "_md_debug": _debug,
207
+ }
208
+
209
+
210
+ def TokenEmphasis(character="_"):
211
+ """
212
+ Italic (`<i>`, `<em>`) text is rendered with one asterisk or underscore
213
+ """
214
+ assert character in ("_", "*")
215
+ return {
216
+ "type": "Characters",
217
+ "data": character,
218
+ "_md_type": mdTokenTypes["TokenEmphasis"],
219
+ }
220
+
221
+
222
+ def TokenEndBlockElement(block):
223
+ """
224
+ `TokenEndBlockElement` is used to denote that we are ending a block element.
225
+ Under most circumstances, this token will not render anything.
226
+ """
227
+ return {
228
+ "type": "SpaceCharacters",
229
+ "data": "",
230
+ "_md_type": mdTokenTypes["TokenEndBlockElement"],
231
+ "_md_debug": block,
232
+ }
233
+
234
+
235
+ def TokenEndBlockquote(depth=1):
236
+ """
237
+ `TokenEndBlockquote` is used to denote that we are ending a blockquote.
238
+ This token will not render anything.
239
+ """
240
+ return {
241
+ "type": "SpaceCharacters",
242
+ "data": "",
243
+ "_md_type": mdTokenTypes["TokenEndBlockquote"],
244
+ "_md_depth": depth,
245
+ }
246
+
247
+
248
+ def TokenEndCode():
249
+ """
250
+ `TokenEndCode` is used to denote that we are ending a `code` tag.
251
+ The render type (inline, indented) is unknown
252
+ """
253
+ return {
254
+ "type": "SpaceCharacters",
255
+ "data": "",
256
+ "_md_type": mdTokenTypes["TokenEndCode"],
257
+ }
258
+
259
+
260
+ def TokenHNStart(h_num):
261
+ token = {
262
+ "type": "Characters",
263
+ "data": "\n%s " % ("#" * h_num),
264
+ "_md_hn": h_num,
265
+ "_md_type": mdTokenTypes["TokenHNStart"],
266
+ }
267
+ return token
268
+
269
+
270
+ def TokenHR():
271
+ return {
272
+ "type": "Characters",
273
+ "data": "\n---\n",
274
+ "_md_type": mdTokenTypes["TokenHR"],
275
+ }
276
+
277
+
278
+ def TokenImgMarkdown(
279
+ src: str,
280
+ alt: str | None = None,
281
+ title: str | None = None,
282
+ reference: int | None = None,
283
+ ) -> dict[str, str | int]:
284
+ """Render an inline or reference-style image."""
285
+ label = (alt or "Image").translate(_LABEL_ESCAPES)
286
+ if reference is not None:
287
+ data = f"![{label}][{reference}]"
288
+ else:
289
+ suffix = f' "{safe_title(title)}"' if title else ""
290
+ data = f"![{label}]({src.translate(_DESTINATION_ESCAPES)}{suffix})"
291
+ token = {
292
+ "type": "Characters",
293
+ "data": data,
294
+ "_md_type": mdTokenTypes["TokenImgMarkdown"],
295
+ }
296
+ return token
297
+
298
+
299
+ def TokenLiStart(depth=0, bullet="*"):
300
+ if bullet in ("*", "+", "-"):
301
+ pass
302
+ else:
303
+ if type(bullet) != int:
304
+ raise ValueError("invalid bullet")
305
+ bullet = "%s." % bullet
306
+ indent = depth - 1 if depth >= 1 else 0
307
+ token = {
308
+ "type": "Characters",
309
+ "data": "\n%s%s " % (" " * indent, bullet),
310
+ "_md_type": mdTokenTypes["TokenLiStart"],
311
+ "_md_list_depth": indent,
312
+ }
313
+ return token
314
+
315
+
316
+ def TokenNewline(blockquoted=None, codeblocked=None):
317
+ return {
318
+ "type": "SpaceCharacters",
319
+ "data": "\n",
320
+ "_md_type": mdTokenTypes["TokenNewline"],
321
+ "_md_bq": blockquoted,
322
+ "_md_cb": codeblocked,
323
+ }
324
+
325
+
326
+ def TokenNewlineBR():
327
+ return {
328
+ "type": "SpaceCharacters",
329
+ "data": "\n",
330
+ "_md_type": mdTokenTypes["TokenNewlineBR"],
331
+ }
332
+
333
+
334
+ def TokenNewlines(blockquoted=None, codeblocked=None):
335
+ return {
336
+ "type": "SpaceCharacters",
337
+ "data": "\n\n",
338
+ "_md_type": mdTokenTypes["TokenNewlines"],
339
+ "_md_bq": blockquoted,
340
+ "_md_cb": codeblocked,
341
+ }
342
+
343
+
344
+ def TokenSpace():
345
+ return {
346
+ "type": "SpaceCharacters",
347
+ "data": " ",
348
+ "_md_type": mdTokenTypes["TokenSpace"],
349
+ }
350
+
351
+
352
+ def TokenStartBlockElement(block):
353
+ """
354
+ `TokenStartBlockElement` is used to denote that we are starting a new block element.
355
+ Under most circumstances, this token will not render anything.
356
+ """
357
+ return {
358
+ "type": "SpaceCharacters",
359
+ "data": "",
360
+ "_md_type": mdTokenTypes["TokenStartBlockElement"],
361
+ "_md_block": block,
362
+ }
363
+
364
+
365
+ def TokenStartBlockquote(depth=1):
366
+ """
367
+ `TokenStartBlockquote` is used to denote that we are starting a new blockquote
368
+ Under most circumstances, this token will not render anything.
369
+ """
370
+ return {
371
+ "type": "SpaceCharacters",
372
+ "data": "",
373
+ "_md_type": mdTokenTypes["TokenStartBlockquote"],
374
+ "_md_depth": depth,
375
+ }
376
+
377
+
378
+ def TokenStartCode():
379
+ """
380
+ `TokenStartCode` is used to denote that we are starting a new `code` tag.
381
+ The render type (inline, indented) is unknown
382
+ """
383
+ return {
384
+ "type": "Characters",
385
+ "data": "",
386
+ "_md_type": mdTokenTypes["TokenStartCode"],
387
+ }
388
+
389
+
390
+ def TokenStrong(character="*"):
391
+ """
392
+ Bold (`<b>`, `<strong>`) text is rendered with two asterisks or underscores
393
+ """
394
+ assert character in ("*", "_")
395
+ return {
396
+ "type": "Characters",
397
+ "data": "*" * 2,
398
+ "_md_type": mdTokenTypes["TokenStrong"],
399
+ }
400
+
401
+
402
+ def TokenTab():
403
+ return {
404
+ "type": "SpaceCharacters",
405
+ "data": "\t",
406
+ "_md_type": mdTokenTypes["TokenTab"],
407
+ }
408
+
409
+
410
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
411
+
412
+
413
+ __all__ = (
414
+ "mdTokenTypes",
415
+ "TokenAEndTag",
416
+ "TokenAMarkdown",
417
+ "TokenAMarkdownReference",
418
+ "TokenAMarkdownSimple",
419
+ "TokenAStartTag",
420
+ "TokenCharactersAdded",
421
+ "TokenCharactersSplit",
422
+ "TokenDebug",
423
+ "TokenEmphasis",
424
+ "TokenEndBlockElement",
425
+ "TokenEndBlockquote",
426
+ "TokenEndCode",
427
+ "TokenHNStart",
428
+ "TokenHR",
429
+ "TokenImgMarkdown",
430
+ "TokenLiStart",
431
+ "TokenNewline",
432
+ "TokenNewlineBR",
433
+ "TokenNewlines",
434
+ "TokenSpace",
435
+ "TokenStartBlockElement",
436
+ "TokenStartBlockquote",
437
+ "TokenStartCode",
438
+ "TokenStrong",
439
+ "TokenTab",
440
+ )