> Source: https://txtfetch.com/extract/legacy-office > Plain-text twin — every page on txtfetch.com has one. https://txtfetch.com/text --- # The 97-2003 binary formats, read properly. OLE2 Compound File Binary Format isn't a smaller version of OOXML. It's a different filesystem-in-a-file, and .doc text isn't stored in reading order. the-problem Before OOXML, Office files were OLE2, the Compound File Binary Format. That's essentially a tiny FAT filesystem embedded in one file, streams and all. It shares nothing structurally with the zip-of-XML that .docx/.xlsx/.pptx use. So a parser built for the modern formats throws outright on a legacy one, rather than degrading gracefully. Word's .doc format compounds this. The document text sits in a WordDocument stream as a piece table. That's a list of byte ranges that must be reassembled in a specific order to produce readable text. Read the stream's raw bytes start to finish and you get scrambled, out-of-order text. It's not garbage exactly, but it's wrong. There's also a classic naming trap. Plenty of files with a .doc extension are actually RTF or a renamed OOXML file underneath. Extension-based routing gets those wrong before extraction even starts. one-request-solution txtfetch detects OLE2's compound-file signature from the file's actual bytes, not its extension. It routes the file to Tika's POI-backed legacy parsers: HWPF for .doc, HSSF for .xls, HSLF for .ppt. Those parsers understand the piece table and reassemble .doc text in the correct reading order. Because detection is byte-based, a file named .doc that's really RTF or renamed OOXML still gets parsed correctly instead of misrouted. curl ```curl curl -X POST https://api.txtfetch.com/v1/extract \ -H "Authorization: Bearer $TXTFETCH_KEY" \ -F file=@1998-contract-template.doc ``` Python ```python import os import requests with open("1998-contract-template.doc", "rb") as f: r = requests.post( "https://api.txtfetch.com/v1/extract", headers={"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"}, files={"file": f}, ) print(r.json()["extracted_text"]) ``` JavaScript ```javascript import { readFile } from "node:fs/promises"; const file = new Blob([await readFile("1998-contract-template.doc")]); const form = new FormData(); form.append("file", file, "1998-contract-template.doc"); const res = await fetch("https://api.txtfetch.com/v1/extract", { method: "POST", headers: { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` }, body: form, }); const { extracted_text } = await res.json(); console.log(extracted_text); ``` Go ```go package main import ( "bytes" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "os" ) type extractResponse struct { Status string `json:"status"` ExtractedText string `json:"extracted_text"` } func main() { f, err := os.Open("1998-contract-template.doc") if err != nil { panic(err) } defer f.Close() var body bytes.Buffer writer := multipart.NewWriter(&body) part, err := writer.CreateFormFile("file", "1998-contract-template.doc") if err != nil { panic(err) } if _, err := io.Copy(part, f); err != nil { panic(err) } writer.Close() req, err := http.NewRequest("POST", "https://api.txtfetch.com/v1/extract", &body) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("TXTFETCH_KEY")) req.Header.Set("Content-Type", writer.FormDataContentType()) resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var result extractResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { panic(err) } fmt.Println(result.ExtractedText) } ``` Or skip the download. Pass a `url` parameter and txtfetch fetches the document server-side: curl ```curl curl -X POST "https://api.txtfetch.com/v1/extract?url=https://example.com/archive/legacy-invoice.xls" \ -H "Authorization: Bearer $TXTFETCH_KEY" ``` Python ```python import os import requests r = requests.post( "https://api.txtfetch.com/v1/extract", headers={"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"}, params={"url": "https://example.com/archive/legacy-invoice.xls"}, ) print(r.json()["extracted_text"]) ``` JavaScript ```javascript const endpoint = new URL("https://api.txtfetch.com/v1/extract"); endpoint.searchParams.set("url", "https://example.com/archive/legacy-invoice.xls"); const res = await fetch(endpoint, { method: "POST", headers: { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` }, }); const { extracted_text } = await res.json(); console.log(extracted_text); ``` Go ```go package main import ( "encoding/json" "fmt" "net/http" "net/url" "os" ) type extractResponse struct { Status string `json:"status"` ExtractedText string `json:"extracted_text"` } func main() { endpoint, err := url.Parse("https://api.txtfetch.com/v1/extract") if err != nil { panic(err) } q := endpoint.Query() q.Set("url", "https://example.com/archive/legacy-invoice.xls") endpoint.RawQuery = q.Encode() req, err := http.NewRequest("POST", endpoint.String(), nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("TXTFETCH_KEY")) resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var result extractResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { panic(err) } fmt.Println(result.ExtractedText) } ``` ``` { "status": "success", "extracted_text": "..." } ``` what-comes-back The corpus behind [/diff](https://txtfetch.com/diff) has no recorded .doc document yet, so we have nothing honest to show you here. Run one of your own instead. The [free converter](https://txtfetch.com/tools/file-to-text) reads the file in your browser, and nothing is uploaded. formats-covered - `.doc` - `.xls` - `.ppt` - `.pub` - `.wpd` faq **Why does my .doc file come out as scrambled or out-of-order gibberish?**: The legacy .doc format stores text as a piece table: byte ranges in the WordDocument stream that aren't laid out in reading order. A byte-scraping extractor that reads the stream start-to-finish gets the pieces in the wrong order. Tika's HWPF parser reassembles them correctly. **Do modern, OOXML-only libraries support .doc and .xls at all?**: Usually not. Many libraries built only for .docx/.xlsx/.pptx throw an error on an OLE2 file rather than degrading. txtfetch routes OLE2 signatures to dedicated legacy parsers instead: POI's HWPF, HSSF, and HSLF. **What if a file is named .doc but is actually RTF or a renamed OOXML file?**: txtfetch detects the real format from the file's byte signature, not its extension, so a mislabeled file still routes to the correct parser. **Are .pub (Publisher) and .wpd (WordPerfect) files supported too?**: Yes. Both are legacy binary formats handled through the same Apache Tika pipeline as .doc/.xls/.ppt. go-further - [Read the .doc guide →](https://txtfetch.com/blog/parse-office-docs-docx-pptx-xlsx-for-llms) - [Drop a real .doc and see the exact text it extracts to, free →](https://txtfetch.com/tools/doc-to-text) - [Drop a real .xls and see every sheet's text, free →](https://txtfetch.com/tools/xls-to-text) - [Drop a real .ppt and see its slide text and speaker notes, free →](https://txtfetch.com/tools/ppt-to-text) - [The modern OOXML formats (.docx/.pptx/.xlsx) →](https://txtfetch.com/extract/docx) - [API quickstart →](https://txtfetch.com/docs) - [How the pipeline works →](https://txtfetch.com/how-it-works) - [Extract it from your language →](https://txtfetch.com/for) - [Get an API key →](https://app.txtfetch.com/signup) Office - [`.docx`](https://txtfetch.com/extract/docx) - [`.pptx`](https://txtfetch.com/extract/pptx) - [`.xlsx`](https://txtfetch.com/extract/xlsx) - [`.rtf`](https://txtfetch.com/extract/rtf) - [All formats →](https://txtfetch.com/extract) ## Send a real .doc through it. One HTTP call returns the text. Read one in your browser first, for free. [Get an API key →](https://app.txtfetch.com/signup) [Open the free .doc reader →](https://txtfetch.com/tools/doc-to-text)