SUBMIT A FILE TYPE CONTACT US SUPPORT ABOUT US

What Is an XLS File? The Excel 97 to 2003 Workbook, and How to Open, Convert and Check One

An XLS file is an Excel workbook saved in the binary format of Excel 97 to 2003. Three readers gave one answer on 8 of the 21 damaged copies we made of one. Jump to your question below, or work through it in order.

32 min read Updated November 2026 Checked against sources and tests

What Is an XLS File? The Excel 97 to 2003 binary workbook, and why it still shows up

Short answer

An XLS file is an Excel workbook saved in the binary format of Excel 97 to Excel 2003. It stores sheets, cell values, formulas and charts in one file. Excel 2007 and later still open it, in Compatibility Mode.

We tested 100 public XLS files with three readers, xlrd, xls2csv and LibreOffice. 85 opened in all three. We then damaged one workbook in 21 ways, and the three readers gave one answer on 8 copies.

To look inside one, see the open steps. To change one into another format, see the convert steps. If your XLS file will not open, begin with the causes.

An XLS file is an Excel workbook saved in the binary format of Excel 97 to Excel 2003. It stores sheets, cell values, formulas and charts in one file. Excel 2007 and later still open it, in Compatibility Mode.

Picture a file named report.xls that a friend sends you. You double-click it, and Excel shows the words Compatibility Mode in the title bar. Picture a second .xls file on an old backup disk. Both files are binary workbooks, and both belong to the same family.

Wikipedia says Excel used a proprietary binary format called Excel Binary File Format (.XLS) as its primary format until the 2007 version. Excel 2007 then made .xlsx the default. Excel first came out for the Mac on 30 September 1985, and the first Windows version came on 19 November 1987.

Microsoft's file format list names the .xls row Excel 97-2003 Workbook. It calls the format the Excel 97 - Excel 2003 Binary file format (BIFF8), and it says a workbook in that format opens in Compatibility Mode.

The MS-XLS specification adds the layout. Excel 97, 2000, 2002 and 2003 use the format. A record is a 2 byte type plus a 2 byte length, and a record is at most 8,224 bytes. The Workbook stream is the primary stream, and its first part is the globals.

The old format has limits. Microsoft's page on earlier versions says Excel 97-2003 supports 65,536 rows by 256 columns, and data past that is not saved. Current Excel supports 1,048,576 rows by 16,384 columns.

Macros are part of the story. Wikipedia says .xls stores data in worksheets, charts and macros. Microsoft says .xlsx cannot store VBA macro code, and .xlsm does. So an XLS file can contain code, and the safety part covers it.

Where our XLS files come from

We read 100 public files from the test data of the Apache POI project, a library for Office files. We chose them at random from 418 .xls files with a fixed seed and removed copies by SHA-256. They are 3,182 to 2,586,112 bytes long, and the median is 28,160 bytes.

They are test files and not workbooks from offices. Many were made for one bug, and 2 are fuzzer files. We opened none in Excel, because Excel needs Windows and our lab uses Linux. We make no claim about what Excel shows when it opens one.

So this page studies how readers treat the layout. We used xlrd 2.0.2, xls2csv 0.95 and LibreOffice 24.2.7.2 as the three readers, and olefile 0.47 for the container.

XLS file facts at a glance

Quick facts about XLS files
Property Value
Meaning Excel 97-2003 Workbook, in the binary format BIFF8, per Microsoft.
Ending .xls for a workbook. A template is .xlt and an add-in is .xla, per Microsoft.
Maker Microsoft.
Reader Excel, which opens it in Compatibility Mode. FileInfo lists LibreOffice, Gnumeric, Google Sheets and others too.
Layout An OLE compound file with a Workbook stream of records, per our tests.
First bytes d0 cf 11 e0 a1 b1 1a e1. Our example then reads 59 and 3 as the version.
Limits 65,536 rows by 256 columns, per Microsoft. Current Excel allows 1,048,576 by 16,384.
Media type application/vnd.ms-excel, per Wikipedia.
Status Replaced as the default by .xlsx in Excel 2007, per Wikipedia.
Our tests 100 files, 21 damaged copies, 3 readers, a check script of 19 lines and a repair script of 10 lines.

Looking for a Word or PowerPoint file? The DOC file page and the PPT file page cover them.

Which Kind of XLS File Do You Have? Three kinds, and the first bytes name the kind

Four kinds of file use the .xls ending in our tests. They are an OLE workbook, an OLE file with only a Book stream, a raw BIFF stream and a ZIP file. The first bytes and the stream names name the kind, and the ending does not.

The ending of an XLS file is a label that anyone can change. All 418 .xls files in the Apache POI test folder use it. 415 start with the OLE bytes, 2 start with a raw BIFF record and 1 starts with the letters PK. Follow the figure.

Decision chart with four kinds of file named .xls: an OLE file with a Workbook stream, an OLE file with only a Book stream, a raw BIFF file with no container and a ZIP file, with the number of files that xlrd, xls2csv and LibreOffice opened.
Figure 1. Four kinds of file named .xls, found by the first bytes and the stream names.

The kinds in detail

