1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
//! Input format enumeration and detection.
//!
//! Mirrors `docling.datamodel.base_models.InputFormat` and its
//! `FormatToExtensions` map.
/// A document format supported by docling.rs backends.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InputFormat {
Docx,
Pptx,
Html,
Image,
Pdf,
Asciidoc,
Md,
Csv,
Xlsx,
/// Word 97–2004 binary (`.doc`) — parsed natively (CFB + MS-DOC), no
/// external converter (docling shells out to LibreOffice for these).
Doc,
/// Excel 97–2004 binary (`.xls`, BIFF8) — parsed natively via calamine.
Xls,
/// PowerPoint 97–2003 binary (`.ppt`) — parsed natively (CFB + MS-PPT).
Ppt,
Odt,
Ods,
Odp,
XmlUspto,
XmlJats,
XmlXbrl,
XmlDoclang,
/// Raw DocTags markup (`.doctags`/`.dt`) — the token stream docling's
/// VLMs emit, parsed by `docling_core::doctags` (#152).
DocTags,
/// A DocLang OPC archive (`.dclx`, the format `--to dclx` writes).
Dclx,
MetsGbs,
JsonDocling,
Audio,
/// Video containers (`.mp4`/`.avi`/`.mov`/`.mkv`/`.webm`) — Phase 1 of
/// issue #138 transcribes the audio track through the ASR pipeline
/// (mirrors docling's `InputFormat.VIDEO`, v2.114).
Video,
Vtt,
Latex,
Email,
Epub,
/// MIME HTML archive (`.mhtml`/`.mht`) — a docling.rs extension; docling
/// has no MHTML backend.
Mhtml,
/// Rich Text Format (`.rtf`) — a docling.rs extension (#209); docling
/// converts RTF only by shelling out to LibreOffice.
Rtf,
/// Microsoft Visio (`.vsdx`, `.vsdm`) — a docling.rs extension (#214);
/// docling has no Visio reader. Pages become sections, shape text flows
/// in reading order, connectors become a relations table.
Visio,
/// SVG (`.svg`) — a docling.rs extension (#212); docling does not accept
/// SVG input. Mirrors the pdf / pdf-text split: the ML build rasterizes
/// (resvg) and rides the image pipeline; without ML — or under `--no-ocr`
/// — `<text>` elements are extracted directly into flat paragraphs.
Svg,
/// Apple Pages (`.pages`) — a docling.rs extension (#213); docling has
/// no iWork reader. Modern (2013+) IWA packages, text-level extraction.
Pages,
/// Apple Numbers (`.numbers`), same IWA machinery as [`Self::Pages`].
Numbers,
/// Apple Keynote (`.key`), same IWA machinery as [`Self::Pages`].
Keynote,
}
impl InputFormat {
/// Stable string identifier, matching the Python enum values.
pub fn as_str(self) -> &'static str {
match self {
InputFormat::Docx => "docx",
InputFormat::Pptx => "pptx",
InputFormat::Html => "html",
InputFormat::Image => "image",
InputFormat::Pdf => "pdf",
InputFormat::Asciidoc => "asciidoc",
InputFormat::Md => "md",
InputFormat::Csv => "csv",
InputFormat::Xlsx => "xlsx",
InputFormat::Doc => "doc",
InputFormat::Xls => "xls",
InputFormat::Ppt => "ppt",
InputFormat::Odt => "odt",
InputFormat::Ods => "ods",
InputFormat::Odp => "odp",
InputFormat::XmlUspto => "xml_uspto",
InputFormat::XmlJats => "xml_jats",
InputFormat::XmlXbrl => "xml_xbrl",
InputFormat::XmlDoclang => "xml_doclang",
InputFormat::DocTags => "doctags",
InputFormat::Dclx => "dclx",
InputFormat::MetsGbs => "mets_gbs",
InputFormat::JsonDocling => "json_docling",
InputFormat::Audio => "audio",
InputFormat::Video => "video",
InputFormat::Vtt => "vtt",
InputFormat::Latex => "latex",
InputFormat::Email => "email",
InputFormat::Epub => "epub",
InputFormat::Mhtml => "mhtml",
InputFormat::Rtf => "rtf",
InputFormat::Visio => "visio",
InputFormat::Svg => "svg",
InputFormat::Pages => "pages",
InputFormat::Numbers => "numbers",
InputFormat::Keynote => "key",
}
}
/// Best-effort format detection from a file extension (case-insensitive).
///
/// Ambiguous extensions (notably bare `xml`) resolve to a single default
/// here; the converter's content sniffing does the real disambiguation.
pub fn from_extension(ext: &str) -> Option<Self> {
Some(match ext.to_ascii_lowercase().as_str() {
"docx" | "dotx" | "docm" | "dotm" => InputFormat::Docx,
"pptx" | "potx" | "ppsx" | "pptm" | "potm" | "ppsm" => InputFormat::Pptx,
"pdf" => InputFormat::Pdf,
"md" | "txt" | "text" | "qmd" | "rmd" => InputFormat::Md,
"html" | "htm" | "xhtml" => InputFormat::Html,
"xml" | "nxml" => InputFormat::XmlJats,
"dclg" => InputFormat::XmlDoclang,
"doctags" | "dt" => InputFormat::DocTags,
"dclx" => InputFormat::Dclx,
// `.gif` decodes through the same content-sniffing `image` path as
// the rest (first frame of an animation), issue #208.
"jpg" | "jpeg" | "png" | "tif" | "tiff" | "bmp" | "webp" | "gif" | "heic" | "heif" => {
InputFormat::Image
}
"adoc" | "asciidoc" | "asc" => InputFormat::Asciidoc,
// `.tsv` rides the CSV backend, whose delimiter sniffing already
// prefers the tab when it dominates the first line (#208).
"csv" | "tsv" => InputFormat::Csv,
// `.xlsb` (binary Excel 2007+) parses through the same calamine
// engine as xlsx — the backend detects the binary workbook part
// and switches readers, issue #210.
"xlsx" | "xlsm" | "xlsb" => InputFormat::Xlsx,
// Legacy binary Office (Word/Excel/PowerPoint 97–2003), issue #127.
// Extension sets mirror docling's FormatToExtensions.
"doc" | "dot" => InputFormat::Doc,
"xls" | "xlt" => InputFormat::Xls,
"ppt" | "pot" | "pps" => InputFormat::Ppt,
"odt" | "ott" => InputFormat::Odt,
"ods" | "ots" => InputFormat::Ods,
"odp" | "otp" => InputFormat::Odp,
"json" => InputFormat::JsonDocling,
// `.mpga` *is* MPEG audio (an mp3 stream) — symphonia probes the
// codec from the bytes, the extension is just the alias (#208).
"wav" | "mp3" | "mpga" | "m4a" | "aac" | "ogg" | "flac" => InputFormat::Audio,
// Upstream's FormatToExtensions[VIDEO] (docling v2.114, #3768):
// the audio track transcribes through the same ASR path.
// `.mpeg`/`.mpg` (#208): MPEG-PS has no symphonia demuxer, so both
// the audio track and the sampled frames come from the ffmpeg
// fallback; an audio-only `.mpeg` still decodes in-process (the
// probe is content-based) and converts to its transcript.
"mp4" | "avi" | "mov" | "mkv" | "webm" | "mpeg" | "mpg" => InputFormat::Video,
"vtt" => InputFormat::Vtt,
"tex" | "latex" => InputFormat::Latex,
"eml" => InputFormat::Email,
"epub" => InputFormat::Epub,
"mhtml" | "mht" => InputFormat::Mhtml,
"rtf" => InputFormat::Rtf,
"vsdx" | "vsdm" => InputFormat::Visio,
"svg" => InputFormat::Svg,
// Apple iWork (#213). `.heic`/`.heif` route to Image above and
// decode behind the opt-in `heif` cargo feature.
"pages" => InputFormat::Pages,
"numbers" => InputFormat::Numbers,
"key" => InputFormat::Keynote,
// METS/Google Books scan packages ship as `*.tar.gz`.
"gz" | "targz" => InputFormat::MetsGbs,
_ => return None,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn audio_and_video_extensions_split_like_upstream() {
// docling v2.114 FormatToExtensions: AUDIO and VIDEO are disjoint
// (docling.rs adds the MPEG aliases on top, #208).
for ext in ["wav", "mp3", "mpga", "m4a", "aac", "ogg", "flac"] {
assert_eq!(InputFormat::from_extension(ext), Some(InputFormat::Audio));
}
for ext in ["mp4", "avi", "mov", "mkv", "webm", "MKV", "mpeg", "mpg"] {
assert_eq!(InputFormat::from_extension(ext), Some(InputFormat::Video));
}
assert_eq!(InputFormat::Video.as_str(), "video");
}
#[test]
fn extension_aliases_route_to_existing_backends() {
// #208/#210: aliases whose decoding machinery predated the mapping.
assert_eq!(InputFormat::from_extension("tsv"), Some(InputFormat::Csv));
assert_eq!(InputFormat::from_extension("gif"), Some(InputFormat::Image));
assert_eq!(InputFormat::from_extension("xlsb"), Some(InputFormat::Xlsx));
}
}