markitdown-gc 0.2.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.
Files changed (53) hide show
  1. markitdown/GUIDE.md +93 -0
  2. markitdown/__about__.py +4 -0
  3. markitdown/__init__.py +34 -0
  4. markitdown/__main__.py +439 -0
  5. markitdown/_base_converter.py +109 -0
  6. markitdown/_exceptions.py +82 -0
  7. markitdown/_hwp_media.py +132 -0
  8. markitdown/_markitdown.py +861 -0
  9. markitdown/_media.py +67 -0
  10. markitdown/_stream_info.py +32 -0
  11. markitdown/_uri_utils.py +75 -0
  12. markitdown/converter_utils/__init__.py +0 -0
  13. markitdown/converter_utils/_image.py +19 -0
  14. markitdown/converter_utils/_xlsx_images.py +114 -0
  15. markitdown/converter_utils/docx/__init__.py +0 -0
  16. markitdown/converter_utils/docx/_images.py +118 -0
  17. markitdown/converter_utils/docx/math/__init__.py +0 -0
  18. markitdown/converter_utils/docx/math/latex_dict.py +291 -0
  19. markitdown/converter_utils/docx/math/omml.py +417 -0
  20. markitdown/converter_utils/docx/pre_process.py +290 -0
  21. markitdown/converters/__init__.py +56 -0
  22. markitdown/converters/_audio_converter.py +106 -0
  23. markitdown/converters/_bing_serp_converter.py +120 -0
  24. markitdown/converters/_csv_converter.py +133 -0
  25. markitdown/converters/_cu_converter.py +570 -0
  26. markitdown/converters/_doc_intel_converter.py +258 -0
  27. markitdown/converters/_docx_converter.py +163 -0
  28. markitdown/converters/_epub_converter.py +185 -0
  29. markitdown/converters/_exiftool.py +57 -0
  30. markitdown/converters/_html_converter.py +112 -0
  31. markitdown/converters/_hwp_converter.py +105 -0
  32. markitdown/converters/_image_converter.py +139 -0
  33. markitdown/converters/_ipynb_converter.py +103 -0
  34. markitdown/converters/_llm_caption.py +50 -0
  35. markitdown/converters/_markdownify.py +178 -0
  36. markitdown/converters/_outlook_msg_converter.py +349 -0
  37. markitdown/converters/_pdf_converter.py +593 -0
  38. markitdown/converters/_plain_text_converter.py +66 -0
  39. markitdown/converters/_pptx_converter.py +387 -0
  40. markitdown/converters/_rss_converter.py +488 -0
  41. markitdown/converters/_transcribe_audio.py +60 -0
  42. markitdown/converters/_wikipedia_converter.py +99 -0
  43. markitdown/converters/_xlsx_converter.py +279 -0
  44. markitdown/converters/_youtube_converter.py +256 -0
  45. markitdown/converters/_zip_converter.py +130 -0
  46. markitdown/ocr_triage.py +943 -0
  47. markitdown/py.typed +0 -0
  48. markitdown_gc-0.2.0.dist-info/METADATA +137 -0
  49. markitdown_gc-0.2.0.dist-info/RECORD +53 -0
  50. markitdown_gc-0.2.0.dist-info/WHEEL +4 -0
  51. markitdown_gc-0.2.0.dist-info/entry_points.txt +2 -0
  52. markitdown_gc-0.2.0.dist-info/licenses/LICENSE +21 -0
  53. markitdown_gc-0.2.0.dist-info/licenses/ThirdPartyNotices.md +232 -0
