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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
//! 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,
/// dBase table (`.dbf`) — a docling.rs extension (#216); the field
/// descriptors become the header row, records the data rows.
Dbf,
/// Data Interchange Format (`.dif`) — a docling.rs extension (#216);
/// sheet snapshot, split into data regions like an ODS sheet.
Dif,
/// SYLK (`.slk`/`.sylk`) — a docling.rs extension (#216); same
/// sheet-region conversion as DIF.
Sylk,
/// Lotus 1-2-3 / Symphony / MS Works spreadsheets (`.wk1`–`.wk4`,
/// `.wks`, `.wrk`, `.123`) — a docling.rs extension (#216); the DOS-era
/// record streams, content-sniffed on the BOF record and split into data
/// regions like an ODS sheet.
Lotus,
/// StarOffice 5 binaries (`.sdw`/`.sda`/`.sdd`/`.vor`) — a docling.rs
/// extension (#215); CFB containers parsed natively (text-level
/// extraction). The document kind comes from the stream inside, so a
/// `.vor` template of any application dispatches by content. StarCalc
/// (`.sdc`) is a follow-up.
StarOffice5,
}
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",
InputFormat::Dbf => "dbf",
InputFormat::Dif => "dif",
InputFormat::Sylk => "sylk",
InputFormat::Lotus => "lotus",
InputFormat::StarOffice5 => "staroffice5",
}
}
/// 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,
// StarOffice / OpenOffice 1.x XML and flat ODF (#215, docling.rs
// extensions): the shared ODF backend parses the older namespace
// vocabulary through a local-name mapping layer, and the flat
// variants are the same XML uncompressed in a single file.
// Templates (`.stw`/`.sti`/`.stc`) and the Writer master document
// (`.sxg`) ride the same parsers as their document counterparts.
"odt" | "ott" | "sxw" | "stw" | "sxg" | "fodt" => InputFormat::Odt,
"ods" | "ots" | "sxc" | "stc" | "fods" => InputFormat::Ods,
"odp" | "otp" | "sxi" | "sti" | "fodp" => 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,
// Outlook .msg (#251): a CFB container of MAPI streams; the email
// backend sniffs the magic and projects it onto RFC 822.
"msg" => 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,
// Legacy spreadsheet-interchange relics (#216): all three parse
// natively and content-sniff inside one backend.
"dbf" => InputFormat::Dbf,
"dif" => InputFormat::Dif,
"slk" | "sylk" => InputFormat::Sylk,
// The Lotus family (#216): .wks is ambiguous (1-2-3 rel 1A and
// MS Works v3 both used it) — the backend sniffs the BOF.
"wk1" | "wk2" | "wk3" | "wk4" | "wks" | "wrk" | "123" => InputFormat::Lotus,
// StarOffice 5 binaries (#215): .vor templates dispatch by the
// CFB stream inside (writer/draw/impress share the container).
// .sdc routes here too so StarCalc gets its targeted
// "save as .ods" error instead of an unknown-extension one.
"sdw" | "sda" | "sdd" | "sdc" | "vor" => InputFormat::StarOffice5,
// 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));
}
}