Four kinds of file named .xls and how three readers handled them
Kind Files and results
Workbook stream, BIFF8 97 of 100 files. xlrd and LibreOffice opened 95, and xls2csv opened 85. 2 of the 97 also contain a Book stream. xlrd and LibreOffice opened both of them, and xls2csv opened 1.
Book stream only 1 file. 64130.xls has a Book stream and no Workbook stream. xlrd said it could not determine the BIFF version. xls2csv and LibreOffice opened it.
Raw BIFF records 2 of 418 files. testEXCEL_3.xls and testEXCEL_4.xls start with a record and have no OLE container. xlrd reports BIFF versions 30 and 40. xlrd and LibreOffice opened both, and xls2csv refused both.
A ZIP file 1 of 418 files. Its name ends in .xlsb.xls, it starts with PK and it ends at 1,048,576 bytes. 7-Zip says the archive ends early. All three readers refused it.

Read the first bytes

Every OLE file starts with the same 8 bytes, so they name the container and not the kind of workbook. We read four more values in the 100 OLE files. The script in the check part reads all four.

First bytes and names in 100 files
Value What we measured
Root CLSID 83 files say 00020820, 5 say 00020810 and 12 say nothing. All three readers opened 72 of the 83, 4 of the 5 and 9 of the 12. We found no Microsoft page that names these values, so we make no claim about them.
Minor version 62 in 95 files and 59 in 5. All three readers opened 81 of the 95 and 4 of the 5.
Stream name Workbook in 96 files, Book in 1 and capital letters, WORKBOOK or BOOK, in 3. All three readers opened all 3 files with capital letters.
File size 96 of 100 files are an exact multiple of 512 bytes. The other 4 are 443, 110, 50 and 1 byte over a multiple.
First bytes first The OLE magic names the container. A file without it is a raw record stream or another kind of file.
Stream name second In our files a Workbook stream means BIFF8. A Book stream is older, and two of our files contain both.
Version third The BOF record must read version 0600, per Microsoft. Our script flags any other value.
The ending last The ending is a label. One .xls file in our folder is a ZIP file, and two are raw record streams.

Is your file a newer workbook with the .xlsx ending? The versus part names the differences.

What Is Inside an XLS File? A header, a FAT, a directory and a Workbook stream

An XLS file is an OLE compound file with a header, a FAT, a directory and streams. Our example is 14,848 bytes long and has 6 directory entries. Its Workbook stream is 10,651 bytes and contains 435 records in three parts.

We looked inside one XLS file because its layout explains every result later on this page. Our MatchFunctionTestCaseData.xls comes from the Apache POI test folder. It has 2 sheets, Read Me with 11 rows and Match with 139 rows and 10 columns. Its start shows the header.

The start of one XLS file
xxd -l 48 MatchFunctionTestCaseData.xls
00000000: d0cf 11e0 a1b1 1ae1 0000 0000 0000 0000  ................
00000010: 0000 0000 0000 0000 3b00 0300 feff 0900  ........;.......
00000020: 0600 0000 0000 0000 0000 0000 0100 0000  ................
file -b MatchFunctionTestCaseData.xls | cut -d, -f1-3
Composite Document File V2 Document, Little Endian, Os: Windows

The first 8 bytes are the OLE magic. Bytes 24 to 27 read 3b 00 03 00, the version 59 and 3. Bytes 28 and 29 read fe ff, the byte order. Bytes 30 and 31 read 09 00, so a sector is 512 bytes. Bytes 32 and 33 read 06 00, so a mini sector is 64 bytes.

Diagram of one XLS file of 14,848 bytes in five parts: the header of 512 bytes, the FAT, the directory, the small streams and the Workbook stream with its 435 records in three substreams, with three rules about the header, the records and the first record.
Figure 2. One XLS file from our tests, MatchFunctionTestCaseData.xls. The parts are not drawn to scale.

The parts, one by one

The file has 28 sectors after the header. The FAT takes sector 0 and lists which sector follows which. The Workbook stream takes sectors 2 to 22, and sector 1 is free. The directory takes sectors 26 and 27 and lists Root Entry and 5 streams.

Four streams are under 4,096 bytes, and they live in a mini stream of 576 bytes that Root Entry points to. Only the Workbook stream, at 10,651 bytes, lies in ordinary sectors.

What every part stores

Parts of XLS files and what we saw
Part What it stores
Magic D0 CF 11 E0 A1 B1 1A E1. Copy A1 changes the first byte. xlrd and xls2csv refused it, and LibreOffice wrote the raw bytes as text. Our repair script restores it.
Version and sector size Version 62 and 3 in 95 of our files and 59 and 3 in 5, all with 512 byte sectors. Copy A4 sets a sector of 1,024 bytes, and xlrd and xls2csv refused it.
FAT One sector here. Copy C2 marks the directory sector as free, and xlrd refused it. xls2csv and LibreOffice read it as the original.
Directory The names, sizes and start sectors of all streams. Copy A7 points to the wrong first sector, and xlrd and xls2csv refused it. LibreOffice wrote raw bytes as text.
Workbook stream A string of records. Every record is a 2 byte type, a 2 byte length and its data. Copy C7 sets one length to FFFF, and xlrd and xls2csv refused it.
BOF and EOF records A BOF record opens a part and an EOF record closes it. Copy C5 changes the BOF version from 0600 to 0500, and xlrd refused it. Copy C6 zeroes the last EOF type, and xlrd refused that too.
Summary streams Title, author and similar facts. Copy C8 flips one byte in SummaryInformation, and all three readers read it as the original.

What the Microsoft page says about the first record