@@ -0,0 +1,82 @@
1
+ from typing import Optional, List, Any
2
+
3
+ MISSING_DEPENDENCY_MESSAGE = """{converter} recognized the input as a potential {extension} file, but the dependencies needed to read {extension} files have not been installed. To resolve this error, include the optional dependency [{feature}] or [all] when installing MarkItDown. For example:
4
+
5
+ * pip install 'markitdown-gc[{feature}]'
6
+ * pip install 'markitdown-gc[all]'
7
+ * pip install 'markitdown-gc[{feature}, ...]'
8
+ * etc."""
9
+
10
+
11
+ class MarkItDownException(Exception):
12
+ """
13
+ Base exception class for MarkItDown.
14
+ """
15
+
16
+ pass
17
+
18
+
19
+ class MissingDependencyException(MarkItDownException):
20
+ """
21
+ Converters shipped with MarkItDown may depend on optional
22
+ dependencies. This exception is thrown when a converter's
23
+ convert() method is called, but the required dependency is not
24
+ installed. This is not necessarily a fatal error, as the converter
25
+ will simply be skipped (an error will bubble up only if no other
26
+ suitable converter is found).
27
+
28
+ Error messages should clearly indicate which dependency is missing.
29
+ """
30
+
31
+ pass
32
+
33
+
34
+ class UnsupportedFormatException(MarkItDownException):
35
+ """
36
+ Thrown when no suitable converter was found for the given file.
37
+ """
38
+
39
+ pass
40
+
41
+
42
+ class FailedConversionAttempt(object):
43
+ """
44
+ Represents a single attempt to convert a file.
45
+ """
46
+
47
+ def __init__(self, converter: Any, exc_info: Optional[tuple] = None):
48
+ self.converter = converter
49
+ self.exc_info = exc_info
50
+
51
+
52
+ class FileConversionException(MarkItDownException):
53
+ """
54
+ Thrown when a suitable converter was found, but the conversion
55
+ process fails for any reason.
56
+ """
57
+
58
+ def __init__(
59
+ self,
60
+ message: Optional[str] = None,
61
+ attempts: Optional[List[FailedConversionAttempt]] = None,
62
+ ):
63
+ self.attempts = attempts
64
+
65
+ if message is None:
66
+ if attempts is None:
67
+ message = "File conversion failed."
68
+ else:
69
+ message = f"File conversion failed after {len(attempts)} attempts:\n"
70
+ for attempt in attempts:
71
+ if attempt.exc_info is None:
72
+ message += f" - {type(attempt.converter).__name__} provided no execution info."
73
+ else:
74
+ message += f" - {type(attempt.converter).__name__} threw {attempt.exc_info[0].__name__} with message: {attempt.exc_info[1]}\n"
75
+
76
+ if isinstance(attempt.exc_info[1], UnicodeDecodeError):
77
+ message += (
78
+ "The charset may have been automatically guessed incorrectly. "
79
+ "If you didn't specify the charset, please retry with an explicitly defined one.\n"
80
+ )
81
+
82
+ super().__init__(message)
@@ -0,0 +1,132 @@
1
+ """Borrow the HWP parser with guaranteed temporary-file cleanup."""
2
+
3
+ import io
4
+ import os
5
+ import re
6
+ import tempfile
7
+ import zipfile
8
+ from contextlib import contextmanager
9
+
10
+ from ._media import save_media, validate_members
11
+
12
+
13
+ @contextmanager
14
+ def hwp_reader(data, suffix):
15
+ import hwp_hwpx_parser as parser
16
+
17
+ temporary = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
18
+ try:
19
+ with temporary:
20
+ temporary.write(data)
21
+ reader = parser.read(temporary.name)
22
+ try:
23
+ yield reader
24
+ finally:
25
+ reader.close()
26
+ finally:
27
+ os.unlink(temporary.name)
28
+
29
+
30
+ def hwp_images(data, suffix, reader):
31
+ """Return image payloads with stable member/stream identities, including omissions."""
32
+ images = reader.get_images()
33
+ if suffix == ".hwpx":
34
+ with zipfile.ZipFile(io.BytesIO(data)) as archive:
35
+ validate_members(archive)
36
+ returned = {i.filename: i for i in images}
37
+ rows = []
38
+ for name in sorted(archive.namelist()):
39
+ if not name.startswith("BinData/") or name.endswith("/"):
40
+ continue
41
+ ext = name.rsplit(".", 1)[-1].lower()
42
+ from .ocr_triage import _IMAGE_EXTENSIONS
43
+
44
+ if ext not in _IMAGE_EXTENSIONS:
45
+ continue
46
+ image = returned.get(name.rsplit("/", 1)[-1])
47
+ rows.append((name, archive.read(name), ext, image is not None))
48
+ return rows
49
+ rows = [("BinData/" + i.filename, i.data, i.format.lower(), True) for i in images]
50
+ # The parser omits unknown encodings. Inspect stored OLE names as well.
51
+ import olefile
52
+
53
+ try:
54
+ with olefile.OleFileIO(io.BytesIO(data)) as ole:
55
+ known = {r[0] for r in rows}
56
+ for path in sorted(ole.listdir()):
57
+ name = "/".join(path)
58
+ if len(path) == 2 and path[0] == "BinData" and name not in known:
59
+ rows.append(
60
+ (
61
+ name,
62
+ ole.openstream(path).read(),
63
+ path[-1].rsplit(".", 1)[-1].lower(),
64
+ False,
65
+ )
66
+ )
67
+ except (OSError, ValueError):
68
+ # Parser payloads remain usable when stored-stream inspection is unavailable.
69
+ pass
70
+ return sorted(rows, key=lambda row: row[0])
71
+
72
+
73
+ def extract_hwp_markdown(markdown, rows, **options):
74
+ links = {}
75
+ for name, payload, ext, supported in rows:
76
+ if supported:
77
+ path = save_media(payload, ext, **options)
78
+ link = f"![Image]({path})"
79
+ else:
80
+ label = ext.upper() if re.fullmatch("[a-z0-9]{1,12}", ext) else "Image"
81
+ link = f"![{label} omitted]"
82
+ basename = name.rsplit("/", 1)[-1]
83
+ for identity in (basename, basename.rsplit(".", 1)[0]):
84
+ if identity in links and links[identity] != link:
85
+ raise ValueError("Unsafe or colliding image reference")
86
+ links[identity] = link
87
+ used = set()
88
+
89
+ def replace(match):
90
+ identity = match.group(1)
91
+ link = links.get(identity)
92
+ if link is None:
93
+ return "![Image omitted]"
94
+ used.add(link)
95
+ return link
96
+
97
+ markdown = re.sub(r"\[IMAGE: ([^\]\r\n]+)\]", replace, markdown)
98
+ # Orphaned/skipped media remain visible, never silently disappear.
99
+ for link in dict.fromkeys(links.values()):
100
+ if link not in used:
101
+ markdown += "\n\n" + link
102
+ return markdown
103
+
104
+
105
+ def has_hwp_images(data, suffix):
106
+ """Observe package structures, never search converted/document text."""
107
+ try:
108
+ if suffix == ".hwpx":
109
+ from defusedxml import ElementTree
110
+
111
+ with zipfile.ZipFile(io.BytesIO(data)) as archive:
112
+ if any(
113
+ n.startswith("BinData/") and not n.endswith("/")
114
+ for n in archive.namelist()
115
+ ):
116
+ return True
117
+ for name in archive.namelist():
118
+ if re.fullmatch(r"Contents/section\d+\.xml", name):
119
+ root = ElementTree.fromstring(archive.read(name))
120
+ if any(
121
+ e.tag.rsplit("}", 1)[-1] in ("pic", "img")
122
+ for e in root.iter()
123
+ ):
124
+ return True
125
+ else:
126
+ import olefile
127
+
128
+ with olefile.OleFileIO(io.BytesIO(data)) as ole:
129
+ return any(p[0] == "BinData" for p in ole.listdir())
130
+ except (OSError, ValueError, zipfile.BadZipFile):
131
+ pass
132
+ return False