[{"data":1,"prerenderedAt":2445},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fconvert-several-ranges-into-excel-tables-at-once":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fconvert-several-ranges-into-excel-tables-at-once":2412,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fconvert-several-ranges-into-excel-tables-at-once":2420,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fconvert-several-ranges-into-excel-tables-at-once":2425,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fconvert-several-ranges-into-excel-tables-at-once":2426},{"id":4,"title":5,"body":6,"dateModified":2378,"datePublished":2378,"description":2379,"extension":2380,"faq":2381,"keyword":2390,"kind":2391,"meta":2392,"navigation":102,"path":2401,"seo":2402,"slug":2405,"stem":2406,"topics":2407,"type":2410,"__hash__":2411},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fconvert-several-ranges-into-excel-tables-at-once\u002Findex.md","Convert Several Ranges into Excel Tables at Once",{"type":7,"value":8,"toc":2366},"minimark",[9,24,29,55,70,74,515,522,668,672,947,980,1050,1054,1792,1810,1813,1999,2003,2226,2229,2233,2236,2240,2283,2287,2290,2294,2311,2317,2323,2329,2333,2362],[10,11,12,13,17,18,23],"p",{},"Excel tables — the banded ranges created with ",[14,15,16],"em",{},"Insert › Table"," — bring filters, structured references, automatic expansion and a name that Power Query, pivot tables and pandas can find. Workbooks that grew by hand rarely use them: a typical one has a dozen sheets, some with two or three separate blocks of data side by side or one above the other, and none of them is a table. Converting them one at a time in Excel is tedious, and doing it carelessly produces files Excel refuses to open, because tables have rules — headers must be unique text, names must be valid and unique across the workbook, and tables must not overlap. This page converts every data block in a workbook into a table in one pass with openpyxl, repairing headers and generating names as it goes. It belongs to ",[19,20,22],"a",{"href":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002F","Creating Excel Tables and Autofilters with 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 openpyxl\n","bash","",[37,38,39],"code",{"__ignoreMap":35},[40,41,44,48,52],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sMTad","pip",[40,49,51],{"class":50},"srMev"," install",[40,53,54],{"class":50}," openpyxl\n",[10,56,57,58,61,62,65,66,69],{},"The example workbook has three sheets. ",[14,59,60],{},"Sales 2026"," has a quarterly block and, separated by an empty column, a block of reps and targets, plus a note below. ",[14,63,64],{},"Stock"," has a title, then a block whose header has a duplicated name and a blank. ",[14,67,68],{},"2026 Targets"," has a number as one of its headings. Each of these is something Excel would reject in a table header if it were written as is.",[25,71,73],{"id":72},"step-1-find-the-blocks-of-data-on-each-sheet","Step 1 — Find the blocks of data on each sheet",[30,75,79],{"className":76,"code":77,"language":78,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import load_workbook\n\ndef find_blocks(ws):\n    \"\"\"Rectangles of non-empty cells separated by fully empty rows or columns.\"\"\"\n    filled = {(c.row, c.column) for row in ws.iter_rows() for c in row if c.value not in (None, \"\")}\n    seen, blocks = set(), []\n    for start in sorted(filled):\n        if start in seen:\n            continue\n        stack, cells = [start], set()\n        while stack:                                    # flood fill over touching cells (incl. diagonals)\n            r, c = stack.pop()\n            if (r, c) in cells:\n                continue\n            cells.add((r, c))\n            stack += [(r + dr, c + dc) for dr in (-1, 0, 1) for dc in (-1, 0, 1) if (r + dr, c + dc) in filled]\n        seen |= cells\n        rows, cols = [r for r, _ in cells], [c for _, c in cells]\n        blocks.append((min(rows), min(cols), max(rows), max(cols)))\n    return blocks\n\nwb = load_workbook(\"branch_data.xlsx\")\nfor ws in wb.worksheets:\n    print(ws.title, find_blocks(ws))\n","python",[37,80,81,97,104,117,123,184,198,215,228,234,251,264,275,289,295,301,392,404,435,462,471,476,493,506],{"__ignoreMap":35},[40,82,83,87,91,94],{"class":42,"line":43},[40,84,86],{"class":85},"s-kum","from",[40,88,90],{"class":89},"skGVy"," openpyxl ",[40,92,93],{"class":85},"import",[40,95,96],{"class":89}," load_workbook\n",[40,98,100],{"class":42,"line":99},2,[40,101,103],{"emptyLinePlaceholder":102},true,"\n",[40,105,107,110,114],{"class":42,"line":106},3,[40,108,109],{"class":85},"def",[40,111,113],{"class":112},"s_Opv"," find_blocks",[40,115,116],{"class":89},"(ws):\n",[40,118,120],{"class":42,"line":119},4,[40,121,122],{"class":50},"    \"\"\"Rectangles of non-empty cells separated by fully empty rows or columns.\"\"\"\n",[40,124,126,129,132,135,138,141,144,147,149,152,154,156,159,162,165,168,171,175,178,181],{"class":42,"line":125},5,[40,127,128],{"class":89},"    filled ",[40,130,131],{"class":85},"=",[40,133,134],{"class":89}," {(c.row, c.column) ",[40,136,137],{"class":85},"for",[40,139,140],{"class":89}," row ",[40,142,143],{"class":85},"in",[40,145,146],{"class":89}," ws.iter_rows() ",[40,148,137],{"class":85},[40,150,151],{"class":89}," c ",[40,153,143],{"class":85},[40,155,140],{"class":89},[40,157,158],{"class":85},"if",[40,160,161],{"class":89}," c.value ",[40,163,164],{"class":85},"not",[40,166,167],{"class":85}," in",[40,169,170],{"class":89}," (",[40,172,174],{"class":173},"sP0c6","None",[40,176,177],{"class":89},", ",[40,179,180],{"class":50},"\"\"",[40,182,183],{"class":89},")}\n",[40,185,187,190,192,195],{"class":42,"line":186},6,[40,188,189],{"class":89},"    seen, blocks ",[40,191,131],{"class":85},[40,193,194],{"class":173}," set",[40,196,197],{"class":89},"(), []\n",[40,199,201,204,207,209,212],{"class":42,"line":200},7,[40,202,203],{"class":85},"    for",[40,205,206],{"class":89}," start ",[40,208,143],{"class":85},[40,210,211],{"class":173}," sorted",[40,213,214],{"class":89},"(filled):\n",[40,216,218,221,223,225],{"class":42,"line":217},8,[40,219,220],{"class":85},"        if",[40,222,206],{"class":89},[40,224,143],{"class":85},[40,226,227],{"class":89}," seen:\n",[40,229,231],{"class":42,"line":230},9,[40,232,233],{"class":85},"            continue\n",[40,235,237,240,242,245,248],{"class":42,"line":236},10,[40,238,239],{"class":89},"        stack, cells ",[40,241,131],{"class":85},[40,243,244],{"class":89}," [start], ",[40,246,247],{"class":173},"set",[40,249,250],{"class":89},"()\n",[40,252,254,257,260],{"class":42,"line":253},11,[40,255,256],{"class":85},"        while",[40,258,259],{"class":89}," stack:                                    ",[40,261,263],{"class":262},"s-wDw","# flood fill over touching cells (incl. diagonals)\n",[40,265,267,270,272],{"class":42,"line":266},12,[40,268,269],{"class":89},"            r, c ",[40,271,131],{"class":85},[40,273,274],{"class":89}," stack.pop()\n",[40,276,278,281,284,286],{"class":42,"line":277},13,[40,279,280],{"class":85},"            if",[40,282,283],{"class":89}," (r, c) ",[40,285,143],{"class":85},[40,287,288],{"class":89}," cells:\n",[40,290,292],{"class":42,"line":291},14,[40,293,294],{"class":85},"                continue\n",[40,296,298],{"class":42,"line":297},15,[40,299,300],{"class":89},"            cells.add((r, c))\n",[40,302,304,307,310,313,316,319,321,324,326,329,331,333,336,339,341,344,346,348,351,353,356,358,360,362,364,366,368,370,372,374,376,379,381,383,385,387,389],{"class":42,"line":303},16,[40,305,306],{"class":89},"            stack ",[40,308,309],{"class":85},"+=",[40,311,312],{"class":89}," [(r ",[40,314,315],{"class":85},"+",[40,317,318],{"class":89}," dr, c ",[40,320,315],{"class":85},[40,322,323],{"class":89}," dc) ",[40,325,137],{"class":85},[40,327,328],{"class":89}," dr ",[40,330,143],{"class":85},[40,332,170],{"class":89},[40,334,335],{"class":85},"-",[40,337,338],{"class":173},"1",[40,340,177],{"class":89},[40,342,343],{"class":173},"0",[40,345,177],{"class":89},[40,347,338],{"class":173},[40,349,350],{"class":89},") ",[40,352,137],{"class":85},[40,354,355],{"class":89}," dc ",[40,357,143],{"class":85},[40,359,170],{"class":89},[40,361,335],{"class":85},[40,363,338],{"class":173},[40,365,177],{"class":89},[40,367,343],{"class":173},[40,369,177],{"class":89},[40,371,338],{"class":173},[40,373,350],{"class":89},[40,375,158],{"class":85},[40,377,378],{"class":89}," (r ",[40,380,315],{"class":85},[40,382,318],{"class":89},[40,384,315],{"class":85},[40,386,323],{"class":89},[40,388,143],{"class":85},[40,390,391],{"class":89}," filled]\n",[40,393,395,398,401],{"class":42,"line":394},17,[40,396,397],{"class":89},"        seen ",[40,399,400],{"class":85},"|=",[40,402,403],{"class":89}," cells\n",[40,405,407,410,412,415,417,420,422,425,427,430,432],{"class":42,"line":406},18,[40,408,409],{"class":89},"        rows, cols ",[40,411,131],{"class":85},[40,413,414],{"class":89}," [r ",[40,416,137],{"class":85},[40,418,419],{"class":89}," r, _ ",[40,421,143],{"class":85},[40,423,424],{"class":89}," cells], [c ",[40,426,137],{"class":85},[40,428,429],{"class":89}," _, c ",[40,431,143],{"class":85},[40,433,434],{"class":89}," cells]\n",[40,436,438,441,444,447,449,452,455,457,459],{"class":42,"line":437},19,[40,439,440],{"class":89},"        blocks.append((",[40,442,443],{"class":173},"min",[40,445,446],{"class":89},"(rows), ",[40,448,443],{"class":173},[40,450,451],{"class":89},"(cols), ",[40,453,454],{"class":173},"max",[40,456,446],{"class":89},[40,458,454],{"class":173},[40,460,461],{"class":89},"(cols)))\n",[40,463,465,468],{"class":42,"line":464},20,[40,466,467],{"class":85},"    return",[40,469,470],{"class":89}," blocks\n",[40,472,474],{"class":42,"line":473},21,[40,475,103],{"emptyLinePlaceholder":102},[40,477,479,482,484,487,490],{"class":42,"line":478},22,[40,480,481],{"class":89},"wb ",[40,483,131],{"class":85},[40,485,486],{"class":89}," load_workbook(",[40,488,489],{"class":50},"\"branch_data.xlsx\"",[40,491,492],{"class":89},")\n",[40,494,496,498,501,503],{"class":42,"line":495},23,[40,497,137],{"class":85},[40,499,500],{"class":89}," ws ",[40,502,143],{"class":85},[40,504,505],{"class":89}," wb.worksheets:\n",[40,507,509,512],{"class":42,"line":508},24,[40,510,511],{"class":173},"    print",[40,513,514],{"class":89},"(ws.title, find_blocks(ws))\n",[10,516,517,518,521],{},"A block is a group of filled cells that touch each other, and the function finds them with a flood fill: start at a filled cell, collect every filled neighbour, and repeat until the group stops growing. The rectangle around the group is the candidate table. Blocks separated by an empty row or column come out separately, which is how the quarterly figures and the rep targets on the first sheet become two blocks, and the note below them a third. A gap ",[14,519,520],{},"inside"," a block — a missing value in the middle of the data — does not split it, because the cells around the gap still touch it.",[523,524,532,533,532,537,532,541,532,548,532,558,532,565,532,570,532,576,532,581,532,586,532,592,532,596,532,600,532,604,532,607,532,611,532,617,532,620,532,623,532,627,532,631,532,634,532,639,532,644,532,648,532,651,532,655,532,658,532,661,532,665],"svg",{"viewBox":525,"role":526,"ariaLabelledBy":527,"xmlns":530,"style":531},"4 0 752 126","img",[528,529],"rt-steps-t","rt-steps-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[534,535,536],"title",{"id":528},"From a sheet to tables",[538,539,540],"desc",{"id":529},"Each sheet is scanned for blocks of touching filled cells. Blocks that are too small, such as a title or a note, are skipped. The header row of each remaining block is repaired, a unique table name is generated, overlaps with existing tables are checked, and the table is added.",[542,543],"rect",{"x":544,"y":343,"width":545,"height":546,"fill":547},"4","752","126","#ffffff",[542,549],{"x":550,"y":551,"width":552,"height":553,"rx":554,"fill":555,"stroke":556,"style":557},"20.0","16.0","115.2","94.0","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[559,560,564],"text",{"x":561,"y":562,"style":563},"77.6","59.0","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Find blocks",[559,566,569],{"x":561,"y":567,"style":568},"78.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","flood fill",[42,571],{"x1":572,"y1":573,"x2":574,"y2":573,"stroke":575,"style":557},"140.2","63.0","162.2","var(--brand,#5b5cf0)",[577,578],"polygon",{"points":579,"fill":580},"166.2,63.0 157.2,68.0 157.2,58.0","#5b5cf0",[542,582],{"x":583,"y":551,"width":552,"height":553,"rx":554,"fill":584,"stroke":585,"style":557},"171.2","#fdefd8","var(--gold,#b4740a)",[559,587,591],{"x":588,"y":589,"style":590},"228.8","50.2","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Skip small",[559,593,595],{"x":588,"y":594,"style":590},"67.8","ones",[559,597,599],{"x":588,"y":598,"style":568},"87.2","titles, notes",[42,601],{"x1":602,"y1":573,"x2":603,"y2":573,"stroke":575,"style":557},"291.4","313.4",[577,605],{"points":606,"fill":580},"317.4,63.0 308.4,68.0 308.4,58.0",[542,608],{"x":609,"y":551,"width":552,"height":553,"rx":554,"fill":610,"stroke":575,"style":557},"322.4","#f0f4ff",[559,612,616],{"x":613,"y":614,"style":615},"380.0","41.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Repair",[559,618,619],{"x":613,"y":562,"style":615},"header",[559,621,622],{"x":613,"y":567,"style":568},"text, unique, not",[559,624,626],{"x":613,"y":625,"style":568},"96.0","blank",[42,628],{"x1":629,"y1":573,"x2":630,"y2":573,"stroke":575,"style":557},"442.6","464.6",[577,632],{"points":633,"fill":580},"468.6,63.0 459.6,68.0 459.6,58.0",[542,635],{"x":636,"y":551,"width":552,"height":553,"rx":554,"fill":637,"stroke":638,"style":557},"473.6","#d9f4f1","var(--teal,#0f9488)",[559,640,643],{"x":641,"y":589,"style":642},"531.2","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Name it",[559,645,647],{"x":641,"y":646,"style":568},"69.8","valid and",[559,649,650],{"x":641,"y":598,"style":568},"unique",[42,652],{"x1":653,"y1":573,"x2":654,"y2":573,"stroke":575,"style":557},"593.8","615.8",[577,656],{"points":657,"fill":580},"619.8,63.0 610.8,68.0 610.8,58.0",[542,659],{"x":660,"y":551,"width":552,"height":553,"rx":554,"fill":610,"stroke":575,"style":557},"624.8",[559,662,664],{"x":663,"y":562,"style":615},"682.4","Add table",[559,666,667],{"x":663,"y":567,"style":568},"no overlaps",[25,669,671],{"id":670},"step-2-repair-the-header-row","Step 2 — Repair the header row",[30,673,675],{"className":76,"code":674,"language":78,"meta":35,"style":35},"def repair_header(ws, top, left, right):\n    \"\"\"Make every header cell non-empty, text and unique (case-insensitively); return the changes.\"\"\"\n    changes, used = [], set()\n    for c in range(left, right + 1):\n        cell = ws.cell(top, c)\n        name = str(cell.value).strip() if cell.value not in (None, \"\") else f\"Column{c - left + 1}\"\n        base, n = name, 2\n        while name.lower() in used:\n            name = f\"{base}_{n}\"\n            n += 1\n        used.add(name.lower())\n        if cell.value != name:\n            changes.append(f\"{cell.coordinate}: {cell.value!r} -> {name!r}\")\n            cell.value = name\n    return changes\n",[37,676,677,687,692,706,728,738,801,814,826,857,867,872,884,930,940],{"__ignoreMap":35},[40,678,679,681,684],{"class":42,"line":43},[40,680,109],{"class":85},[40,682,683],{"class":112}," repair_header",[40,685,686],{"class":89},"(ws, top, left, right):\n",[40,688,689],{"class":42,"line":99},[40,690,691],{"class":50},"    \"\"\"Make every header cell non-empty, text and unique (case-insensitively); return the changes.\"\"\"\n",[40,693,694,697,699,702,704],{"class":42,"line":106},[40,695,696],{"class":89},"    changes, used ",[40,698,131],{"class":85},[40,700,701],{"class":89}," [], ",[40,703,247],{"class":173},[40,705,250],{"class":89},[40,707,708,710,712,714,717,720,722,725],{"class":42,"line":119},[40,709,203],{"class":85},[40,711,151],{"class":89},[40,713,143],{"class":85},[40,715,716],{"class":173}," range",[40,718,719],{"class":89},"(left, right ",[40,721,315],{"class":85},[40,723,724],{"class":173}," 1",[40,726,727],{"class":89},"):\n",[40,729,730,733,735],{"class":42,"line":125},[40,731,732],{"class":89},"        cell ",[40,734,131],{"class":85},[40,736,737],{"class":89}," ws.cell(top, c)\n",[40,739,740,743,745,748,751,753,756,758,760,762,764,766,768,770,773,776,779,783,786,788,791,793,795,798],{"class":42,"line":186},[40,741,742],{"class":89},"        name ",[40,744,131],{"class":85},[40,746,747],{"class":173}," str",[40,749,750],{"class":89},"(cell.value).strip() ",[40,752,158],{"class":85},[40,754,755],{"class":89}," cell.value ",[40,757,164],{"class":85},[40,759,167],{"class":85},[40,761,170],{"class":89},[40,763,174],{"class":173},[40,765,177],{"class":89},[40,767,180],{"class":50},[40,769,350],{"class":89},[40,771,772],{"class":85},"else",[40,774,775],{"class":85}," f",[40,777,778],{"class":50},"\"Column",[40,780,782],{"class":781},"sSjpA","{",[40,784,785],{"class":89},"c ",[40,787,335],{"class":85},[40,789,790],{"class":89}," left ",[40,792,315],{"class":85},[40,794,724],{"class":173},[40,796,797],{"class":781},"}",[40,799,800],{"class":50},"\"\n",[40,802,803,806,808,811],{"class":42,"line":200},[40,804,805],{"class":89},"        base, n ",[40,807,131],{"class":85},[40,809,810],{"class":89}," name, ",[40,812,813],{"class":173},"2\n",[40,815,816,818,821,823],{"class":42,"line":217},[40,817,256],{"class":85},[40,819,820],{"class":89}," name.lower() ",[40,822,143],{"class":85},[40,824,825],{"class":89}," used:\n",[40,827,828,831,833,835,838,840,843,845,848,850,853,855],{"class":42,"line":230},[40,829,830],{"class":89},"            name ",[40,832,131],{"class":85},[40,834,775],{"class":85},[40,836,837],{"class":50},"\"",[40,839,782],{"class":781},[40,841,842],{"class":89},"base",[40,844,797],{"class":781},[40,846,847],{"class":50},"_",[40,849,782],{"class":781},[40,851,852],{"class":89},"n",[40,854,797],{"class":781},[40,856,800],{"class":50},[40,858,859,862,864],{"class":42,"line":236},[40,860,861],{"class":89},"            n ",[40,863,309],{"class":85},[40,865,866],{"class":173}," 1\n",[40,868,869],{"class":42,"line":253},[40,870,871],{"class":89},"        used.add(name.lower())\n",[40,873,874,876,878,881],{"class":42,"line":266},[40,875,220],{"class":85},[40,877,755],{"class":89},[40,879,880],{"class":85},"!=",[40,882,883],{"class":89}," name:\n",[40,885,886,889,892,894,896,899,901,904,906,909,912,914,917,919,922,924,926,928],{"class":42,"line":277},[40,887,888],{"class":89},"            changes.append(",[40,890,891],{"class":85},"f",[40,893,837],{"class":50},[40,895,782],{"class":781},[40,897,898],{"class":89},"cell.coordinate",[40,900,797],{"class":781},[40,902,903],{"class":50},": ",[40,905,782],{"class":781},[40,907,908],{"class":89},"cell.value",[40,910,911],{"class":85},"!r",[40,913,797],{"class":781},[40,915,916],{"class":50}," -> ",[40,918,782],{"class":781},[40,920,921],{"class":89},"name",[40,923,911],{"class":85},[40,925,797],{"class":781},[40,927,837],{"class":50},[40,929,492],{"class":89},[40,931,932,935,937],{"class":42,"line":291},[40,933,934],{"class":89},"            cell.value ",[40,936,131],{"class":85},[40,938,939],{"class":89}," name\n",[40,941,942,944],{"class":42,"line":297},[40,943,467],{"class":85},[40,945,946],{"class":89}," changes\n",[10,948,949,950,177,953,956,957,960,961,964,965,968,969,972,973,975,976,979],{},"Excel insists on three things for a table's header row: every cell has a value, every value is text, and no two values are the same, ignoring case. A file that breaks any of these opens with a repair message, and Excel quietly renames columns — so it is better to fix them deliberately and report what changed. Blank headings become ",[14,951,952],{},"Column1",[14,954,955],{},"Column2"," and so on, in the same style Excel uses; numbers become their text form, so the heading ",[14,958,959],{},"2026"," is stored as the string ",[37,962,963],{},"\"2026\"","; and duplicates get a suffix, so a second ",[14,966,967],{},"Qty"," becomes ",[14,970,971],{},"Qty_2",". Comparing in lower case catches ",[14,974,967],{}," and ",[14,977,978],{},"QTY",", which Excel also treats as the same.",[523,981,532,986,532,989,532,992,532,995,532,1000,532,1006,532,1012,532,1017,532,1021,532,1025,532,1028,532,1033,532,1037,532,1040,532,1043,532,1046],{"viewBox":982,"role":526,"ariaLabelledBy":983,"xmlns":530,"style":531},"4 0 752 199",[984,985],"rt-rules-t","rt-rules-d",[534,987,988],{"id":984},"What Excel requires of a table",[538,990,991],{"id":985},"The header row must be non-empty, text and unique ignoring case. The table name must start with a letter or underscore, must not look like a cell address, and must be unique across the workbook. Tables on one sheet must not overlap.",[542,993],{"x":544,"y":343,"width":545,"height":994,"fill":547},"199",[542,996],{"x":550,"y":551,"width":997,"height":998,"rx":999,"fill":610,"stroke":575,"style":557},"351.0","167.0","14",[559,1001,1005],{"x":1002,"y":1003,"style":1004},"195.5","44.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Header row",[42,1007],{"x1":1008,"y1":1009,"x2":1010,"y2":1009,"stroke":575,"style":1011},"36.0","57.0","355.0","stroke-width:1px",[559,1013,1016],{"x":1002,"y":1014,"style":1015},"77.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","no blank cells",[559,1018,1020],{"x":1002,"y":1019,"style":1015},"103.0","text, not numbers",[559,1022,1024],{"x":1002,"y":1023,"style":1015},"129.0","unique, ignoring case",[542,1026],{"x":1027,"y":551,"width":997,"height":998,"rx":999,"fill":637,"stroke":638,"style":557},"389.0",[559,1029,1032],{"x":1030,"y":1003,"style":1031},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Name and range",[42,1034],{"x1":1035,"y1":1009,"x2":1036,"y2":1009,"stroke":638,"style":1011},"405.0","724.0",[559,1038,1039],{"x":1030,"y":1014,"style":1015},"starts with a letter or _",[559,1041,1042],{"x":1030,"y":1019,"style":1015},"not shaped like A1 or R",[559,1044,1045],{"x":1030,"y":1023,"style":1015},"unique in the workbook",[559,1047,1049],{"x":1030,"y":1048,"style":1015},"155.0","no overlap with other tables",[25,1051,1053],{"id":1052},"step-3-valid-unique-names-and-the-conversion-itself","Step 3 — Valid, unique names and the conversion itself",[30,1055,1057],{"className":76,"code":1056,"language":78,"meta":35,"style":35},"import re\nfrom openpyxl.worksheet.table import Table, TableStyleInfo\nfrom openpyxl.utils import get_column_letter as col\n\ndef table_name(ws, top, left, taken):\n    heading = str(ws.cell(top, left).value or \"Data\")\n    name = re.sub(r\"\\W+\", \"_\", f\"{ws.title}_{heading}\").strip(\"_\")\n    if not re.match(r\"[A-Za-z_]\", name):\n        name = \"T_\" + name                              # must start with a letter or underscore\n    if re.fullmatch(r\"[A-Za-z]{1,3}\\d+\", name) or name.upper() in (\"R\", \"C\"):\n        name = \"T_\" + name                              # must not look like a cell address\n    base, n = name[:240], 2\n    while name.lower() in taken:\n        name = f\"{base}_{n}\"\n        n += 1\n    taken.add(name.lower())\n    return name\n\ntaken = {t.lower() for ws in wb.worksheets for t in ws.tables}\nreport = []\nfor ws in wb.worksheets:\n    existing = [ws.tables[t].ref for t in ws.tables]\n    for top, left, bottom, right in find_blocks(ws):\n        if bottom - top \u003C 1 or right - left \u003C 1:\n            report.append(f\"{ws.title}: skipped {col(left)}{top} (too small for a table)\")\n            continue\n        ref = f\"{col(left)}{top}:{col(right)}{bottom}\"\n        if any(ref == e for e in existing):\n            continue\n        fixes = repair_header(ws, top, left, right)\n        name = table_name(ws, top, left, taken)\n        t = Table(displayName=name, ref=ref)\n        t.tableStyleInfo = TableStyleInfo(name=\"TableStyleMedium2\", showRowStripes=True)\n        ws.add_table(t)\n        report.append(f\"{ws.title}: {name} = {ref}\" + (f\"  (header fixes: {'; '.join(fixes)})\" if fixes else \"\"))\nwb.save(\"branch_data_tables.xlsx\")\nprint(\"\\n\".join(report))\n",[37,1058,1059,1066,1078,1096,1100,1110,1130,1188,1217,1235,1285,1300,1318,1330,1356,1365,1370,1376,1380,1409,1419,1429,1448,1460,1494,1531,1536,1575,1601,1606,1617,1627,1655,1685,1691,1762,1773],{"__ignoreMap":35},[40,1060,1061,1063],{"class":42,"line":43},[40,1062,93],{"class":85},[40,1064,1065],{"class":89}," re\n",[40,1067,1068,1070,1073,1075],{"class":42,"line":99},[40,1069,86],{"class":85},[40,1071,1072],{"class":89}," openpyxl.worksheet.table ",[40,1074,93],{"class":85},[40,1076,1077],{"class":89}," Table, TableStyleInfo\n",[40,1079,1080,1082,1085,1087,1090,1093],{"class":42,"line":106},[40,1081,86],{"class":85},[40,1083,1084],{"class":89}," openpyxl.utils ",[40,1086,93],{"class":85},[40,1088,1089],{"class":89}," get_column_letter ",[40,1091,1092],{"class":85},"as",[40,1094,1095],{"class":89}," col\n",[40,1097,1098],{"class":42,"line":119},[40,1099,103],{"emptyLinePlaceholder":102},[40,1101,1102,1104,1107],{"class":42,"line":125},[40,1103,109],{"class":85},[40,1105,1106],{"class":112}," table_name",[40,1108,1109],{"class":89},"(ws, top, left, taken):\n",[40,1111,1112,1115,1117,1119,1122,1125,1128],{"class":42,"line":186},[40,1113,1114],{"class":89},"    heading ",[40,1116,131],{"class":85},[40,1118,747],{"class":173},[40,1120,1121],{"class":89},"(ws.cell(top, left).value ",[40,1123,1124],{"class":85},"or",[40,1126,1127],{"class":50}," \"Data\"",[40,1129,492],{"class":89},[40,1131,1132,1135,1137,1140,1143,1145,1148,1150,1152,1154,1157,1159,1161,1163,1165,1168,1170,1172,1174,1177,1179,1181,1184,1186],{"class":42,"line":200},[40,1133,1134],{"class":89},"    name ",[40,1136,131],{"class":85},[40,1138,1139],{"class":89}," re.sub(",[40,1141,1142],{"class":85},"r",[40,1144,837],{"class":50},[40,1146,1147],{"class":173},"\\W",[40,1149,315],{"class":85},[40,1151,837],{"class":50},[40,1153,177],{"class":89},[40,1155,1156],{"class":50},"\"_\"",[40,1158,177],{"class":89},[40,1160,891],{"class":85},[40,1162,837],{"class":50},[40,1164,782],{"class":781},[40,1166,1167],{"class":89},"ws.title",[40,1169,797],{"class":781},[40,1171,847],{"class":50},[40,1173,782],{"class":781},[40,1175,1176],{"class":89},"heading",[40,1178,797],{"class":781},[40,1180,837],{"class":50},[40,1182,1183],{"class":89},").strip(",[40,1185,1156],{"class":50},[40,1187,492],{"class":89},[40,1189,1190,1193,1196,1199,1201,1203,1206,1209,1212,1214],{"class":42,"line":217},[40,1191,1192],{"class":85},"    if",[40,1194,1195],{"class":85}," not",[40,1197,1198],{"class":89}," re.match(",[40,1200,1142],{"class":85},[40,1202,837],{"class":50},[40,1204,1205],{"class":173},"[",[40,1207,1208],{"class":781},"A-Za-z_",[40,1210,1211],{"class":173},"]",[40,1213,837],{"class":50},[40,1215,1216],{"class":89},", name):\n",[40,1218,1219,1221,1223,1226,1229,1232],{"class":42,"line":230},[40,1220,742],{"class":89},[40,1222,131],{"class":85},[40,1224,1225],{"class":50}," \"T_\"",[40,1227,1228],{"class":85}," +",[40,1230,1231],{"class":89}," name                              ",[40,1233,1234],{"class":262},"# must start with a letter or underscore\n",[40,1236,1237,1239,1242,1244,1246,1248,1251,1253,1256,1259,1261,1263,1266,1268,1271,1273,1275,1278,1280,1283],{"class":42,"line":236},[40,1238,1192],{"class":85},[40,1240,1241],{"class":89}," re.fullmatch(",[40,1243,1142],{"class":85},[40,1245,837],{"class":50},[40,1247,1205],{"class":173},[40,1249,1250],{"class":781},"A-Za-z",[40,1252,1211],{"class":173},[40,1254,1255],{"class":85},"{1,3}",[40,1257,1258],{"class":173},"\\d",[40,1260,315],{"class":85},[40,1262,837],{"class":50},[40,1264,1265],{"class":89},", name) ",[40,1267,1124],{"class":85},[40,1269,1270],{"class":89}," name.upper() ",[40,1272,143],{"class":85},[40,1274,170],{"class":89},[40,1276,1277],{"class":50},"\"R\"",[40,1279,177],{"class":89},[40,1281,1282],{"class":50},"\"C\"",[40,1284,727],{"class":89},[40,1286,1287,1289,1291,1293,1295,1297],{"class":42,"line":253},[40,1288,742],{"class":89},[40,1290,131],{"class":85},[40,1292,1225],{"class":50},[40,1294,1228],{"class":85},[40,1296,1231],{"class":89},[40,1298,1299],{"class":262},"# must not look like a cell address\n",[40,1301,1302,1305,1307,1310,1313,1316],{"class":42,"line":266},[40,1303,1304],{"class":89},"    base, n ",[40,1306,131],{"class":85},[40,1308,1309],{"class":89}," name[:",[40,1311,1312],{"class":173},"240",[40,1314,1315],{"class":89},"], ",[40,1317,813],{"class":173},[40,1319,1320,1323,1325,1327],{"class":42,"line":277},[40,1321,1322],{"class":85},"    while",[40,1324,820],{"class":89},[40,1326,143],{"class":85},[40,1328,1329],{"class":89}," taken:\n",[40,1331,1332,1334,1336,1338,1340,1342,1344,1346,1348,1350,1352,1354],{"class":42,"line":291},[40,1333,742],{"class":89},[40,1335,131],{"class":85},[40,1337,775],{"class":85},[40,1339,837],{"class":50},[40,1341,782],{"class":781},[40,1343,842],{"class":89},[40,1345,797],{"class":781},[40,1347,847],{"class":50},[40,1349,782],{"class":781},[40,1351,852],{"class":89},[40,1353,797],{"class":781},[40,1355,800],{"class":50},[40,1357,1358,1361,1363],{"class":42,"line":297},[40,1359,1360],{"class":89},"        n ",[40,1362,309],{"class":85},[40,1364,866],{"class":173},[40,1366,1367],{"class":42,"line":303},[40,1368,1369],{"class":89},"    taken.add(name.lower())\n",[40,1371,1372,1374],{"class":42,"line":394},[40,1373,467],{"class":85},[40,1375,939],{"class":89},[40,1377,1378],{"class":42,"line":406},[40,1379,103],{"emptyLinePlaceholder":102},[40,1381,1382,1385,1387,1390,1392,1394,1396,1399,1401,1404,1406],{"class":42,"line":437},[40,1383,1384],{"class":89},"taken ",[40,1386,131],{"class":85},[40,1388,1389],{"class":89}," {t.lower() ",[40,1391,137],{"class":85},[40,1393,500],{"class":89},[40,1395,143],{"class":85},[40,1397,1398],{"class":89}," wb.worksheets ",[40,1400,137],{"class":85},[40,1402,1403],{"class":89}," t ",[40,1405,143],{"class":85},[40,1407,1408],{"class":89}," ws.tables}\n",[40,1410,1411,1414,1416],{"class":42,"line":464},[40,1412,1413],{"class":89},"report ",[40,1415,131],{"class":85},[40,1417,1418],{"class":89}," []\n",[40,1420,1421,1423,1425,1427],{"class":42,"line":473},[40,1422,137],{"class":85},[40,1424,500],{"class":89},[40,1426,143],{"class":85},[40,1428,505],{"class":89},[40,1430,1431,1434,1436,1439,1441,1443,1445],{"class":42,"line":478},[40,1432,1433],{"class":89},"    existing ",[40,1435,131],{"class":85},[40,1437,1438],{"class":89}," [ws.tables[t].ref ",[40,1440,137],{"class":85},[40,1442,1403],{"class":89},[40,1444,143],{"class":85},[40,1446,1447],{"class":89}," ws.tables]\n",[40,1449,1450,1452,1455,1457],{"class":42,"line":495},[40,1451,203],{"class":85},[40,1453,1454],{"class":89}," top, left, bottom, right ",[40,1456,143],{"class":85},[40,1458,1459],{"class":89}," find_blocks(ws):\n",[40,1461,1462,1464,1467,1469,1472,1475,1477,1480,1483,1485,1487,1489,1491],{"class":42,"line":508},[40,1463,220],{"class":85},[40,1465,1466],{"class":89}," bottom ",[40,1468,335],{"class":85},[40,1470,1471],{"class":89}," top ",[40,1473,1474],{"class":85},"\u003C",[40,1476,724],{"class":173},[40,1478,1479],{"class":85}," or",[40,1481,1482],{"class":89}," right ",[40,1484,335],{"class":85},[40,1486,790],{"class":89},[40,1488,1474],{"class":85},[40,1490,724],{"class":173},[40,1492,1493],{"class":89},":\n",[40,1495,1497,1500,1502,1504,1506,1508,1510,1513,1515,1518,1521,1524,1526,1529],{"class":42,"line":1496},25,[40,1498,1499],{"class":89},"            report.append(",[40,1501,891],{"class":85},[40,1503,837],{"class":50},[40,1505,782],{"class":781},[40,1507,1167],{"class":89},[40,1509,797],{"class":781},[40,1511,1512],{"class":50},": skipped ",[40,1514,782],{"class":781},[40,1516,1517],{"class":89},"col(left)",[40,1519,1520],{"class":781},"}{",[40,1522,1523],{"class":89},"top",[40,1525,797],{"class":781},[40,1527,1528],{"class":50}," (too small for a table)\"",[40,1530,492],{"class":89},[40,1532,1534],{"class":42,"line":1533},26,[40,1535,233],{"class":85},[40,1537,1539,1542,1544,1546,1548,1550,1552,1554,1556,1558,1561,1563,1566,1568,1571,1573],{"class":42,"line":1538},27,[40,1540,1541],{"class":89},"        ref ",[40,1543,131],{"class":85},[40,1545,775],{"class":85},[40,1547,837],{"class":50},[40,1549,782],{"class":781},[40,1551,1517],{"class":89},[40,1553,1520],{"class":781},[40,1555,1523],{"class":89},[40,1557,797],{"class":781},[40,1559,1560],{"class":50},":",[40,1562,782],{"class":781},[40,1564,1565],{"class":89},"col(right)",[40,1567,1520],{"class":781},[40,1569,1570],{"class":89},"bottom",[40,1572,797],{"class":781},[40,1574,800],{"class":50},[40,1576,1578,1580,1583,1586,1589,1592,1594,1596,1598],{"class":42,"line":1577},28,[40,1579,220],{"class":85},[40,1581,1582],{"class":173}," any",[40,1584,1585],{"class":89},"(ref ",[40,1587,1588],{"class":85},"==",[40,1590,1591],{"class":89}," e ",[40,1593,137],{"class":85},[40,1595,1591],{"class":89},[40,1597,143],{"class":85},[40,1599,1600],{"class":89}," existing):\n",[40,1602,1604],{"class":42,"line":1603},29,[40,1605,233],{"class":85},[40,1607,1609,1612,1614],{"class":42,"line":1608},30,[40,1610,1611],{"class":89},"        fixes ",[40,1613,131],{"class":85},[40,1615,1616],{"class":89}," repair_header(ws, top, left, right)\n",[40,1618,1620,1622,1624],{"class":42,"line":1619},31,[40,1621,742],{"class":89},[40,1623,131],{"class":85},[40,1625,1626],{"class":89}," table_name(ws, top, left, taken)\n",[40,1628,1630,1633,1635,1638,1642,1644,1647,1650,1652],{"class":42,"line":1629},32,[40,1631,1632],{"class":89},"        t ",[40,1634,131],{"class":85},[40,1636,1637],{"class":89}," Table(",[40,1639,1641],{"class":1640},"sa561","displayName",[40,1643,131],{"class":85},[40,1645,1646],{"class":89},"name, ",[40,1648,1649],{"class":1640},"ref",[40,1651,131],{"class":85},[40,1653,1654],{"class":89},"ref)\n",[40,1656,1658,1661,1663,1666,1668,1670,1673,1675,1678,1680,1683],{"class":42,"line":1657},33,[40,1659,1660],{"class":89},"        t.tableStyleInfo ",[40,1662,131],{"class":85},[40,1664,1665],{"class":89}," TableStyleInfo(",[40,1667,921],{"class":1640},[40,1669,131],{"class":85},[40,1671,1672],{"class":50},"\"TableStyleMedium2\"",[40,1674,177],{"class":89},[40,1676,1677],{"class":1640},"showRowStripes",[40,1679,131],{"class":85},[40,1681,1682],{"class":173},"True",[40,1684,492],{"class":89},[40,1686,1688],{"class":42,"line":1687},34,[40,1689,1690],{"class":89},"        ws.add_table(t)\n",[40,1692,1694,1697,1699,1701,1703,1705,1707,1709,1711,1713,1715,1718,1720,1722,1724,1726,1728,1730,1732,1735,1737,1740,1743,1745,1748,1751,1754,1756,1759],{"class":42,"line":1693},35,[40,1695,1696],{"class":89},"        report.append(",[40,1698,891],{"class":85},[40,1700,837],{"class":50},[40,1702,782],{"class":781},[40,1704,1167],{"class":89},[40,1706,797],{"class":781},[40,1708,903],{"class":50},[40,1710,782],{"class":781},[40,1712,921],{"class":89},[40,1714,797],{"class":781},[40,1716,1717],{"class":50}," = ",[40,1719,782],{"class":781},[40,1721,1649],{"class":89},[40,1723,797],{"class":781},[40,1725,837],{"class":50},[40,1727,1228],{"class":85},[40,1729,170],{"class":89},[40,1731,891],{"class":85},[40,1733,1734],{"class":50},"\"  (header fixes: ",[40,1736,782],{"class":781},[40,1738,1739],{"class":50},"'; '",[40,1741,1742],{"class":89},".join(fixes)",[40,1744,797],{"class":781},[40,1746,1747],{"class":50},")\"",[40,1749,1750],{"class":85}," if",[40,1752,1753],{"class":89}," fixes ",[40,1755,772],{"class":85},[40,1757,1758],{"class":50}," \"\"",[40,1760,1761],{"class":89},"))\n",[40,1763,1765,1768,1771],{"class":42,"line":1764},36,[40,1766,1767],{"class":89},"wb.save(",[40,1769,1770],{"class":50},"\"branch_data_tables.xlsx\"",[40,1772,492],{"class":89},[40,1774,1776,1779,1782,1784,1787,1789],{"class":42,"line":1775},37,[40,1777,1778],{"class":173},"print",[40,1780,1781],{"class":89},"(",[40,1783,837],{"class":50},[40,1785,1786],{"class":781},"\\n",[40,1788,837],{"class":50},[40,1790,1791],{"class":89},".join(report))\n",[10,1793,1794,1795,177,1798,1801,1802,1805,1806,1809],{},"Table names follow the rules for defined names, and they must be unique across the whole workbook, not just the sheet. The generated name combines the sheet title and the block's first heading — ",[14,1796,1797],{},"Sales_2026_Region",[14,1799,1800],{},"Stock_SKU"," — with anything that is not a letter, digit or underscore replaced by an underscore. Two checks catch names Excel would reject: a name must not start with a digit, and it must not look like a cell address, such as ",[14,1803,1804],{},"ABC1"," or ",[14,1807,1808],{},"R",". Existing table names are collected first so a new name never collides with one already in the file.",[10,1811,1812],{},"Blocks smaller than two rows or two columns are skipped and reported: a title, a single note or a lone label is not a table, and converting it would only add clutter. The report lists every table created and every header cell that was changed, which is the record someone needs when a formula elsewhere refers to a column by its old heading.",[523,1814,532,1820,532,1823,532,1826,532,1831,532,1836,532,1840,532,1846,532,1850,532,1854,532,1858,532,1862,532,1866,532,1870,532,1874,532,1877,532,1880,532,1884,532,1886,532,1889,532,1891,532,1894,532,1896,532,1899,532,1902,532,1906,532,1908,532,1910,532,1912,532,1914,532,1916,532,1919,532,1921,532,1924,532,1926,532,1930,532,1932,532,1934,532,1936,532,1939,532,1941,532,1944,532,1946,532,1948,532,1951,532,1954,532,1956,532,1958,532,1960,532,1962,532,1964,532,1967,532,1969,532,1973,532,1976,532,1980,532,1982,532,1984,532,1986,532,1989,532,1991,532,1994,532,1996],{"viewBox":1815,"role":526,"ariaLabelledBy":1816,"xmlns":530,"style":1819},"73 0 614 196",[1817,1818],"rt-report-t","rt-report-d","width:100%;max-width:614px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[534,1821,1822],{"id":1817},"What the conversion did",[538,1824,1825],{"id":1818},"The Sales 2026 sheet gets two tables and a skipped note block. On the Stock sheet the title is skipped, and the table's duplicate Qty becomes Qty_2 and the blank heading becomes Column4. The 2026 Targets sheet gets a table whose numeric heading is stored as text and whose name starts with T_ because the sheet name starts with a digit.",[542,1827],{"x":1828,"y":343,"width":1829,"height":1830,"fill":547},"73","614","196",[542,1832],{"x":1833,"y":551,"width":1834,"height":1835,"rx":343,"fill":555,"stroke":556,"style":1011},"89.5","32.0","24.0",[542,1837],{"x":1838,"y":551,"width":1839,"height":1835,"rx":343,"fill":555,"stroke":556,"style":1011},"121.5","107.5",[559,1841,1845],{"x":1842,"y":1843,"style":1844},"175.2","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[542,1847],{"x":1848,"y":551,"width":1849,"height":1835,"rx":343,"fill":555,"stroke":556,"style":1011},"229.0","163.5",[559,1851,1853],{"x":1852,"y":1843,"style":1844},"310.7","B",[542,1855],{"x":1856,"y":551,"width":1857,"height":1835,"rx":343,"fill":555,"stroke":556,"style":1011},"392.5","62.9",[559,1859,1861],{"x":1860,"y":1843,"style":1844},"424.0","C",[542,1863],{"x":1864,"y":551,"width":1865,"height":1835,"rx":343,"fill":555,"stroke":556,"style":1011},"455.4","215.1",[559,1867,1869],{"x":1868,"y":1843,"style":1844},"563.0","D",[542,1871],{"x":1833,"y":1872,"width":1834,"height":1873,"rx":343,"fill":555,"stroke":556,"style":1011},"40.0","28.0",[559,1875,338],{"x":1876,"y":562,"style":1844},"105.5",[542,1878],{"x":1838,"y":1872,"width":1839,"height":1873,"rx":343,"fill":610,"stroke":575,"style":1879},"stroke-width:1.5px",[559,1881,1883],{"x":1842,"y":562,"style":1882},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Sheet",[542,1885],{"x":1848,"y":1872,"width":1849,"height":1873,"rx":343,"fill":610,"stroke":575,"style":1879},[559,1887,1888],{"x":1852,"y":562,"style":1882},"Table",[542,1890],{"x":1856,"y":1872,"width":1857,"height":1873,"rx":343,"fill":610,"stroke":575,"style":1879},[559,1892,1893],{"x":1860,"y":562,"style":1882},"Range",[542,1895],{"x":1864,"y":1872,"width":1865,"height":1873,"rx":343,"fill":610,"stroke":575,"style":1879},[559,1897,1898],{"x":1868,"y":562,"style":1882},"Header fixes",[542,1900],{"x":1833,"y":1901,"width":1834,"height":1873,"rx":343,"fill":555,"stroke":556,"style":1011},"68.0",[559,1903,1905],{"x":1876,"y":1904,"style":1844},"87.0","2",[542,1907],{"x":1838,"y":1901,"width":1839,"height":1873,"rx":343,"fill":547,"stroke":556,"style":1011},[559,1909,60],{"x":1842,"y":1904,"style":1015},[542,1911],{"x":1848,"y":1901,"width":1849,"height":1873,"rx":343,"fill":547,"stroke":556,"style":1011},[559,1913,1797],{"x":1852,"y":1904,"style":1015},[542,1915],{"x":1856,"y":1901,"width":1857,"height":1873,"rx":343,"fill":547,"stroke":556,"style":1011},[559,1917,1918],{"x":1860,"y":1904,"style":1015},"A1:D4",[542,1920],{"x":1864,"y":1901,"width":1865,"height":1873,"rx":343,"fill":547,"stroke":556,"style":1011},[559,1922,1923],{"x":1868,"y":1904,"style":1015},"—",[542,1925],{"x":1833,"y":625,"width":1834,"height":1873,"rx":343,"fill":555,"stroke":556,"style":1011},[559,1927,1929],{"x":1876,"y":1928,"style":1844},"115.0","3",[542,1931],{"x":1838,"y":625,"width":1839,"height":1873,"rx":343,"fill":547,"stroke":556,"style":1011},[559,1933,60],{"x":1842,"y":1928,"style":1015},[542,1935],{"x":1848,"y":625,"width":1849,"height":1873,"rx":343,"fill":547,"stroke":556,"style":1011},[559,1937,1938],{"x":1852,"y":1928,"style":1015},"Sales_2026_Rep",[542,1940],{"x":1856,"y":625,"width":1857,"height":1873,"rx":343,"fill":547,"stroke":556,"style":1011},[559,1942,1943],{"x":1860,"y":1928,"style":1015},"F1:G4",[542,1945],{"x":1864,"y":625,"width":1865,"height":1873,"rx":343,"fill":547,"stroke":556,"style":1011},[559,1947,1923],{"x":1868,"y":1928,"style":1015},[542,1949],{"x":1833,"y":1950,"width":1834,"height":1873,"rx":343,"fill":555,"stroke":556,"style":1011},"124.0",[559,1952,544],{"x":1876,"y":1953,"style":1844},"143.0",[542,1955],{"x":1838,"y":1950,"width":1839,"height":1873,"rx":343,"fill":547,"stroke":556,"style":1011},[559,1957,64],{"x":1842,"y":1953,"style":1015},[542,1959],{"x":1848,"y":1950,"width":1849,"height":1873,"rx":343,"fill":547,"stroke":556,"style":1011},[559,1961,1800],{"x":1852,"y":1953,"style":1015},[542,1963],{"x":1856,"y":1950,"width":1857,"height":1873,"rx":343,"fill":547,"stroke":556,"style":1011},[559,1965,1966],{"x":1860,"y":1953,"style":1015},"B4:F7",[542,1968],{"x":1864,"y":1950,"width":1865,"height":1873,"rx":343,"fill":584,"stroke":585,"style":1879},[559,1970,1972],{"x":1868,"y":1953,"style":1971},"font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Qty → Qty_2, blank → Column4",[542,1974],{"x":1833,"y":1975,"width":1834,"height":1873,"rx":343,"fill":555,"stroke":556,"style":1011},"152.0",[559,1977,1979],{"x":1876,"y":1978,"style":1844},"171.0","5",[542,1981],{"x":1838,"y":1975,"width":1839,"height":1873,"rx":343,"fill":547,"stroke":556,"style":1011},[559,1983,68],{"x":1842,"y":1978,"style":1015},[542,1985],{"x":1848,"y":1975,"width":1849,"height":1873,"rx":343,"fill":584,"stroke":585,"style":1879},[559,1987,1988],{"x":1852,"y":1978,"style":1971},"T_2026_Targets_2026",[542,1990],{"x":1856,"y":1975,"width":1857,"height":1873,"rx":343,"fill":547,"stroke":556,"style":1011},[559,1992,1993],{"x":1860,"y":1978,"style":1015},"A1:B3",[542,1995],{"x":1864,"y":1975,"width":1865,"height":1873,"rx":343,"fill":547,"stroke":556,"style":1011},[559,1997,1998],{"x":1868,"y":1978,"style":1015},"2026 → \"2026\"",[25,2000,2002],{"id":2001},"step-4-check-the-result","Step 4 — Check the result",[30,2004,2006],{"className":76,"code":2005,"language":78,"meta":35,"style":35},"from itertools import combinations\nfrom openpyxl.worksheet.cell_range import CellRange\n\nwb2 = load_workbook(\"branch_data_tables.xlsx\")\nfor ws in wb2.worksheets:\n    ranges = [(n, CellRange(ws.tables[n].ref)) for n in ws.tables]\n    clashes = [(a, b) for (a, ra), (b, rb) in combinations(ranges, 2) if not ra.isdisjoint(rb)]\n    headers_ok = all(isinstance(ws.cell(r.min_row, c).value, str)\n                     for _, r in ranges for c in range(r.min_col, r.max_col + 1))\n    print(f\"{ws.title:13} tables={[n for n, _ in ranges]} overlaps={clashes} text_headers={headers_ok}\")\n",[37,2007,2008,2020,2032,2036,2049,2060,2079,2110,2133,2163],{"__ignoreMap":35},[40,2009,2010,2012,2015,2017],{"class":42,"line":43},[40,2011,86],{"class":85},[40,2013,2014],{"class":89}," itertools ",[40,2016,93],{"class":85},[40,2018,2019],{"class":89}," combinations\n",[40,2021,2022,2024,2027,2029],{"class":42,"line":99},[40,2023,86],{"class":85},[40,2025,2026],{"class":89}," openpyxl.worksheet.cell_range ",[40,2028,93],{"class":85},[40,2030,2031],{"class":89}," CellRange\n",[40,2033,2034],{"class":42,"line":106},[40,2035,103],{"emptyLinePlaceholder":102},[40,2037,2038,2041,2043,2045,2047],{"class":42,"line":119},[40,2039,2040],{"class":89},"wb2 ",[40,2042,131],{"class":85},[40,2044,486],{"class":89},[40,2046,1770],{"class":50},[40,2048,492],{"class":89},[40,2050,2051,2053,2055,2057],{"class":42,"line":125},[40,2052,137],{"class":85},[40,2054,500],{"class":89},[40,2056,143],{"class":85},[40,2058,2059],{"class":89}," wb2.worksheets:\n",[40,2061,2062,2065,2067,2070,2072,2075,2077],{"class":42,"line":186},[40,2063,2064],{"class":89},"    ranges ",[40,2066,131],{"class":85},[40,2068,2069],{"class":89}," [(n, CellRange(ws.tables[n].ref)) ",[40,2071,137],{"class":85},[40,2073,2074],{"class":89}," n ",[40,2076,143],{"class":85},[40,2078,1447],{"class":89},[40,2080,2081,2084,2086,2089,2091,2094,2096,2099,2101,2103,2105,2107],{"class":42,"line":200},[40,2082,2083],{"class":89},"    clashes ",[40,2085,131],{"class":85},[40,2087,2088],{"class":89}," [(a, b) ",[40,2090,137],{"class":85},[40,2092,2093],{"class":89}," (a, ra), (b, rb) ",[40,2095,143],{"class":85},[40,2097,2098],{"class":89}," combinations(ranges, ",[40,2100,1905],{"class":173},[40,2102,350],{"class":89},[40,2104,158],{"class":85},[40,2106,1195],{"class":85},[40,2108,2109],{"class":89}," ra.isdisjoint(rb)]\n",[40,2111,2112,2115,2117,2120,2122,2125,2128,2131],{"class":42,"line":217},[40,2113,2114],{"class":89},"    headers_ok ",[40,2116,131],{"class":85},[40,2118,2119],{"class":173}," all",[40,2121,1781],{"class":89},[40,2123,2124],{"class":173},"isinstance",[40,2126,2127],{"class":89},"(ws.cell(r.min_row, c).value, ",[40,2129,2130],{"class":173},"str",[40,2132,492],{"class":89},[40,2134,2135,2138,2141,2143,2146,2148,2150,2152,2154,2157,2159,2161],{"class":42,"line":230},[40,2136,2137],{"class":85},"                     for",[40,2139,2140],{"class":89}," _, r ",[40,2142,143],{"class":85},[40,2144,2145],{"class":89}," ranges ",[40,2147,137],{"class":85},[40,2149,151],{"class":89},[40,2151,143],{"class":85},[40,2153,716],{"class":173},[40,2155,2156],{"class":89},"(r.min_col, r.max_col ",[40,2158,315],{"class":85},[40,2160,724],{"class":173},[40,2162,1761],{"class":89},[40,2164,2165,2167,2169,2171,2173,2175,2177,2180,2182,2185,2187,2190,2192,2195,2197,2200,2202,2205,2207,2210,2212,2215,2217,2220,2222,2224],{"class":42,"line":236},[40,2166,511],{"class":173},[40,2168,1781],{"class":89},[40,2170,891],{"class":85},[40,2172,837],{"class":50},[40,2174,782],{"class":781},[40,2176,1167],{"class":89},[40,2178,2179],{"class":85},":13",[40,2181,797],{"class":781},[40,2183,2184],{"class":50}," tables=",[40,2186,782],{"class":781},[40,2188,2189],{"class":89},"[n ",[40,2191,137],{"class":85},[40,2193,2194],{"class":89}," n, _ ",[40,2196,143],{"class":85},[40,2198,2199],{"class":89}," ranges]",[40,2201,797],{"class":781},[40,2203,2204],{"class":50}," overlaps=",[40,2206,782],{"class":781},[40,2208,2209],{"class":89},"clashes",[40,2211,797],{"class":781},[40,2213,2214],{"class":50}," text_headers=",[40,2216,782],{"class":781},[40,2218,2219],{"class":89},"headers_ok",[40,2221,797],{"class":781},[40,2223,837],{"class":50},[40,2225,492],{"class":89},[10,2227,2228],{},"Reading the saved file back confirms the two conditions that most often break a batch conversion: no two tables on a sheet overlap, and every header cell is text. Running the check as part of the script means a workbook with an odd layout — two blocks that touch diagonally, say, and were merged into one — is caught before anyone opens it in Excel.",[25,2230,2232],{"id":2231},"when-a-block-is-not-really-a-table","When a block is not really a table",[10,2234,2235],{},"The flood fill is deliberately simple, and some layouts need a person's judgement. A report-style sheet with a title row directly above its headings, with no blank row between, will be detected as one block whose header row is the title; leave a blank row, or pass the header row explicitly for that sheet. Two tables placed side by side with no empty column between them come out as one block. Cross-tab layouts with headings in both the first row and the first column convert fine, but the first column becomes an ordinary column of the table. Running the conversion on a copy and reviewing the report is the safe routine; anything odd can be fixed in the source and the conversion rerun.",[25,2237,2239],{"id":2238},"common-pitfalls","Common pitfalls",[2241,2242,2243,2251,2262,2271,2277],"ul",{},[2244,2245,2246,2250],"li",{},[2247,2248,2249],"strong",{},"Numbers or blanks in headers."," Excel repairs the file and renames columns; fix them first.",[2244,2252,2253,2256,2257,975,2259,2261],{},[2247,2254,2255],{},"Duplicate headings in a different case."," ",[37,2258,967],{},[37,2260,978],{}," clash; compare in lower case.",[2244,2263,2264,2267,2268,23],{},[2247,2265,2266],{},"Names starting with a digit or shaped like cells."," Prefix them, e.g. ",[37,2269,2270],{},"T_2026_...",[2244,2272,2273,2276],{},[2247,2274,2275],{},"Names unique only per sheet."," Table names must be unique in the whole workbook.",[2244,2278,2279,2282],{},[2247,2280,2281],{},"Titles and notes as tables."," Skip blocks smaller than two rows or columns.",[25,2284,2286],{"id":2285},"conclusion","Conclusion",[10,2288,2289],{},"To turn every block of data in a workbook into an Excel table, find the blocks on each sheet with a flood fill over filled cells, skip the ones too small to be tables, repair each header row so it is non-empty, text and unique, generate a valid name that is unique across the workbook, and add the table with openpyxl. Report every change, and check the saved file for overlaps and text headers before anyone opens it.",[25,2291,2293],{"id":2292},"frequently-asked-questions","Frequently asked questions",[10,2295,2296,2299,2300,2303,2304,2307,2308,23],{},[2247,2297,2298],{},"How do I convert a range to an Excel table with openpyxl?","\nCreate ",[37,2301,2302],{},"Table(displayName=\"Name\", ref=\"A1:D10\")",", optionally set a ",[37,2305,2306],{},"TableStyleInfo",", and add it with ",[37,2309,2310],{},"ws.add_table(table)",[10,2312,2313,2316],{},[2247,2314,2315],{},"Why does Excel say it repaired my file after adding a table?","\nUsually the header row has a blank, a number or a duplicate heading, or two tables overlap; make every header unique text and keep tables apart.",[10,2318,2319,2322],{},[2247,2320,2321],{},"What are the rules for Excel table names?","\nThey start with a letter or underscore, contain only letters, digits, underscores and full stops, must not look like a cell reference, and must be unique in the workbook.",[10,2324,2325,2328],{},[2247,2326,2327],{},"Can one sheet hold several tables?","\nYes, as long as their ranges do not overlap.",[25,2330,2332],{"id":2331},"related","Related",[2241,2334,2335,2341,2348,2355],{},[2244,2336,2337,2338,2340],{},"Up one level: ",[19,2339,22],{"href":21}," — tables and filters.",[2244,2342,2343,2347],{},[19,2344,2346],{"href":2345},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fcreate-an-excel-table-with-python\u002F","Create an Excel Table with Python"," — one table at a time.",[2244,2349,2350,2354],{},[19,2351,2353],{"href":2352},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fread-multiple-tables-from-one-excel-sheet\u002F","Read Multiple Tables from One Excel Sheet"," — finding blocks when reading.",[2244,2356,2357,2361],{},[19,2358,2360],{"href":2359},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fread-an-excel-table-by-name-into-pandas\u002F","Read an Excel Table by Name into pandas"," — using the new tables.",[2363,2364,2365],"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-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}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":99,"depth":99,"links":2367},[2368,2369,2370,2371,2372,2373,2374,2375,2376,2377],{"id":27,"depth":99,"text":28},{"id":72,"depth":99,"text":73},{"id":670,"depth":99,"text":671},{"id":1052,"depth":99,"text":1053},{"id":2001,"depth":99,"text":2002},{"id":2231,"depth":99,"text":2232},{"id":2238,"depth":99,"text":2239},{"id":2285,"depth":99,"text":2286},{"id":2292,"depth":99,"text":2293},{"id":2331,"depth":99,"text":2332},"2026-10-02","Turn every block of data in a workbook into a proper Excel table in one pass with openpyxl: find the separate rectangular blocks on each sheet, check and repair their header rows so Excel will accept them, give each table a unique valid name from its sheet and heading, skip blocks that are too small or already tables, and verify that nothing overlaps.","md",[2382,2384,2386,2388],{"q":2298,"a":2383},"Create Table(displayName=\"Name\", ref=\"A1:D10\"), optionally set a TableStyleInfo, and add it with ws.add_table(table).",{"q":2315,"a":2385},"Usually the header row has a blank, a number or a duplicate heading, or two tables overlap; make every header unique text and keep tables apart.",{"q":2321,"a":2387},"They start with a letter or underscore, contain only letters, digits, underscores and full stops, must not look like a cell reference, and must be unique in the workbook.",{"q":2327,"a":2389},"Yes, as long as their ranges do not overlap.","convert ranges to excel tables python",null,{"breadcrumb":2393},[2394,2397,2400],{"name":2395,"item":2396},"Home","\u002F",{"name":2398,"item":2399},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":22,"item":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fconvert-several-ranges-into-excel-tables-at-once",{"title":2403,"description":2404},"Convert Several Ranges into Excel Tables at Once with Python","Batch-convert data blocks to Excel tables with openpyxl: detect blocks on each sheet, fix blank and duplicate headers, generate unique valid table names, skip existing tables and check for overlaps.","convert-several-ranges-into-excel-tables-at-once","formatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fconvert-several-ranges-into-excel-tables-at-once\u002Findex",[2408,2409],"tables-and-filters","openpyxl","how-to","YCVHNAAxp6vTMnRlwE60aHpEoBvxfwEgzSaAog222v4",[2413,2417],{"title":2414,"path":2415,"stem":2416,"children":-1},"Convert an Excel Table Back to a Normal Range","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fconvert-an-excel-table-back-to-a-normal-range","formatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fconvert-an-excel-table-back-to-a-normal-range\u002Findex",{"title":2346,"path":2418,"stem":2419,"children":-1},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fcreate-an-excel-table-with-python","formatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fcreate-an-excel-table-with-python\u002Findex",[2421,2423],{"path":2422,"title":22},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python",{"path":2424,"title":2398},"\u002Fformatting-and-charting-excel-reports-with-python",[],[2427,2430,2433,2436,2439,2442],{"path":2428,"title":2429,"kind":2410},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fapply-conditional-formatting-to-an-excel-table-that-grows","Apply Conditional Formatting to an Excel Table That Grows",{"path":2431,"title":2432,"kind":2410},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fsort-and-filter-an-excel-online-table-with-microsoft-graph","Sort and Filter an Excel Online Table with Microsoft Graph",{"path":2434,"title":2435,"kind":2410},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-an-excel-table-with-formula-columns-using-xlsxwriter","Write an Excel Table with Formula Columns Using XlsxWriter",{"path":2437,"title":2438,"kind":2410},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fmake-an-excel-chart-grow-with-its-data","Make an Excel Chart Grow with Its Data",{"path":2440,"title":2441,"kind":2410},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-a-total-row-to-an-excel-table","Add a Total Row to an Excel Table with Python",{"path":2443,"title":2444,"kind":2410},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-an-autofilter-to-an-excel-sheet-with-openpyxl","Add an Autofilter to an Excel Sheet with openpyxl",1790972116445]