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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
//! 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,
/// EBCDIC mainframe data + JSON copybook layout (#252, docling 2.118).
Ebcdic,
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,
/// AbiWord (`.abw`/`.zabw`/`.awt`) — a docling.rs extension (#216);
/// AWML XML (gzip-wrapped for `.zabw`), parsed natively.
Abiword,
/// WordPerfect 5.x / 6.x+ documents (`.wpd`, `.wp`, `.wp5`, `.wp6`,
/// `.wpt`) — a docling.rs extension (#216); the `ÿWPC` byte stream is
/// parsed natively (docling reaches WordPerfect only via LibreOffice's
/// libwpd), text-level with bold/italic/underline runs.
WordPerfect,
/// Microsoft Works word-processor documents (`.wps`) — a docling.rs
/// extension (#216); Works 2.x DOS / 3 / 4 (`WPS4`) and Works 2000 / 6–9
/// (`WPS8`, OLE `CONTENTS`) parsed natively after libwps' readers.
Works,
/// 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,
/// Quattro Pro spreadsheets (`.wq1`/`.wq2` DOS, `.wb1`–`.wb3` Windows,
/// `.qpw` 9–X9) — a docling.rs extension (#216) parsed natively after
/// libwps' readers.
QuattroPro,
/// 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::Ebcdic => "ebc",
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::Abiword => "abiword",
InputFormat::WordPerfect => "wordperfect",
InputFormat::Works => "works",
InputFormat::Dbf => "dbf",
InputFormat::Dif => "dif",
InputFormat::Sylk => "sylk",
InputFormat::Lotus => "lotus",
InputFormat::QuattroPro => "quattro",
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,
"ebc" | "ebcdic" => InputFormat::Ebcdic,
// 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,
// AbiWord (#216): AWML XML; .zabw is the same file gzip-wrapped,
// .awt the template flavor.
"abw" | "zabw" | "awt" => InputFormat::Abiword,
// WordPerfect (#216): `.wp` was the DOS-era default (WP 5.x),
// `.wpd` the Windows one; `.wpt` is the template flavor. The
// backend reads the version from the prefix header, not the
// extension.
"wpd" | "wp" | "wp5" | "wp6" | "wpt" => InputFormat::WordPerfect,
// Microsoft Works word processor (#216): the backend tells the
// generations apart by the stream (raw 2.x header, OLE MN0 for
// 3/4, OLE CONTENTS for 2000+); .wks/.wdb are the spreadsheet
// and database and route elsewhere.
"wps" => InputFormat::Works,
// 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,
// Quattro Pro (#216): the cell records differ per generation, so
// the family has its own reader (the backend sniffs the BOF /
// OLE stream, not the extension).
"wq1" | "wq2" | "wb1" | "wb2" | "wb3" | "qpw" => InputFormat::QuattroPro,
// MS Works 6–9 spreadsheet (#216): a BIFF8 `Workbook` stream in
// an OLE container — Excel 97's own layout under another
// extension, so the XLS reader takes it.
"xlr" => InputFormat::Xls,
// 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));
}
}