The BOF record page says the version field must read 0x0600. Its type field reads 0x0005 for the workbook globals and 0x0010 for a worksheet. Our example shows those values at bytes 0, 3,526 and 4,268, and the three BOF and EOF pairs agree.

Four of our 100 files break the rule. 2 are fuzzer files that start with another record, and 64130.xls has a Book stream with version 0580. The fourth, 61300.xls, has 1 BOF record and 0 EOF records.

The map is small. Our script checks the magic, the header fields, the stream names, the first record and the record walk. It reads no cell values. Copies A3, A6, B1, C2, C3 and C4 show the limit, because the script printed OK for all six and a reader refused the copy or changed its answer.

What Did We Find in 100 Test XLS Files? Three readers, and 85 files opened in all of them

We tested 100 public XLS files with three readers. 85 opened in all three, 4 opened in none and 11 split the readers. On undamaged files the reader decided the result more than the file did. xls2csv refused 10 files that xlrd and LibreOffice opened.

We counted a file as opened when a reader gave output without an error. xlrd listed the sheets and read the cells. xls2csv wrote text. LibreOffice wrote a CSV file of the first sheet, and we did not count raw bytes as output. We used xlrd 2.0.2, xls2csv 0.95 and LibreOffice 24.2.7.2 on Linux.

Bar chart of 100 public test XLS files by size, from 3,182 bytes to 2,586,112, next to the number of files that three readers opened, a grid of results by kind of file, and four measured facts.
Figure 3. 100 files, three readers and four measured facts.
100 public test files
85 opened in all three readers
11 opened in only some readers
4 opened in none

What the numbers show

Findings from 100 test files
Finding What we measured
Most files open 85 of 100 files opened in all three readers. xlrd opened 95, xls2csv 86 and LibreOffice 96.
xls2csv refused the most It refused 10 files that xlrd and LibreOffice opened, among them SimpleWithImages.xls and StringContinueRecords.xls. We did not test why.
One file is encrypted 51832.xls has a FILEPASS record at byte 24. All three readers refused it, and our script warns about it.
Three files fail for a clear reason 61300.xls has a Workbook stream with 1 BOF record and 0 EOF records. Two fuzzer files have a broken container. All three readers failed on them, and LibreOffice wrote raw bytes as text.
One file has only a Book stream 64130.xls has version 0580 in its first record. xlrd refused it, and xls2csv and LibreOffice opened it.
All sizes fit a sector All 100 files use 512 byte sectors, and 96 are an exact multiple of 512 bytes. The folder of 418 files adds 3 that do not start with the OLE bytes. See the kinds.

What this study cannot show you

These are test files from a library for Office files. Many are made for one bug, and 2 are fuzzer files. A refusal on the 4 broken files is the right answer.

We opened no file in Excel. Output from a reader does not mean Excel would open the file, and it does not mean the values are right. On 13 files the first sheet is blank, so LibreOffice wrote a nearly empty CSV file. We counted those as opened.

What Happens When an XLS File Is Damaged? 21 broken copies, and the three readers agreed on 8

We damaged one XLS file in 21 ways. The three readers gave one answer on 8 copies and split on 13. Our script flagged 8 copies, and all 8 changed what at least one reader returned.

Our base file is MatchFunctionTestCaseData.xls, 14,848 bytes. We changed one thing at a time and saved 21 copies. Same means a reader gave the answer it gave on the original. Refused means it stopped with an error. Differs means it gave another answer.

Grid of 21 damaged copies of one XLS file against xlrd, xls2csv, LibreOffice, our check script and a repair script. The three readers gave one answer on 8 copies, our script flagged 8, and the repair script restored 3 copies.
Figure 4. 21 damaged copies, three readers, our script and a repair script.

The three readers check different things, and one flipped byte can change two answers and leave the third alone. LibreOffice refused none of the 21 copies. It wrote raw bytes as text on 6, an empty file on 2 and a changed sheet on 1.

What the grid shows

Results for 21 damaged copies of one XLS file
Group of copies What happened
A wrong magic Copy A1 changes the first byte. xlrd and xls2csv refused it, and LibreOffice wrote raw bytes as text. The repair script restores it, and all three readers then gave the original answer.
A wrong version or sector size Copy A2 sets the minor version to 0, A5 sets a mini shift of 7, A8 sets a mini cutoff of 1,024 and A9 fills six reserved bytes. All three read all four as the original. Copy A4 sets a sector of 1,024 bytes, and xlrd and xls2csv refused it.
A wrong byte order Copy A3 swaps the byte order marker. Only xlrd refused it. xls2csv and LibreOffice read it as the original, and the repair script restores it.
A wrong FAT or directory Copy A6 zeroes the FAT count, and only xls2csv refused it. Copy A7 names a wrong directory sector, and xlrd and xls2csv refused it. Copy C2 frees a FAT entry, and only xlrd refused it. Copy C1 zeroes the first byte of the Root Entry name, and all three read it as the original.
A short or long file Copy B1 removes the last 512 bytes, and xlrd and xls2csv refused it while LibreOffice read it as the original. B2 cuts the file in half, and xlrd and xls2csv refused it. B3 adds 512 zero bytes, and all three read it as the original.
A text mode transfer Copy B4 changes every LF into CRLF. xlrd and xls2csv refused it, and LibreOffice wrote raw bytes as text.
A flipped byte in a record Copies C3 and C4 flip one byte in the string table and in a number cell. xlrd and xls2csv gave another answer on both. LibreOffice changed on C3 and read C4 as the original.
A broken record header Copy C5 changes the BOF version, and only xlrd refused it. Copy C6 zeroes the last EOF type, and only xlrd refused it. Copy C7 sets one length to FFFF, and xlrd and xls2csv refused it while LibreOffice wrote an empty file.
A flipped byte in the summary Copy C8 flips one byte in SummaryInformation. All three readers read it as the original.

