[{"data":1,"prerenderedAt":2246},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-excel-2003-xml-spreadsheet-files":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-excel-2003-xml-spreadsheet-files":2211,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-excel-2003-xml-spreadsheet-files":2220,"children:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-excel-2003-xml-spreadsheet-files":2225,"related:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-excel-2003-xml-spreadsheet-files":2226},{"id":4,"title":5,"body":6,"dateModified":2177,"datePublished":2177,"description":2178,"extension":2179,"faq":2180,"keyword":2189,"kind":2190,"meta":2191,"navigation":130,"path":2200,"seo":2201,"slug":2204,"stem":2205,"topics":2206,"type":2209,"__hash__":2210},"docs\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-excel-2003-xml-spreadsheet-files\u002Findex.md","Read Excel 2003 XML Spreadsheet Files",{"type":7,"value":8,"toc":2165},"minimark",[9,45,50,81,84,88,391,426,607,611,725,751,755,1532,1591,1605,1724,1728,1952,1963,1967,1978,1998,2002,2047,2051,2076,2080,2098,2107,2116,2128,2132,2161],[10,11,12,13,17,18,21,22,25,26,29,30,34,35,38,39,44],"p",{},"A file called ",[14,15,16],"code",{},"statement.xls"," arrives from a bank portal or an ERP export. Excel opens it, perhaps after a warning that \"the file format and extension don't match\". pandas does not: ",[14,19,20],{},"read_excel"," says the format cannot be determined, xlrd complains that it expected a BOF record and found ",[14,23,24],{},"\u003C?xml",", and calamine cannot detect the format either. The file is not an old binary ",[14,27,28],{},".xls"," at all. It is an ",[31,32,33],"em",{},"XML Spreadsheet 2003"," document — a plain-text XML format that Excel introduced in 2002 and that many systems still generate because it is easy to write without any library. No pandas engine reads it, but it is simple XML, and a short parser turns it into DataFrames. This page shows how to recognise the format, reads it with the standard library, handles the details that trip up naive parsers, and converts the result to a real ",[14,36,37],{},".xlsx",". It belongs to ",[40,41,43],"a",{"href":42},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002F","Handling Excel File Formats and Conversions",".",[46,47,49],"h2",{"id":48},"prerequisites","Prerequisites",[51,52,57],"pre",{"className":53,"code":54,"language":55,"meta":56,"style":56},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl xlrd\n","bash","",[14,58,59],{"__ignoreMap":56},[60,61,64,68,72,75,78],"span",{"class":62,"line":63},"line",1,[60,65,67],{"class":66},"sMTad","pip",[60,69,71],{"class":70},"srMev"," install",[60,73,74],{"class":70}," pandas",[60,76,77],{"class":70}," openpyxl",[60,79,80],{"class":70}," xlrd\n",[10,82,83],{},"xlrd is only installed here to show its error message; the parser uses the standard library's ElementTree. The sample is a two-sheet statement of the kind a bank portal exports: a merged title row, a header, transactions with a skipped cell and a skipped row, and a formula.",[46,85,87],{"id":86},"step-1-recognise-the-format","Step 1 — Recognise the format",[51,89,93],{"className":90,"code":91,"language":92,"meta":56,"style":56},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\nfrom pathlib import Path\n\nfor engine in (None, \"xlrd\", \"calamine\"):\n    try:\n        pd.read_excel(\"statement.xls\", engine=engine)\n    except Exception as e:\n        print(f\"{engine or 'default':9} {type(e).__name__}: {e}\")\n\ndef is_xml_spreadsheet(path):\n    head = Path(path).read_bytes()[:2048].lstrip(b\"\\xef\\xbb\\xbf\").lower()\n    return head.startswith(b\"\u003C?xml\") and (b\"excel.sheet\" in head or b\"office:spreadsheet\" in head)\n\nprint(\"XML Spreadsheet 2003:\", is_xml_spreadsheet(\"statement.xls\"))\n","python",[14,94,95,111,125,132,165,174,195,210,273,278,291,321,367,372],{"__ignoreMap":56},[60,96,97,101,105,108],{"class":62,"line":63},[60,98,100],{"class":99},"s-kum","import",[60,102,104],{"class":103},"skGVy"," pandas ",[60,106,107],{"class":99},"as",[60,109,110],{"class":103}," pd\n",[60,112,114,117,120,122],{"class":62,"line":113},2,[60,115,116],{"class":99},"from",[60,118,119],{"class":103}," pathlib ",[60,121,100],{"class":99},[60,123,124],{"class":103}," Path\n",[60,126,128],{"class":62,"line":127},3,[60,129,131],{"emptyLinePlaceholder":130},true,"\n",[60,133,135,138,141,144,147,151,154,157,159,162],{"class":62,"line":134},4,[60,136,137],{"class":99},"for",[60,139,140],{"class":103}," engine ",[60,142,143],{"class":99},"in",[60,145,146],{"class":103}," (",[60,148,150],{"class":149},"sP0c6","None",[60,152,153],{"class":103},", ",[60,155,156],{"class":70},"\"xlrd\"",[60,158,153],{"class":103},[60,160,161],{"class":70},"\"calamine\"",[60,163,164],{"class":103},"):\n",[60,166,168,171],{"class":62,"line":167},5,[60,169,170],{"class":99},"    try",[60,172,173],{"class":103},":\n",[60,175,177,180,183,185,189,192],{"class":62,"line":176},6,[60,178,179],{"class":103},"        pd.read_excel(",[60,181,182],{"class":70},"\"statement.xls\"",[60,184,153],{"class":103},[60,186,188],{"class":187},"sa561","engine",[60,190,191],{"class":99},"=",[60,193,194],{"class":103},"engine)\n",[60,196,198,201,204,207],{"class":62,"line":197},7,[60,199,200],{"class":99},"    except",[60,202,203],{"class":149}," Exception",[60,205,206],{"class":99}," as",[60,208,209],{"class":103}," e:\n",[60,211,213,216,219,222,225,229,232,235,238,241,244,247,250,253,256,258,261,263,266,268,270],{"class":62,"line":212},8,[60,214,215],{"class":149},"        print",[60,217,218],{"class":103},"(",[60,220,221],{"class":99},"f",[60,223,224],{"class":70},"\"",[60,226,228],{"class":227},"sSjpA","{",[60,230,231],{"class":103},"engine ",[60,233,234],{"class":99},"or",[60,236,237],{"class":70}," 'default'",[60,239,240],{"class":99},":9",[60,242,243],{"class":227},"}",[60,245,246],{"class":227}," {",[60,248,249],{"class":149},"type",[60,251,252],{"class":103},"(e).",[60,254,255],{"class":149},"__name__",[60,257,243],{"class":227},[60,259,260],{"class":70},": ",[60,262,228],{"class":227},[60,264,265],{"class":103},"e",[60,267,243],{"class":227},[60,269,224],{"class":70},[60,271,272],{"class":103},")\n",[60,274,276],{"class":62,"line":275},9,[60,277,131],{"emptyLinePlaceholder":130},[60,279,281,284,288],{"class":62,"line":280},10,[60,282,283],{"class":99},"def",[60,285,287],{"class":286},"s_Opv"," is_xml_spreadsheet",[60,289,290],{"class":103},"(path):\n",[60,292,294,297,299,302,305,308,311,313,316,318],{"class":62,"line":293},11,[60,295,296],{"class":103},"    head ",[60,298,191],{"class":99},[60,300,301],{"class":103}," Path(path).read_bytes()[:",[60,303,304],{"class":149},"2048",[60,306,307],{"class":103},"].lstrip(",[60,309,310],{"class":99},"b",[60,312,224],{"class":70},[60,314,315],{"class":227},"\\xef\\xbb\\xbf",[60,317,224],{"class":70},[60,319,320],{"class":103},").lower()\n",[60,322,324,327,330,332,335,338,341,343,345,348,351,354,356,359,362,364],{"class":62,"line":323},12,[60,325,326],{"class":99},"    return",[60,328,329],{"class":103}," head.startswith(",[60,331,310],{"class":99},[60,333,334],{"class":70},"\"\u003C?xml\"",[60,336,337],{"class":103},") ",[60,339,340],{"class":99},"and",[60,342,146],{"class":103},[60,344,310],{"class":99},[60,346,347],{"class":70},"\"excel.sheet\"",[60,349,350],{"class":99}," in",[60,352,353],{"class":103}," head ",[60,355,234],{"class":99},[60,357,358],{"class":99}," b",[60,360,361],{"class":70},"\"office:spreadsheet\"",[60,363,350],{"class":99},[60,365,366],{"class":103}," head)\n",[60,368,370],{"class":62,"line":369},13,[60,371,131],{"emptyLinePlaceholder":130},[60,373,375,378,380,383,386,388],{"class":62,"line":374},14,[60,376,377],{"class":149},"print",[60,379,218],{"class":103},[60,381,382],{"class":70},"\"XML Spreadsheet 2003:\"",[60,384,385],{"class":103},", is_xml_spreadsheet(",[60,387,182],{"class":70},[60,389,390],{"class":103},"))\n",[10,392,393,394,396,397,400,401,403,404,407,408,410,411,414,415,417,418,421,422,425],{},"The three error messages are worth recognising, because they all mean the same thing when the file starts with XML. pandas could not determine the format from the content, xlrd expected the binary record that starts a real ",[14,395,28],{}," file and found the text ",[14,398,399],{},"\u003C?xml ve"," instead, and calamine could not detect a format it knows. Looking at the first bytes settles it: a real ",[14,402,28],{}," starts with the binary signature ",[14,405,406],{},"D0 CF 11 E0",", an ",[14,409,37],{}," with ",[14,412,413],{},"PK"," (it is a zip archive), and an XML spreadsheet with ",[14,416,24],{},", usually followed by the processing instruction ",[14,419,420],{},"\u003C?mso-application progid=\"Excel.Sheet\"?>"," and the namespace ",[14,423,424],{},"urn:schemas-microsoft-com:office:spreadsheet",". The check skips a UTF-8 byte-order mark, which some exporters write before the XML declaration.",[427,428,436,437,436,441,436,445,436,453,436,462,436,466,436,473,436,477,436,481,436,485,436,489,436,493,436,498,436,503,436,507,436,509,436,512,436,514,436,517,436,520,436,524,436,526,436,529,436,531,436,534,436,536,436,539,436,542,436,546,436,548,436,551,436,553,436,556,436,558,436,561,436,564,436,568,436,572,436,576,436,578,436,580,436,582,436,585,436,588,436,592,436,594,436,597,436,599,436,602,436,604],"svg",{"viewBox":429,"role":430,"ariaLabelledBy":431,"xmlns":434,"style":435},"103 0 554 196","img",[432,433],"x03-sigs-t","x03-sigs-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:554px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[438,439,440],"title",{"id":432},"What the first bytes tell you",[442,443,444],"desc",{"id":433},"A real .xls file starts with the binary signature D0 CF 11 E0 and is read by xlrd. An .xlsx starts with PK because it is a zip archive and is read by openpyxl or calamine. An XML Spreadsheet 2003 file starts with the text \u003C?xml and needs its own parser.",[446,447],"rect",{"x":448,"y":449,"width":450,"height":451,"fill":452},"103","0","554","196","#ffffff",[446,454],{"x":455,"y":456,"width":457,"height":458,"rx":449,"fill":459,"stroke":460,"style":461},"119.1","16.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[446,463],{"x":464,"y":456,"width":465,"height":458,"rx":449,"fill":459,"stroke":460,"style":461},"151.1","153.7",[467,468,472],"text",{"x":469,"y":470,"style":471},"228.0","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[446,474],{"x":475,"y":456,"width":476,"height":458,"rx":449,"fill":459,"stroke":460,"style":461},"304.8","191.8",[467,478,480],{"x":479,"y":470,"style":471},"400.7","B",[446,482],{"x":483,"y":456,"width":484,"height":458,"rx":449,"fill":459,"stroke":460,"style":461},"496.7","144.2",[467,486,488],{"x":487,"y":470,"style":471},"568.8","C",[446,490],{"x":455,"y":491,"width":457,"height":492,"rx":449,"fill":459,"stroke":460,"style":461},"40.0","28.0",[467,494,497],{"x":495,"y":496,"style":471},"135.1","59.0","1",[446,499],{"x":464,"y":491,"width":465,"height":492,"rx":449,"fill":500,"stroke":501,"style":502},"#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[467,504,506],{"x":469,"y":496,"style":505},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","First bytes",[446,508],{"x":475,"y":491,"width":476,"height":492,"rx":449,"fill":500,"stroke":501,"style":502},[467,510,511],{"x":479,"y":496,"style":505},"Format",[446,513],{"x":483,"y":491,"width":484,"height":492,"rx":449,"fill":500,"stroke":501,"style":502},[467,515,516],{"x":487,"y":496,"style":505},"Read with",[446,518],{"x":455,"y":519,"width":457,"height":492,"rx":449,"fill":459,"stroke":460,"style":461},"68.0",[467,521,523],{"x":495,"y":522,"style":471},"87.0","2",[446,525],{"x":464,"y":519,"width":465,"height":492,"rx":449,"fill":452,"stroke":460,"style":461},[467,527,406],{"x":469,"y":522,"style":528},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[446,530],{"x":475,"y":519,"width":476,"height":492,"rx":449,"fill":452,"stroke":460,"style":461},[467,532,533],{"x":479,"y":522,"style":528},"Excel 97–2003 binary (.xls)",[446,535],{"x":483,"y":519,"width":484,"height":492,"rx":449,"fill":452,"stroke":460,"style":461},[467,537,538],{"x":487,"y":522,"style":528},"xlrd, calamine",[446,540],{"x":455,"y":541,"width":457,"height":492,"rx":449,"fill":459,"stroke":460,"style":461},"96.0",[467,543,545],{"x":495,"y":544,"style":471},"115.0","3",[446,547],{"x":464,"y":541,"width":465,"height":492,"rx":449,"fill":452,"stroke":460,"style":461},[467,549,550],{"x":469,"y":544,"style":528},"50 4B (PK)",[446,552],{"x":475,"y":541,"width":476,"height":492,"rx":449,"fill":452,"stroke":460,"style":461},[467,554,555],{"x":479,"y":544,"style":528},"Office Open XML (.xlsx)",[446,557],{"x":483,"y":541,"width":484,"height":492,"rx":449,"fill":452,"stroke":460,"style":461},[467,559,560],{"x":487,"y":544,"style":528},"openpyxl, calamine",[446,562],{"x":455,"y":563,"width":457,"height":492,"rx":449,"fill":459,"stroke":460,"style":461},"124.0",[467,565,567],{"x":495,"y":566,"style":471},"143.0","4",[446,569],{"x":464,"y":563,"width":465,"height":492,"rx":449,"fill":570,"stroke":571,"style":502},"#d9f4f1","var(--teal,#0f9488)",[467,573,575],{"x":469,"y":566,"style":574},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","\u003C?xml … Excel.Sheet",[446,577],{"x":475,"y":563,"width":476,"height":492,"rx":449,"fill":570,"stroke":571,"style":502},[467,579,33],{"x":479,"y":566,"style":574},[446,581],{"x":483,"y":563,"width":484,"height":492,"rx":449,"fill":570,"stroke":571,"style":502},[467,583,584],{"x":487,"y":566,"style":574},"the parser below",[446,586],{"x":455,"y":587,"width":457,"height":492,"rx":449,"fill":459,"stroke":460,"style":461},"152.0",[467,589,591],{"x":495,"y":590,"style":471},"171.0","5",[446,593],{"x":464,"y":587,"width":465,"height":492,"rx":449,"fill":452,"stroke":460,"style":461},[467,595,596],{"x":469,"y":590,"style":528},"\u003Chtml or \u003Ctable",[446,598],{"x":475,"y":587,"width":476,"height":492,"rx":449,"fill":452,"stroke":460,"style":461},[467,600,601],{"x":479,"y":590,"style":528},"HTML saved as .xls",[446,603],{"x":483,"y":587,"width":484,"height":492,"rx":449,"fill":452,"stroke":460,"style":461},[467,605,606],{"x":487,"y":590,"style":528},"pandas.read_html",[46,608,610],{"id":609},"how-the-xml-is-organised","How the XML is organised",[427,612,436,618,436,621,436,624,436,629,436,635,436,641,436,646,436,650,436,655,436,660,436,665,436,669,436,673,436,676,436,679,436,684,436,688,436,692,436,695,436,698,436,703,436,707,436,711,436,714,436,717,436,721],{"viewBox":613,"role":430,"ariaLabelledBy":614,"xmlns":434,"style":617},"84 0 592 471",[615,616],"x03-tree-t","x03-tree-d","width:100%;max-width:592px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[438,619,620],{"id":615},"The nesting of an XML Spreadsheet 2003 file",[442,622,623],{"id":616},"A Workbook element contains Styles and one Worksheet per sheet. Each Worksheet holds a Table of Row elements, each Row holds Cell elements, and each Cell holds a Data element with an ss:Type attribute and the value as text.",[446,625],{"x":626,"y":449,"width":627,"height":628,"fill":452},"84","592","471",[446,630],{"x":631,"y":456,"width":632,"height":496,"rx":633,"fill":500,"stroke":501,"style":634},"100.0","560.0","12","stroke-width:2px",[467,636,640],{"x":637,"y":638,"style":639},"380.0","41.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Workbook",[467,642,645],{"x":637,"y":643,"style":644},"61.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","namespace …office:spreadsheet",[62,647],{"x1":637,"y1":648,"x2":637,"y2":649,"stroke":501,"style":634},"80.0","102.0",[651,652],"polygon",{"points":653,"fill":654},"380.0,106.0 375.0,97.0 385.0,97.0","#5b5cf0",[446,656],{"x":631,"y":657,"width":632,"height":496,"rx":633,"fill":658,"stroke":659,"style":634},"111.0","#fdefd8","var(--gold,#b4740a)",[467,661,664],{"x":637,"y":662,"style":663},"136.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Worksheet ss:Name",[467,666,668],{"x":637,"y":667,"style":644},"156.0","one per sheet",[62,670],{"x1":637,"y1":671,"x2":637,"y2":672,"stroke":501,"style":634},"175.0","197.0",[651,674],{"points":675,"fill":654},"380.0,201.0 375.0,192.0 385.0,192.0",[446,677],{"x":631,"y":678,"width":632,"height":496,"rx":633,"fill":459,"stroke":460,"style":634},"206.0",[467,680,683],{"x":637,"y":681,"style":682},"231.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Table → Row",[467,685,687],{"x":637,"y":686,"style":644},"251.0","optional ss:Index",[62,689],{"x1":637,"y1":690,"x2":637,"y2":691,"stroke":501,"style":634},"270.0","292.0",[651,693],{"points":694,"fill":654},"380.0,296.0 375.0,287.0 385.0,287.0",[446,696],{"x":631,"y":697,"width":632,"height":496,"rx":633,"fill":570,"stroke":571,"style":634},"301.0",[467,699,702],{"x":637,"y":700,"style":701},"326.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Cell",[467,704,706],{"x":637,"y":705,"style":644},"346.0","ss:Index, MergeAcross, Formula",[62,708],{"x1":637,"y1":709,"x2":637,"y2":710,"stroke":501,"style":634},"365.0","387.0",[651,712],{"points":713,"fill":654},"380.0,391.0 375.0,382.0 385.0,382.0",[446,715],{"x":631,"y":716,"width":632,"height":496,"rx":633,"fill":500,"stroke":501,"style":634},"396.0",[467,718,720],{"x":637,"y":719,"style":639},"421.5","Data ss:Type",[467,722,724],{"x":637,"y":723,"style":644},"441.0","String, Number, DateTime, Boolean",[10,726,727,728,730,731,734,735,738,739,742,743,746,747,750],{},"Everything lives in one namespace, ",[14,729,424],{},", which appears both as the default namespace for elements and with the ",[14,732,733],{},"ss:"," prefix for attributes. ElementTree reports elements and attributes with the full namespace in braces, so the parser keeps the namespace in a small mapping for ",[14,736,737],{},"findall"," and a prefix string for attribute names. The ",[14,740,741],{},"Styles"," section holds number formats such as ",[31,744,745],{},"Short Date",", referenced from cells by ",[14,748,749],{},"ss:StyleID","; since every value already carries its type, the styles are not needed to read the data.",[46,752,754],{"id":753},"step-2-parse-every-worksheet-into-a-dataframe","Step 2 — Parse every worksheet into a DataFrame",[51,756,758],{"className":90,"code":757,"language":92,"meta":56,"style":56},"import xml.etree.ElementTree as ET\nfrom datetime import datetime\n\nNS = {\"ss\": \"urn:schemas-microsoft-com:office:spreadsheet\"}\nA = \"{urn:schemas-microsoft-com:office:spreadsheet}\"\n\ndef convert(data):\n    kind, text = data.get(f\"{A}Type\"), data.text or \"\"\n    if kind == \"Number\":\n        return float(text)\n    if kind == \"DateTime\":\n        return datetime.fromisoformat(text[:19])\n    if kind == \"Boolean\":\n        return text.strip() == \"1\"\n    return text                                            # String and Error\n\ndef read_xml_spreadsheet(path):\n    root = ET.parse(path).getroot()\n    sheets = {}\n    for ws in root.findall(\"ss:Worksheet\", NS):\n        rows, r = [], 0\n        for row in ws.findall(\"ss:Table\u002Fss:Row\", NS):\n            r = int(row.get(f\"{A}Index\", r + 1))           # rows can skip ahead\n            values, c = {}, 0\n            for cell in row.findall(\"ss:Cell\", NS):\n                c = int(cell.get(f\"{A}Index\", c + 1))      # cells can skip ahead too\n                data = cell.find(\"ss:Data\", NS)\n                if data is not None:\n                    values[c] = convert(data)\n                c += int(cell.get(f\"{A}MergeAcross\", 0))   # a merged cell occupies extra columns\n            rows.append((r, values))\n        n_rows = max((r for r, _ in rows), default=0)\n        n_cols = max((c for _, v in rows for c in v), default=0)\n        grid = [[None] * n_cols for _ in range(n_rows)]\n        for r, values in rows:\n            for c, v in values.items():\n                grid[r - 1][c - 1] = v\n        sheets[ws.get(f\"{A}Name\")] = pd.DataFrame(grid)\n    return sheets\n\nsheets = read_xml_spreadsheet(\"statement.xls\")\nfor name, df in sheets.items():\n    print(f\"--- {name}\\n{df.to_string()}\")\n",[14,759,760,772,784,788,809,831,835,845,876,892,903,916,929,942,954,966,971,981,995,1006,1029,1043,1066,1108,1121,1144,1182,1202,1222,1233,1268,1274,1307,1348,1383,1396,1409,1434,1461,1469,1474,1489,1502],{"__ignoreMap":56},[60,761,762,764,767,769],{"class":62,"line":63},[60,763,100],{"class":99},[60,765,766],{"class":103}," xml.etree.ElementTree ",[60,768,107],{"class":99},[60,770,771],{"class":149}," ET\n",[60,773,774,776,779,781],{"class":62,"line":113},[60,775,116],{"class":99},[60,777,778],{"class":103}," datetime ",[60,780,100],{"class":99},[60,782,783],{"class":103}," datetime\n",[60,785,786],{"class":62,"line":127},[60,787,131],{"emptyLinePlaceholder":130},[60,789,790,793,796,798,801,803,806],{"class":62,"line":134},[60,791,792],{"class":149},"NS",[60,794,795],{"class":99}," =",[60,797,246],{"class":103},[60,799,800],{"class":70},"\"ss\"",[60,802,260],{"class":103},[60,804,805],{"class":70},"\"urn:schemas-microsoft-com:office:spreadsheet\"",[60,807,808],{"class":103},"}\n",[60,810,811,814,816,819,822,825,828],{"class":62,"line":167},[60,812,813],{"class":103},"A ",[60,815,191],{"class":99},[60,817,818],{"class":70}," \"",[60,820,821],{"class":227},"{urn",[60,823,824],{"class":99},":",[60,826,827],{"class":227},"schemas-microsoft-com:office:spreadsheet}",[60,829,830],{"class":70},"\"\n",[60,832,833],{"class":62,"line":176},[60,834,131],{"emptyLinePlaceholder":130},[60,836,837,839,842],{"class":62,"line":197},[60,838,283],{"class":99},[60,840,841],{"class":286}," convert",[60,843,844],{"class":103},"(data):\n",[60,846,847,850,852,855,857,859,861,863,865,868,871,873],{"class":62,"line":212},[60,848,849],{"class":103},"    kind, text ",[60,851,191],{"class":99},[60,853,854],{"class":103}," data.get(",[60,856,221],{"class":99},[60,858,224],{"class":70},[60,860,228],{"class":227},[60,862,472],{"class":103},[60,864,243],{"class":227},[60,866,867],{"class":70},"Type\"",[60,869,870],{"class":103},"), data.text ",[60,872,234],{"class":99},[60,874,875],{"class":70}," \"\"\n",[60,877,878,881,884,887,890],{"class":62,"line":275},[60,879,880],{"class":99},"    if",[60,882,883],{"class":103}," kind ",[60,885,886],{"class":99},"==",[60,888,889],{"class":70}," \"Number\"",[60,891,173],{"class":103},[60,893,894,897,900],{"class":62,"line":280},[60,895,896],{"class":99},"        return",[60,898,899],{"class":149}," float",[60,901,902],{"class":103},"(text)\n",[60,904,905,907,909,911,914],{"class":62,"line":293},[60,906,880],{"class":99},[60,908,883],{"class":103},[60,910,886],{"class":99},[60,912,913],{"class":70}," \"DateTime\"",[60,915,173],{"class":103},[60,917,918,920,923,926],{"class":62,"line":323},[60,919,896],{"class":99},[60,921,922],{"class":103}," datetime.fromisoformat(text[:",[60,924,925],{"class":149},"19",[60,927,928],{"class":103},"])\n",[60,930,931,933,935,937,940],{"class":62,"line":369},[60,932,880],{"class":99},[60,934,883],{"class":103},[60,936,886],{"class":99},[60,938,939],{"class":70}," \"Boolean\"",[60,941,173],{"class":103},[60,943,944,946,949,951],{"class":62,"line":374},[60,945,896],{"class":99},[60,947,948],{"class":103}," text.strip() ",[60,950,886],{"class":99},[60,952,953],{"class":70}," \"1\"\n",[60,955,957,959,962],{"class":62,"line":956},15,[60,958,326],{"class":99},[60,960,961],{"class":103}," text                                            ",[60,963,965],{"class":964},"s-wDw","# String and Error\n",[60,967,969],{"class":62,"line":968},16,[60,970,131],{"emptyLinePlaceholder":130},[60,972,974,976,979],{"class":62,"line":973},17,[60,975,283],{"class":99},[60,977,978],{"class":286}," read_xml_spreadsheet",[60,980,290],{"class":103},[60,982,984,987,989,992],{"class":62,"line":983},18,[60,985,986],{"class":103},"    root ",[60,988,191],{"class":99},[60,990,991],{"class":149}," ET",[60,993,994],{"class":103},".parse(path).getroot()\n",[60,996,998,1001,1003],{"class":62,"line":997},19,[60,999,1000],{"class":103},"    sheets ",[60,1002,191],{"class":99},[60,1004,1005],{"class":103}," {}\n",[60,1007,1009,1012,1015,1017,1020,1023,1025,1027],{"class":62,"line":1008},20,[60,1010,1011],{"class":99},"    for",[60,1013,1014],{"class":103}," ws ",[60,1016,143],{"class":99},[60,1018,1019],{"class":103}," root.findall(",[60,1021,1022],{"class":70},"\"ss:Worksheet\"",[60,1024,153],{"class":103},[60,1026,792],{"class":149},[60,1028,164],{"class":103},[60,1030,1032,1035,1037,1040],{"class":62,"line":1031},21,[60,1033,1034],{"class":103},"        rows, r ",[60,1036,191],{"class":99},[60,1038,1039],{"class":103}," [], ",[60,1041,1042],{"class":149},"0\n",[60,1044,1046,1049,1052,1054,1057,1060,1062,1064],{"class":62,"line":1045},22,[60,1047,1048],{"class":99},"        for",[60,1050,1051],{"class":103}," row ",[60,1053,143],{"class":99},[60,1055,1056],{"class":103}," ws.findall(",[60,1058,1059],{"class":70},"\"ss:Table\u002Fss:Row\"",[60,1061,153],{"class":103},[60,1063,792],{"class":149},[60,1065,164],{"class":103},[60,1067,1069,1072,1074,1077,1080,1082,1084,1086,1088,1090,1093,1096,1099,1102,1105],{"class":62,"line":1068},23,[60,1070,1071],{"class":103},"            r ",[60,1073,191],{"class":99},[60,1075,1076],{"class":149}," int",[60,1078,1079],{"class":103},"(row.get(",[60,1081,221],{"class":99},[60,1083,224],{"class":70},[60,1085,228],{"class":227},[60,1087,472],{"class":103},[60,1089,243],{"class":227},[60,1091,1092],{"class":70},"Index\"",[60,1094,1095],{"class":103},", r ",[60,1097,1098],{"class":99},"+",[60,1100,1101],{"class":149}," 1",[60,1103,1104],{"class":103},"))           ",[60,1106,1107],{"class":964},"# rows can skip ahead\n",[60,1109,1111,1114,1116,1119],{"class":62,"line":1110},24,[60,1112,1113],{"class":103},"            values, c ",[60,1115,191],{"class":99},[60,1117,1118],{"class":103}," {}, ",[60,1120,1042],{"class":149},[60,1122,1124,1127,1130,1132,1135,1138,1140,1142],{"class":62,"line":1123},25,[60,1125,1126],{"class":99},"            for",[60,1128,1129],{"class":103}," cell ",[60,1131,143],{"class":99},[60,1133,1134],{"class":103}," row.findall(",[60,1136,1137],{"class":70},"\"ss:Cell\"",[60,1139,153],{"class":103},[60,1141,792],{"class":149},[60,1143,164],{"class":103},[60,1145,1147,1150,1152,1154,1157,1159,1161,1163,1165,1167,1169,1172,1174,1176,1179],{"class":62,"line":1146},26,[60,1148,1149],{"class":103},"                c ",[60,1151,191],{"class":99},[60,1153,1076],{"class":149},[60,1155,1156],{"class":103},"(cell.get(",[60,1158,221],{"class":99},[60,1160,224],{"class":70},[60,1162,228],{"class":227},[60,1164,472],{"class":103},[60,1166,243],{"class":227},[60,1168,1092],{"class":70},[60,1170,1171],{"class":103},", c ",[60,1173,1098],{"class":99},[60,1175,1101],{"class":149},[60,1177,1178],{"class":103},"))      ",[60,1180,1181],{"class":964},"# cells can skip ahead too\n",[60,1183,1185,1188,1190,1193,1196,1198,1200],{"class":62,"line":1184},27,[60,1186,1187],{"class":103},"                data ",[60,1189,191],{"class":99},[60,1191,1192],{"class":103}," cell.find(",[60,1194,1195],{"class":70},"\"ss:Data\"",[60,1197,153],{"class":103},[60,1199,792],{"class":149},[60,1201,272],{"class":103},[60,1203,1205,1208,1211,1214,1217,1220],{"class":62,"line":1204},28,[60,1206,1207],{"class":99},"                if",[60,1209,1210],{"class":103}," data ",[60,1212,1213],{"class":99},"is",[60,1215,1216],{"class":99}," not",[60,1218,1219],{"class":149}," None",[60,1221,173],{"class":103},[60,1223,1225,1228,1230],{"class":62,"line":1224},29,[60,1226,1227],{"class":103},"                    values[c] ",[60,1229,191],{"class":99},[60,1231,1232],{"class":103}," convert(data)\n",[60,1234,1236,1238,1241,1243,1245,1247,1249,1251,1253,1255,1258,1260,1262,1265],{"class":62,"line":1235},30,[60,1237,1149],{"class":103},[60,1239,1240],{"class":99},"+=",[60,1242,1076],{"class":149},[60,1244,1156],{"class":103},[60,1246,221],{"class":99},[60,1248,224],{"class":70},[60,1250,228],{"class":227},[60,1252,472],{"class":103},[60,1254,243],{"class":227},[60,1256,1257],{"class":70},"MergeAcross\"",[60,1259,153],{"class":103},[60,1261,449],{"class":149},[60,1263,1264],{"class":103},"))   ",[60,1266,1267],{"class":964},"# a merged cell occupies extra columns\n",[60,1269,1271],{"class":62,"line":1270},31,[60,1272,1273],{"class":103},"            rows.append((r, values))\n",[60,1275,1277,1280,1282,1285,1288,1290,1293,1295,1298,1301,1303,1305],{"class":62,"line":1276},32,[60,1278,1279],{"class":103},"        n_rows ",[60,1281,191],{"class":99},[60,1283,1284],{"class":149}," max",[60,1286,1287],{"class":103},"((r ",[60,1289,137],{"class":99},[60,1291,1292],{"class":103}," r, _ ",[60,1294,143],{"class":99},[60,1296,1297],{"class":103}," rows), ",[60,1299,1300],{"class":187},"default",[60,1302,191],{"class":99},[60,1304,449],{"class":149},[60,1306,272],{"class":103},[60,1308,1310,1313,1315,1317,1320,1322,1325,1327,1330,1332,1335,1337,1340,1342,1344,1346],{"class":62,"line":1309},33,[60,1311,1312],{"class":103},"        n_cols ",[60,1314,191],{"class":99},[60,1316,1284],{"class":149},[60,1318,1319],{"class":103},"((c ",[60,1321,137],{"class":99},[60,1323,1324],{"class":103}," _, v ",[60,1326,143],{"class":99},[60,1328,1329],{"class":103}," rows ",[60,1331,137],{"class":99},[60,1333,1334],{"class":103}," c ",[60,1336,143],{"class":99},[60,1338,1339],{"class":103}," v), ",[60,1341,1300],{"class":187},[60,1343,191],{"class":99},[60,1345,449],{"class":149},[60,1347,272],{"class":103},[60,1349,1351,1354,1356,1359,1361,1364,1367,1370,1372,1375,1377,1380],{"class":62,"line":1350},34,[60,1352,1353],{"class":103},"        grid ",[60,1355,191],{"class":99},[60,1357,1358],{"class":103}," [[",[60,1360,150],{"class":149},[60,1362,1363],{"class":103},"] ",[60,1365,1366],{"class":99},"*",[60,1368,1369],{"class":103}," n_cols ",[60,1371,137],{"class":99},[60,1373,1374],{"class":103}," _ ",[60,1376,143],{"class":99},[60,1378,1379],{"class":149}," range",[60,1381,1382],{"class":103},"(n_rows)]\n",[60,1384,1386,1388,1391,1393],{"class":62,"line":1385},35,[60,1387,1048],{"class":99},[60,1389,1390],{"class":103}," r, values ",[60,1392,143],{"class":99},[60,1394,1395],{"class":103}," rows:\n",[60,1397,1399,1401,1404,1406],{"class":62,"line":1398},36,[60,1400,1126],{"class":99},[60,1402,1403],{"class":103}," c, v ",[60,1405,143],{"class":99},[60,1407,1408],{"class":103}," values.items():\n",[60,1410,1412,1415,1418,1420,1423,1425,1427,1429,1431],{"class":62,"line":1411},37,[60,1413,1414],{"class":103},"                grid[r ",[60,1416,1417],{"class":99},"-",[60,1419,1101],{"class":149},[60,1421,1422],{"class":103},"][c ",[60,1424,1417],{"class":99},[60,1426,1101],{"class":149},[60,1428,1363],{"class":103},[60,1430,191],{"class":99},[60,1432,1433],{"class":103}," v\n",[60,1435,1437,1440,1442,1444,1446,1448,1450,1453,1456,1458],{"class":62,"line":1436},38,[60,1438,1439],{"class":103},"        sheets[ws.get(",[60,1441,221],{"class":99},[60,1443,224],{"class":70},[60,1445,228],{"class":227},[60,1447,472],{"class":103},[60,1449,243],{"class":227},[60,1451,1452],{"class":70},"Name\"",[60,1454,1455],{"class":103},")] ",[60,1457,191],{"class":99},[60,1459,1460],{"class":103}," pd.DataFrame(grid)\n",[60,1462,1464,1466],{"class":62,"line":1463},39,[60,1465,326],{"class":99},[60,1467,1468],{"class":103}," sheets\n",[60,1470,1472],{"class":62,"line":1471},40,[60,1473,131],{"emptyLinePlaceholder":130},[60,1475,1477,1480,1482,1485,1487],{"class":62,"line":1476},41,[60,1478,1479],{"class":103},"sheets ",[60,1481,191],{"class":99},[60,1483,1484],{"class":103}," read_xml_spreadsheet(",[60,1486,182],{"class":70},[60,1488,272],{"class":103},[60,1490,1492,1494,1497,1499],{"class":62,"line":1491},42,[60,1493,137],{"class":99},[60,1495,1496],{"class":103}," name, df ",[60,1498,143],{"class":99},[60,1500,1501],{"class":103}," sheets.items():\n",[60,1503,1505,1508,1510,1512,1515,1517,1520,1523,1526,1528,1530],{"class":62,"line":1504},43,[60,1506,1507],{"class":149},"    print",[60,1509,218],{"class":103},[60,1511,221],{"class":99},[60,1513,1514],{"class":70},"\"--- ",[60,1516,228],{"class":227},[60,1518,1519],{"class":103},"name",[60,1521,1522],{"class":227},"}\\n{",[60,1524,1525],{"class":103},"df.to_string()",[60,1527,243],{"class":227},[60,1529,224],{"class":70},[60,1531,272],{"class":103},[10,1533,1534,1535,1537,1538,1541,1542,1545,1546,1549,1550,1552,1553,1556,1557,1560,1561,1564,1565,1568,1569,1572,1573,1576,1577,1580,1581,1584,1585,1587,1588,1590],{},"The structure is straightforward — a ",[14,1536,640],{}," holds ",[14,1539,1540],{},"Worksheet"," elements, each with a ",[14,1543,1544],{},"Table"," of ",[14,1547,1548],{},"Row"," elements containing ",[14,1551,702],{}," elements whose ",[14,1554,1555],{},"Data"," child holds the value and its type — but three details decide whether the result is right. First, cells are positional, not addressed: a cell's column is one more than the previous cell's, ",[31,1558,1559],{},"unless"," it has an ",[14,1562,1563],{},"ss:Index"," attribute, which jumps ahead and leaves the skipped columns empty. In the sample, the second transaction has no description, so its amount carries ",[14,1566,1567],{},"ss:Index=\"3\"",". Rows work the same way, which is why the third transaction lands on row 6 after an empty row 5. Second, a cell with ",[14,1570,1571],{},"ss:MergeAcross=\"3\""," spans four columns, so the next cell starts four columns later. Third, every value has a type: ",[14,1574,1575],{},"Number"," becomes a float, ",[14,1578,1579],{},"DateTime"," an ISO timestamp, ",[14,1582,1583],{},"Boolean"," a ",[14,1586,497],{}," or ",[14,1589,449],{},", and everything else text.",[10,1592,1593,1594,1597,1598,1600,1601,1604],{},"Formulas are stored in R1C1 notation in an ",[14,1595,1596],{},"ss:Formula"," attribute, alongside a ",[14,1599,1555],{}," element with the last calculated value. The parser reads the value, which is what an export is for; the formula itself is ignored. If the formulas matter — to rebuild a template, say — they can be read from the attribute, but they would need translating from R1C1 to A1 notation before openpyxl could write them back, since ",[14,1602,1603],{},"=R[-2]C*2"," means \"twice the cell two rows up in the same column\".",[427,1606,436,1612,436,1615,436,1618,436,1622,436,1627,436,1632,436,1636,436,1640,436,1645,436,1648,436,1651,436,1655,436,1657,436,1659,436,1663,436,1666,436,1669,436,1672,436,1675,436,1679,436,1681,436,1685,436,1688,436,1691,436,1695,436,1698,436,1701,436,1705,436,1708,436,1711,436,1715,436,1718,436,1721],{"viewBox":1607,"role":430,"ariaLabelledBy":1608,"xmlns":434,"style":1611},"4 0 752 126",[1609,1610],"x03-parse-t","x03-parse-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[438,1613,1614],{"id":1609},"How the parser places each value",[442,1616,1617],{"id":1610},"For each row the parser takes its index, either from ss:Index or one more than the previous row. For each cell it takes the column the same way, reads the typed Data value, then advances the column by any MergeAcross span before the next cell.",[446,1619],{"x":567,"y":449,"width":1620,"height":1621,"fill":452},"752","126",[446,1623],{"x":1624,"y":456,"width":1625,"height":1626,"rx":633,"fill":459,"stroke":460,"style":634},"20.0","115.2","94.0",[467,1628,1631],{"x":1629,"y":1630,"style":682},"77.6","50.2","Row number",[467,1633,1635],{"x":1629,"y":1634,"style":644},"69.8","ss:Index or",[467,1637,1639],{"x":1629,"y":1638,"style":644},"87.2","previous + 1",[62,1641],{"x1":1642,"y1":1643,"x2":1644,"y2":1643,"stroke":501,"style":634},"140.2","63.0","162.2",[651,1646],{"points":1647,"fill":654},"166.2,63.0 157.2,68.0 157.2,58.0",[446,1649],{"x":1650,"y":456,"width":1625,"height":1626,"rx":633,"fill":500,"stroke":501,"style":634},"171.2",[467,1652,1654],{"x":1653,"y":1630,"style":639},"228.8","Cell column",[467,1656,1635],{"x":1653,"y":1634,"style":644},[467,1658,1639],{"x":1653,"y":1638,"style":644},[62,1660],{"x1":1661,"y1":1643,"x2":1662,"y2":1643,"stroke":501,"style":634},"291.4","313.4",[651,1664],{"points":1665,"fill":654},"317.4,63.0 308.4,68.0 308.4,58.0",[446,1667],{"x":1668,"y":456,"width":1625,"height":1626,"rx":633,"fill":658,"stroke":659,"style":634},"322.4",[467,1670,1671],{"x":637,"y":638,"style":663},"Typed value",[467,1673,1674],{"x":637,"y":643,"style":644},"Number,",[467,1676,1678],{"x":637,"y":1677,"style":644},"78.5","DateTime,",[467,1680,1583],{"x":637,"y":541,"style":644},[62,1682],{"x1":1683,"y1":1643,"x2":1684,"y2":1643,"stroke":501,"style":634},"442.6","464.6",[651,1686],{"points":1687,"fill":654},"468.6,63.0 459.6,68.0 459.6,58.0",[446,1689],{"x":1690,"y":456,"width":1625,"height":1626,"rx":633,"fill":570,"stroke":571,"style":634},"473.6",[467,1692,1694],{"x":1693,"y":1630,"style":701},"531.2","Skip merged",[467,1696,60],{"x":1693,"y":1697,"style":701},"67.8",[467,1699,1700],{"x":1693,"y":1638,"style":644},"MergeAcross",[62,1702],{"x1":1703,"y1":1643,"x2":1704,"y2":1643,"stroke":501,"style":634},"593.8","615.8",[651,1706],{"points":1707,"fill":654},"619.8,63.0 610.8,68.0 610.8,58.0",[446,1709],{"x":1710,"y":456,"width":1625,"height":1626,"rx":633,"fill":500,"stroke":501,"style":634},"624.8",[467,1712,1714],{"x":1713,"y":638,"style":639},"682.4","Grid →",[467,1716,1717],{"x":1713,"y":496,"style":639},"DataFrame",[467,1719,1720],{"x":1713,"y":1677,"style":644},"one per",[467,1722,1723],{"x":1713,"y":541,"style":644},"worksheet",[46,1725,1727],{"id":1726},"step-3-tidy-the-table-and-save-it-as-xlsx","Step 3 — Tidy the table and save it as .xlsx",[51,1729,1731],{"className":90,"code":1730,"language":92,"meta":56,"style":56},"tx = sheets[\"Transactions\"]\ntx = tx.iloc[2:].set_axis(tx.iloc[1].tolist(), axis=1).dropna(how=\"all\").reset_index(drop=True)\ntx = tx.astype({\"Date\": \"datetime64[ns]\", \"Amount\": \"float64\", \"Cleared\": \"boolean\"})\nprint(tx.dtypes.to_dict())\nprint(tx.to_string())\n\nwith pd.ExcelWriter(\"statement.xlsx\") as xw:\n    tx.to_excel(xw, sheet_name=\"Transactions\", index=False)\n    sheets[\"Summary\"].to_excel(xw, sheet_name=\"Summary\", index=False, header=False)\nprint(\"total:\", tx[\"Amount\"].sum())\n",[14,1732,1733,1749,1799,1839,1846,1853,1857,1875,1899,1935],{"__ignoreMap":56},[60,1734,1735,1738,1740,1743,1746],{"class":62,"line":63},[60,1736,1737],{"class":103},"tx ",[60,1739,191],{"class":99},[60,1741,1742],{"class":103}," sheets[",[60,1744,1745],{"class":70},"\"Transactions\"",[60,1747,1748],{"class":103},"]\n",[60,1750,1751,1753,1755,1758,1760,1763,1765,1768,1771,1773,1775,1778,1781,1783,1786,1789,1792,1794,1797],{"class":62,"line":113},[60,1752,1737],{"class":103},[60,1754,191],{"class":99},[60,1756,1757],{"class":103}," tx.iloc[",[60,1759,523],{"class":149},[60,1761,1762],{"class":103},":].set_axis(tx.iloc[",[60,1764,497],{"class":149},[60,1766,1767],{"class":103},"].tolist(), ",[60,1769,1770],{"class":187},"axis",[60,1772,191],{"class":99},[60,1774,497],{"class":149},[60,1776,1777],{"class":103},").dropna(",[60,1779,1780],{"class":187},"how",[60,1782,191],{"class":99},[60,1784,1785],{"class":70},"\"all\"",[60,1787,1788],{"class":103},").reset_index(",[60,1790,1791],{"class":187},"drop",[60,1793,191],{"class":99},[60,1795,1796],{"class":149},"True",[60,1798,272],{"class":103},[60,1800,1801,1803,1805,1808,1811,1813,1816,1818,1821,1823,1826,1828,1831,1833,1836],{"class":62,"line":127},[60,1802,1737],{"class":103},[60,1804,191],{"class":99},[60,1806,1807],{"class":103}," tx.astype({",[60,1809,1810],{"class":70},"\"Date\"",[60,1812,260],{"class":103},[60,1814,1815],{"class":70},"\"datetime64[ns]\"",[60,1817,153],{"class":103},[60,1819,1820],{"class":70},"\"Amount\"",[60,1822,260],{"class":103},[60,1824,1825],{"class":70},"\"float64\"",[60,1827,153],{"class":103},[60,1829,1830],{"class":70},"\"Cleared\"",[60,1832,260],{"class":103},[60,1834,1835],{"class":70},"\"boolean\"",[60,1837,1838],{"class":103},"})\n",[60,1840,1841,1843],{"class":62,"line":134},[60,1842,377],{"class":149},[60,1844,1845],{"class":103},"(tx.dtypes.to_dict())\n",[60,1847,1848,1850],{"class":62,"line":167},[60,1849,377],{"class":149},[60,1851,1852],{"class":103},"(tx.to_string())\n",[60,1854,1855],{"class":62,"line":176},[60,1856,131],{"emptyLinePlaceholder":130},[60,1858,1859,1862,1865,1868,1870,1872],{"class":62,"line":197},[60,1860,1861],{"class":99},"with",[60,1863,1864],{"class":103}," pd.ExcelWriter(",[60,1866,1867],{"class":70},"\"statement.xlsx\"",[60,1869,337],{"class":103},[60,1871,107],{"class":99},[60,1873,1874],{"class":103}," xw:\n",[60,1876,1877,1880,1883,1885,1887,1889,1892,1894,1897],{"class":62,"line":212},[60,1878,1879],{"class":103},"    tx.to_excel(xw, ",[60,1881,1882],{"class":187},"sheet_name",[60,1884,191],{"class":99},[60,1886,1745],{"class":70},[60,1888,153],{"class":103},[60,1890,1891],{"class":187},"index",[60,1893,191],{"class":99},[60,1895,1896],{"class":149},"False",[60,1898,272],{"class":103},[60,1900,1901,1904,1907,1910,1912,1914,1916,1918,1920,1922,1924,1926,1929,1931,1933],{"class":62,"line":275},[60,1902,1903],{"class":103},"    sheets[",[60,1905,1906],{"class":70},"\"Summary\"",[60,1908,1909],{"class":103},"].to_excel(xw, ",[60,1911,1882],{"class":187},[60,1913,191],{"class":99},[60,1915,1906],{"class":70},[60,1917,153],{"class":103},[60,1919,1891],{"class":187},[60,1921,191],{"class":99},[60,1923,1896],{"class":149},[60,1925,153],{"class":103},[60,1927,1928],{"class":187},"header",[60,1930,191],{"class":99},[60,1932,1896],{"class":149},[60,1934,272],{"class":103},[60,1936,1937,1939,1941,1944,1947,1949],{"class":62,"line":280},[60,1938,377],{"class":149},[60,1940,218],{"class":103},[60,1942,1943],{"class":70},"\"total:\"",[60,1945,1946],{"class":103},", tx[",[60,1948,1820],{"class":70},[60,1950,1951],{"class":103},"].sum())\n",[10,1953,1954,1955,1957,1958,1962],{},"The raw grid has the title row and the header row as data, which is how the file was laid out. Taking row 1 as the header, dropping the title and any fully empty rows, and setting proper types gives a clean table: dates as datetimes, amounts as floats, and the cleared flag as a nullable boolean. Writing it to a real ",[14,1956,37],{}," means the rest of the pipeline, and anyone downstream, can read it with ordinary tools. For files that arrive with untidy layouts, the techniques in ",[40,1959,1961],{"href":1960},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Ffind-the-header-row-in-an-excel-sheet-with-a-shifting-layout\u002F","Find the Header Row in an Excel Sheet with a Shifting Layout"," apply to the grid just as they do to a normal sheet.",[46,1964,1966],{"id":1965},"converting-with-libreoffice-instead","Converting with LibreOffice instead",[10,1968,1969,1970,1973,1974,1977],{},"LibreOffice can open XML Spreadsheet 2003 files, so ",[14,1971,1972],{},"soffice --headless --convert-to xlsx statement.xls"," turns one into a real workbook without any parsing code. That is handy for occasional files, but check typed columns afterwards: in a test with the sample file, LibreOffice's conversion kept the dates, text and numbers but left the Boolean ",[31,1975,1976],{},"Cleared"," column empty. The parser above is a few dozen lines, has no external dependency and handles every type, so for a recurring import it is the more reliable route.",[10,1979,1980,1981,1984,1985,1987,1988,1587,1991,1994,1995,1997],{},"If the files come from outside the organisation, parse them with ",[14,1982,1983],{},"defusedxml.ElementTree"," instead of the standard library module — it has the same interface and refuses XML tricks such as entity expansion bombs. Some systems also produce HTML tables saved with an ",[14,1986,28],{}," extension; those start with ",[14,1989,1990],{},"\u003Chtml",[14,1992,1993],{},"\u003Ctable",", and ",[14,1996,606],{}," reads them.",[46,1999,2001],{"id":2000},"common-pitfalls","Common pitfalls",[2003,2004,2005,2016,2024,2031,2041],"ul",{},[2006,2007,2008,2012,2013,2015],"li",{},[2009,2010,2011],"strong",{},"Trusting the extension."," Check the first bytes; ",[14,2014,28],{}," may be XML, HTML or real binary.",[2006,2017,2018,2023],{},[2009,2019,2020,2021,44],{},"Ignoring ",[14,2022,1563],{}," Values shift left into the wrong columns.",[2006,2025,2026,2030],{},[2009,2027,2020,2028,44],{},[14,2029,1700],{}," Every cell after a merged title is one column off per merged cell.",[2006,2032,2033,2036,2037,2040],{},[2009,2034,2035],{},"Everything as text."," Use the ",[14,2038,2039],{},"ss:Type"," attribute to convert numbers, dates and booleans.",[2006,2042,2043,2046],{},[2009,2044,2045],{},"Converting blindly."," Check typed columns after a LibreOffice conversion.",[46,2048,2050],{"id":2049},"conclusion","Conclusion",[10,2052,2053,2054,2056,2057,2059,2060,2062,2063,2066,2067,2069,2070,2072,2073,2075],{},"An ",[14,2055,28],{}," that pandas, xlrd and calamine all reject, and that starts with ",[14,2058,24],{},", is an XML Spreadsheet 2003 file. Read it with ElementTree: walk worksheets, rows and cells, honour ",[14,2061,1563],{}," on rows and cells and ",[14,2064,2065],{},"ss:MergeAcross"," on cells, and convert each ",[14,2068,1555],{}," value by its ",[14,2071,2039],{},". Turn each grid into a DataFrame, tidy the header and types, and write a real ",[14,2074,37],{}," for everything downstream.",[46,2077,2079],{"id":2078},"frequently-asked-questions","Frequently asked questions",[10,2081,2082,2085,2086,2088,2089,2091,2092,2094,2095,2097],{},[2009,2083,2084],{},"Why does pandas say the Excel file format cannot be determined?","\nThe file is neither a binary ",[14,2087,28],{}," nor a zipped ",[14,2090,37],{},"; check its first bytes — ",[14,2093,24],{}," means an XML Spreadsheet 2003 file and ",[14,2096,1990],{}," an HTML table.",[10,2099,2100,2103,2104,2106],{},[2009,2101,2102],{},"What does \"Expected BOF record; found b'\u003C?xml ve'\" mean?","\nxlrd expected a binary Excel file but found XML; the file is an XML spreadsheet with an ",[14,2105,28],{}," extension.",[10,2108,2109,2112,2113,2115],{},[2009,2110,2111],{},"Can pandas read Excel 2003 XML files?","\nNot with ",[14,2114,20],{},"; parse the XML with ElementTree into a DataFrame, or convert the file with LibreOffice first.",[10,2117,2118,2121,2122,2124,2125,2127],{},[2009,2119,2120],{},"How do I handle skipped cells in SpreadsheetML?","\nA cell with ",[14,2123,1563],{}," starts at that column; columns in between are empty. Rows with ",[14,2126,1563],{}," work the same way.",[46,2129,2131],{"id":2130},"related","Related",[2003,2133,2134,2140,2147,2154],{},[2006,2135,2136,2137,2139],{},"Up one level: ",[40,2138,43],{"href":42}," — formats and conversions.",[2006,2141,2142,2146],{},[40,2143,2145],{"href":2144},"\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"," — which engine reads what.",[2006,2148,2149,2153],{},[40,2150,2152],{"href":2151},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-xml-to-excel-with-pandas\u002F","Convert XML to Excel with pandas read_xml"," — other XML data.",[2006,2155,2156,2160],{},[40,2157,2159],{"href":2158},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fread-untrusted-excel-files-safely-in-python\u002F","Read Untrusted Excel Files Safely in Python"," — files from outside.",[2162,2163,2164],"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}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":56,"searchDepth":113,"depth":113,"links":2166},[2167,2168,2169,2170,2171,2172,2173,2174,2175,2176],{"id":48,"depth":113,"text":49},{"id":86,"depth":113,"text":87},{"id":609,"depth":113,"text":610},{"id":753,"depth":113,"text":754},{"id":1726,"depth":113,"text":1727},{"id":1965,"depth":113,"text":1966},{"id":2000,"depth":113,"text":2001},{"id":2049,"depth":113,"text":2050},{"id":2078,"depth":113,"text":2079},{"id":2130,"depth":113,"text":2131},"2026-10-02","Many banking, ERP and web systems export \"Excel\" files that are really Excel 2003 XML Spreadsheet documents, often with an .xls extension, and pandas, xlrd and calamine all refuse them. Recognise the format from its first bytes, read every worksheet into pandas with a short ElementTree parser that respects skipped cells, skipped rows, merged cells and the Number, DateTime and Boolean types, and convert the result to a real .xlsx.","md",[2181,2183,2185,2187],{"q":2084,"a":2182},"The file is neither a binary .xls nor a zipped .xlsx; check its first bytes — \u003C?xml means an XML Spreadsheet 2003 file and \u003Chtml an HTML table.",{"q":2102,"a":2184},"xlrd expected a binary Excel file but found XML; the file is an XML spreadsheet with an .xls extension.",{"q":2111,"a":2186},"Not with read_excel; parse the XML with ElementTree into a DataFrame, or convert the file with LibreOffice first.",{"q":2120,"a":2188},"A cell with ss:Index starts at that column; columns in between are empty. Rows with ss:Index work the same way.","excel 2003 xml spreadsheet python",null,{"breadcrumb":2192},[2193,2196,2199],{"name":2194,"item":2195},"Home","\u002F",{"name":2197,"item":2198},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":43,"item":42},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-excel-2003-xml-spreadsheet-files",{"title":2202,"description":2203},"Read Excel 2003 XML Spreadsheet Files with Python","Read Excel 2003 XML (SpreadsheetML) files saved as .xls: the pandas, xlrd and calamine errors, detecting the format, an ElementTree parser for ss:Index, MergeAcross and data types, and conversion to .xlsx.","read-excel-2003-xml-spreadsheet-files","getting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-excel-2003-xml-spreadsheet-files\u002Findex",[2207,2208],"file-formats","troubleshooting","how-to","OjJOZZgDRJSqmoO-gJtInjDg8_e7uR8X-v_JUvmz5hI",[2212,2216],{"title":2213,"path":2214,"stem":2215,"children":-1},"Read European CSV Exports with Semicolons and Decimal Commas","\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-csv-exports-with-semicolons-and-decimal-commas","getting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-csv-exports-with-semicolons-and-decimal-commas\u002Findex",{"title":2217,"path":2218,"stem":2219,"children":-1},"Read Strict Open XML Excel Files in Python","\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-strict-open-xml-excel-files-in-python","getting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-strict-open-xml-excel-files-in-python\u002Findex",[2221,2223],{"path":2222,"title":43},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions",{"path":2224,"title":2197},"\u002Fgetting-started-with-python-excel-automation",[],[2227,2230,2233,2237,2239,2243],{"path":2228,"title":2229,"kind":2209},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fconvert-excel-files-to-parquet-with-python","Convert Excel Files to Parquet with Python",{"path":2231,"title":2232,"kind":2209},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fconvert-excel-to-csv-with-python","Convert Excel to CSV with Python",{"path":2234,"title":2235,"kind":2236},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fexcel-vs-csv-vs-parquet-for-python-data-pipelines","Excel vs CSV vs Parquet for Python Data Pipelines","compare",{"path":2238,"title":2145,"kind":2236},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fpick-an-excel-engine-for-xlsx-xlsm-xls-xlsb-and-ods",{"path":2240,"title":2241,"kind":2242},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Ffix-garbled-characters-in-excel-data","Fix Garbled Characters Like \"CafÃ©\" and \"â€™\" in Excel Data","fix",{"path":2244,"title":2245,"kind":2209},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fextract-vba-macro-code-from-an-excel-file","Extract VBA Macro Code from an Excel File",1790972064818]