html-to-markdown 1.1.0__py3-none-any.whl → 1.2.1__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.
Potentially problematic release.
This version of html-to-markdown might be problematic. Click here for more details.
- html_to_markdown/__init__.py +3 -1
- html_to_markdown/__main__.py +8 -4
- html_to_markdown/constants.py +8 -8
- html_to_markdown/converters.py +13 -16
- html_to_markdown/processing.py +35 -19
- html_to_markdown-1.2.1.dist-info/METADATA +220 -0
- html_to_markdown-1.2.1.dist-info/RECORD +12 -0
- {html_to_markdown-1.1.0.dist-info → html_to_markdown-1.2.1.dist-info}/WHEEL +1 -1
- {html_to_markdown-1.1.0.dist-info → html_to_markdown-1.2.1.dist-info}/licenses/LICENSE +1 -1
- html_to_markdown-1.1.0.dist-info/METADATA +0 -101
- html_to_markdown-1.1.0.dist-info/RECORD +0 -13
- html_to_markdown-1.1.0.dist-info/entry_points.txt +0 -2
html_to_markdown/__init__.py
CHANGED
html_to_markdown/__main__.py
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
|
|
3
|
-
from html_to_markdown.dli import cli
|
|
4
|
-
|
|
5
3
|
if __name__ == "__main__":
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
from html_to_markdown.cli import main
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
result = main(sys.argv[1:])
|
|
8
|
+
print(result) # noqa: T201
|
|
9
|
+
except ValueError as e:
|
|
10
|
+
print(str(e), file=sys.stderr) # noqa: T201
|
|
11
|
+
sys.exit(1)
|
html_to_markdown/constants.py
CHANGED
|
@@ -2,17 +2,17 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
from re import Pattern
|
|
5
|
-
from typing import Final
|
|
5
|
+
from typing import Final
|
|
6
6
|
|
|
7
7
|
convert_heading_re: Final[Pattern[str]] = re.compile(r"convert_h(\d+)")
|
|
8
8
|
line_beginning_re: Final[Pattern[str]] = re.compile(r"^", re.MULTILINE)
|
|
9
9
|
whitespace_re: Final[Pattern[str]] = re.compile(r"[\t ]+")
|
|
10
10
|
html_heading_re: Final[Pattern[str]] = re.compile(r"h[1-6]")
|
|
11
11
|
|
|
12
|
-
ASTERISK: Final
|
|
13
|
-
ATX: Final
|
|
14
|
-
ATX_CLOSED: Final
|
|
15
|
-
BACKSLASH: Final
|
|
16
|
-
UNDERLINED: Final
|
|
17
|
-
SPACES: Final
|
|
18
|
-
UNDERSCORE: Final
|
|
12
|
+
ASTERISK: Final = "*"
|
|
13
|
+
ATX: Final = "atx"
|
|
14
|
+
ATX_CLOSED: Final = "atx_closed"
|
|
15
|
+
BACKSLASH: Final = "backslash"
|
|
16
|
+
UNDERLINED: Final = "underlined"
|
|
17
|
+
SPACES: Final = "spaces"
|
|
18
|
+
UNDERSCORE: Final = "_"
|
html_to_markdown/converters.py
CHANGED
|
@@ -55,17 +55,19 @@ SupportedElements = Literal[
|
|
|
55
55
|
"kbd",
|
|
56
56
|
]
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
ConvertersMap = Mapping[SupportedElements, Callable[[str, Tag], str]]
|
|
59
59
|
|
|
60
60
|
T = TypeVar("T")
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
def _create_inline_converter(markup_prefix: str) -> Callable[[Tag, str], str]:
|
|
64
|
-
"""
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
"""Create an inline converter for a markup pattern or tag.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
markup_prefix: The markup prefix to insert.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
A function that can be used to convert HTML to Markdown.
|
|
69
71
|
"""
|
|
70
72
|
|
|
71
73
|
def implementation(*, tag: Tag, text: str) -> str:
|
|
@@ -83,7 +85,7 @@ def _create_inline_converter(markup_prefix: str) -> Callable[[Tag, str], str]:
|
|
|
83
85
|
|
|
84
86
|
return f"{prefix}{markup_prefix}{text}{markup_suffix}{suffix}"
|
|
85
87
|
|
|
86
|
-
return cast(Callable[[Tag, str], str], implementation)
|
|
88
|
+
return cast("Callable[[Tag, str], str]", implementation)
|
|
87
89
|
|
|
88
90
|
|
|
89
91
|
def _get_colspan(tag: Tag) -> int:
|
|
@@ -185,7 +187,7 @@ def _convert_li(*, tag: Tag, text: str, bullets: str) -> str:
|
|
|
185
187
|
parent = tag.parent
|
|
186
188
|
if parent is not None and parent.name == "ol":
|
|
187
189
|
start = (
|
|
188
|
-
int(cast(str, parent["start"]))
|
|
190
|
+
int(cast("str", parent["start"]))
|
|
189
191
|
if isinstance(parent.get("start"), str) and str(parent.get("start")).isnumeric()
|
|
190
192
|
else 1
|
|
191
193
|
)
|
|
@@ -261,7 +263,6 @@ def _convert_tr(*, tag: Tag, text: str) -> str:
|
|
|
261
263
|
overline = ""
|
|
262
264
|
underline = ""
|
|
263
265
|
if is_headrow and not tag.previous_sibling:
|
|
264
|
-
# first row and is headline: print headline underline
|
|
265
266
|
full_colspan = 0
|
|
266
267
|
for cell in cells:
|
|
267
268
|
if "colspan" in cell.attrs and cell["colspan"].isdigit():
|
|
@@ -270,12 +271,8 @@ def _convert_tr(*, tag: Tag, text: str) -> str:
|
|
|
270
271
|
full_colspan += 1
|
|
271
272
|
underline += "| " + " | ".join(["---"] * full_colspan) + " |" + "\n"
|
|
272
273
|
elif not tag.previous_sibling and (
|
|
273
|
-
parent_name == "table" or (parent_name == "tbody" and not cast(Tag, tag.parent).previous_sibling)
|
|
274
|
+
parent_name == "table" or (parent_name == "tbody" and not cast("Tag", tag.parent).previous_sibling)
|
|
274
275
|
):
|
|
275
|
-
# first row, not headline, and:
|
|
276
|
-
# - the parent is table or
|
|
277
|
-
# - the parent is tbody at the beginning of a table.
|
|
278
|
-
# print empty headline above this row
|
|
279
276
|
overline += "| " + " | ".join([""] * len(cells)) + " |" + "\n"
|
|
280
277
|
overline += "| " + " | ".join(["---"] * len(cells)) + " |" + "\n"
|
|
281
278
|
return overline + "|" + text + "\n" + underline
|
|
@@ -295,7 +292,7 @@ def create_converters_map(
|
|
|
295
292
|
sup_symbol: str,
|
|
296
293
|
wrap: bool,
|
|
297
294
|
wrap_width: int,
|
|
298
|
-
) ->
|
|
295
|
+
) -> ConvertersMap:
|
|
299
296
|
"""Create a mapping of HTML elements to their corresponding conversion functions.
|
|
300
297
|
|
|
301
298
|
Args:
|
|
@@ -332,7 +329,7 @@ def create_converters_map(
|
|
|
332
329
|
return func(**kwargs)
|
|
333
330
|
return func(text)
|
|
334
331
|
|
|
335
|
-
return cast(Callable[[str, Tag], T], _inner)
|
|
332
|
+
return cast("Callable[[str, Tag], T]", _inner)
|
|
336
333
|
|
|
337
334
|
return {
|
|
338
335
|
"a": _wrapper(partial(_convert_a, autolinks=autolinks, default_title=default_title)),
|
html_to_markdown/processing.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from itertools import chain
|
|
3
4
|
from typing import TYPE_CHECKING, Any, Callable, Literal, cast
|
|
4
5
|
|
|
5
6
|
from bs4 import BeautifulSoup, Comment, Doctype, NavigableString, Tag
|
|
@@ -11,7 +12,7 @@ from html_to_markdown.constants import (
|
|
|
11
12
|
html_heading_re,
|
|
12
13
|
whitespace_re,
|
|
13
14
|
)
|
|
14
|
-
from html_to_markdown.converters import
|
|
15
|
+
from html_to_markdown.converters import ConvertersMap, create_converters_map
|
|
15
16
|
from html_to_markdown.utils import escape
|
|
16
17
|
|
|
17
18
|
if TYPE_CHECKING:
|
|
@@ -76,18 +77,23 @@ def _is_nested_tag(el: PageElement) -> bool:
|
|
|
76
77
|
|
|
77
78
|
def _process_tag(
|
|
78
79
|
tag: Tag,
|
|
79
|
-
converters_map:
|
|
80
|
+
converters_map: ConvertersMap,
|
|
80
81
|
*,
|
|
81
|
-
convert:
|
|
82
|
+
convert: set[str] | None,
|
|
82
83
|
convert_as_inline: bool = False,
|
|
83
84
|
escape_asterisks: bool,
|
|
84
85
|
escape_misc: bool,
|
|
85
86
|
escape_underscores: bool,
|
|
86
|
-
strip:
|
|
87
|
+
strip: set[str] | None,
|
|
87
88
|
) -> str:
|
|
89
|
+
should_convert_tag = _should_convert_tag(tag_name=tag.name, strip=strip, convert=convert)
|
|
90
|
+
tag_name: SupportedTag | None = (
|
|
91
|
+
cast("SupportedTag", tag.name.lower()) if tag.name.lower() in converters_map else None
|
|
92
|
+
)
|
|
88
93
|
text = ""
|
|
94
|
+
|
|
89
95
|
is_heading = html_heading_re.match(tag.name) is not None
|
|
90
|
-
is_cell =
|
|
96
|
+
is_cell = tag_name in {"td", "th"}
|
|
91
97
|
convert_children_as_inline = convert_as_inline or is_heading or is_cell
|
|
92
98
|
|
|
93
99
|
if _is_nested_tag(tag):
|
|
@@ -121,9 +127,7 @@ def _process_tag(
|
|
|
121
127
|
strip=strip,
|
|
122
128
|
)
|
|
123
129
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if tag_name and _should_convert_tag(tag_name=tag.name, strip=strip, convert=convert):
|
|
130
|
+
if tag_name and should_convert_tag:
|
|
127
131
|
return converters_map[tag_name]( # type: ignore[call-arg]
|
|
128
132
|
tag=tag, text=text, convert_as_inline=convert_as_inline
|
|
129
133
|
)
|
|
@@ -140,11 +144,9 @@ def _process_text(
|
|
|
140
144
|
) -> str:
|
|
141
145
|
text = str(el) or ""
|
|
142
146
|
|
|
143
|
-
# normalize whitespace if we're not inside a preformatted element
|
|
144
147
|
if not el.find_parent("pre"):
|
|
145
148
|
text = whitespace_re.sub(" ", text)
|
|
146
149
|
|
|
147
|
-
# escape special characters if we're not inside a preformatted or code element
|
|
148
150
|
if not el.find_parent(["pre", "code", "kbd", "samp"]):
|
|
149
151
|
text = escape(
|
|
150
152
|
text=text,
|
|
@@ -153,9 +155,6 @@ def _process_text(
|
|
|
153
155
|
escape_underscores=escape_underscores,
|
|
154
156
|
)
|
|
155
157
|
|
|
156
|
-
# remove trailing whitespaces if any of the following condition is true:
|
|
157
|
-
# - current text node is the last node in li
|
|
158
|
-
# - current text node is followed by an embedded list
|
|
159
158
|
if (
|
|
160
159
|
el.parent
|
|
161
160
|
and el.parent.name == "li"
|
|
@@ -166,7 +165,7 @@ def _process_text(
|
|
|
166
165
|
return text
|
|
167
166
|
|
|
168
167
|
|
|
169
|
-
def _should_convert_tag(*, tag_name: str, strip:
|
|
168
|
+
def _should_convert_tag(*, tag_name: str, strip: set[str] | None, convert: set[str] | None) -> bool:
|
|
170
169
|
if strip is not None:
|
|
171
170
|
return tag_name not in strip
|
|
172
171
|
if convert is not None:
|
|
@@ -174,6 +173,14 @@ def _should_convert_tag(*, tag_name: str, strip: Iterable[str] | None, convert:
|
|
|
174
173
|
return True
|
|
175
174
|
|
|
176
175
|
|
|
176
|
+
def _as_optional_set(value: str | Iterable[str] | None) -> set[str] | None:
|
|
177
|
+
if value is None:
|
|
178
|
+
return None
|
|
179
|
+
if isinstance(value, str):
|
|
180
|
+
return set(",".split(value))
|
|
181
|
+
return {*chain(*[v.split(",") for v in value])}
|
|
182
|
+
|
|
183
|
+
|
|
177
184
|
def convert_to_markdown(
|
|
178
185
|
source: str | BeautifulSoup,
|
|
179
186
|
*,
|
|
@@ -181,7 +188,7 @@ def convert_to_markdown(
|
|
|
181
188
|
bullets: str = "*+-",
|
|
182
189
|
code_language: str = "",
|
|
183
190
|
code_language_callback: Callable[[Any], str] | None = None,
|
|
184
|
-
convert: Iterable[str] | None = None,
|
|
191
|
+
convert: str | Iterable[str] | None = None,
|
|
185
192
|
default_title: bool = False,
|
|
186
193
|
escape_asterisks: bool = True,
|
|
187
194
|
escape_misc: bool = True,
|
|
@@ -189,7 +196,7 @@ def convert_to_markdown(
|
|
|
189
196
|
heading_style: Literal["underlined", "atx", "atx_closed"] = UNDERLINED,
|
|
190
197
|
keep_inline_images_in: Iterable[str] | None = None,
|
|
191
198
|
newline_style: Literal["spaces", "backslash"] = SPACES,
|
|
192
|
-
strip: Iterable[str] | None = None,
|
|
199
|
+
strip: str | Iterable[str] | None = None,
|
|
193
200
|
strong_em_symbol: Literal["*", "_"] = ASTERISK,
|
|
194
201
|
sub_symbol: str = "",
|
|
195
202
|
sup_symbol: str = "",
|
|
@@ -221,13 +228,22 @@ def convert_to_markdown(
|
|
|
221
228
|
wrap_width: The number of characters at which to wrap text. Defaults to 80.
|
|
222
229
|
convert_as_inline: Treat the content as inline elements (no block elements like paragraphs). Defaults to False.
|
|
223
230
|
|
|
231
|
+
Raises:
|
|
232
|
+
ValueError: If both 'strip' and 'convert' are specified, or when the input HTML is empty.
|
|
233
|
+
|
|
224
234
|
Returns:
|
|
225
235
|
str: A string of Markdown-formatted text converted from the given HTML.
|
|
226
236
|
"""
|
|
227
237
|
if isinstance(source, str):
|
|
228
238
|
from bs4 import BeautifulSoup
|
|
229
239
|
|
|
230
|
-
|
|
240
|
+
if "".join(source.split("\n")):
|
|
241
|
+
source = BeautifulSoup(source, "html.parser")
|
|
242
|
+
else:
|
|
243
|
+
raise ValueError("The input HTML is empty.")
|
|
244
|
+
|
|
245
|
+
if strip is not None and convert is not None:
|
|
246
|
+
raise ValueError("Only one of 'strip' and 'convert' can be specified.")
|
|
231
247
|
|
|
232
248
|
converters_map = create_converters_map(
|
|
233
249
|
autolinks=autolinks,
|
|
@@ -248,10 +264,10 @@ def convert_to_markdown(
|
|
|
248
264
|
return _process_tag(
|
|
249
265
|
source,
|
|
250
266
|
converters_map,
|
|
251
|
-
convert=convert,
|
|
267
|
+
convert=_as_optional_set(convert),
|
|
252
268
|
convert_as_inline=convert_as_inline,
|
|
253
269
|
escape_asterisks=escape_asterisks,
|
|
254
270
|
escape_misc=escape_misc,
|
|
255
271
|
escape_underscores=escape_underscores,
|
|
256
|
-
strip=strip,
|
|
272
|
+
strip=_as_optional_set(strip),
|
|
257
273
|
)
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: html-to-markdown
|
|
3
|
+
Version: 1.2.1
|
|
4
|
+
Summary: Convert HTML to markdown
|
|
5
|
+
Project-URL: homepage, https://github.com/Goldziher/html-to-markdown
|
|
6
|
+
Author-email: Na'aman Hirschfeld <nhirschfeld@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: converter,html,markdown,text-extraction,text-processing
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Text Processing
|
|
20
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
21
|
+
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
22
|
+
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
23
|
+
Classifier: Topic :: Utilities
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Requires-Dist: beautifulsoup4>=4.12.3
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# html-to-markdown
|
|
30
|
+
|
|
31
|
+
A modern, fully typed Python library for converting HTML to Markdown. This library is a completely rewritten fork
|
|
32
|
+
of [markdownify](https://pypi.org/project/markdownify/) with a modernized codebase, strict type safety and support for
|
|
33
|
+
Python 3.9+.
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- Full type safety with strict MyPy adherence
|
|
38
|
+
- Functional API design
|
|
39
|
+
- Extensive test coverage
|
|
40
|
+
- Configurable conversion options
|
|
41
|
+
- CLI tool for easy conversions
|
|
42
|
+
- Support for pre-configured BeautifulSoup instances
|
|
43
|
+
- Strict semver versioning
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```shell
|
|
48
|
+
pip install html-to-markdown
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
Convert HTML to Markdown with a single function call:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from html_to_markdown import convert_to_markdown
|
|
57
|
+
|
|
58
|
+
html = """
|
|
59
|
+
<article>
|
|
60
|
+
<h1>Welcome</h1>
|
|
61
|
+
<p>This is a <strong>sample</strong> with a <a href="https://example.com">link</a>.</p>
|
|
62
|
+
<ul>
|
|
63
|
+
<li>Item 1</li>
|
|
64
|
+
<li>Item 2</li>
|
|
65
|
+
</ul>
|
|
66
|
+
</article>
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
markdown = convert_to_markdown(html)
|
|
70
|
+
print(markdown)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Output:
|
|
74
|
+
|
|
75
|
+
```markdown
|
|
76
|
+
# Welcome
|
|
77
|
+
|
|
78
|
+
This is a **sample** with a [link](https://example.com).
|
|
79
|
+
|
|
80
|
+
* Item 1
|
|
81
|
+
* Item 2
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Working with BeautifulSoup
|
|
85
|
+
|
|
86
|
+
If you need more control over HTML parsing, you can pass a pre-configured BeautifulSoup instance:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from bs4 import BeautifulSoup
|
|
90
|
+
from html_to_markdown import convert_to_markdown
|
|
91
|
+
|
|
92
|
+
# Configure BeautifulSoup with your preferred parser
|
|
93
|
+
soup = BeautifulSoup(html, "lxml") # Note: lxml requires additional installation
|
|
94
|
+
markdown = convert_to_markdown(soup)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Advanced Usage
|
|
98
|
+
|
|
99
|
+
### Customizing Conversion Options
|
|
100
|
+
|
|
101
|
+
The library offers extensive customization through various options:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from html_to_markdown import convert_to_markdown
|
|
105
|
+
|
|
106
|
+
html = "<div>Your content here...</div>"
|
|
107
|
+
markdown = convert_to_markdown(
|
|
108
|
+
html,
|
|
109
|
+
heading_style="atx", # Use # style headers
|
|
110
|
+
strong_em_symbol="*", # Use * for bold/italic
|
|
111
|
+
bullets="*+-", # Define bullet point characters
|
|
112
|
+
wrap=True, # Enable text wrapping
|
|
113
|
+
wrap_width=100, # Set wrap width
|
|
114
|
+
escape_asterisks=True, # Escape * characters
|
|
115
|
+
code_language="python", # Default code block language
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Configuration Options
|
|
120
|
+
|
|
121
|
+
| Option | Type | Default | Description |
|
|
122
|
+
| -------------------- | ---- | -------------- | ------------------------------------------------------ |
|
|
123
|
+
| `autolinks` | bool | `True` | Auto-convert URLs to Markdown links |
|
|
124
|
+
| `bullets` | str | `'*+-'` | Characters to use for bullet points |
|
|
125
|
+
| `code_language` | str | `''` | Default language for code blocks |
|
|
126
|
+
| `heading_style` | str | `'underlined'` | Header style (`'underlined'`, `'atx'`, `'atx_closed'`) |
|
|
127
|
+
| `escape_asterisks` | bool | `True` | Escape * characters |
|
|
128
|
+
| `escape_underscores` | bool | `True` | Escape _ characters |
|
|
129
|
+
| `wrap` | bool | `False` | Enable text wrapping |
|
|
130
|
+
| `wrap_width` | int | `80` | Text wrap width |
|
|
131
|
+
|
|
132
|
+
For a complete list of options, see the [Configuration](#configuration) section below.
|
|
133
|
+
|
|
134
|
+
## CLI Usage
|
|
135
|
+
|
|
136
|
+
Convert HTML files directly from the command line:
|
|
137
|
+
|
|
138
|
+
```shell
|
|
139
|
+
# Convert a file
|
|
140
|
+
html_to_markdown input.html > output.md
|
|
141
|
+
|
|
142
|
+
# Process stdin
|
|
143
|
+
cat input.html | html_to_markdown > output.md
|
|
144
|
+
|
|
145
|
+
# Use custom options
|
|
146
|
+
html_to_markdown --heading-style atx --wrap --wrap-width 100 input.html > output.md
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
View all available options:
|
|
150
|
+
|
|
151
|
+
```shell
|
|
152
|
+
html_to_markdown --help
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Migration from Markdownify
|
|
156
|
+
|
|
157
|
+
For existing projects using Markdownify, a compatibility layer is provided:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
# Old code
|
|
161
|
+
from markdownify import markdownify as md
|
|
162
|
+
|
|
163
|
+
# New code - works the same way
|
|
164
|
+
from html_to_markdown import markdownify as md
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The `markdownify` function is an alias for `convert_to_markdown` and provides identical functionality.
|
|
168
|
+
|
|
169
|
+
## Configuration
|
|
170
|
+
|
|
171
|
+
Full list of configuration options:
|
|
172
|
+
|
|
173
|
+
- `autolinks`: Convert valid URLs to Markdown links automatically
|
|
174
|
+
- `bullets`: Characters to use for bullet points in lists
|
|
175
|
+
- `code_language`: Default language for fenced code blocks
|
|
176
|
+
- `code_language_callback`: Function to determine code block language
|
|
177
|
+
- `convert`: List of HTML tags to convert (None = all supported tags)
|
|
178
|
+
- `default_title`: Use default titles for elements like links
|
|
179
|
+
- `escape_asterisks`: Escape * characters
|
|
180
|
+
- `escape_misc`: Escape miscellaneous Markdown characters
|
|
181
|
+
- `escape_underscores`: Escape _ characters
|
|
182
|
+
- `heading_style`: Header style (underlined/atx/atx_closed)
|
|
183
|
+
- `keep_inline_images_in`: Tags where inline images should be kept
|
|
184
|
+
- `newline_style`: Style for handling newlines (spaces/backslash)
|
|
185
|
+
- `strip`: Tags to remove from output
|
|
186
|
+
- `strong_em_symbol`: Symbol for strong/emphasized text (\* or \_)
|
|
187
|
+
- `sub_symbol`: Symbol for subscript text
|
|
188
|
+
- `sup_symbol`: Symbol for superscript text
|
|
189
|
+
- `wrap`: Enable text wrapping
|
|
190
|
+
- `wrap_width`: Width for text wrapping
|
|
191
|
+
- `convert_as_inline`: Treat content as inline elements
|
|
192
|
+
|
|
193
|
+
## Contribution
|
|
194
|
+
|
|
195
|
+
This library is open to contribution. Feel free to open issues or submit PRs. Its better to discuss issues before
|
|
196
|
+
submitting PRs to avoid disappointment.
|
|
197
|
+
|
|
198
|
+
### Local Development
|
|
199
|
+
|
|
200
|
+
1. Clone the repo
|
|
201
|
+
|
|
202
|
+
1. Install the system dependencies
|
|
203
|
+
|
|
204
|
+
1. Install the full dependencies with `uv sync`
|
|
205
|
+
|
|
206
|
+
1. Install the pre-commit hooks with:
|
|
207
|
+
|
|
208
|
+
```shell
|
|
209
|
+
pre-commit install && pre-commit install --hook-type commit-msg
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
1. Make your changes and submit a PR
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
This library uses the MIT license.
|
|
217
|
+
|
|
218
|
+
## Acknowledgments
|
|
219
|
+
|
|
220
|
+
Special thanks to the original [markdownify](https://pypi.org/project/markdownify/) project creators and contributors.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
html_to_markdown/__init__.py,sha256=95S7_7mR_g88uTnFI0FaRNykrtAaSKb6sJbwSea2zjk,145
|
|
2
|
+
html_to_markdown/__main__.py,sha256=u5xevySlT5eIGyLUaethdDQIKJygaKnc3F2sHWoz75g,264
|
|
3
|
+
html_to_markdown/cli.py,sha256=HVnzmcyrYwah_yWhZ87mZcG0VgnKYp6y89fJh2R-Rlw,4532
|
|
4
|
+
html_to_markdown/constants.py,sha256=Usk67k18tuRovJpKDsiEXdgH20KgqI9KOnK4Fbx-M5c,547
|
|
5
|
+
html_to_markdown/converters.py,sha256=W6Dq2PAwVe5nxE3LSaeO8_hm0eWzSBlRLxf0ryasL6Q,11844
|
|
6
|
+
html_to_markdown/processing.py,sha256=nh_Or-4faI_qh6gF8-xY2qNiqX4eH-jCnBnFpHJbc2M,8632
|
|
7
|
+
html_to_markdown/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
html_to_markdown/utils.py,sha256=HJUDej5HSpXRtYv-OkCyD0hwnPnVfQCwY6rBRlIOt9s,1989
|
|
9
|
+
html_to_markdown-1.2.1.dist-info/METADATA,sha256=-raxzt9vDtzHOOsR0nkbQN-r80V5gRFfeHjDOLWrDwk,6902
|
|
10
|
+
html_to_markdown-1.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
+
html_to_markdown-1.2.1.dist-info/licenses/LICENSE,sha256=3J_HR5BWvUM1mlIrlkF32-uC1FM64gy8JfG17LBuheQ,1122
|
|
12
|
+
html_to_markdown-1.2.1.dist-info/RECORD,,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
3
|
Copyright 2012-2018 Matthew Tretter
|
|
4
|
-
Copyright 2024 Na'aman Hirschfeld
|
|
4
|
+
Copyright 2024-2025 Na'aman Hirschfeld
|
|
5
5
|
|
|
6
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.3
|
|
2
|
-
Name: html-to-markdown
|
|
3
|
-
Version: 1.1.0
|
|
4
|
-
Summary: Convert HTML to markdown
|
|
5
|
-
Author-email: Na'aman Hirschfeld <nhirschfeld@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
License-File: LICENSE
|
|
8
|
-
Keywords: beautifulsoup,converter,html,markdown,text-processing
|
|
9
|
-
Classifier: Intended Audience :: Developers
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Operating System :: OS Independent
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
-
Classifier: Topic :: Text Processing
|
|
18
|
-
Classifier: Topic :: Text Processing :: Markup
|
|
19
|
-
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
20
|
-
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
21
|
-
Classifier: Topic :: Utilities
|
|
22
|
-
Classifier: Typing :: Typed
|
|
23
|
-
Requires-Python: >=3.9
|
|
24
|
-
Requires-Dist: beautifulsoup4>=4.12.3
|
|
25
|
-
Description-Content-Type: text/markdown
|
|
26
|
-
|
|
27
|
-
# html_to_markdown
|
|
28
|
-
|
|
29
|
-
This library is a refactored and modernized fork of [markdownify](https://pypi.org/project/markdownify/), supporting
|
|
30
|
-
Python 3.9 and above.
|
|
31
|
-
|
|
32
|
-
### Differences with the Markdownify
|
|
33
|
-
|
|
34
|
-
- The refactored codebase uses a strict functional approach - no classes are involved.
|
|
35
|
-
- There is full typing with strict MyPy strict adherence and a py.typed file included.
|
|
36
|
-
- The `convert_to_markdown` function allows passing a pre-configured instance of `BeautifulSoup` instead of html.
|
|
37
|
-
- This library releases follows standard semver. Its version v1.0.0 was branched from markdownify's v0.13.1, at which
|
|
38
|
-
point versioning is no longer aligned.
|
|
39
|
-
|
|
40
|
-
## Installation
|
|
41
|
-
|
|
42
|
-
```shell
|
|
43
|
-
pip install html_to_markdown
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Usage
|
|
47
|
-
|
|
48
|
-
Convert an string HTML to Markdown:
|
|
49
|
-
|
|
50
|
-
```python
|
|
51
|
-
from html_to_markdown import convert_to_markdown
|
|
52
|
-
|
|
53
|
-
convert_to_markdown('<b>Yay</b> <a href="http://github.com">GitHub</a>') # > '**Yay** [GitHub](http://github.com)'
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Or pass a pre-configured instance of `BeautifulSoup`:
|
|
57
|
-
|
|
58
|
-
```python
|
|
59
|
-
from bs4 import BeautifulSoup
|
|
60
|
-
from html_to_markdown import convert_to_markdown
|
|
61
|
-
|
|
62
|
-
soup = BeautifulSoup('<b>Yay</b> <a href="http://github.com">GitHub</a>', 'lxml') # lxml requires an extra dependency.
|
|
63
|
-
|
|
64
|
-
convert_to_markdown(soup) # > '**Yay** [GitHub](http://github.com)'
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### Options
|
|
68
|
-
|
|
69
|
-
The `convert_to_markdown` function accepts the following kwargs:
|
|
70
|
-
|
|
71
|
-
- autolinks (bool): Automatically convert valid URLs into Markdown links. Defaults to True.
|
|
72
|
-
- bullets (str): A string of characters to use for bullet points in lists. Defaults to '*+-'.
|
|
73
|
-
- code_language (str): Default language identifier for fenced code blocks. Defaults to an empty string.
|
|
74
|
-
- code_language_callback (Callable[[Any], str] | None): Function to dynamically determine the language for code blocks.
|
|
75
|
-
- convert (Iterable[str] | None): A list of tag names to convert to Markdown. If None, all supported tags are converted.
|
|
76
|
-
- default_title (bool): Use the default title when converting certain elements (e.g., links). Defaults to False.
|
|
77
|
-
- escape_asterisks (bool): Escape asterisks (*) to prevent unintended Markdown formatting. Defaults to True.
|
|
78
|
-
- escape_misc (bool): Escape miscellaneous characters to prevent conflicts in Markdown. Defaults to True.
|
|
79
|
-
- escape_underscores (bool): Escape underscores (_) to prevent unintended italic formatting. Defaults to True.
|
|
80
|
-
- heading_style (Literal["underlined", "atx", "atx_closed"]): The style to use for Markdown headings. Defaults to "
|
|
81
|
-
underlined".
|
|
82
|
-
- keep_inline_images_in (Iterable[str] | None): Tags in which inline images should be preserved. Defaults to None.
|
|
83
|
-
- newline_style (Literal["spaces", "backslash"]): Style for handling newlines in text content. Defaults to "spaces".
|
|
84
|
-
- strip (Iterable[str] | None): Tags to strip from the output. Defaults to None.
|
|
85
|
-
- strong_em_symbol (Literal["*", "_"]): Symbol to use for strong/emphasized text. Defaults to "*".
|
|
86
|
-
- sub_symbol (str): Custom symbol for subscript text. Defaults to an empty string.
|
|
87
|
-
- sup_symbol (str): Custom symbol for superscript text. Defaults to an empty string.
|
|
88
|
-
- wrap (bool): Wrap text to the specified width. Defaults to False.
|
|
89
|
-
- wrap_width (int): The number of characters at which to wrap text. Defaults to 80.
|
|
90
|
-
- convert_as_inline (bool): Treat the content as inline elements (no block elements like paragraphs). Defaults to False.
|
|
91
|
-
|
|
92
|
-
## CLI
|
|
93
|
-
|
|
94
|
-
For compatibility with the original markdownify, a CLI is provided. Use `html_to_markdown example.html > example.md` or
|
|
95
|
-
pipe input from stdin:
|
|
96
|
-
|
|
97
|
-
```shell
|
|
98
|
-
cat example.html | html_to_markdown > example.md
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Use `html_to_markdown -h` to see all available options. They are the same as listed above and take the same arguments.
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
html_to_markdown/__init__.py,sha256=_WXeqic-7b6hvidTXkPQwAfLa4YOEAEP-mOUXjx_25k,95
|
|
2
|
-
html_to_markdown/__main__.py,sha256=wFMASncZdH8lvhswugwYhqB7kmOUZc0wDyk0IwxqymI,140
|
|
3
|
-
html_to_markdown/cli.py,sha256=HVnzmcyrYwah_yWhZ87mZcG0VgnKYp6y89fJh2R-Rlw,4532
|
|
4
|
-
html_to_markdown/constants.py,sha256=vUjffZ0vFq56jbXF5bBNzomfJwgsp0TWqdUzhkp6bks,687
|
|
5
|
-
html_to_markdown/converters.py,sha256=Dwl0FTqSqxX7x9ee-a2rkeFVv6NYSnZ0q5-Rpu382VM,12220
|
|
6
|
-
html_to_markdown/processing.py,sha256=95lrmCQbegYrZ51XIigdB42R_ZGE_wy59X6t6Ls7R78,8256
|
|
7
|
-
html_to_markdown/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
html_to_markdown/utils.py,sha256=HJUDej5HSpXRtYv-OkCyD0hwnPnVfQCwY6rBRlIOt9s,1989
|
|
9
|
-
html_to_markdown-1.1.0.dist-info/METADATA,sha256=_OpPlJyt1nRej7lS2o2GebVTJNMxL97w8ldZDP91LQ8,4647
|
|
10
|
-
html_to_markdown-1.1.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
11
|
-
html_to_markdown-1.1.0.dist-info/entry_points.txt,sha256=jhMqXDYvIyzQDLKjCn4xCyzCCbAMl94tzQx_HiG5Qi0,67
|
|
12
|
-
html_to_markdown-1.1.0.dist-info/licenses/LICENSE,sha256=06BS7zd6oPCrbzAqrThGFboRlbssgBsqDJGqKyZW2Og,1117
|
|
13
|
-
html_to_markdown-1.1.0.dist-info/RECORD,,
|