The pattern is clear. When the container breaks, xlrd and xls2csv stop and LibreOffice writes raw bytes. When a record breaks, xlrd stops first, and xls2csv read C5 and C6 as the original. A flipped data byte changed the answer in two readers.

Our script and the repair script

Our check script printed OK for 13 copies and FLAG for 8. All 8 flagged copies changed what a reader returned. Of the 13 OK copies, 7 read as the original in all three readers. For the other 6, a reader refused the copy or changed its answer. See the script.

The repair script rewrites the header fields. It made 6 copies match the original byte for byte, A1, A3, A4, A5, A8 and A9. For A1, A3 and A4 that fixed a refusal or a split. It left the other 13 copies as they were.

The script cannot restore a cut file, a broken FAT, a changed record or a text mode copy. We did not test a repaired file in Excel.

Why a text mode copy is special

Copy B4 changes LF into CRLF, and a text mode transfer does the same. Our base file has 211 LF bytes and no CRLF pairs, so the change is reversible there. Removing every CR from B4 gave back the original file, byte for byte.

21 of the 100 files contain a CRLF pair by chance. A tool that removes every CR from those 21 would damage them. So a blind CR removal is not safe, and a fresh download in binary mode is the better fix.

What Does Our Expert Say About XLS Files? A note from Neeraj Singh on trusting one tool

We asked our expert what the grid means for someone who has an XLS file and has to decide if it is damaged. He read the 21 copies and the three reader columns.

Neeraj Singh

Expert insight

Never trust the exit code of one tool. In our tests LibreOffice refused none of the 21 damaged copies, and on 9 of them it wrote raw bytes, an empty file or a changed sheet. Open the output, then open the file with a second reader before you decide it is sound.

Neeraj Singh Manager, Tech Support & Operations 19+ years fixing Windows and system errors

That advice fits the grid. xlrd and xls2csv gave the same answer on 16 of the 21 copies. xlrd and LibreOffice agreed on 8, and xls2csv and LibreOffice on 11. A second reader that checks the bytes in a different way is the cheapest test.

How Do You Open an XLS File? Excel, LibreOffice and tools that need no Excel

To open an XLS file, double-click it in Excel, which opens it in Compatibility Mode. Without Excel, LibreOffice opened 96 of our 100 files, xlrd 95 and xls2csv 86. A reader that opens an XLS file does not prove the XLS file is sound.

Excel 2010 and later opens an Excel 97-2003 workbook in Compatibility Mode, per Microsoft, and shows the words in square brackets in the title bar. You cannot switch that mode on by hand. We used no Windows machine, so we did not test Excel.

Your options

Microsoft Excel Not tested Opens it in Compatibility Mode, per Microsoft. Our lab uses Linux, so we did not use it.
LibreOffice Tested Opened 96 of our 100 files. It writes the first sheet as CSV by default, and a filter token writes every sheet.
xlrd Tested A Python library. It opened 95 of 100 and lists every sheet with its size. It refused the encrypted file.
xls2csv Tested Part of catdoc. It opened 86 of 100, wrote every sheet and skipped empty rows.
Google Sheets Not tested FileInfo lists it as a program that opens XLS files. We did not use it.
olefile Tested A Python library for OLE files. It opened all 100 and lists the streams, but it cannot read cells.

List the sheets without Excel

Start with the sheet list. It changes nothing in the XLS file. Our example has two sheets, Read Me and Match, and xlrd prints their sizes in rows and columns. The 80 at the end is the BIFF version that xlrd reports.

The sheets of one XLS file, listed with xlrd
python3 -c "import xlrd; b = xlrd.open_workbook('MatchFunctionTestCaseData.xls'); print([(s.name, s.nrows, s.ncols) for s in b.sheets()], b.biff_version)"
[('Read Me', 11, 1), ('Match', 139, 10)] 80

Change the sheets into text

xls2csv writes every sheet to one stream, and a form feed marks the end of a sheet. It wrote 3,057 bytes and 61 lines for our example. That is fewer lines than the sheets have rows, because xls2csv skips empty rows. The Match sheet has 139 rows, and 51 of them contain text.

The first lines of xls2csv output
xls2csv MatchFunctionTestCaseData.xls | head -3
"This spreadsheet contains various test cases for lookup functions: VLOOKUP, HLOOKUP, LOOKUP and MATCH"
"Name of the main junit test class which uses this spreadsheet:"
"org.apache.poi.ss.formula.functions.TestMatchFunctionsFromSpreadsheet"

LibreOffice writes the first sheet only by default. Our example gave 11 lines and 1,400 bytes, and the Match sheet was missing. A filter token ending in -1 writes every sheet to its own file. We tested the command on LibreOffice 24.2.7.2.

