[{"data":1,"prerenderedAt":2048},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-index-0-is-invalid-0-worksheets-found":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-index-0-is-invalid-0-worksheets-found":2013,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-index-0-is-invalid-0-worksheets-found":2021,"children:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-index-0-is-invalid-0-worksheets-found":2026,"related:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-index-0-is-invalid-0-worksheets-found":2027},{"id":4,"title":5,"body":6,"dateModified":1979,"datePublished":1979,"description":1980,"extension":1981,"faq":1982,"keyword":1991,"kind":1992,"meta":1993,"navigation":134,"path":2002,"seo":2003,"slug":2006,"stem":2007,"topics":2008,"type":2011,"__hash__":2012},"docs\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-index-0-is-invalid-0-worksheets-found\u002Findex.md","Fix \"Worksheet index 0 is invalid, 0 worksheets found\"",{"type":7,"value":8,"toc":1966},"minimark",[9,13,24,33,38,67,73,77,84,281,303,363,374,378,397,655,666,847,854,857,861,864,898,909,913,916,1256,1267,1349,1353,1359,1413,1429,1432,1436,1439,1703,1710,1839,1843,1880,1884,1898,1902,1908,1914,1920,1926,1930,1962],[10,11,12],"p",{},"A file opens fine in Excel, but pandas refuses it:",[14,15,21],"pre",{"className":16,"code":18,"language":19,"meta":20},[17],"language-text","ValueError: Worksheet index 0 is invalid, 0 worksheets found\n","text","",[22,23,18],"code",{"__ignoreMap":20},[10,25,26,27,32],{},"pandas asked openpyxl for the first worksheet and openpyxl reported that the workbook has none. The data is there; openpyxl could not find it. This almost always happens with files produced by some other system — an ERP export, a reporting tool, a web application's \"download as Excel\" — whose internal file paths are written in a way Excel tolerates and openpyxl does not. This page shows how to confirm that, and three ways around it. It is part of ",[28,29,31],"a",{"href":30},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002F","Troubleshooting Common Python Excel Errors",".",[34,35,37],"h2",{"id":36},"prerequisites","Prerequisites",[14,39,43],{"className":40,"code":41,"language":42,"meta":20,"style":20},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl python-calamine\n","bash",[22,44,45],{"__ignoreMap":20},[46,47,50,54,58,61,64],"span",{"class":48,"line":49},"line",1,[46,51,53],{"class":52},"sMTad","pip",[46,55,57],{"class":56},"srMev"," install",[46,59,60],{"class":56}," pandas",[46,62,63],{"class":56}," openpyxl",[46,65,66],{"class":56}," python-calamine\n",[10,68,69,72],{},[22,70,71],{},"erp_export.xlsx"," stands in for the problem file: it opens in Excel and LibreOffice with a three-row sales table, and fails in pandas with the default engine.",[34,74,76],{"id":75},"step-1-reproduce-and-confirm-it-is-no-worksheets-not-a-bad-file","Step 1 — Reproduce and confirm it is \"no worksheets\", not a bad file",[10,78,79,80,83],{},"First make sure the file is a real ",[22,81,82],{},".xlsx"," and see what openpyxl finds in it:",[14,85,89],{"className":86,"code":87,"language":88,"meta":20,"style":20},"language-python shiki shiki-themes github-light github-dark-high-contrast","import zipfile\nimport pandas as pd\nfrom openpyxl import load_workbook\n\nprint(\"zip archive:\", zipfile.is_zipfile(\"erp_export.xlsx\"))\ntry:\n    pd.read_excel(\"erp_export.xlsx\")\nexcept ValueError as e:\n    print(\"pandas:\", e)\nwb = load_workbook(\"erp_export.xlsx\")\nprint(\"openpyxl sheet names:\", wb.sheetnames)\nprint(\"calamine sees:\", list(pd.read_excel(\"erp_export.xlsx\", sheet_name=None, engine=\"calamine\")))\n","python",[22,90,91,101,115,129,136,158,167,178,193,207,223,236],{"__ignoreMap":20},[46,92,93,97],{"class":48,"line":49},[46,94,96],{"class":95},"s-kum","import",[46,98,100],{"class":99},"skGVy"," zipfile\n",[46,102,104,106,109,112],{"class":48,"line":103},2,[46,105,96],{"class":95},[46,107,108],{"class":99}," pandas ",[46,110,111],{"class":95},"as",[46,113,114],{"class":99}," pd\n",[46,116,118,121,124,126],{"class":48,"line":117},3,[46,119,120],{"class":95},"from",[46,122,123],{"class":99}," openpyxl ",[46,125,96],{"class":95},[46,127,128],{"class":99}," load_workbook\n",[46,130,132],{"class":48,"line":131},4,[46,133,135],{"emptyLinePlaceholder":134},true,"\n",[46,137,139,143,146,149,152,155],{"class":48,"line":138},5,[46,140,142],{"class":141},"sP0c6","print",[46,144,145],{"class":99},"(",[46,147,148],{"class":56},"\"zip archive:\"",[46,150,151],{"class":99},", zipfile.is_zipfile(",[46,153,154],{"class":56},"\"erp_export.xlsx\"",[46,156,157],{"class":99},"))\n",[46,159,161,164],{"class":48,"line":160},6,[46,162,163],{"class":95},"try",[46,165,166],{"class":99},":\n",[46,168,170,173,175],{"class":48,"line":169},7,[46,171,172],{"class":99},"    pd.read_excel(",[46,174,154],{"class":56},[46,176,177],{"class":99},")\n",[46,179,181,184,187,190],{"class":48,"line":180},8,[46,182,183],{"class":95},"except",[46,185,186],{"class":141}," ValueError",[46,188,189],{"class":95}," as",[46,191,192],{"class":99}," e:\n",[46,194,196,199,201,204],{"class":48,"line":195},9,[46,197,198],{"class":141},"    print",[46,200,145],{"class":99},[46,202,203],{"class":56},"\"pandas:\"",[46,205,206],{"class":99},", e)\n",[46,208,210,213,216,219,221],{"class":48,"line":209},10,[46,211,212],{"class":99},"wb ",[46,214,215],{"class":95},"=",[46,217,218],{"class":99}," load_workbook(",[46,220,154],{"class":56},[46,222,177],{"class":99},[46,224,226,228,230,233],{"class":48,"line":225},11,[46,227,142],{"class":141},[46,229,145],{"class":99},[46,231,232],{"class":56},"\"openpyxl sheet names:\"",[46,234,235],{"class":99},", wb.sheetnames)\n",[46,237,239,241,243,246,249,252,255,257,259,263,265,268,270,273,275,278],{"class":48,"line":238},12,[46,240,142],{"class":141},[46,242,145],{"class":99},[46,244,245],{"class":56},"\"calamine sees:\"",[46,247,248],{"class":99},", ",[46,250,251],{"class":141},"list",[46,253,254],{"class":99},"(pd.read_excel(",[46,256,154],{"class":56},[46,258,248],{"class":99},[46,260,262],{"class":261},"sa561","sheet_name",[46,264,215],{"class":95},[46,266,267],{"class":141},"None",[46,269,248],{"class":99},[46,271,272],{"class":261},"engine",[46,274,215],{"class":95},[46,276,277],{"class":56},"\"calamine\"",[46,279,280],{"class":99},")))\n",[10,282,283,284,287,288,291,292,295,296,298,299,302],{},"The same root cause produces two other messages, depending on how the file is read. With ",[22,285,286],{},"sheet_name=None",", asking for every sheet, pandas raises ",[22,289,290],{},"ValueError: Sheet name is an empty list",". With openpyxl directly, ",[22,293,294],{},"wb.active"," is ",[22,297,267],{},", and the first attribute access fails with ",[22,300,301],{},"AttributeError: 'NoneType' object has no attribute 'max_row'",", which looks like a bug in your own code rather than in the file:",[14,304,306],{"className":86,"code":305,"language":88,"meta":20,"style":20},"print(repr(wb.active))\ntry:\n    pd.read_excel(\"erp_export.xlsx\", sheet_name=None)\nexcept ValueError as e:\n    print(\"sheet_name=None:\", e)\n",[22,307,308,320,326,342,352],{"__ignoreMap":20},[46,309,310,312,314,317],{"class":48,"line":49},[46,311,142],{"class":141},[46,313,145],{"class":99},[46,315,316],{"class":141},"repr",[46,318,319],{"class":99},"(wb.active))\n",[46,321,322,324],{"class":48,"line":103},[46,323,163],{"class":95},[46,325,166],{"class":99},[46,327,328,330,332,334,336,338,340],{"class":48,"line":117},[46,329,172],{"class":99},[46,331,154],{"class":56},[46,333,248],{"class":99},[46,335,262],{"class":261},[46,337,215],{"class":95},[46,339,267],{"class":141},[46,341,177],{"class":99},[46,343,344,346,348,350],{"class":48,"line":131},[46,345,183],{"class":95},[46,347,186],{"class":141},[46,349,189],{"class":95},[46,351,192],{"class":99},[46,353,354,356,358,361],{"class":48,"line":138},[46,355,198],{"class":141},[46,357,145],{"class":99},[46,359,360],{"class":56},"\"sheet_name=None:\"",[46,362,206],{"class":99},[10,364,365,366,369,370,32],{},"The file is a valid zip, openpyxl loads it without an error, and still lists no sheets, while a different reader sees ",[22,367,368],{},"Sheet1",". That combination points at how openpyxl locates sheets. If the file is not a zip at all, the problem is different: see ",[28,371,373],{"href":372},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-badzipfile-error-when-reading-excel-in-python\u002F","Fix BadZipFile When Reading an Excel File in Python",[34,375,377],{"id":376},"step-2-compare-the-relationships-with-the-zip-contents","Step 2 — Compare the relationships with the zip contents",[10,379,380,381,383,384,387,388,391,392,396],{},"An ",[22,382,82],{}," file is a zip of XML parts. ",[22,385,386],{},"xl\u002Fworkbook.xml"," lists the sheets; ",[22,389,390],{},"xl\u002F_rels\u002Fworkbook.xml.rels"," maps each sheet to the path of its part inside the zip. openpyxl looks up each path ",[393,394,395],"em",{},"exactly",", and silently skips any sheet whose path does not match a member of the archive. Excel matches paths without regard to case. Print both sides:",[14,398,400],{"className":86,"code":399,"language":88,"meta":20,"style":20},"import posixpath\nimport re\n\nwith zipfile.ZipFile(\"erp_export.xlsx\") as z:\n    names = z.namelist()\n    rels = z.read(\"xl\u002F_rels\u002Fworkbook.xml.rels\").decode()\ntargets = re.findall(r'Target=\"([^\"]+)\"', rels)\nfor t in targets:\n    path = posixpath.normpath(posixpath.join(\"xl\", t)) if not t.startswith(\"\u002F\") else t.lstrip(\"\u002F\")\n    exact = path in names\n    loose = [n for n in names if n.lower() == path.lower()]\n    print(f\"{t:26} exact match: {exact!s:5}  case-insensitive match: {loose}\")\n",[22,401,402,409,416,420,438,448,464,505,519,559,574,605],{"__ignoreMap":20},[46,403,404,406],{"class":48,"line":49},[46,405,96],{"class":95},[46,407,408],{"class":99}," posixpath\n",[46,410,411,413],{"class":48,"line":103},[46,412,96],{"class":95},[46,414,415],{"class":99}," re\n",[46,417,418],{"class":48,"line":117},[46,419,135],{"emptyLinePlaceholder":134},[46,421,422,425,428,430,433,435],{"class":48,"line":131},[46,423,424],{"class":95},"with",[46,426,427],{"class":99}," zipfile.ZipFile(",[46,429,154],{"class":56},[46,431,432],{"class":99},") ",[46,434,111],{"class":95},[46,436,437],{"class":99}," z:\n",[46,439,440,443,445],{"class":48,"line":138},[46,441,442],{"class":99},"    names ",[46,444,215],{"class":95},[46,446,447],{"class":99}," z.namelist()\n",[46,449,450,453,455,458,461],{"class":48,"line":160},[46,451,452],{"class":99},"    rels ",[46,454,215],{"class":95},[46,456,457],{"class":99}," z.read(",[46,459,460],{"class":56},"\"xl\u002F_rels\u002Fworkbook.xml.rels\"",[46,462,463],{"class":99},").decode()\n",[46,465,466,469,471,474,477,480,483,486,490,493,496,499,502],{"class":48,"line":169},[46,467,468],{"class":99},"targets ",[46,470,215],{"class":95},[46,472,473],{"class":99}," re.findall(",[46,475,476],{"class":95},"r",[46,478,479],{"class":56},"'Target=\"",[46,481,482],{"class":141},"([",[46,484,485],{"class":95},"^",[46,487,489],{"class":488},"sSjpA","\"",[46,491,492],{"class":141},"]",[46,494,495],{"class":95},"+",[46,497,498],{"class":141},")",[46,500,501],{"class":56},"\"'",[46,503,504],{"class":99},", rels)\n",[46,506,507,510,513,516],{"class":48,"line":180},[46,508,509],{"class":95},"for",[46,511,512],{"class":99}," t ",[46,514,515],{"class":95},"in",[46,517,518],{"class":99}," targets:\n",[46,520,521,524,526,529,532,535,538,541,544,547,549,552,555,557],{"class":48,"line":195},[46,522,523],{"class":99},"    path ",[46,525,215],{"class":95},[46,527,528],{"class":99}," posixpath.normpath(posixpath.join(",[46,530,531],{"class":56},"\"xl\"",[46,533,534],{"class":99},", t)) ",[46,536,537],{"class":95},"if",[46,539,540],{"class":95}," not",[46,542,543],{"class":99}," t.startswith(",[46,545,546],{"class":56},"\"\u002F\"",[46,548,432],{"class":99},[46,550,551],{"class":95},"else",[46,553,554],{"class":99}," t.lstrip(",[46,556,546],{"class":56},[46,558,177],{"class":99},[46,560,561,564,566,569,571],{"class":48,"line":209},[46,562,563],{"class":99},"    exact ",[46,565,215],{"class":95},[46,567,568],{"class":99}," path ",[46,570,515],{"class":95},[46,572,573],{"class":99}," names\n",[46,575,576,579,581,584,586,589,591,594,596,599,602],{"class":48,"line":225},[46,577,578],{"class":99},"    loose ",[46,580,215],{"class":95},[46,582,583],{"class":99}," [n ",[46,585,509],{"class":95},[46,587,588],{"class":99}," n ",[46,590,515],{"class":95},[46,592,593],{"class":99}," names ",[46,595,537],{"class":95},[46,597,598],{"class":99}," n.lower() ",[46,600,601],{"class":95},"==",[46,603,604],{"class":99}," path.lower()]\n",[46,606,607,609,611,614,616,619,622,625,628,631,633,636,639,641,644,646,649,651,653],{"class":48,"line":238},[46,608,198],{"class":141},[46,610,145],{"class":99},[46,612,613],{"class":95},"f",[46,615,489],{"class":56},[46,617,618],{"class":488},"{",[46,620,621],{"class":99},"t",[46,623,624],{"class":95},":26",[46,626,627],{"class":488},"}",[46,629,630],{"class":56}," exact match: ",[46,632,618],{"class":488},[46,634,635],{"class":99},"exact",[46,637,638],{"class":95},"!s:5",[46,640,627],{"class":488},[46,642,643],{"class":56},"  case-insensitive match: ",[46,645,618],{"class":488},[46,647,648],{"class":99},"loose",[46,650,627],{"class":488},[46,652,489],{"class":56},[46,654,177],{"class":99},[10,656,657,658,661,662,665],{},"The relationship points at ",[22,659,660],{},"Worksheets\u002FSheet1.xml","; the archive contains ",[22,663,664],{},"xl\u002Fworksheets\u002Fsheet1.xml",". Excel resolves that; openpyxl does not, so the sheet is skipped and the workbook appears to have none.",[667,668,676,677,676,681,676,685,676,693,676,703,676,714,676,720,676,725,676,729,676,735,676,740,676,744,676,751,676,756,676,759,676,762,676,766,676,769,676,774,676,781,676,787,676,791,676,795,676,798,676,803,676,810,676,815,676,818,676,821,676,825,676,828,676,831,676,837,676,841,676,844],"svg",{"viewBox":669,"role":670,"ariaLabelledBy":671,"xmlns":674,"style":675},"4 0 752 142","img",[672,673],"ws-lookup-t","ws-lookup-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[678,679,680],"title",{"id":672},"How openpyxl finds a sheet, and where it loses it",[682,683,684],"desc",{"id":673},"workbook.xml names the sheet, the relationships file gives its path, and openpyxl looks for that exact path in the zip. The relationship says Worksheets\u002FSheet1.xml, the zip holds worksheets\u002Fsheet1.xml, the exact lookup fails, and the sheet is skipped without a warning.",[686,687],"rect",{"x":688,"y":689,"width":690,"height":691,"fill":692},"4","0","752","142","#ffffff",[686,694],{"x":695,"y":696,"width":697,"height":698,"rx":699,"fill":700,"stroke":701,"style":702},"20.0","16.0","115.2","110.5","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[704,705,707],"g",{"transform":706},"translate(65.6,29.0) scale(1.000)",[708,709],"path",{"d":710,"fill":711,"stroke":712,"style":713},"M6 3h9l4 4v14H6z M15 3v4h4","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[19,715,719],{"x":716,"y":717,"style":718},"77.6","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","workbook.xml",[19,721,724],{"x":716,"y":722,"style":723},"95.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","sheet Sheet1",[19,726,728],{"x":716,"y":727,"style":723},"112.5","rId1",[48,730],{"x1":731,"y1":732,"x2":733,"y2":732,"stroke":734,"style":702},"140.2","71.2","162.2","var(--brand,#5b5cf0)",[736,737],"polygon",{"points":738,"fill":739},"166.2,71.2 157.2,76.2 157.2,66.2","#5b5cf0",[686,741],{"x":742,"y":696,"width":697,"height":698,"rx":699,"fill":743,"stroke":734,"style":702},"171.2","#f0f4ff",[704,745,747],{"transform":746},"translate(216.8,29.0) scale(1.000)",[708,748],{"d":749,"fill":711,"stroke":750,"style":713},"M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1 M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1","var(--brand-strong,#4338ca)",[19,752,755],{"x":753,"y":717,"style":754},"228.8","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Relationships",[19,757,758],{"x":753,"y":722,"style":723},"points to",[19,760,761],{"x":753,"y":727,"style":723},"Worksheets\u002F…",[48,763],{"x1":764,"y1":732,"x2":765,"y2":732,"stroke":734,"style":702},"291.4","313.4",[736,767],{"points":768,"fill":739},"317.4,71.2 308.4,76.2 308.4,66.2",[686,770],{"x":771,"y":696,"width":697,"height":698,"rx":699,"fill":772,"stroke":773,"style":702},"322.4","#fdefd8","var(--gold,#b4740a)",[704,775,777],{"transform":776},"translate(368.0,37.8) scale(1.000)",[708,778],{"d":779,"fill":711,"stroke":780,"style":713},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5","var(--gold-ink,#7a4e06)",[19,782,786],{"x":783,"y":784,"style":785},"380.0","84.2","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Exact lookup",[19,788,790],{"x":783,"y":789,"style":723},"103.8","in the zip",[48,792],{"x1":793,"y1":732,"x2":794,"y2":732,"stroke":734,"style":702},"442.6","464.6",[736,796],{"points":797,"fill":739},"468.6,71.2 459.6,76.2 459.6,66.2",[686,799],{"x":800,"y":696,"width":697,"height":698,"rx":699,"fill":801,"stroke":802,"style":702},"473.6","#fce9e9","var(--danger,#dc2626)",[704,804,806],{"transform":805},"translate(519.2,29.0) scale(1.000)",[708,807],{"d":808,"fill":711,"stroke":809,"style":713},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--accent-ink,#be185d)",[19,811,814],{"x":812,"y":717,"style":813},"531.2","font-size:13.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Not found",[19,816,817],{"x":812,"y":722,"style":723},"sheet skipped",[19,819,820],{"x":812,"y":727,"style":723},"silently",[48,822],{"x1":823,"y1":732,"x2":824,"y2":732,"stroke":734,"style":702},"593.8","615.8",[736,826],{"points":827,"fill":739},"619.8,71.2 610.8,76.2 610.8,66.2",[686,829],{"x":830,"y":696,"width":697,"height":698,"rx":699,"fill":801,"stroke":802,"style":702},"624.8",[704,832,834],{"transform":833},"translate(670.4,29.0) scale(1.000)",[708,835],{"d":836,"fill":711,"stroke":809,"style":713},"M12 3c-4 0-4 1.6-4 3v2h4.2v1H6c-2 0-3 1.4-3 4s1 4 3 4h2v-2.6c0-1.6 1.3-2.9 2.9-2.9h4.2c1.3 0 2.4-1.1 2.4-2.4V6c0-1.6-1.8-3-5.5-3z M10 5.5h.01 M12 21c4 0 4-1.6 4-3v-2h-4.2v-1H18c2 0 3-1.4 3-4s-1-4-3-4h-2v2.6c0 1.6-1.3 2.9-2.9 2.9H8.9c-1.3 0-2.4 1.1-2.4 2.4V18c0 1.6 1.8 3 5.5 3z M14 18.5h.01",[19,838,840],{"x":839,"y":717,"style":813},"682.4","pandas",[19,842,843],{"x":839,"y":722,"style":723},"0 worksheets",[19,845,846],{"x":839,"y":727,"style":723},"found",[10,848,849,850,853],{},"Why no warning? In openpyxl's reader, each sheet's path is checked against the list of files in the archive, and a sheet whose path is not in that list is skipped with a plain ",[22,851,852],{},"continue",". That behaviour makes sense for genuinely optional parts, but for a worksheet it turns a malformed file into an apparently empty one. Knowing this is the difference between an afternoon spent looking for data that \"vanished\" and a two-minute diagnosis.",[10,855,856],{},"Other mismatches produce the same symptom: backslashes in member names written by some Windows zip libraries, or a relationship pointing at a part that is missing altogether. The comparison above shows all of them.",[34,858,860],{"id":859},"fix-1-read-it-with-calamine","Fix 1 — Read it with calamine",[10,862,863],{},"The calamine engine resolves the parts differently and reads the file as it is. For reading data, this is the quickest fix:",[14,865,867],{"className":86,"code":866,"language":88,"meta":20,"style":20},"df = pd.read_excel(\"erp_export.xlsx\", engine=\"calamine\")\nprint(df)\n",[22,868,869,891],{"__ignoreMap":20},[46,870,871,874,876,879,881,883,885,887,889],{"class":48,"line":49},[46,872,873],{"class":99},"df ",[46,875,215],{"class":95},[46,877,878],{"class":99}," pd.read_excel(",[46,880,154],{"class":56},[46,882,248],{"class":99},[46,884,272],{"class":261},[46,886,215],{"class":95},[46,888,277],{"class":56},[46,890,177],{"class":99},[46,892,893,895],{"class":48,"line":103},[46,894,142],{"class":141},[46,896,897],{"class":99},"(df)\n",[10,899,900,901,904,905,32],{},"Nothing else in the code has to change, so this is also the least risky fix for a scheduled job that has just started failing on a new supplier file. calamine is also considerably faster than openpyxl on large files. It reads values only, not formatting, which is all ",[22,902,903],{},"read_excel"," needs anyway; see ",[28,906,908],{"href":907},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fpick-an-excel-engine-for-xlsx-xlsm-xls-xlsb-and-ods\u002F","Pick an Excel Engine for .xlsx, .xlsm, .xls, .xlsb and .ods",[34,910,912],{"id":911},"fix-2-repair-the-paths-in-the-zip","Fix 2 — Repair the paths in the zip",[10,914,915],{},"If the file must go through openpyxl, for example to keep formatting or edit it, rewrite the relationships so each target matches the archive member's actual name. The repair copies every part unchanged except the relationships files:",[14,917,919],{"className":86,"code":918,"language":88,"meta":20,"style":20},"def repair_part_names(src, dst):\n    with zipfile.ZipFile(src) as zi:\n        names = zi.namelist()\n        by_lower = {n.lower(): n for n in names}\n        with zipfile.ZipFile(dst, \"w\", zipfile.ZIP_DEFLATED) as zo:\n            for item in zi.infolist():\n                data = zi.read(item.filename)\n                if item.filename.endswith(\".rels\"):\n                    base = posixpath.dirname(posixpath.dirname(item.filename))\n                    def fix(m):\n                        target = m.group(1)\n                        full = posixpath.normpath(posixpath.join(base, target)) if not target.startswith(\"\u002F\") else target.lstrip(\"\u002F\")\n                        actual = by_lower.get(full.lower())\n                        if actual and actual != full:\n                            return f'Target=\"{posixpath.relpath(actual, base or \".\")}\"'\n                        return m.group(0)\n                    data = re.sub(r'Target=\"([^\"]+)\"', fix, data.decode()).encode()\n                zo.writestr(item, data)\n\nrepair_part_names(\"erp_export.xlsx\", \"erp_export_repaired.xlsx\")\nprint(load_workbook(\"erp_export_repaired.xlsx\").sheetnames)\nprint(pd.read_excel(\"erp_export_repaired.xlsx\"))\n",[22,920,921,933,946,956,975,999,1012,1022,1036,1046,1057,1072,1102,1113,1133,1162,1174,1206,1212,1217,1232,1245],{"__ignoreMap":20},[46,922,923,926,930],{"class":48,"line":49},[46,924,925],{"class":95},"def",[46,927,929],{"class":928},"s_Opv"," repair_part_names",[46,931,932],{"class":99},"(src, dst):\n",[46,934,935,938,941,943],{"class":48,"line":103},[46,936,937],{"class":95},"    with",[46,939,940],{"class":99}," zipfile.ZipFile(src) ",[46,942,111],{"class":95},[46,944,945],{"class":99}," zi:\n",[46,947,948,951,953],{"class":48,"line":117},[46,949,950],{"class":99},"        names ",[46,952,215],{"class":95},[46,954,955],{"class":99}," zi.namelist()\n",[46,957,958,961,963,966,968,970,972],{"class":48,"line":131},[46,959,960],{"class":99},"        by_lower ",[46,962,215],{"class":95},[46,964,965],{"class":99}," {n.lower(): n ",[46,967,509],{"class":95},[46,969,588],{"class":99},[46,971,515],{"class":95},[46,973,974],{"class":99}," names}\n",[46,976,977,980,983,986,989,992,994,996],{"class":48,"line":138},[46,978,979],{"class":95},"        with",[46,981,982],{"class":99}," zipfile.ZipFile(dst, ",[46,984,985],{"class":56},"\"w\"",[46,987,988],{"class":99},", zipfile.",[46,990,991],{"class":141},"ZIP_DEFLATED",[46,993,432],{"class":99},[46,995,111],{"class":95},[46,997,998],{"class":99}," zo:\n",[46,1000,1001,1004,1007,1009],{"class":48,"line":160},[46,1002,1003],{"class":95},"            for",[46,1005,1006],{"class":99}," item ",[46,1008,515],{"class":95},[46,1010,1011],{"class":99}," zi.infolist():\n",[46,1013,1014,1017,1019],{"class":48,"line":169},[46,1015,1016],{"class":99},"                data ",[46,1018,215],{"class":95},[46,1020,1021],{"class":99}," zi.read(item.filename)\n",[46,1023,1024,1027,1030,1033],{"class":48,"line":180},[46,1025,1026],{"class":95},"                if",[46,1028,1029],{"class":99}," item.filename.endswith(",[46,1031,1032],{"class":56},"\".rels\"",[46,1034,1035],{"class":99},"):\n",[46,1037,1038,1041,1043],{"class":48,"line":195},[46,1039,1040],{"class":99},"                    base ",[46,1042,215],{"class":95},[46,1044,1045],{"class":99}," posixpath.dirname(posixpath.dirname(item.filename))\n",[46,1047,1048,1051,1054],{"class":48,"line":209},[46,1049,1050],{"class":95},"                    def",[46,1052,1053],{"class":928}," fix",[46,1055,1056],{"class":99},"(m):\n",[46,1058,1059,1062,1064,1067,1070],{"class":48,"line":225},[46,1060,1061],{"class":99},"                        target ",[46,1063,215],{"class":95},[46,1065,1066],{"class":99}," m.group(",[46,1068,1069],{"class":141},"1",[46,1071,177],{"class":99},[46,1073,1074,1077,1079,1082,1084,1086,1089,1091,1093,1095,1098,1100],{"class":48,"line":238},[46,1075,1076],{"class":99},"                        full ",[46,1078,215],{"class":95},[46,1080,1081],{"class":99}," posixpath.normpath(posixpath.join(base, target)) ",[46,1083,537],{"class":95},[46,1085,540],{"class":95},[46,1087,1088],{"class":99}," target.startswith(",[46,1090,546],{"class":56},[46,1092,432],{"class":99},[46,1094,551],{"class":95},[46,1096,1097],{"class":99}," target.lstrip(",[46,1099,546],{"class":56},[46,1101,177],{"class":99},[46,1103,1105,1108,1110],{"class":48,"line":1104},13,[46,1106,1107],{"class":99},"                        actual ",[46,1109,215],{"class":95},[46,1111,1112],{"class":99}," by_lower.get(full.lower())\n",[46,1114,1116,1119,1122,1125,1127,1130],{"class":48,"line":1115},14,[46,1117,1118],{"class":95},"                        if",[46,1120,1121],{"class":99}," actual ",[46,1123,1124],{"class":95},"and",[46,1126,1121],{"class":99},[46,1128,1129],{"class":95},"!=",[46,1131,1132],{"class":99}," full:\n",[46,1134,1136,1139,1142,1144,1146,1149,1152,1155,1157,1159],{"class":48,"line":1135},15,[46,1137,1138],{"class":95},"                            return",[46,1140,1141],{"class":95}," f",[46,1143,479],{"class":56},[46,1145,618],{"class":488},[46,1147,1148],{"class":99},"posixpath.relpath(actual, base ",[46,1150,1151],{"class":95},"or",[46,1153,1154],{"class":56}," \".\"",[46,1156,498],{"class":99},[46,1158,627],{"class":488},[46,1160,1161],{"class":56},"\"'\n",[46,1163,1165,1168,1170,1172],{"class":48,"line":1164},16,[46,1166,1167],{"class":95},"                        return",[46,1169,1066],{"class":99},[46,1171,689],{"class":141},[46,1173,177],{"class":99},[46,1175,1177,1180,1182,1185,1187,1189,1191,1193,1195,1197,1199,1201,1203],{"class":48,"line":1176},17,[46,1178,1179],{"class":99},"                    data ",[46,1181,215],{"class":95},[46,1183,1184],{"class":99}," re.sub(",[46,1186,476],{"class":95},[46,1188,479],{"class":56},[46,1190,482],{"class":141},[46,1192,485],{"class":95},[46,1194,489],{"class":488},[46,1196,492],{"class":141},[46,1198,495],{"class":95},[46,1200,498],{"class":141},[46,1202,501],{"class":56},[46,1204,1205],{"class":99},", fix, data.decode()).encode()\n",[46,1207,1209],{"class":48,"line":1208},18,[46,1210,1211],{"class":99},"                zo.writestr(item, data)\n",[46,1213,1215],{"class":48,"line":1214},19,[46,1216,135],{"emptyLinePlaceholder":134},[46,1218,1220,1223,1225,1227,1230],{"class":48,"line":1219},20,[46,1221,1222],{"class":99},"repair_part_names(",[46,1224,154],{"class":56},[46,1226,248],{"class":99},[46,1228,1229],{"class":56},"\"erp_export_repaired.xlsx\"",[46,1231,177],{"class":99},[46,1233,1235,1237,1240,1242],{"class":48,"line":1234},21,[46,1236,142],{"class":141},[46,1238,1239],{"class":99},"(load_workbook(",[46,1241,1229],{"class":56},[46,1243,1244],{"class":99},").sheetnames)\n",[46,1246,1248,1250,1252,1254],{"class":48,"line":1247},22,[46,1249,142],{"class":141},[46,1251,254],{"class":99},[46,1253,1229],{"class":56},[46,1255,157],{"class":99},[10,1257,1258,1259,1262,1263,1266],{},"Relationship targets are relative to the folder above the ",[22,1260,1261],{},"_rels"," folder that contains them, which is what ",[22,1264,1265],{},"base"," computes; external links such as hyperlinks do not match any member and are left alone.",[667,1268,676,1274,676,1277,676,1280,676,1285,676,1291,676,1295,676,1301,676,1305,676,1309,676,1313,676,1319,676,1321,676,1323,676,1325,676,1329,676,1332,676,1336,676,1338,676,1341,676,1345],{"viewBox":1269,"role":670,"ariaLabelledBy":1270,"xmlns":674,"style":1273},"206 0 347 116",[1271,1272],"ws-repair-t","ws-repair-d","width:100%;max-width:347px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[678,1275,1276],{"id":1271},"The relationship before and after repair",[682,1278,1279],{"id":1272},"A table comparing the relationship target with the archive member. Before repair the target Worksheets\u002FSheet1.xml does not match the member xl\u002Fworksheets\u002Fsheet1.xml exactly. After repair the target is worksheets\u002Fsheet1.xml and matches.",[686,1281],{"x":1282,"y":689,"width":1283,"height":1284,"fill":692},"206","347","116",[686,1286],{"x":1287,"y":696,"width":1288,"height":1289,"rx":689,"fill":743,"stroke":734,"style":1290},"222.8","61.8","28.0","stroke-width:1.5px",[686,1292],{"x":1293,"y":696,"width":1294,"height":1289,"rx":689,"fill":743,"stroke":734,"style":1290},"284.6","159.0",[19,1296,1300],{"x":1297,"y":1298,"style":1299},"364.1","35.0","font-size:11px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Target in .rels",[686,1302],{"x":1303,"y":696,"width":1304,"height":1289,"rx":689,"fill":743,"stroke":734,"style":1290},"443.6","93.6",[19,1306,1308],{"x":1307,"y":1298,"style":1299},"490.4","Exact match",[686,1310],{"x":1287,"y":1311,"width":1288,"height":1289,"rx":689,"fill":692,"stroke":701,"style":1312},"44.0","stroke-width:1px",[19,1314,1318],{"x":1315,"y":1316,"style":1317},"253.7","63.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Before",[686,1320],{"x":1293,"y":1311,"width":1294,"height":1289,"rx":689,"fill":692,"stroke":701,"style":1312},[19,1322,660],{"x":1297,"y":1316,"style":1317},[686,1324],{"x":1303,"y":1311,"width":1304,"height":1289,"rx":689,"fill":801,"stroke":802,"style":1290},[19,1326,1328],{"x":1307,"y":1316,"style":1327},"font-size:11px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","no",[686,1330],{"x":1287,"y":1331,"width":1288,"height":1289,"rx":689,"fill":692,"stroke":701,"style":1312},"72.0",[19,1333,1335],{"x":1315,"y":1334,"style":1317},"91.0","After",[686,1337],{"x":1293,"y":1331,"width":1294,"height":1289,"rx":689,"fill":692,"stroke":701,"style":1312},[19,1339,1340],{"x":1297,"y":1334,"style":1317},"worksheets\u002Fsheet1.xml",[686,1342],{"x":1303,"y":1331,"width":1304,"height":1289,"rx":689,"fill":1343,"stroke":1344,"style":1290},"#d9f4f1","var(--teal,#0f9488)",[19,1346,1348],{"x":1307,"y":1334,"style":1347},"font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","yes",[34,1350,1352],{"id":1351},"fix-3-let-libreoffice-rewrite-the-file","Fix 3 — Let LibreOffice rewrite the file",[10,1354,1355,1356,1358],{},"When there are many such files and they need to be ordinary ",[22,1357,82],{}," files for other tools, a headless LibreOffice conversion writes a clean file with standard part names:",[14,1360,1362],{"className":40,"code":1361,"language":42,"meta":20,"style":20},"soffice -env:UserInstallation=file:\u002F\u002F$PWD\u002Flo_profile --headless --convert-to xlsx --outdir converted erp_export.xlsx > \u002Fdev\u002Fnull 2>&1\nls converted\n",[22,1363,1364,1405],{"__ignoreMap":20},[46,1365,1366,1369,1372,1375,1378,1381,1384,1387,1390,1393,1396,1399,1402],{"class":48,"line":49},[46,1367,1368],{"class":52},"soffice",[46,1370,1371],{"class":141}," -env:UserInstallation=file:\u002F\u002F",[46,1373,1374],{"class":99},"$PWD",[46,1376,1377],{"class":141},"\u002Flo_profile",[46,1379,1380],{"class":141}," --headless",[46,1382,1383],{"class":141}," --convert-to",[46,1385,1386],{"class":56}," xlsx",[46,1388,1389],{"class":141}," --outdir",[46,1391,1392],{"class":56}," converted",[46,1394,1395],{"class":56}," erp_export.xlsx",[46,1397,1398],{"class":95}," >",[46,1400,1401],{"class":56}," \u002Fdev\u002Fnull",[46,1403,1404],{"class":95}," 2>&1\n",[46,1406,1407,1410],{"class":48,"line":103},[46,1408,1409],{"class":52},"ls",[46,1411,1412],{"class":56}," converted\n",[14,1414,1416],{"className":86,"code":1415,"language":88,"meta":20,"style":20},"print(load_workbook(\"converted\u002Ferp_export.xlsx\").sheetnames)\n",[22,1417,1418],{"__ignoreMap":20},[46,1419,1420,1422,1424,1427],{"class":48,"line":49},[46,1421,142],{"class":141},[46,1423,1239],{"class":99},[46,1425,1426],{"class":56},"\"converted\u002Ferp_export.xlsx\"",[46,1428,1244],{"class":99},[10,1430,1431],{},"Conversion rewrites the whole file, so check that formatting and formulas you care about survive; for a data export they nearly always do.",[34,1433,1435],{"id":1434},"checking-a-folder-of-exports","Checking a folder of exports",[10,1437,1438],{},"When a supplier's system produces these files, every export has the same defect, and a pipeline that processes a folder should detect it up front rather than fail on the first file. Wrap the comparison from Step 2 in a function that returns the mismatched targets, and run it over the folder before reading anything:",[14,1440,1442],{"className":86,"code":1441,"language":88,"meta":20,"style":20},"from pathlib import Path\n\ndef mismatched_parts(path):\n    with zipfile.ZipFile(path) as z:\n        names = set(z.namelist())\n        rels = z.read(\"xl\u002F_rels\u002Fworkbook.xml.rels\").decode()\n    bad = []\n    for t in re.findall(r'Target=\"([^\"]+)\"', rels):\n        full = t.lstrip(\"\u002F\") if t.startswith(\"\u002F\") else posixpath.normpath(posixpath.join(\"xl\", t))\n        if full not in names:\n            bad.append(t)\n    return bad\n\nfor f in sorted(Path(\".\").glob(\"*.xlsx\")):\n    problems = mismatched_parts(f)\n    print(f\"{f.name:28} {'ok' if not problems else 'mismatched: ' + ', '.join(problems)}\")\n",[22,1443,1444,1456,1460,1470,1481,1493,1506,1516,1548,1578,1595,1600,1608,1612,1639,1649],{"__ignoreMap":20},[46,1445,1446,1448,1451,1453],{"class":48,"line":49},[46,1447,120],{"class":95},[46,1449,1450],{"class":99}," pathlib ",[46,1452,96],{"class":95},[46,1454,1455],{"class":99}," Path\n",[46,1457,1458],{"class":48,"line":103},[46,1459,135],{"emptyLinePlaceholder":134},[46,1461,1462,1464,1467],{"class":48,"line":117},[46,1463,925],{"class":95},[46,1465,1466],{"class":928}," mismatched_parts",[46,1468,1469],{"class":99},"(path):\n",[46,1471,1472,1474,1477,1479],{"class":48,"line":131},[46,1473,937],{"class":95},[46,1475,1476],{"class":99}," zipfile.ZipFile(path) ",[46,1478,111],{"class":95},[46,1480,437],{"class":99},[46,1482,1483,1485,1487,1490],{"class":48,"line":138},[46,1484,950],{"class":99},[46,1486,215],{"class":95},[46,1488,1489],{"class":141}," set",[46,1491,1492],{"class":99},"(z.namelist())\n",[46,1494,1495,1498,1500,1502,1504],{"class":48,"line":160},[46,1496,1497],{"class":99},"        rels ",[46,1499,215],{"class":95},[46,1501,457],{"class":99},[46,1503,460],{"class":56},[46,1505,463],{"class":99},[46,1507,1508,1511,1513],{"class":48,"line":169},[46,1509,1510],{"class":99},"    bad ",[46,1512,215],{"class":95},[46,1514,1515],{"class":99}," []\n",[46,1517,1518,1521,1523,1525,1527,1529,1531,1533,1535,1537,1539,1541,1543,1545],{"class":48,"line":180},[46,1519,1520],{"class":95},"    for",[46,1522,512],{"class":99},[46,1524,515],{"class":95},[46,1526,473],{"class":99},[46,1528,476],{"class":95},[46,1530,479],{"class":56},[46,1532,482],{"class":141},[46,1534,485],{"class":95},[46,1536,489],{"class":488},[46,1538,492],{"class":141},[46,1540,495],{"class":95},[46,1542,498],{"class":141},[46,1544,501],{"class":56},[46,1546,1547],{"class":99},", rels):\n",[46,1549,1550,1553,1555,1557,1559,1561,1563,1565,1567,1569,1571,1573,1575],{"class":48,"line":195},[46,1551,1552],{"class":99},"        full ",[46,1554,215],{"class":95},[46,1556,554],{"class":99},[46,1558,546],{"class":56},[46,1560,432],{"class":99},[46,1562,537],{"class":95},[46,1564,543],{"class":99},[46,1566,546],{"class":56},[46,1568,432],{"class":99},[46,1570,551],{"class":95},[46,1572,528],{"class":99},[46,1574,531],{"class":56},[46,1576,1577],{"class":99},", t))\n",[46,1579,1580,1583,1586,1589,1592],{"class":48,"line":209},[46,1581,1582],{"class":95},"        if",[46,1584,1585],{"class":99}," full ",[46,1587,1588],{"class":95},"not",[46,1590,1591],{"class":95}," in",[46,1593,1594],{"class":99}," names:\n",[46,1596,1597],{"class":48,"line":225},[46,1598,1599],{"class":99},"            bad.append(t)\n",[46,1601,1602,1605],{"class":48,"line":238},[46,1603,1604],{"class":95},"    return",[46,1606,1607],{"class":99}," bad\n",[46,1609,1610],{"class":48,"line":1104},[46,1611,135],{"emptyLinePlaceholder":134},[46,1613,1614,1616,1619,1621,1624,1627,1630,1633,1636],{"class":48,"line":1115},[46,1615,509],{"class":95},[46,1617,1618],{"class":99}," f ",[46,1620,515],{"class":95},[46,1622,1623],{"class":141}," sorted",[46,1625,1626],{"class":99},"(Path(",[46,1628,1629],{"class":56},"\".\"",[46,1631,1632],{"class":99},").glob(",[46,1634,1635],{"class":56},"\"*.xlsx\"",[46,1637,1638],{"class":99},")):\n",[46,1640,1641,1644,1646],{"class":48,"line":1135},[46,1642,1643],{"class":99},"    problems ",[46,1645,215],{"class":95},[46,1647,1648],{"class":99}," mismatched_parts(f)\n",[46,1650,1651,1653,1655,1657,1659,1661,1664,1667,1669,1672,1675,1678,1680,1683,1685,1688,1691,1694,1697,1699,1701],{"class":48,"line":1164},[46,1652,198],{"class":141},[46,1654,145],{"class":99},[46,1656,613],{"class":95},[46,1658,489],{"class":56},[46,1660,618],{"class":488},[46,1662,1663],{"class":99},"f.name",[46,1665,1666],{"class":95},":28",[46,1668,627],{"class":488},[46,1670,1671],{"class":488}," {",[46,1673,1674],{"class":56},"'ok'",[46,1676,1677],{"class":95}," if",[46,1679,540],{"class":95},[46,1681,1682],{"class":99}," problems ",[46,1684,551],{"class":95},[46,1686,1687],{"class":56}," 'mismatched: '",[46,1689,1690],{"class":95}," +",[46,1692,1693],{"class":56}," ', '",[46,1695,1696],{"class":99},".join(problems)",[46,1698,627],{"class":488},[46,1700,489],{"class":56},[46,1702,177],{"class":99},[10,1704,1705,1706,1709],{},"A pipeline can then route each file: straight to openpyxl when it is clean, through ",[22,1707,1708],{},"repair_part_names"," or calamine when it is not, and log which files needed help. That log is also the evidence to send to the supplier.",[667,1711,676,1716,676,1719,676,1722,676,1725,676,1729,676,1734,676,1737,676,1741,676,1745,676,1748,676,1751,676,1759,676,1764,676,1769,676,1776,676,1781,676,1785,676,1787,676,1790,676,1794,676,1797,676,1800,676,1806,676,1809,676,1812,676,1814,676,1817,676,1821,676,1824,676,1827,676,1833,676,1836],{"viewBox":1712,"role":670,"ariaLabelledBy":1713,"xmlns":674,"style":675},"4 0 752 272",[1714,1715],"ws-route-t","ws-route-d",[678,1717,1718],{"id":1714},"Choosing the fix",[682,1720,1721],{"id":1715},"A decision diagram. If you only need the data, read with calamine. If the file must be edited or its formatting kept with openpyxl, repair the relationship targets. If many files must become clean xlsx files for other tools, convert them with LibreOffice.",[686,1723],{"x":688,"y":689,"width":690,"height":1724,"fill":692},"272",[686,1726],{"x":1727,"y":696,"width":1728,"height":717,"rx":699,"fill":743,"stroke":734,"style":702},"239.7","280.7",[704,1730,1732],{"transform":1731},"translate(368.0,29.0) scale(1.000)",[708,1733],{"d":779,"fill":711,"stroke":750,"style":713},[19,1735,1736],{"x":783,"y":717,"style":754},"What do you need from the file?",[48,1738],{"x1":783,"y1":1739,"x2":783,"y2":1740,"stroke":734,"style":702},"91.5","111.5",[48,1742],{"x1":1743,"y1":1740,"x2":1744,"y2":1740,"stroke":734,"style":702},"134.7","625.3",[48,1746],{"x1":1743,"y1":1740,"x2":1743,"y2":1747,"stroke":734,"style":702},"157.5",[736,1749],{"points":1750,"fill":739},"134.7,161.5 129.7,152.5 139.7,152.5",[686,1752],{"x":1753,"y":1754,"width":1755,"height":1756,"rx":1757,"fill":692,"stroke":701,"style":1758},"89.2","121.5","90.9","21.0","10","stroke-width:1.2px",[19,1760,1763],{"x":1743,"y":1761,"style":1762},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","just the data",[686,1765],{"x":695,"y":1766,"width":1767,"height":1768,"rx":699,"fill":1343,"stroke":1344,"style":702},"163.5","229.3","93.0",[704,1770,1772],{"transform":1771},"translate(122.7,176.5) scale(1.000)",[708,1773],{"d":1774,"fill":711,"stroke":1775,"style":713},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","var(--teal-ink,#0b6157)",[19,1777,1780],{"x":1743,"y":1778,"style":1779},"223.0","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","calamine",[19,1782,1784],{"x":1743,"y":1783,"style":723},"242.5","engine='calamine'",[48,1786],{"x1":783,"y1":1740,"x2":783,"y2":1747,"stroke":734,"style":702},[736,1788],{"points":1789,"fill":739},"380.0,161.5 375.0,152.5 385.0,152.5",[686,1791],{"x":1792,"y":1754,"width":1793,"height":1756,"rx":1757,"fill":692,"stroke":701,"style":1758},"316.7","126.6",[19,1795,1796],{"x":783,"y":1761,"style":1762},"edit with openpyxl",[686,1798],{"x":1799,"y":1766,"width":1767,"height":1768,"rx":699,"fill":743,"stroke":734,"style":702},"265.3",[704,1801,1803],{"transform":1802},"translate(368.0,176.5) scale(1.000)",[708,1804],{"d":1805,"fill":711,"stroke":750,"style":713},"M12 9a3 3 0 1 0 .01 0z M12 2.5v3 M12 18.5v3 M2.5 12h3 M18.5 12h3 M5.3 5.3l2.1 2.1 M16.6 16.6l2.1 2.1 M5.3 18.7l2.1-2.1 M16.6 7.4l2.1-2.1",[19,1807,1808],{"x":783,"y":1778,"style":754},"Repair paths",[19,1810,1811],{"x":783,"y":1783,"style":723},"rewrite .rels",[48,1813],{"x1":1744,"y1":1740,"x2":1744,"y2":1747,"stroke":734,"style":702},[736,1815],{"points":1816,"fill":739},"625.3,161.5 620.3,152.5 630.3,152.5",[686,1818],{"x":1819,"y":1754,"width":1820,"height":1756,"rx":1757,"fill":692,"stroke":701,"style":1758},"543.0","164.7",[19,1822,1823],{"x":1744,"y":1761,"style":1762},"clean files for other tools",[686,1825],{"x":1826,"y":1766,"width":1767,"height":1768,"rx":699,"fill":772,"stroke":773,"style":702},"510.7",[704,1828,1830],{"transform":1829},"translate(613.3,176.5) scale(1.000)",[708,1831],{"d":1832,"fill":711,"stroke":780,"style":713},"M20 11a8 8 0 1 0-2.3 5.7 M20 4v7h-7",[19,1834,1835],{"x":1744,"y":1778,"style":785},"LibreOffice",[19,1837,1838],{"x":1744,"y":1783,"style":723},"convert-to xlsx",[34,1840,1842],{"id":1841},"common-pitfalls","Common pitfalls",[1844,1845,1846,1854,1864,1874],"ul",{},[1847,1848,1849,1853],"li",{},[1850,1851,1852],"strong",{},"Assuming the file is empty."," \"0 worksheets found\" describes what openpyxl found, not what the file contains; compare with another reader before investigating the data.",[1847,1855,1856,1859,1860,1863],{},[1850,1857,1858],{},"Patching by hand in a zip tool."," Some tools re-compress members with backslashes or change their order; repair with Python's ",[22,1861,1862],{},"zipfile",", which keeps names exactly.",[1847,1865,1866,1869,1870,1873],{},[1850,1867,1868],{},"Only fixing the first sheet."," Check every relationship target, including those for shared strings and styles, which fail with a ",[22,1871,1872],{},"KeyError"," rather than silently.",[1847,1875,1876,1879],{},[1850,1877,1878],{},"Reporting it upstream."," If a supplier's system produces these files, send them the comparison output; the file does not follow the package conventions openpyxl relies on.",[34,1881,1883],{"id":1882},"conclusion","Conclusion",[10,1885,1886,1887,1890,1891,1894,1895,1897],{},"\"Worksheet index 0 is invalid, 0 worksheets found\" means openpyxl skipped every sheet, usually because the workbook's relationships point at part names whose case or form does not exactly match the zip's members. Confirm it by comparing relationship targets with ",[22,1888,1889],{},"zipfile.namelist()",". Read such files with ",[22,1892,1893],{},"engine=\"calamine\"",", repair the relationship targets with ",[22,1896,1862],{}," when openpyxl must open the file, or convert them with LibreOffice.",[34,1899,1901],{"id":1900},"frequently-asked-questions","Frequently asked questions",[10,1903,1904,1907],{},[1850,1905,1906],{},"Why does pandas say 0 worksheets found when the file opens in Excel?","\nExcel resolves the parts inside an .xlsx case-insensitively; openpyxl requires exact matches and skips sheets it cannot find. Files from some exporters use different capitalisation in the relationships than in the zip.",[10,1909,1910,1913],{},[1850,1911,1912],{},"How do I read an Excel file that openpyxl cannot find sheets in?","\nUse pd.read_excel(path, engine=\"calamine\") after installing python-calamine, which reads such files. To use openpyxl, repair the relationship targets or convert the file with LibreOffice first.",[10,1915,1916,1919],{},[1850,1917,1918],{},"Is this the same error as \"Worksheet named ... not found\"?","\nNo. That one means the sheet name you asked for is not in the workbook, often because of spelling or trailing spaces. \"Worksheet index 0 is invalid\" means openpyxl found no worksheets at all.",[10,1921,1922,1925],{},[1850,1923,1924],{},"Can hidden sheets cause this error?","\nNo. Hidden worksheets are still worksheets and are counted. A workbook containing only chart sheets would, since pandas counts worksheets only.",[34,1927,1929],{"id":1928},"related","Related",[1844,1931,1932,1938,1943,1950,1955],{},[1847,1933,1934,1935,1937],{},"Up one level: ",[28,1936,31],{"href":30}," — the other common errors.",[1847,1939,1940,1942],{},[28,1941,373],{"href":372}," — when the file is not a zip at all.",[1847,1944,1945,1949],{},[28,1946,1948],{"href":1947},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-file-format-cannot-be-determined-in-pandas\u002F","Fix \"Excel File Format Cannot Be Determined\" in pandas"," — another reader-selection error.",[1847,1951,1952,1954],{},[28,1953,908],{"href":907}," — calamine and the other engines.",[1847,1956,1957,1961],{},[28,1958,1960],{"href":1959},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-does-not-exist-keyerror-in-openpyxl\u002F","Fix \"Worksheet Does Not Exist\" KeyError in openpyxl"," — the named-sheet version.",[1963,1964,1965],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}",{"title":20,"searchDepth":103,"depth":103,"links":1967},[1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978],{"id":36,"depth":103,"text":37},{"id":75,"depth":103,"text":76},{"id":376,"depth":103,"text":377},{"id":859,"depth":103,"text":860},{"id":911,"depth":103,"text":912},{"id":1351,"depth":103,"text":1352},{"id":1434,"depth":103,"text":1435},{"id":1841,"depth":103,"text":1842},{"id":1882,"depth":103,"text":1883},{"id":1900,"depth":103,"text":1901},{"id":1928,"depth":103,"text":1929},"2026-09-29","pandas raises \"ValueError: Worksheet index 0 is invalid, 0 worksheets found\" when openpyxl opens an .xlsx and finds no worksheets, even though Excel shows the data. The usual cause is a file from another tool whose internal paths differ in upper and lower case. Diagnose it by comparing the workbook's relationships with the zip contents, then read it with calamine or repair the paths.","md",[1983,1985,1987,1989],{"q":1906,"a":1984},"Excel resolves the parts inside an .xlsx case-insensitively; openpyxl requires exact matches and skips sheets it cannot find. Files from some exporters use different capitalisation in the relationships than in the zip.",{"q":1912,"a":1986},"Use pd.read_excel(path, engine=\"calamine\") after installing python-calamine, which reads such files. To use openpyxl, repair the relationship targets or convert the file with LibreOffice first.",{"q":1918,"a":1988},"No. That one means the sheet name you asked for is not in the workbook, often because of spelling or trailing spaces. \"Worksheet index 0 is invalid\" means openpyxl found no worksheets at all.",{"q":1924,"a":1990},"No. Hidden worksheets are still worksheets and are counted. A workbook containing only chart sheets would, since pandas counts worksheets only.","fix worksheet index 0 is invalid 0 worksheets found",null,{"breadcrumb":1994},[1995,1998,2001],{"name":1996,"item":1997},"Home","\u002F",{"name":1999,"item":2000},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":31,"item":30},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-index-0-is-invalid-0-worksheets-found",{"title":2004,"description":2005},"Fix pandas: Worksheet index 0 is invalid, 0 worksheets found","Why pandas.read_excel says 'Worksheet index 0 is invalid, 0 worksheets found' for an .xlsx that opens in Excel: case-mismatched part names from third-party exporters, how to detect them with zipfile, read with engine='calamine', or repair the file.","fix-worksheet-index-0-is-invalid-0-worksheets-found","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-index-0-is-invalid-0-worksheets-found\u002Findex",[2009,2010],"troubleshooting","reading-excel","how-to","DrzjgOXsz7iX0DOodhsuNZf1kOWg3FB7y-6CNSHuAxw",[2014,2017],{"title":1960,"path":2015,"stem":2016,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-does-not-exist-keyerror-in-openpyxl","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-does-not-exist-keyerror-in-openpyxl\u002Findex",{"title":2018,"path":2019,"stem":2020,"children":-1},"Fix Wrong Sheet Order After Writing with pandas","\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-wrong-sheet-order-after-writing-with-pandas","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-wrong-sheet-order-after-writing-with-pandas\u002Findex",[2022,2024],{"path":2023,"title":1999},"\u002Fgetting-started-with-python-excel-automation",{"path":2025,"title":31},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors",[],[2028,2032,2035,2038,2041,2044],{"path":2029,"title":2030,"kind":2031},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fhandle-permission-denied-when-writing-excel-in-python","Handle Permission Denied When Writing Excel in Python","fix",{"path":2033,"title":2034,"kind":2011},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fwhat-openpyxl-loses-when-saving-an-existing-workbook","What openpyxl Loses When You Save an Existing Workbook",{"path":2036,"title":2037,"kind":2031},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Ffix-excel-serial-numbers-showing-instead-of-dates","Fix Excel Serial Numbers Showing Instead of Dates",{"path":2039,"title":2040,"kind":2011},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-daylight-saving-time-in-excel-timestamps","Handle Daylight Saving Time in Excel Timestamps",{"path":2042,"title":2043,"kind":2011},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fhandle-microsoft-graph-throttling-for-excel","Handle Microsoft Graph Throttling in Excel Automation",{"path":2045,"title":2046,"kind":2047},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation","Error Handling and Logging in Excel Automation","guide",1790972067733]