[{"data":1,"prerenderedAt":2198},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fmerge-workbooks-into-one-file-with-a-sheet-per-source":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fmerge-workbooks-into-one-file-with-a-sheet-per-source":2164,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fmerge-workbooks-into-one-file-with-a-sheet-per-source":2173,"children:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fmerge-workbooks-into-one-file-with-a-sheet-per-source":2178,"related:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fmerge-workbooks-into-one-file-with-a-sheet-per-source":2179},{"id":4,"title":5,"body":6,"dateModified":2130,"datePublished":2130,"description":2131,"extension":2132,"faq":2133,"keyword":2142,"kind":2143,"meta":2144,"navigation":122,"path":2153,"seo":2154,"slug":2157,"stem":2158,"topics":2159,"type":2162,"__hash__":2163},"docs\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fmerge-workbooks-into-one-file-with-a-sheet-per-source\u002Findex.md","Merge Workbooks into One File with a Sheet per Source",{"type":7,"value":8,"toc":2117},"minimark",[9,24,29,57,75,79,86,485,515,654,658,661,780,795,799,806,1181,1212,1353,1357,1360,1497,1526,1530,1533,1760,1774,1962,1966,2027,2031,2042,2046,2049,2053,2059,2065,2074,2080,2084,2113],[10,11,12,13,17,18,23],"p",{},"Twelve branch managers each send a workbook. Finance wants one file with twelve tabs, not one long table — each branch's sheet should look the way the branch sent it, and the totals at the bottom of each sheet should still be there. That is a different job from stacking rows with ",[14,15,16],"code",{},"pd.concat",", which is the right tool when the goal is a single dataset. Here the goal is a binder: one workbook, one tab per source, each tab named after its file. The tricky parts are sheet names (Excel is strict about them), formatting (pandas drops it), and sources that have more than one sheet. This page belongs to ",[19,20,22],"a",{"href":21},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002F","Working with Multiple Excel Sheets in Python",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,37,38],{"__ignoreMap":35},[39,40,43,47,51,54],"span",{"class":41,"line":42},"line",1,[39,44,46],{"class":45},"sMTad","pip",[39,48,50],{"class":49},"srMev"," install",[39,52,53],{"class":49}," pandas",[39,55,56],{"class":49}," openpyxl\n",[10,58,59,60,63,64,67,68,71,72,23],{},"The ",[14,61,62],{},"branches\u002F"," folder holds four workbooks. Each has a styled header, a ",[14,65,66],{},"Sales"," sheet with a ",[14,69,70],{},"SUM"," total row and number formats. One file name contains square brackets, one contains a colon and runs well past 31 characters, and two branches produce the same name once cleaned — the realistic mess that breaks naive scripts. One workbook also has a second sheet called ",[14,73,74],{},"Notes",[25,76,78],{"id":77},"step-1-turn-file-names-into-valid-unique-sheet-names","Step 1 — Turn file names into valid, unique sheet names",[10,80,81,82,85],{},"Excel sheet names must be 1–31 characters, cannot contain ",[14,83,84],{},"[ ] : * ? \u002F \\",", cannot start or end with an apostrophe, and must be unique ignoring case. Clean each name, shorten it, and add a counter when it collides:",[30,87,91],{"className":88,"code":89,"language":90,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","import re\nfrom pathlib import Path\n\ndef sheet_name(raw, taken):\n    name = re.sub(r\"[\\[\\]:*?\u002F\\\\]\", \" \", raw)\n    name = re.sub(r\"\\s+\", \" \", name).strip().strip(\"'\") or \"Sheet\"\n    base, n = name[:31].rstrip(), 1\n    candidate = base\n    while candidate.lower() in taken:\n        n += 1\n        suffix = f\" ({n})\"\n        candidate = base[:31 - len(suffix)].rstrip() + suffix\n    taken.add(candidate.lower())\n    return candidate\n\nfiles = sorted(Path(\"branches\").glob(\"*.xlsx\"))\ntaken = set()\nnames = {f: sheet_name(f.stem, taken) for f in files}\nfor f, n in names.items():\n    print(f\"{f.name:55} -> {n!r} ({len(n)})\")\n","python",[14,92,93,103,117,124,137,184,224,244,255,270,282,308,335,341,350,355,381,395,417,430],{"__ignoreMap":35},[39,94,95,99],{"class":41,"line":42},[39,96,98],{"class":97},"s-kum","import",[39,100,102],{"class":101},"skGVy"," re\n",[39,104,106,109,112,114],{"class":41,"line":105},2,[39,107,108],{"class":97},"from",[39,110,111],{"class":101}," pathlib ",[39,113,98],{"class":97},[39,115,116],{"class":101}," Path\n",[39,118,120],{"class":41,"line":119},3,[39,121,123],{"emptyLinePlaceholder":122},true,"\n",[39,125,127,130,134],{"class":41,"line":126},4,[39,128,129],{"class":97},"def",[39,131,133],{"class":132},"s_Opv"," sheet_name",[39,135,136],{"class":101},"(raw, taken):\n",[39,138,140,143,146,149,152,155,159,163,167,170,173,175,178,181],{"class":41,"line":139},5,[39,141,142],{"class":101},"    name ",[39,144,145],{"class":97},"=",[39,147,148],{"class":101}," re.sub(",[39,150,151],{"class":97},"r",[39,153,154],{"class":49},"\"",[39,156,158],{"class":157},"sP0c6","[",[39,160,162],{"class":161},"s_b0D","\\[\\]",[39,164,166],{"class":165},"sSjpA",":*?\u002F",[39,168,169],{"class":161},"\\\\",[39,171,172],{"class":157},"]",[39,174,154],{"class":49},[39,176,177],{"class":101},", ",[39,179,180],{"class":49},"\" \"",[39,182,183],{"class":101},", raw)\n",[39,185,187,189,191,193,195,197,200,203,205,207,209,212,215,218,221],{"class":41,"line":186},6,[39,188,142],{"class":101},[39,190,145],{"class":97},[39,192,148],{"class":101},[39,194,151],{"class":97},[39,196,154],{"class":49},[39,198,199],{"class":157},"\\s",[39,201,202],{"class":97},"+",[39,204,154],{"class":49},[39,206,177],{"class":101},[39,208,180],{"class":49},[39,210,211],{"class":101},", name).strip().strip(",[39,213,214],{"class":49},"\"'\"",[39,216,217],{"class":101},") ",[39,219,220],{"class":97},"or",[39,222,223],{"class":49}," \"Sheet\"\n",[39,225,227,230,232,235,238,241],{"class":41,"line":226},7,[39,228,229],{"class":101},"    base, n ",[39,231,145],{"class":97},[39,233,234],{"class":101}," name[:",[39,236,237],{"class":157},"31",[39,239,240],{"class":101},"].rstrip(), ",[39,242,243],{"class":157},"1\n",[39,245,247,250,252],{"class":41,"line":246},8,[39,248,249],{"class":101},"    candidate ",[39,251,145],{"class":97},[39,253,254],{"class":101}," base\n",[39,256,258,261,264,267],{"class":41,"line":257},9,[39,259,260],{"class":97},"    while",[39,262,263],{"class":101}," candidate.lower() ",[39,265,266],{"class":97},"in",[39,268,269],{"class":101}," taken:\n",[39,271,273,276,279],{"class":41,"line":272},10,[39,274,275],{"class":101},"        n ",[39,277,278],{"class":97},"+=",[39,280,281],{"class":157}," 1\n",[39,283,285,288,290,293,296,299,302,305],{"class":41,"line":284},11,[39,286,287],{"class":101},"        suffix ",[39,289,145],{"class":97},[39,291,292],{"class":97}," f",[39,294,295],{"class":49},"\" (",[39,297,298],{"class":165},"{",[39,300,301],{"class":101},"n",[39,303,304],{"class":165},"}",[39,306,307],{"class":49},")\"\n",[39,309,311,314,316,319,321,324,327,330,332],{"class":41,"line":310},12,[39,312,313],{"class":101},"        candidate ",[39,315,145],{"class":97},[39,317,318],{"class":101}," base[:",[39,320,237],{"class":157},[39,322,323],{"class":97}," -",[39,325,326],{"class":157}," len",[39,328,329],{"class":101},"(suffix)].rstrip() ",[39,331,202],{"class":97},[39,333,334],{"class":101}," suffix\n",[39,336,338],{"class":41,"line":337},13,[39,339,340],{"class":101},"    taken.add(candidate.lower())\n",[39,342,344,347],{"class":41,"line":343},14,[39,345,346],{"class":97},"    return",[39,348,349],{"class":101}," candidate\n",[39,351,353],{"class":41,"line":352},15,[39,354,123],{"emptyLinePlaceholder":122},[39,356,358,361,363,366,369,372,375,378],{"class":41,"line":357},16,[39,359,360],{"class":101},"files ",[39,362,145],{"class":97},[39,364,365],{"class":157}," sorted",[39,367,368],{"class":101},"(Path(",[39,370,371],{"class":49},"\"branches\"",[39,373,374],{"class":101},").glob(",[39,376,377],{"class":49},"\"*.xlsx\"",[39,379,380],{"class":101},"))\n",[39,382,384,387,389,392],{"class":41,"line":383},17,[39,385,386],{"class":101},"taken ",[39,388,145],{"class":97},[39,390,391],{"class":157}," set",[39,393,394],{"class":101},"()\n",[39,396,398,401,403,406,409,412,414],{"class":41,"line":397},18,[39,399,400],{"class":101},"names ",[39,402,145],{"class":97},[39,404,405],{"class":101}," {f: sheet_name(f.stem, taken) ",[39,407,408],{"class":97},"for",[39,410,411],{"class":101}," f ",[39,413,266],{"class":97},[39,415,416],{"class":101}," files}\n",[39,418,420,422,425,427],{"class":41,"line":419},19,[39,421,408],{"class":97},[39,423,424],{"class":101}," f, n ",[39,426,266],{"class":97},[39,428,429],{"class":101}," names.items():\n",[39,431,433,436,439,442,444,446,449,452,454,457,459,461,464,466,469,471,474,477,479,482],{"class":41,"line":432},20,[39,434,435],{"class":157},"    print",[39,437,438],{"class":101},"(",[39,440,441],{"class":97},"f",[39,443,154],{"class":49},[39,445,298],{"class":165},[39,447,448],{"class":101},"f.name",[39,450,451],{"class":97},":55",[39,453,304],{"class":165},[39,455,456],{"class":49}," -> ",[39,458,298],{"class":165},[39,460,301],{"class":101},[39,462,463],{"class":97},"!r",[39,465,304],{"class":165},[39,467,468],{"class":49}," (",[39,470,298],{"class":165},[39,472,473],{"class":157},"len",[39,475,476],{"class":101},"(n)",[39,478,304],{"class":165},[39,480,481],{"class":49},")\"",[39,483,484],{"class":101},")\n",[10,486,487,488,491,492,495,496,499,500,503,504,491,507,510,511,23],{},"The colon and brackets become spaces, the long Birmingham name is cut at 31 characters and the trailing space that leaves is trimmed, and the two Leeds files — ",[14,489,490],{},"Leeds [North]"," and ",[14,493,494],{},"Leeds (North)"," — stay distinct because brackets and parentheses are treated differently; had they collided, the second would have become ",[14,497,498],{},"… (2)",". Lower-casing the ",[14,501,502],{},"taken"," set matters: Excel considers ",[14,505,506],{},"Leeds",[14,508,509],{},"LEEDS"," the same sheet. The rules themselves are covered in ",[19,512,514],{"href":513},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-invalid-worksheet-name-errors\u002F","Fix Invalid Worksheet Name Errors When Writing Excel",[516,517,525,526,525,530,525,534,525,542,525,550,525,557,525,563,525,568,525,571,525,576,525,579,525,583,525,586,525,590,525,594,525,598,525,602,525,606,525,608,525,610,525,612,525,615,525,617,525,620,525,625,525,628,525,635,525,640,525,648],"svg",{"viewBox":518,"role":519,"ariaLabelledBy":520,"xmlns":523,"style":524},"76 0 608 196","img",[521,522],"mw-names-t","mw-names-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:608px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[527,528,529],"title",{"id":521},"File names cleaned into valid sheet names",[531,532,533],"desc",{"id":522},"On the left four file names, one with square brackets, one with a colon and over 31 characters. On the right the sheet names after cleaning, with brackets and colon replaced and the long name truncated to 31 characters.",[535,536],"rect",{"x":537,"y":538,"width":539,"height":540,"fill":541},"76","0","608","196","#ffffff",[535,543],{"x":544,"y":545,"width":546,"height":547,"rx":538,"fill":541,"stroke":548,"style":549},"92.2","40.0","208.1","28.0","var(--line,#cdd5e6)","stroke-width:1px",[551,552,556],"text",{"x":553,"y":554,"style":555},"196.3","59.0","font-size:10.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","source file",[535,558],{"x":544,"y":559,"width":546,"height":547,"rx":538,"fill":560,"stroke":561,"style":562},"68.0","#fdefd8","var(--gold,#b4740a)","stroke-width:1.5px",[551,564,567],{"x":553,"y":565,"style":566},"87.0","font-size:10.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Birmingham: Bull Ring & Digbeth…",[535,569],{"x":544,"y":570,"width":546,"height":547,"rx":538,"fill":541,"stroke":548,"style":549},"96.0",[551,572,575],{"x":553,"y":573,"style":574},"115.0","font-size:10.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Leeds (North).xlsx",[535,577],{"x":544,"y":578,"width":546,"height":547,"rx":538,"fill":560,"stroke":561,"style":562},"124.0",[551,580,582],{"x":553,"y":581,"style":566},"143.0","Leeds [North].xlsx",[535,584],{"x":544,"y":585,"width":546,"height":547,"rx":538,"fill":541,"stroke":548,"style":549},"152.0",[551,587,589],{"x":553,"y":588,"style":574},"171.0","Manchester Central.xlsx",[535,591],{"x":592,"y":545,"width":593,"height":547,"rx":538,"fill":541,"stroke":548,"style":549},"474.4","193.4",[551,595,597],{"x":596,"y":554,"style":555},"571.1","tab",[535,599],{"x":592,"y":559,"width":593,"height":547,"rx":538,"fill":600,"stroke":601,"style":562},"#d9f4f1","var(--teal,#0f9488)",[551,603,605],{"x":596,"y":565,"style":604},"font-size:10.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Birmingham Bull Ring & Digbeth",[535,607],{"x":592,"y":570,"width":593,"height":547,"rx":538,"fill":541,"stroke":548,"style":549},[551,609,494],{"x":596,"y":573,"style":574},[535,611],{"x":592,"y":578,"width":593,"height":547,"rx":538,"fill":600,"stroke":601,"style":562},[551,613,614],{"x":596,"y":581,"style":604},"Leeds North",[535,616],{"x":592,"y":585,"width":593,"height":547,"rx":538,"fill":541,"stroke":548,"style":549},[551,618,619],{"x":596,"y":588,"style":574},"Manchester Central",[551,621,624],{"x":553,"y":622,"style":623},"29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","file name",[551,626,627],{"x":596,"y":622,"style":623},"sheet name",[41,629],{"x1":630,"y1":631,"x2":632,"y2":631,"stroke":633,"style":634},"310.3","110.0","460.4","var(--brand,#5b5cf0)","stroke-width:2px",[636,637],"polygon",{"points":638,"fill":639},"464.4,110.0 455.4,115.0 455.4,105.0","#5b5cf0",[535,641],{"x":642,"y":643,"width":644,"height":645,"rx":646,"fill":541,"stroke":548,"style":647},"333.7","77.0","107.2","22.0","11","stroke-width:1.2px",[551,649,653],{"x":650,"y":651,"style":652},"387.4","92.0","font-size:11.5px;font-weight:400;fill:var(--brand-strong,#4338ca);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","sheet_name()",[25,655,657],{"id":656},"step-2-the-quick-way-values-with-pandas","Step 2 — The quick way: values with pandas",[10,659,660],{},"When formatting does not matter, read each source with pandas and write each frame to its own sheet:",[30,662,664],{"className":88,"code":663,"language":90,"meta":35,"style":35},"import pandas as pd\n\nwith pd.ExcelWriter(\"binder_values.xlsx\", engine=\"openpyxl\") as xw:\n    for f, tab in names.items():\n        pd.read_excel(f, sheet_name=\"Sales\").to_excel(xw, sheet_name=tab, index=False)\nwith pd.ExcelFile(\"binder_values.xlsx\") as xf:\n    print(xf.sheet_names)\n",[14,665,666,679,683,712,724,757,773],{"__ignoreMap":35},[39,667,668,670,673,676],{"class":41,"line":42},[39,669,98],{"class":97},[39,671,672],{"class":101}," pandas ",[39,674,675],{"class":97},"as",[39,677,678],{"class":101}," pd\n",[39,680,681],{"class":41,"line":105},[39,682,123],{"emptyLinePlaceholder":122},[39,684,685,688,691,694,696,700,702,705,707,709],{"class":41,"line":119},[39,686,687],{"class":97},"with",[39,689,690],{"class":101}," pd.ExcelWriter(",[39,692,693],{"class":49},"\"binder_values.xlsx\"",[39,695,177],{"class":101},[39,697,699],{"class":698},"sa561","engine",[39,701,145],{"class":97},[39,703,704],{"class":49},"\"openpyxl\"",[39,706,217],{"class":101},[39,708,675],{"class":97},[39,710,711],{"class":101}," xw:\n",[39,713,714,717,720,722],{"class":41,"line":126},[39,715,716],{"class":97},"    for",[39,718,719],{"class":101}," f, tab ",[39,721,266],{"class":97},[39,723,429],{"class":101},[39,725,726,729,732,734,737,740,742,744,747,750,752,755],{"class":41,"line":139},[39,727,728],{"class":101},"        pd.read_excel(f, ",[39,730,731],{"class":698},"sheet_name",[39,733,145],{"class":97},[39,735,736],{"class":49},"\"Sales\"",[39,738,739],{"class":101},").to_excel(xw, ",[39,741,731],{"class":698},[39,743,145],{"class":97},[39,745,746],{"class":101},"tab, ",[39,748,749],{"class":698},"index",[39,751,145],{"class":97},[39,753,754],{"class":157},"False",[39,756,484],{"class":101},[39,758,759,761,764,766,768,770],{"class":41,"line":186},[39,760,687],{"class":97},[39,762,763],{"class":101}," pd.ExcelFile(",[39,765,693],{"class":49},[39,767,217],{"class":101},[39,769,675],{"class":97},[39,771,772],{"class":101}," xf:\n",[39,774,775,777],{"class":41,"line":226},[39,776,435],{"class":157},[39,778,779],{"class":101},"(xf.sheet_names)\n",[10,781,782,783,785,786,789,790,794],{},"This is short and fast, but it loses everything except values: the teal header, column widths and number formats are gone, and the totals row is worse than gone — the source files were saved by openpyxl without calculated results, so the ",[14,784,70],{}," cells read as ",[14,787,788],{},"NaN",". Even with cached results, pandas would copy the ",[791,792,793],"em",{},"number",", not the formula. Use this route for data dumps, not for files people will read.",[25,796,798],{"id":797},"step-3-the-faithful-way-copy-cells-and-styles-with-openpyxl","Step 3 — The faithful way: copy cells and styles with openpyxl",[10,800,801,802,805],{},"openpyxl's ",[14,803,804],{},"copy_worksheet"," only works inside one workbook, so copying between workbooks means copying cell by cell — value, style, then the sheet-level settings:",[30,807,809],{"className":88,"code":808,"language":90,"meta":35,"style":35},"from copy import copy\nfrom openpyxl import Workbook, load_workbook\n\ndef copy_sheet(src, dst):\n    for row in src.iter_rows():\n        for cell in row:\n            if type(cell).__name__ == \"MergedCell\":\n                continue\n            new = dst.cell(cell.row, cell.column, cell.value)\n            if cell.has_style:\n                new.font, new.fill, new.border = copy(cell.font), copy(cell.fill), copy(cell.border)\n                new.alignment, new.protection = copy(cell.alignment), copy(cell.protection)\n                new.number_format = cell.number_format\n    for key, dim in src.column_dimensions.items():\n        dst.column_dimensions[key].width = dim.width\n    for key, dim in src.row_dimensions.items():\n        if dim.height:\n            dst.row_dimensions[key].height = dim.height\n    for rng in src.merged_cells.ranges:\n        dst.merge_cells(str(rng))\n    dst.freeze_panes = src.freeze_panes\n    dst.sheet_properties.tabColor = src.sheet_properties.tabColor\n\nbinder = Workbook()\nbinder.remove(binder.active)\nfor f, tab in names.items():\n    src = load_workbook(f)[\"Sales\"]\n    copy_sheet(src, binder.create_sheet(tab))\nbinder.save(\"binder.xlsx\")\ncheck = load_workbook(\"binder.xlsx\")\nws = check[\"Manchester Central\"]\nprint(check.sheetnames)\nprint([c.value for c in ws[ws.max_row]], ws[\"A1\"].fill.fgColor.rgb, ws.column_dimensions[\"A\"].width)\n",[14,810,811,823,835,839,849,861,874,897,902,912,919,929,939,949,961,971,982,990,1000,1012,1023,1034,1045,1050,1061,1067,1078,1094,1100,1111,1126,1142,1151],{"__ignoreMap":35},[39,812,813,815,818,820],{"class":41,"line":42},[39,814,108],{"class":97},[39,816,817],{"class":101}," copy ",[39,819,98],{"class":97},[39,821,822],{"class":101}," copy\n",[39,824,825,827,830,832],{"class":41,"line":105},[39,826,108],{"class":97},[39,828,829],{"class":101}," openpyxl ",[39,831,98],{"class":97},[39,833,834],{"class":101}," Workbook, load_workbook\n",[39,836,837],{"class":41,"line":119},[39,838,123],{"emptyLinePlaceholder":122},[39,840,841,843,846],{"class":41,"line":126},[39,842,129],{"class":97},[39,844,845],{"class":132}," copy_sheet",[39,847,848],{"class":101},"(src, dst):\n",[39,850,851,853,856,858],{"class":41,"line":139},[39,852,716],{"class":97},[39,854,855],{"class":101}," row ",[39,857,266],{"class":97},[39,859,860],{"class":101}," src.iter_rows():\n",[39,862,863,866,869,871],{"class":41,"line":186},[39,864,865],{"class":97},"        for",[39,867,868],{"class":101}," cell ",[39,870,266],{"class":97},[39,872,873],{"class":101}," row:\n",[39,875,876,879,882,885,888,891,894],{"class":41,"line":226},[39,877,878],{"class":97},"            if",[39,880,881],{"class":157}," type",[39,883,884],{"class":101},"(cell).",[39,886,887],{"class":157},"__name__",[39,889,890],{"class":97}," ==",[39,892,893],{"class":49}," \"MergedCell\"",[39,895,896],{"class":101},":\n",[39,898,899],{"class":41,"line":246},[39,900,901],{"class":97},"                continue\n",[39,903,904,907,909],{"class":41,"line":257},[39,905,906],{"class":101},"            new ",[39,908,145],{"class":97},[39,910,911],{"class":101}," dst.cell(cell.row, cell.column, cell.value)\n",[39,913,914,916],{"class":41,"line":272},[39,915,878],{"class":97},[39,917,918],{"class":101}," cell.has_style:\n",[39,920,921,924,926],{"class":41,"line":284},[39,922,923],{"class":101},"                new.font, new.fill, new.border ",[39,925,145],{"class":97},[39,927,928],{"class":101}," copy(cell.font), copy(cell.fill), copy(cell.border)\n",[39,930,931,934,936],{"class":41,"line":310},[39,932,933],{"class":101},"                new.alignment, new.protection ",[39,935,145],{"class":97},[39,937,938],{"class":101}," copy(cell.alignment), copy(cell.protection)\n",[39,940,941,944,946],{"class":41,"line":337},[39,942,943],{"class":101},"                new.number_format ",[39,945,145],{"class":97},[39,947,948],{"class":101}," cell.number_format\n",[39,950,951,953,956,958],{"class":41,"line":343},[39,952,716],{"class":97},[39,954,955],{"class":101}," key, dim ",[39,957,266],{"class":97},[39,959,960],{"class":101}," src.column_dimensions.items():\n",[39,962,963,966,968],{"class":41,"line":352},[39,964,965],{"class":101},"        dst.column_dimensions[key].width ",[39,967,145],{"class":97},[39,969,970],{"class":101}," dim.width\n",[39,972,973,975,977,979],{"class":41,"line":357},[39,974,716],{"class":97},[39,976,955],{"class":101},[39,978,266],{"class":97},[39,980,981],{"class":101}," src.row_dimensions.items():\n",[39,983,984,987],{"class":41,"line":383},[39,985,986],{"class":97},"        if",[39,988,989],{"class":101}," dim.height:\n",[39,991,992,995,997],{"class":41,"line":397},[39,993,994],{"class":101},"            dst.row_dimensions[key].height ",[39,996,145],{"class":97},[39,998,999],{"class":101}," dim.height\n",[39,1001,1002,1004,1007,1009],{"class":41,"line":419},[39,1003,716],{"class":97},[39,1005,1006],{"class":101}," rng ",[39,1008,266],{"class":97},[39,1010,1011],{"class":101}," src.merged_cells.ranges:\n",[39,1013,1014,1017,1020],{"class":41,"line":432},[39,1015,1016],{"class":101},"        dst.merge_cells(",[39,1018,1019],{"class":157},"str",[39,1021,1022],{"class":101},"(rng))\n",[39,1024,1026,1029,1031],{"class":41,"line":1025},21,[39,1027,1028],{"class":101},"    dst.freeze_panes ",[39,1030,145],{"class":97},[39,1032,1033],{"class":101}," src.freeze_panes\n",[39,1035,1037,1040,1042],{"class":41,"line":1036},22,[39,1038,1039],{"class":101},"    dst.sheet_properties.tabColor ",[39,1041,145],{"class":97},[39,1043,1044],{"class":101}," src.sheet_properties.tabColor\n",[39,1046,1048],{"class":41,"line":1047},23,[39,1049,123],{"emptyLinePlaceholder":122},[39,1051,1053,1056,1058],{"class":41,"line":1052},24,[39,1054,1055],{"class":101},"binder ",[39,1057,145],{"class":97},[39,1059,1060],{"class":101}," Workbook()\n",[39,1062,1064],{"class":41,"line":1063},25,[39,1065,1066],{"class":101},"binder.remove(binder.active)\n",[39,1068,1070,1072,1074,1076],{"class":41,"line":1069},26,[39,1071,408],{"class":97},[39,1073,719],{"class":101},[39,1075,266],{"class":97},[39,1077,429],{"class":101},[39,1079,1081,1084,1086,1089,1091],{"class":41,"line":1080},27,[39,1082,1083],{"class":101},"    src ",[39,1085,145],{"class":97},[39,1087,1088],{"class":101}," load_workbook(f)[",[39,1090,736],{"class":49},[39,1092,1093],{"class":101},"]\n",[39,1095,1097],{"class":41,"line":1096},28,[39,1098,1099],{"class":101},"    copy_sheet(src, binder.create_sheet(tab))\n",[39,1101,1103,1106,1109],{"class":41,"line":1102},29,[39,1104,1105],{"class":101},"binder.save(",[39,1107,1108],{"class":49},"\"binder.xlsx\"",[39,1110,484],{"class":101},[39,1112,1114,1117,1119,1122,1124],{"class":41,"line":1113},30,[39,1115,1116],{"class":101},"check ",[39,1118,145],{"class":97},[39,1120,1121],{"class":101}," load_workbook(",[39,1123,1108],{"class":49},[39,1125,484],{"class":101},[39,1127,1129,1132,1134,1137,1140],{"class":41,"line":1128},31,[39,1130,1131],{"class":101},"ws ",[39,1133,145],{"class":97},[39,1135,1136],{"class":101}," check[",[39,1138,1139],{"class":49},"\"Manchester Central\"",[39,1141,1093],{"class":101},[39,1143,1145,1148],{"class":41,"line":1144},32,[39,1146,1147],{"class":157},"print",[39,1149,1150],{"class":101},"(check.sheetnames)\n",[39,1152,1154,1156,1159,1161,1164,1166,1169,1172,1175,1178],{"class":41,"line":1153},33,[39,1155,1147],{"class":157},[39,1157,1158],{"class":101},"([c.value ",[39,1160,408],{"class":97},[39,1162,1163],{"class":101}," c ",[39,1165,266],{"class":97},[39,1167,1168],{"class":101}," ws[ws.max_row]], ws[",[39,1170,1171],{"class":49},"\"A1\"",[39,1173,1174],{"class":101},"].fill.fgColor.rgb, ws.column_dimensions[",[39,1176,1177],{"class":49},"\"A\"",[39,1179,1180],{"class":101},"].width)\n",[10,1182,1183,1184,1187,1188,1191,1192,1195,1196,1199,1200,1203,1204,1207,1208,23],{},"Each tab now has the teal header, the column widths and number formats — and the total row still holds ",[14,1185,1186],{},"=SUM(B2:B3)",", a live formula, because the cell's value ",[791,1189,1190],{},"is"," the formula text when the workbook is loaded without ",[14,1193,1194],{},"data_only",". Formulas that refer to cells on the same sheet keep working after the copy. The six style attributes are copied one by one on purpose. The shortcut that works inside a single workbook — ",[14,1197,1198],{},"new._style = copy(cell._style)"," — copies a set of ",[791,1201,1202],{},"index numbers"," into the source workbook's style tables; in another workbook those numbers point at styles that do not exist, and reading the cell's fill then fails with ",[14,1205,1206],{},"IndexError: list index out of range"," (or, worse, silently picks up a different style). Within one workbook the shortcut is fine, as explained in ",[19,1209,1211],{"href":1210},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fcopy-cell-styles-with-openpyxl\u002F","Copy Cell Styles with openpyxl",[516,1213,525,1219,525,1222,525,1225,525,1230,525,1238,525,1249,525,1255,525,1260,525,1266,525,1273,525,1279,525,1282,525,1287,525,1290,525,1293,525,1296,525,1301,525,1304,525,1308,525,1311,525,1313,525,1316,525,1321,525,1324,525,1328,525,1331,525,1333,525,1336,525,1341,525,1344,525,1348,525,1351],{"viewBox":1214,"role":519,"ariaLabelledBy":1215,"xmlns":523,"style":1218},"4 0 752 453",[1216,1217],"mw-fan-t","mw-fan-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[527,1220,1221],{"id":1216},"Four source workbooks become four tabs in one binder",[531,1223,1224],{"id":1217},"The four branch files on the left each feed one tab of binder.xlsx on the right, keeping their header styling, widths, number formats and SUM formulas.",[535,1226],{"x":1227,"y":538,"width":1228,"height":1229,"fill":541},"4","752","453",[535,1231],{"x":1232,"y":1233,"width":1234,"height":1235,"rx":1236,"fill":1237,"stroke":633,"style":634},"520.0","180.0","220.0","93.0","12","#f0f4ff",[1239,1240,1242],"g",{"transform":1241},"translate(618.0,193.0) scale(1.000)",[1243,1244],"path",{"d":1245,"fill":1246,"stroke":1247,"style":1248},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[551,1250,1254],{"x":1251,"y":1252,"style":1253},"630.0","239.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","binder.xlsx",[551,1256,1259],{"x":1251,"y":1257,"style":1258},"259.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","one tab per source",[535,1261],{"x":1262,"y":1263,"width":1264,"height":1265,"rx":1236,"fill":600,"stroke":601,"style":634},"20.0","16.0","250.0","109.0",[1239,1267,1269],{"transform":1268},"translate(133.0,29.0) scale(1.000)",[1243,1270],{"d":1271,"fill":1246,"stroke":1272,"style":1248},"M6 3h9l4 4v14H6z M15 3v4h4","var(--teal-ink,#0b6157)",[551,1274,1278],{"x":1275,"y":1276,"style":1277},"145.0","75.0","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Birmingham … combined",[551,1280,1281],{"x":1275,"y":651,"style":1277},"store.xlsx",[551,1283,1286],{"x":1275,"y":1284,"style":1285},"111.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","tab Birmingham Bull Ring & Digbeth",[1243,1288],{"d":1289,"fill":1246,"stroke":633,"style":634},"M274.0,70.5 C394.0,70.5 394.0,226.5 510.0,226.5",[636,1291],{"points":1292,"fill":639},"514.0,226.5 505.0,231.5 505.0,221.5",[535,1294],{"x":1262,"y":1295,"width":1264,"height":651,"rx":1236,"fill":600,"stroke":601,"style":634},"137.0",[1239,1297,1299],{"transform":1298},"translate(133.0,150.0) scale(1.000)",[1243,1300],{"d":1271,"fill":1246,"stroke":1272,"style":1248},[551,1302,575],{"x":1275,"y":1303,"style":1277},"196.0",[551,1305,1307],{"x":1275,"y":1306,"style":1285},"215.0","tab Leeds (North)",[1243,1309],{"d":1310,"fill":1246,"stroke":633,"style":634},"M274.0,183.0 C394.0,183.0 394.0,226.5 510.0,226.5",[636,1312],{"points":1292,"fill":639},[535,1314],{"x":1262,"y":1315,"width":1264,"height":651,"rx":1236,"fill":600,"stroke":601,"style":634},"241.0",[1239,1317,1319],{"transform":1318},"translate(133.0,254.0) scale(1.000)",[1243,1320],{"d":1271,"fill":1246,"stroke":1272,"style":1248},[551,1322,582],{"x":1275,"y":1323,"style":1277},"300.0",[551,1325,1327],{"x":1275,"y":1326,"style":1285},"319.0","tab Leeds North",[1243,1329],{"d":1330,"fill":1246,"stroke":633,"style":634},"M274.0,287.0 C394.0,287.0 394.0,226.5 510.0,226.5",[636,1332],{"points":1292,"fill":639},[535,1334],{"x":1262,"y":1335,"width":1264,"height":651,"rx":1236,"fill":600,"stroke":601,"style":634},"345.0",[1239,1337,1339],{"transform":1338},"translate(133.0,358.0) scale(1.000)",[1243,1340],{"d":1271,"fill":1246,"stroke":1272,"style":1248},[551,1342,589],{"x":1275,"y":1343,"style":1277},"404.0",[551,1345,1347],{"x":1275,"y":1346,"style":1285},"423.0","tab Manchester Central",[1243,1349],{"d":1350,"fill":1246,"stroke":633,"style":634},"M274.0,391.0 C394.0,391.0 394.0,226.5 510.0,226.5",[636,1352],{"points":1292,"fill":639},[25,1354,1356],{"id":1355},"step-4-sources-with-more-than-one-sheet","Step 4 — Sources with more than one sheet",[10,1358,1359],{},"Some sources carry extra sheets — notes, a lookup list, a second month. Copy every sheet and prefix its tab name with the source, still within the 31-character budget:",[30,1361,1363],{"className":88,"code":1362,"language":90,"meta":35,"style":35},"binder = Workbook()\nbinder.remove(binder.active)\ntaken = set()\nfor f in files:\n    wb = load_workbook(f)\n    for src in wb.worksheets:\n        label = f.stem if len(wb.worksheets) == 1 else f\"{f.stem} - {src.title}\"\n        copy_sheet(src, binder.create_sheet(sheet_name(label, taken)))\nbinder.save(\"binder_all.xlsx\")\nprint(load_workbook(\"binder_all.xlsx\").sheetnames)\n",[14,1364,1365,1373,1377,1387,1398,1408,1420,1471,1476,1485],{"__ignoreMap":35},[39,1366,1367,1369,1371],{"class":41,"line":42},[39,1368,1055],{"class":101},[39,1370,145],{"class":97},[39,1372,1060],{"class":101},[39,1374,1375],{"class":41,"line":105},[39,1376,1066],{"class":101},[39,1378,1379,1381,1383,1385],{"class":41,"line":119},[39,1380,386],{"class":101},[39,1382,145],{"class":97},[39,1384,391],{"class":157},[39,1386,394],{"class":101},[39,1388,1389,1391,1393,1395],{"class":41,"line":126},[39,1390,408],{"class":97},[39,1392,411],{"class":101},[39,1394,266],{"class":97},[39,1396,1397],{"class":101}," files:\n",[39,1399,1400,1403,1405],{"class":41,"line":139},[39,1401,1402],{"class":101},"    wb ",[39,1404,145],{"class":97},[39,1406,1407],{"class":101}," load_workbook(f)\n",[39,1409,1410,1412,1415,1417],{"class":41,"line":186},[39,1411,716],{"class":97},[39,1413,1414],{"class":101}," src ",[39,1416,266],{"class":97},[39,1418,1419],{"class":101}," wb.worksheets:\n",[39,1421,1422,1425,1427,1430,1433,1435,1438,1441,1444,1447,1449,1451,1453,1456,1458,1461,1463,1466,1468],{"class":41,"line":226},[39,1423,1424],{"class":101},"        label ",[39,1426,145],{"class":97},[39,1428,1429],{"class":101}," f.stem ",[39,1431,1432],{"class":97},"if",[39,1434,326],{"class":157},[39,1436,1437],{"class":101},"(wb.worksheets) ",[39,1439,1440],{"class":97},"==",[39,1442,1443],{"class":157}," 1",[39,1445,1446],{"class":97}," else",[39,1448,292],{"class":97},[39,1450,154],{"class":49},[39,1452,298],{"class":165},[39,1454,1455],{"class":101},"f.stem",[39,1457,304],{"class":165},[39,1459,1460],{"class":49}," - ",[39,1462,298],{"class":165},[39,1464,1465],{"class":101},"src.title",[39,1467,304],{"class":165},[39,1469,1470],{"class":49},"\"\n",[39,1472,1473],{"class":41,"line":246},[39,1474,1475],{"class":101},"        copy_sheet(src, binder.create_sheet(sheet_name(label, taken)))\n",[39,1477,1478,1480,1483],{"class":41,"line":257},[39,1479,1105],{"class":101},[39,1481,1482],{"class":49},"\"binder_all.xlsx\"",[39,1484,484],{"class":101},[39,1486,1487,1489,1492,1494],{"class":41,"line":272},[39,1488,1147],{"class":157},[39,1490,1491],{"class":101},"(load_workbook(",[39,1493,1482],{"class":49},[39,1495,1496],{"class":101},").sheetnames)\n",[10,1498,1499,1500,1503,1504,491,1507,1510,1511,1514,1515,1518,1519,1521,1522,23],{},"The Leeds ",[39,1501,1502],{},"North"," workbook contributes two tabs, ",[14,1505,1506],{},"Leeds North - Sales",[14,1508,1509],{},"Leeds North - Notes","; every other source contributes one tab named after the file alone. Formulas that point at ",[791,1512,1513],{},"another"," sheet of the source — ",[14,1516,1517],{},"=Notes!A1"," — will now point at a sheet called ",[14,1520,74],{}," in the binder, which does not exist. Rewrite such references with the new tab names, or convert them to values before copying, as in ",[19,1523,1525],{"href":1524},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fconvert-excel-formulas-to-values-with-python\u002F","Convert Excel Formulas to Values with Python",[25,1527,1529],{"id":1528},"step-5-add-an-index-sheet","Step 5 — Add an index sheet",[10,1531,1532],{},"A binder with dozens of tabs needs a front page. Insert a first sheet listing every tab with a link to it and the file it came from:",[30,1534,1536],{"className":88,"code":1535,"language":90,"meta":35,"style":35},"from openpyxl.styles import Font\n\nbinder = load_workbook(\"binder.xlsx\")\nindex = binder.create_sheet(\"Index\", 0)\nindex.append([\"Sheet\", \"Source file\"])\nindex[\"A1\"].font = index[\"B1\"].font = Font(bold=True)\nfor f, tab in names.items():\n    index.append([tab, f.name])\n    cell = index.cell(index.max_row, 1)\n    cell.hyperlink = f\"#'{tab}'!A1\"\n    cell.style = \"Hyperlink\"\nindex.column_dimensions[\"A\"].width = 34\nindex.column_dimensions[\"B\"].width = 52\nbinder.active = 0\nbinder.save(\"binder.xlsx\")\nprint(load_workbook(\"binder.xlsx\").sheetnames[:2])\n",[14,1537,1538,1550,1554,1566,1585,1601,1636,1646,1651,1666,1687,1697,1712,1726,1736,1744],{"__ignoreMap":35},[39,1539,1540,1542,1545,1547],{"class":41,"line":42},[39,1541,108],{"class":97},[39,1543,1544],{"class":101}," openpyxl.styles ",[39,1546,98],{"class":97},[39,1548,1549],{"class":101}," Font\n",[39,1551,1552],{"class":41,"line":105},[39,1553,123],{"emptyLinePlaceholder":122},[39,1555,1556,1558,1560,1562,1564],{"class":41,"line":119},[39,1557,1055],{"class":101},[39,1559,145],{"class":97},[39,1561,1121],{"class":101},[39,1563,1108],{"class":49},[39,1565,484],{"class":101},[39,1567,1568,1571,1573,1576,1579,1581,1583],{"class":41,"line":126},[39,1569,1570],{"class":101},"index ",[39,1572,145],{"class":97},[39,1574,1575],{"class":101}," binder.create_sheet(",[39,1577,1578],{"class":49},"\"Index\"",[39,1580,177],{"class":101},[39,1582,538],{"class":157},[39,1584,484],{"class":101},[39,1586,1587,1590,1593,1595,1598],{"class":41,"line":139},[39,1588,1589],{"class":101},"index.append([",[39,1591,1592],{"class":49},"\"Sheet\"",[39,1594,177],{"class":101},[39,1596,1597],{"class":49},"\"Source file\"",[39,1599,1600],{"class":101},"])\n",[39,1602,1603,1606,1608,1611,1613,1616,1619,1621,1623,1626,1629,1631,1634],{"class":41,"line":186},[39,1604,1605],{"class":101},"index[",[39,1607,1171],{"class":49},[39,1609,1610],{"class":101},"].font ",[39,1612,145],{"class":97},[39,1614,1615],{"class":101}," index[",[39,1617,1618],{"class":49},"\"B1\"",[39,1620,1610],{"class":101},[39,1622,145],{"class":97},[39,1624,1625],{"class":101}," Font(",[39,1627,1628],{"class":698},"bold",[39,1630,145],{"class":97},[39,1632,1633],{"class":157},"True",[39,1635,484],{"class":101},[39,1637,1638,1640,1642,1644],{"class":41,"line":226},[39,1639,408],{"class":97},[39,1641,719],{"class":101},[39,1643,266],{"class":97},[39,1645,429],{"class":101},[39,1647,1648],{"class":41,"line":246},[39,1649,1650],{"class":101},"    index.append([tab, f.name])\n",[39,1652,1653,1656,1658,1661,1664],{"class":41,"line":257},[39,1654,1655],{"class":101},"    cell ",[39,1657,145],{"class":97},[39,1659,1660],{"class":101}," index.cell(index.max_row, ",[39,1662,1663],{"class":157},"1",[39,1665,484],{"class":101},[39,1667,1668,1671,1673,1675,1678,1680,1682,1684],{"class":41,"line":272},[39,1669,1670],{"class":101},"    cell.hyperlink ",[39,1672,145],{"class":97},[39,1674,292],{"class":97},[39,1676,1677],{"class":49},"\"#'",[39,1679,298],{"class":165},[39,1681,597],{"class":101},[39,1683,304],{"class":165},[39,1685,1686],{"class":49},"'!A1\"\n",[39,1688,1689,1692,1694],{"class":41,"line":284},[39,1690,1691],{"class":101},"    cell.style ",[39,1693,145],{"class":97},[39,1695,1696],{"class":49}," \"Hyperlink\"\n",[39,1698,1699,1702,1704,1707,1709],{"class":41,"line":310},[39,1700,1701],{"class":101},"index.column_dimensions[",[39,1703,1177],{"class":49},[39,1705,1706],{"class":101},"].width ",[39,1708,145],{"class":97},[39,1710,1711],{"class":157}," 34\n",[39,1713,1714,1716,1719,1721,1723],{"class":41,"line":337},[39,1715,1701],{"class":101},[39,1717,1718],{"class":49},"\"B\"",[39,1720,1706],{"class":101},[39,1722,145],{"class":97},[39,1724,1725],{"class":157}," 52\n",[39,1727,1728,1731,1733],{"class":41,"line":343},[39,1729,1730],{"class":101},"binder.active ",[39,1732,145],{"class":97},[39,1734,1735],{"class":157}," 0\n",[39,1737,1738,1740,1742],{"class":41,"line":352},[39,1739,1105],{"class":101},[39,1741,1108],{"class":49},[39,1743,484],{"class":101},[39,1745,1746,1748,1750,1752,1755,1758],{"class":41,"line":357},[39,1747,1147],{"class":157},[39,1749,1491],{"class":101},[39,1751,1108],{"class":49},[39,1753,1754],{"class":101},").sheetnames[:",[39,1756,1757],{"class":157},"2",[39,1759,1600],{"class":101},[10,1761,59,1762,1765,1766,1769,1770,23],{},[14,1763,1764],{},"#'Tab'!A1"," form is an internal link; the single quotes are required whenever a sheet name contains spaces or punctuation, which most cleaned file names do. Setting ",[14,1767,1768],{},"binder.active = 0"," makes the file open on the index. For a richer contents page, see ",[19,1771,1773],{"href":1772},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-a-table-of-contents-sheet-with-hyperlinks-in-excel\u002F","Add a Table of Contents Sheet with Hyperlinks in Excel",[516,1775,525,1781,525,1784,525,1787,525,1792,525,1799,525,1804,525,1810,525,1815,525,1820,525,1823,525,1829,525,1833,525,1837,525,1840,525,1844,525,1846,525,1850,525,1852,525,1856,525,1859,525,1862,525,1864,525,1866,525,1868,525,1871,525,1874,525,1877,525,1879,525,1881,525,1883,525,1885,525,1888,525,1891,525,1893,525,1895,525,1897,525,1899,525,1902,525,1906,525,1908,525,1910,525,1912,525,1914,525,1919,525,1925,525,1931,525,1935,525,1940,525,1944,525,1947,525,1951,525,1954,525,1958],{"viewBox":1776,"role":519,"ariaLabelledBy":1777,"xmlns":523,"style":1780},"131 0 513 250",[1778,1779],"mw-index-t","mw-index-d","width:100%;max-width:513px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[527,1782,1783],{"id":1778},"The index sheet that opens first",[531,1785,1786],{"id":1779},"The Index tab lists each sheet name as a hyperlink next to the file it came from, followed by the branch tabs along the bottom of the workbook window.",[535,1788],{"x":1789,"y":538,"width":1790,"height":1791,"fill":541},"131","513","250",[535,1793],{"x":1794,"y":1263,"width":1795,"height":1796,"rx":1797,"fill":1798,"stroke":548,"style":562},"147.5","465.0","30.0","8","#e7ebef",[535,1800],{"x":1794,"y":1801,"width":1795,"height":1802,"rx":538,"fill":1798,"stroke":548,"style":1803},"38.0","8.0","stroke-width:0px",[1239,1805,1807],{"transform":1806},"translate(157.5,23.0) scale(0.667)",[1243,1808],{"d":1245,"fill":1246,"stroke":1272,"style":1809},"stroke-width:2.70px;stroke-linecap:round;stroke-linejoin:round",[551,1811,1254],{"x":1812,"y":1813,"style":1814},"179.5","36.0","font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:start",[535,1816],{"x":1794,"y":1817,"width":1818,"height":1819,"rx":538,"fill":1798,"stroke":548,"style":549},"46.0","32.0","24.0",[535,1821],{"x":1812,"y":1817,"width":1822,"height":1819,"rx":538,"fill":1798,"stroke":548,"style":549},"201.5",[551,1824,1828],{"x":1825,"y":1826,"style":1827},"280.2","63.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[535,1830],{"x":1831,"y":1817,"width":1832,"height":1819,"rx":538,"fill":1798,"stroke":548,"style":549},"381.0","231.5",[551,1834,1836],{"x":1835,"y":1826,"style":1827},"496.7","B",[535,1838],{"x":1794,"y":1839,"width":1818,"height":547,"rx":538,"fill":1798,"stroke":548,"style":549},"70.0",[551,1841,1663],{"x":1842,"y":1843,"style":1827},"163.5","89.0",[535,1845],{"x":1812,"y":1839,"width":1822,"height":547,"rx":538,"fill":1798,"stroke":548,"style":562},[551,1847,1849],{"x":1825,"y":1843,"style":1848},"font-size:11px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Sheet",[535,1851],{"x":1831,"y":1839,"width":1832,"height":547,"rx":538,"fill":1798,"stroke":548,"style":562},[551,1853,1855],{"x":1835,"y":1843,"style":1854},"font-size:11px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Source file",[535,1857],{"x":1794,"y":1858,"width":1818,"height":547,"rx":538,"fill":1798,"stroke":548,"style":549},"98.0",[551,1860,1757],{"x":1842,"y":1861,"style":1827},"117.0",[535,1863],{"x":1812,"y":1858,"width":1822,"height":547,"rx":538,"fill":541,"stroke":548,"style":549},[551,1865,605],{"x":1825,"y":1861,"style":1848},[535,1867],{"x":1831,"y":1858,"width":1832,"height":547,"rx":538,"fill":541,"stroke":548,"style":549},[551,1869,1870],{"x":1835,"y":1861,"style":1285},"Birmingham: Bull Ring & … store.xlsx",[535,1872],{"x":1794,"y":1873,"width":1818,"height":547,"rx":538,"fill":1798,"stroke":548,"style":549},"126.0",[551,1875,1876],{"x":1842,"y":1275,"style":1827},"3",[535,1878],{"x":1812,"y":1873,"width":1822,"height":547,"rx":538,"fill":541,"stroke":548,"style":549},[551,1880,494],{"x":1825,"y":1275,"style":1848},[535,1882],{"x":1831,"y":1873,"width":1832,"height":547,"rx":538,"fill":541,"stroke":548,"style":549},[551,1884,575],{"x":1835,"y":1275,"style":1285},[535,1886],{"x":1794,"y":1887,"width":1818,"height":547,"rx":538,"fill":1798,"stroke":548,"style":549},"154.0",[551,1889,1227],{"x":1842,"y":1890,"style":1827},"173.0",[535,1892],{"x":1812,"y":1887,"width":1822,"height":547,"rx":538,"fill":541,"stroke":548,"style":549},[551,1894,614],{"x":1825,"y":1890,"style":1848},[535,1896],{"x":1831,"y":1887,"width":1832,"height":547,"rx":538,"fill":541,"stroke":548,"style":549},[551,1898,582],{"x":1835,"y":1890,"style":1285},[535,1900],{"x":1794,"y":1901,"width":1818,"height":547,"rx":538,"fill":1798,"stroke":548,"style":549},"182.0",[551,1903,1905],{"x":1842,"y":1904,"style":1827},"201.0","5",[535,1907],{"x":1812,"y":1901,"width":1822,"height":547,"rx":538,"fill":541,"stroke":548,"style":549},[551,1909,619],{"x":1825,"y":1904,"style":1848},[535,1911],{"x":1831,"y":1901,"width":1832,"height":547,"rx":538,"fill":541,"stroke":548,"style":549},[551,1913,589],{"x":1835,"y":1904,"style":1285},[535,1915],{"x":1916,"y":1917,"width":1918,"height":1819,"rx":538,"fill":541,"stroke":548,"style":549},"155.5","210.0","56.7",[41,1920],{"x1":1921,"y1":1922,"x2":1923,"y2":1922,"stroke":601,"style":1924},"157.5","233.0","210.2","stroke-width:3px",[551,1926,1930],{"x":1927,"y":1928,"style":1929},"183.9","226.5","font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Index",[535,1932],{"x":1933,"y":1917,"width":1934,"height":1819,"rx":538,"fill":1798,"stroke":548,"style":549},"214.2","105.7",[551,1936,1939],{"x":1937,"y":1928,"style":1938},"267.1","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","Birmingham …",[535,1941],{"x":1942,"y":1917,"width":1943,"height":1819,"rx":538,"fill":1798,"stroke":548,"style":549},"322.0","102.8",[551,1945,494],{"x":1946,"y":1928,"style":1938},"373.4",[535,1948],{"x":1949,"y":1917,"width":1950,"height":1819,"rx":538,"fill":1798,"stroke":548,"style":549},"426.8","94.0",[551,1952,614],{"x":1953,"y":1928,"style":1938},"473.8",[535,1955],{"x":1956,"y":1917,"width":1957,"height":1819,"rx":538,"fill":1798,"stroke":548,"style":549},"522.8","105.6",[551,1959,1961],{"x":1960,"y":1928,"style":1938},"575.6","Manchester …",[25,1963,1965],{"id":1964},"common-pitfalls","Common pitfalls",[1967,1968,1969,1977,1987,1997,2003,2013],"ul",{},[1970,1971,1972,1976],"li",{},[1973,1974,1975],"strong",{},"Invalid or duplicate sheet names."," Clean, truncate to 31 characters and de-duplicate case-insensitively before creating the sheet.",[1970,1978,1979,1982,1983,1986],{},[1973,1980,1981],{},"Expecting pandas to keep formatting."," ",[14,1984,1985],{},"to_excel"," writes values; use the openpyxl copy for anything people will read.",[1970,1988,1989,1996],{},[1973,1990,1991,1992,1995],{},"Copying ",[14,1993,1994],{},"_style"," across workbooks."," Style indices only mean something in the workbook they came from; copy font, fill, border, alignment, protection and number format individually.",[1970,1998,1999,2002],{},[1973,2000,2001],{},"Cross-sheet formulas."," References to other sheets of the source break in the binder; rewrite them or freeze them as values.",[1970,2004,2005,2008,2009,2012],{},[1973,2006,2007],{},"What the copy leaves behind."," Charts, images, conditional formatting and data validation are not copied by the cell loop; copy conditional formats with ",[14,2010,2011],{},"dst.conditional_formatting = src.conditional_formatting"," only when the ranges are unchanged, and re-create charts.",[1970,2014,2015,2018,2019,2022,2023,23],{},[1973,2016,2017],{},"Lock files in the folder."," Skip ",[14,2020,2021],{},"~$"," files that appear while a source is open, as in ",[19,2024,2026],{"href":2025},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fignore-excel-lock-files-when-processing-a-folder\u002F","Ignore Excel Lock Files When Processing a Folder",[25,2028,2030],{"id":2029},"performance-notes","Performance notes",[10,2032,2033,2034,2037,2038,2041],{},"The cell-by-cell copy is linear in the number of cells: a few thousand rows per source copy in well under a second each. For very large sources, copy values with pandas and re-apply a standard style to the header and number formats afterwards, which is faster than copying every cell's style. Load sources with ",[14,2035,2036],{},"load_workbook(f)"," rather than ",[14,2039,2040],{},"read_only=True",", because read-only worksheets do not expose column widths and merged ranges.",[25,2043,2045],{"id":2044},"conclusion","Conclusion",[10,2047,2048],{},"To merge workbooks into one file with a tab per source, first turn file names into valid, unique, 31-character sheet names. Copy values with pandas when formatting does not matter; otherwise copy each cell's value and style with openpyxl, along with widths, heights, merges and freeze panes, which keeps same-sheet formulas live. Prefix tab names for multi-sheet sources, fix cross-sheet references, and finish with a hyperlinked index sheet so readers can find their way.",[25,2050,2052],{"id":2051},"frequently-asked-questions","Frequently asked questions",[10,2054,2055,2058],{},[1973,2056,2057],{},"How do I combine multiple Excel files into one workbook with separate sheets?","\nLoop over the files, create a sheet per file in a new workbook, and copy each source sheet into it — with pd.read_excel and to_excel(sheet_name=...) for values only, or cell by cell with openpyxl to keep formatting.",[10,2060,2061,2064],{},[1973,2062,2063],{},"Can openpyxl copy a sheet from one workbook to another?","\nNot directly; copy_worksheet only copies within the same workbook. Copy each cell's value and _style into a sheet of the target workbook, then copy column widths, row heights, merged ranges and freeze panes.",[10,2066,2067,2070,2071,2073],{},[1973,2068,2069],{},"How do I make a file name a valid Excel sheet name?","\nReplace the characters ",[39,2072],{}," : * ? \u002F \\ with spaces, trim leading and trailing apostrophes, cut the result to 31 characters, and add a counter if the name (compared case-insensitively) is already taken.",[10,2075,2076,2079],{},[1973,2077,2078],{},"Do formulas still work after copying sheets into one workbook?","\nFormulas that refer to cells on the same sheet keep working, because their text is copied unchanged. References to other sheets of the source workbook break unless those sheets are copied too and keep the same names.",[25,2081,2083],{"id":2082},"related","Related",[1967,2085,2086,2092,2099,2106],{},[1970,2087,2088,2089,2091],{},"Up one level: ",[19,2090,22],{"href":21}," — reading, combining and splitting sheets.",[1970,2093,2094,2098],{},[19,2095,2097],{"href":2096},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fcopy-a-sheet-between-excel-workbooks-with-python\u002F","Copy a Sheet Between Excel Workbooks with Python"," — the single-sheet version in depth.",[1970,2100,2101,2105],{},[19,2102,2104],{"href":2103},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fcombine-multiple-excel-files-into-one-python\u002F","Combine Multiple Excel Files into One with Python"," — stacking rows into one table instead.",[1970,2107,2108,2112],{},[19,2109,2111],{"href":2110},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fsave-each-sheet-of-a-workbook-as-a-separate-file\u002F","Save Each Sheet of a Workbook as a Separate File"," — the reverse operation.",[2114,2115,2116],"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 .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s_b0D, html code.shiki .s_b0D{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":35,"searchDepth":105,"depth":105,"links":2118},[2119,2120,2121,2122,2123,2124,2125,2126,2127,2128,2129],{"id":27,"depth":105,"text":28},{"id":77,"depth":105,"text":78},{"id":656,"depth":105,"text":657},{"id":797,"depth":105,"text":798},{"id":1355,"depth":105,"text":1356},{"id":1528,"depth":105,"text":1529},{"id":1964,"depth":105,"text":1965},{"id":2029,"depth":105,"text":2030},{"id":2044,"depth":105,"text":2045},{"id":2051,"depth":105,"text":2052},{"id":2082,"depth":105,"text":2083},"2026-10-02","Collect a folder of workbooks into a single file where each source keeps its own tab: turn file names into valid, unique sheet names under the 31-character limit, copy values quickly with pandas or copy values, styles, widths and merged cells with openpyxl, handle source files with several sheets, and add an index sheet that links to every tab.","md",[2134,2136,2138,2140],{"q":2057,"a":2135},"Loop over the files, create a sheet per file in a new workbook, and copy each source sheet into it — with pd.read_excel and to_excel(sheet_name=...) for values only, or cell by cell with openpyxl to keep formatting.",{"q":2063,"a":2137},"Not directly; copy_worksheet only copies within the same workbook. Copy each cell's value and _style into a sheet of the target workbook, then copy column widths, row heights, merged ranges and freeze panes.",{"q":2069,"a":2139},"Replace the characters [ ] : * ? \u002F \\ with spaces, trim leading and trailing apostrophes, cut the result to 31 characters, and add a counter if the name (compared case-insensitively) is already taken.",{"q":2078,"a":2141},"Formulas that refer to cells on the same sheet keep working, because their text is copied unchanged. References to other sheets of the source workbook break unless those sheets are copied too and keep the same names.","merge workbooks into one file with a sheet per source",null,{"breadcrumb":2145},[2146,2149,2152],{"name":2147,"item":2148},"Home","\u002F",{"name":2150,"item":2151},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":22,"item":21},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fmerge-workbooks-into-one-file-with-a-sheet-per-source",{"title":2155,"description":2156},"Merge Excel Workbooks into One File, One Sheet per File","Combine many Excel files into one workbook with a tab per source: safe unique sheet names (31 chars, no []:*?\u002F\\), pandas for values, openpyxl for styles, widths and merges, multi-sheet sources and a hyperlinked index sheet.","merge-workbooks-into-one-file-with-a-sheet-per-source","getting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fmerge-workbooks-into-one-file-with-a-sheet-per-source\u002Findex",[2160,2161],"sheets-and-files","openpyxl","how-to","YfNcAtaqOEDNxmgWIacOLKu18oOPqb-2CSJ3F4mJ-eI",[2165,2169],{"title":2166,"path":2167,"stem":2168,"children":-1},"List Every Sheet with Its Size and Visibility","\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Flist-every-sheet-with-its-size-and-visibility","getting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Flist-every-sheet-with-its-size-and-visibility\u002Findex",{"title":2170,"path":2171,"stem":2172,"children":-1},"Read All Sheets from an Excel File into DataFrames","\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fread-all-sheets-from-an-excel-file-into-dataframes","getting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fread-all-sheets-from-an-excel-file-into-dataframes\u002Findex",[2174,2176],{"path":2175,"title":2150},"\u002Fgetting-started-with-python-excel-automation",{"path":2177,"title":22},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python",[],[2180,2183,2186,2189,2192,2195],{"path":2181,"title":2182,"kind":2162},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fconsolidate-timesheets-from-many-workbooks","Consolidate Timesheets from Many Workbooks with Python",{"path":2184,"title":2185,"kind":2162},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fcombine-sheets-with-different-layouts-into-one-table","Combine Sheets with Different Layouts into One Table",{"path":2187,"title":2188,"kind":2162},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fappend-new-rows-to-a-master-excel-file-without-duplicates","Append New Rows to a Master Excel File Without Duplicates",{"path":2190,"title":2191,"kind":2162},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fconcatenate-excel-sheets-with-different-columns","Concatenate Excel Sheets with Different Columns",{"path":2193,"title":2194,"kind":2162},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmerge-excel-files-with-different-column-names","Merge Excel Files with Different Column Names in pandas",{"path":2196,"title":2197,"kind":2162},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmerge-more-than-two-excel-files-on-a-key-column","Merge More Than Two Excel Files on a Key Column",1790972076173]