LibreOffice writes one sheet, or every sheet
soffice --headless --convert-to csv MatchFunctionTestCaseData.xls
soffice --headless --convert-to 'csv:Text - txt - csv (StarCalc):44,34,76,1,,0,false,true,false,false,false,-1' --outdir all MatchFunctionTestCaseData.xls
wc -c all/*.csv
2293 all/MatchFunctionTestCaseData-Match.csv
1400 all/MatchFunctionTestCaseData-Read Me.csv
List the sheets first xlrd shows every sheet and its size before you write anything.
Mind the first sheet LibreOffice writes the first sheet only. On 13 of our files that sheet is blank.
Mind the skipped rows xls2csv skips empty rows, so a row number in its output is not a row number in the sheet.
Know the limit Text output has no formulas, charts or styles. Excel keeps them in the workbook.

Is the file a Word document? The page on Word DOC files covers that format.

How Do You Convert an XLS File? To XLSX or CSV, with the commands we tested

To convert an XLS file to the XLSX format, open it in Excel and use File, Info, Compatibility Mode and Convert, or use Save As. Without Excel, LibreOffice converted our example in one command. All 227 values matched, and the XLS file shrank from 14,848 to 9,562 bytes.

Microsoft lists two ways to leave Compatibility Mode, in its page on earlier versions of Excel. Both start with the workbook open. Our lab has no Excel, so we did not test either one.

  1. Copy the XLS file to a local folder and keep the original.
  2. Open the copy in Excel. The title bar shows Compatibility Mode.
  3. Go to File, then Info, then Compatibility Mode, then the Convert button.
  4. Click OK if Excel warns about converting workbooks, then click Yes to close and reopen the workbook.
  5. Or go to File, then Save As, and select the current workbook type.
  6. Check the new file, and keep the old one until you are sure.

Convert it without Excel

LibreOffice wrote a workbook in the XLSX format from our example. We checked the result with xlrd and openpyxl. The XLSX file starts with PK, because it is a ZIP file.

LibreOffice converts one XLS file to XLSX
soffice --headless --convert-to xlsx --outdir xl MatchFunctionTestCaseData.xls
wc -c xl/MatchFunctionTestCaseData.xlsx
9562 xl/MatchFunctionTestCaseData.xlsx
What we checked after the conversion
Check Result
Sheets Two sheets with the same names, Read Me and Match.
Size Read Me is 11 rows by 1 column and Match is 139 rows by 10 columns, the same as in the XLS file.
Values All 227 cells with a value matched. Numbers matched within one billionth, and text matched exactly.
Formulas and errors 53 cells stayed formulas, and the 13 error values stayed error values.
File size 9,562 bytes in the XLSX file against 14,848 bytes in the XLS file.

Convert it to CSV

A CSV file keeps one sheet. Microsoft's format list says CSV saves only the active sheet. LibreOffice does the same with the first sheet unless you add the filter token shown above.

What we did not test. We used no Excel and no Windows machine. We did not test Save As, the Convert button or the Compatibility Checker. We converted one workbook, and we make no claim about charts, macros or pivot tables in other files.

Does the conversion fail? Read the list of causes before you try another tool.

How Do You Check an XLS File? Six steps and a script of 19 lines

Check an XLS file in six steps. Copy it, read the first bytes, read the version and sector size, list the streams, read the first record and use a second reader. Our script of 19 lines does steps two to five.

  1. Copy the XLS file and work on the copy. Keep the original.
  2. Read the first bytes with xxd -l 48 file.xls. The first eight bytes must read d0 cf 11 e0 a1 b1 1a e1.
  3. Read bytes 24 to 27. 3e 00 03 00 and 3b 00 03 00 both opened in our tests. Bytes 30 and 31 read 09 00 for 512 byte sectors.
  4. List the streams. A workbook has a Workbook stream, and two of our files also have a Book stream.
  5. Read the first record of that stream. It must start 09 08 10 00 00 06 05 00, which is a BOF record of version 0600 and type 0005.
  6. Open the XLS file with a second reader like xlrd or LibreOffice. The three readers gave one answer on only 8 of 21 damaged copies.

The check script

The script reads one file at a time. It prints OK when the header, the stream names and the record walk agree. It prints WARN for a small oddity and FLAG when something is wrong. It uses the Python library olefile.

xls_peek.py, 19 lines
import struct, sys, olefile
d = open(sys.argv[1], 'rb').read(); w = []
if d[:8] != bytes.fromhex('d0cf11e0a1b11ae1'):
    kind = {b'\x09\x08': 'BIFF5 to BIFF8', b'\x09\x04': 'BIFF4', b'\x09\x02': 'BIFF3', b'\x09\x00': 'BIFF2'}.get(d[:2])
    print('FLAG not an OLE file, ' + ('first bytes are a raw %s record' % kind if kind and d[2:4] in (b'\x06\x00', b'\x10\x00', b'\x08\x00') else 'a ZIP file (xlsx, xlsm or xlsb)' if d[:2] == b'PK' else 'magic bytes do not match')); sys.exit()
try:
    o = olefile.OleFileIO(sys.argv[1]); names = {'/'.join(p).lower(): '/'.join(p) for p in o.listdir()}; name = names.get('workbook') or names.get('book')
    if o.root.clsid[:8] not in ('00020820', '00020810'): w.append('WARN root CLSID is ' + (o.root.clsid or 'empty'))
    if not name: print('\n'.join(w + ['FLAG no Workbook or Book stream'])); sys.exit()
    s = o.openstream(name).read()
except Exception as e: print('FLAG container unreadable: ' + type(e).__name__); sys.exit()
rt, cb, vers, dt = struct.unpack('<HHHH', (s + bytes(8))[:8])
if rt != 0x809 or dt != 5 or vers != 0x600: w.append('FLAG first record is %04x, version %04x, type %04x' % (rt, vers, dt))
i = n = bof = eof = last = enc = 0
while i + 4 <= len(s) and s[i:].strip(b'\0'):
    last, cb = struct.unpack('<HH', s[i:i + 4]); i += 4 + cb; n += 1; bof += last in (9, 0x209, 0x409, 0x809); eof += last == 10; enc += last == 0x2F
if enc: w.append('WARN FILEPASS record found, the workbook is encrypted')
if i > len(s) or last != 10 or bof != eof: w.append('FLAG record walk breaks at byte %d of %d, %d BOF and %d EOF' % (min(i, len(s)), len(s), bof, eof))
print('\n'.join(w) or 'OK %s stream, %d records, %d BOF and EOF pairs agree' % (name, n, bof))
What the script prints
python3 xls_peek.py MatchFunctionTestCaseData.xls
OK Workbook stream, 435 records, 3 BOF and EOF pairs agree
python3 xls_peek.py A1.xls
FLAG not an OLE file, magic bytes do not match
python3 xls_peek.py C5.xls
FLAG first record is 0809, version 0500, type 0005
python3 xls_peek.py 51832.xls
WARN FILEPASS record found, the workbook is encrypted

On our 100 files it printed OK for 86, WARN for 10 and FLAG for 4. The 4 FLAG files are the broken ones. No reader opened 3 of them, and two readers opened the fourth, 64130.xls. 9 of the 10 WARN files opened in all three readers, and the tenth is the encrypted file.

So a FLAG is a reason to look, and not a verdict. On the 21 damaged copies it printed FLAG for 8 and OK for 13. The damage grid shows why A3, A6, B1, C2, C3 and C4 got an OK while a reader refused the copy or changed its answer.

The repair script

The repair script rewrites the fields of the header. It writes the magic, the byte order, the mini sector shift 6, six zero reserved bytes and the mini stream cutoff 4096. It takes the sector size from the major version, then pads the XLS file to a sector. It leaves the minor version alone.

xls_fix.py, 10 lines
import struct, sys
d = bytearray(open(sys.argv[1], 'rb').read())
if len(d) < 512: sys.exit('too short')
d[0:8] = bytes.fromhex('d0cf11e0a1b11ae1'); d[28:30] = b'\xfe\xff'
d[32:34] = struct.pack('<H', 6); d[34:40] = bytes(6)
d[30:32] = struct.pack('<H', 12 if struct.unpack('<H', d[26:28])[0] == 4 else 9)
d[56:60] = struct.pack('<I', 4096)
sz = 1 << struct.unpack('<H', d[30:32])[0]
d += bytes(-len(d) % sz)
open(sys.argv[2], 'wb').write(d)

It changes the header only, so damage past the first 512 bytes stays. Work on a copy and keep the original. We checked the result with three readers on Linux and not with Excel.

Is an XLS File Safe to Open? Reading is safe, and opening needs care

Reading the bytes of an XLS file starts nothing. Opening it in Excel is the risk, because an XLS file can contain macros. Open an XLS file only from a source you trust, and work on a copy when you only want to look.

Wikipedia says .xls stores data in worksheets, charts and macros. The MS-XLS page on the VBA storage names the storage for a macro project, _VBA_PROJECT_CUR. Code is the reason to take care.

Our lab only read files. We used xxd, olefile, xlrd, xls2csv and LibreOffice on 100 files and opened none in Excel. We scanned no file for malware and started no macro, so we make no claim about the files in our set or about yours.

An XLS file can contain macros 10 of our 100 files contain a _VBA_PROJECT_CUR storage. We read none of the code and started none of it.
Reading the bytes is safe xxd, olefile and our check script read bytes and start nothing. We used them on 100 files.
The ending is a label One of our .xls files is a ZIP file. Read the first bytes before you trust the ending.
A password is not a scan 1 of our 100 files is encrypted. All three readers refused it, and that says nothing about what is inside.
Work on a copy Keep the original unchanged. Our scripts write a new file and leave the source alone.
Know where the file came from Our 100 files are test files from a library. They say nothing about a workbook you got by email.

Microsoft gives the same advice for a file whose ending does not match its format. If you cannot verify who made the file, do not open it. Microsoft's page on a mismatched ending lists a rename on purpose to mislead as one cause.

A file named .xls that does not start with d0 cf 11 e0 a1 b1 1a e1 is not an OLE workbook. Our check steps show the first look. For a Windows program, read the EXE file page.

Is the file an archive? The ZIP file page covers compressed archives.

Why Will Your XLS File Not Open? Eight causes, and what to do about every one

Eight causes stop an XLS file from opening, and the XLS file is the cause in only some of them. Start with the first bytes. A file that does not start with the OLE bytes is another kind of file, and a file with a FILEPASS record needs its password.

Work down the list in order. Causes one and two come from Microsoft's notes on a mismatched ending and on repair. Causes three to seven come from our 21 damaged copies and 100 files, so every fix names a copy or a count. Cause eight names a trap.

1. The ending does not match the format Excel says so when a file was renamed by accident, on purpose or by someone trying to mislead you, per Microsoft. Corruption is the fourth cause it names. Read the first bytes. One .xls file in our folder is a ZIP file, and all three readers refused it.
2. The workbook is corrupt Microsoft says to use Open and Repair. Go to File, then Open, select the XLS file, click the arrow beside Open and click Open and Repair. Select Repair first and Extract Data second. We did not test Excel.
3. The workbook is encrypted 51832.xls has a FILEPASS record at byte 24, and all three readers refused it. Our script warns about it. You need the password, and a repair script cannot supply it.
4. The file is cut short Copy B2 cuts the XLS file in half, and xlrd and xls2csv refused it. Copy B1 removes 512 bytes, and the same two refused it while LibreOffice read it as the original. Get a full copy.
5. A text mode transfer changed the bytes Copy B4 changes every LF into CRLF. xlrd and xls2csv refused it. Download the XLS file again in binary mode. Removing the CR bytes restored our example, but that is not safe for every file.
6. A header or a record is damaged Copies A1, A4, A7, C5, C6 and C7 break the magic, a sector size, the directory or a record. Our script flagged all six. The repair script restored A1 and A4 and left the others.
7. It is an older record stream testEXCEL_3.xls and testEXCEL_4.xls have no OLE container. xlrd and LibreOffice opened both, and xls2csv refused both. Try xlrd or LibreOffice.
8. The output looks incomplete LibreOffice wrote the first sheet only, and xls2csv skipped empty rows. The file opened, and the output lost data. See the extract steps.

What to try first

Read the first bytes Use xxd -l 48 file.xls. Without d0 cf 11 e0 a1 b1 1a e1 the XLS file is another kind.
Use the check script See the script. It flags a broken header, record or first record, and it warns about encryption.
Try a second reader xlrd and xls2csv agreed on 16 of 21 damaged copies, and xlrd and LibreOffice on only 8.
Get a new copy A cut file or a changed record does not come back. Download it again in binary mode.

Excel has its own repair. Microsoft also lists a recovery by manual calculation and by links to the workbook. We tested none of them. Move a file off a failing disk or network drive first, per Microsoft.

How Does an XLS File Differ From XLSX, XLSM and CSV Files? One old workbook, its neighbors compared

An XLS file is the Excel 97 to 2003 binary workbook. An XLSX file is the XML-based workbook that became the default in Excel 2007. An XLSM file adds macros, and a CSV file keeps one sheet of text.

The table lists the neighbors of an XLS file. We studied XLS files only. The other rows come from Microsoft, so they are short and not tested by us.

XLS and its neighbors compared
Type What separates it
XLS The Excel 97 - Excel 2003 Binary file format (BIFF8), per Microsoft. 65,536 rows by 256 columns. 100 of our files, and 85 opened in all three readers.
XLSX The default XML-based format of Excel 2007 and 2010, per Microsoft. It cannot store VBA macro code. Our LibreOffice copy starts with PK and is 9,562 bytes.
XLSM The XML-based, macro-enabled format. It stores VBA macro code or Excel 4.0 macro sheets, per Microsoft. We studied none.
XLSB The binary format BIFF12 for Excel 2010 and 2007, per Microsoft. We studied none.
CSV Text with commas. Microsoft says it saves only the active sheet. xls2csv and LibreOffice wrote it from our files.
XLT and XLA A template and an add-in in the same 97-2003 family, per Microsoft. The .xla row supports VBA projects.

XLS or XLSX

Excel 2007 made .xlsx the default, per Wikipedia. An XLSX file is XML-based, per Microsoft, and our converted copy starts with PK, the first bytes of a ZIP file. An XLS file starts with the OLE bytes. The row limit also differs, at 65,536 against 1,048,576.

XLS or XLSM

Microsoft says .xlsx cannot store VBA macro code and .xlsm stores it. Wikipedia says .xls stores macros. So an XLS file and an XLSM file can both contain code, and an XLSX file cannot. Check the ending and the first bytes.

XLS or CSV

A CSV file is text. It has no formulas, styles or second sheet that we could count, and Microsoft says it saves only the active sheet. LibreOffice writes the first sheet by default, and a filter token writes them all.

Which one do you have

DOC files Read the guide to DOC files for a Word document.
PPT files Read the guide to PPT files for a PowerPoint file.
ZIP files Read the guide to ZIP files for a compressed archive.
XLW files Read the guide to XLW files for an Excel workspace file.

What Is the XLS File Format Made Of? The facts we could check, and where they came from

An XLS file stores a header, a FAT, a directory and a Workbook stream of records. Facts below come from our checks of 100 files and 21 damaged copies. Sources are Wikipedia, Microsoft and the test tools.

Facts about XLS files
Part What we could check
Maker Microsoft. Excel first came out for the Mac on 30 September 1985, and the first Windows version came on 19 November 1987, per Wikipedia.
Name Excel 97-2003 Workbook, with the ending .xls, per Microsoft. The media type is application/vnd.ms-excel, per Wikipedia.
First bytes d0 cf 11 e0 a1 b1 1a e1. 95 of our files then read version 62 and 3, and 5 read 59 and 3.
Layout A header of 512 bytes, then a FAT, a directory, a mini stream and the Workbook stream. Our example has 28 sectors and 6 directory entries.
Records A 2 byte type, a 2 byte length and the data, at most 8,224 bytes, then Continue records, per Microsoft. Our example has 435 records.
BOF record Its version must read 0x0600, per Microsoft. The type is 0x0005 for the globals, 0x0010 for a worksheet, 0x0020 for a chart and 0x0040 for a macro sheet.
Limits 65,536 rows by 256 columns in Excel 97-2003, per Microsoft. Current Excel allows 1,048,576 by 16,384.
Macros The storage _VBA_PROJECT_CUR stores a VBA project, per Microsoft. 10 of our 100 files contain it.
Encryption A FILEPASS record marks an encrypted workbook. 1 of our 100 files has one, at byte 24.
Our tests 100 files, 21 damaged copies, 3 readers, a check script of 19 lines and a repair script of 10 lines. xlrd 2.0.2, xls2csv 0.95, LibreOffice 24.2.7.2 and olefile 0.47 on Linux.

About the values we list

Facts about the layout come from our own reading of one example file and 100 test files, and from the pages we name. Microsoft's list of file formats is the source for the format name, and Wikipedia is the source for the history.

We read the Microsoft pages for the file formats, the mismatched ending, the repair steps, the limits and the BOF record. We did not read the full MS-XLS specification. We report what we measured, and we make no claim about records we did not open.

What we left out

We used no Windows machine. We did not open a file in Excel, test Open and Repair, check a macro or scan a file for malware. Our readers and scripts worked on Linux, and Excel can accept or refuse files that they treat differently.

All 100 files come from one test library, and many test one bug. Our damaged copies come from one small workbook and change one thing at a time. We did not decode cell types, styles or charts. We also did not look up what the root CLSID values mean.

Sources checked

For the format and its history, see the Wikipedia article on Microsoft Excel and Microsoft's list of supported file formats. For the limits and Compatibility Mode, see the page on earlier versions and the specifications and limits page.

For the records, see the MS-XLS BOF page and the VBA storage page. For repair, see Microsoft's steps to repair a workbook. For the tools, see olefile. The test files come from the Apache POI repository.

Frequently Asked Questions About XLS Files

What is an XLS file?

An XLS file is an Excel workbook saved in the binary format of Excel 97 to Excel 2003. Microsoft calls it BIFF8. It stores sheets, cell values, formulas and charts in one file, and Excel 2007 and later open it in Compatibility Mode.

How do I open an XLS file?

Double-click it to open it in Excel. Without Excel, LibreOffice opened 96 of our 100 files, xlrd 95 and xls2csv 86. FileInfo lists Google Sheets and others too. We did not test Excel or Google Sheets.

How do I open an XLS file without Excel?

Use LibreOffice, or read it with xlrd or xls2csv. xlrd lists every sheet with its size. xls2csv writes every sheet as text and skips empty rows. LibreOffice writes the first sheet only unless you add a filter token.

How do I convert XLS to XLSX?

In Excel, use File, Info, Compatibility Mode and Convert, or use Save As, per Microsoft. Without Excel, LibreOffice converted our example in one command. All 227 values matched, and 53 formulas stayed formulas. We did not test Excel.

How do I convert XLS to CSV?

Use LibreOffice or xls2csv. A CSV file keeps one sheet, and Microsoft says Excel saves only the active sheet. LibreOffice writes the first sheet by default, and a filter token ending in -1 writes every sheet to its own file.

What is the difference between XLS and XLSX?

XLS is the binary format of Excel 97 to 2003, and XLSX is the XML-based default since Excel 2007. XLS allows 65,536 rows by 256 columns, and current Excel allows 1,048,576 by 16,384. An XLSX file cannot store VBA macros.

Why does Excel say the file format and extension do not match?

The file was renamed by accident, on purpose or to mislead you, or it is corrupt, per Microsoft. Read the first bytes. One .xls file in our folder is a ZIP file. If you cannot verify who made the XLS file, do not open it.

Is an XLS file safe to open?

Looking at the bytes starts nothing. Opening the XLS file in Excel is the risk, because an XLS file can contain macros. 10 of our 100 files contain a VBA project storage. We scanned no file for malware and started no macro.

How do I repair a corrupt XLS file?

Microsoft says to use Open and Repair, then Repair, then Extract Data. Our repair script rewrites header fields and made 6 of 21 damaged copies match the original byte for byte. A cut file or a changed record does not come back.

How many rows can an XLS file have?

Excel 97-2003 supports 65,536 rows by 256 columns, per Microsoft, and data past that is not saved. Current Excel supports 1,048,576 rows by 16,384 columns. A row limit belongs to the Excel version and to the format.

What is Compatibility Mode?

Excel 2010 and later opens an Excel 97-2003 workbook in Compatibility Mode and shows the words in the title bar, per Microsoft. New features are not available. You cannot switch it on by hand, and converting the workbook ends it.

How can I check if an XLS file is damaged?

Read the first eight bytes, the version, the stream names and the first record, then open the XLS file with a second reader. Our script flagged 8 of 21 damaged copies, and none read as the original in all three readers. Use two readers.

Still Stuck With an XLS File? Where to go next, depending on what went wrong

Your XLS file will not open

Read the first bytes and use the check script. The cause list names what to try in order.

You do not know the kind

The kinds table names the kind, and the neighbors name the formats that look alike.

Is it a Word file? The DOC file overview covers the Word document.

Is it a slide deck? The PPT file overview covers the PowerPoint file.

Is it an archive? The ZIP file overview covers the compressed archive.

Is it an Excel workspace? The XLW file overview covers the workspace file.

Is it a file type you do not know? Browse the file extension resource library, or send it on the submit a file type page.

Do you need the format facts? Read the format part for the layout, the records and the sources of XLS files.