[{"data":1,"prerenderedAt":2821},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-external-links-and-broken-references-in-a-workbook":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-external-links-and-broken-references-in-a-workbook":2787,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-external-links-and-broken-references-in-a-workbook":2795,"children:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-external-links-and-broken-references-in-a-workbook":2800,"related:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-external-links-and-broken-references-in-a-workbook":2801},{"id":4,"title":5,"body":6,"dateModified":2752,"datePublished":2752,"description":2753,"extension":2754,"faq":2755,"keyword":2764,"kind":2765,"meta":2766,"navigation":360,"path":2775,"seo":2776,"slug":2779,"stem":2780,"topics":2781,"type":2785,"__hash__":2786},"docs\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-external-links-and-broken-references-in-a-workbook\u002Findex.md","Find External Links and Broken References in a Workbook with Python",{"type":7,"value":8,"toc":2739},"minimark",[9,27,218,223,251,254,767,771,774,840,847,1086,1090,1096,1747,2014,2017,2021,2024,2174,2178,2181,2235,2242,2371,2377,2381,2388,2580,2588,2592,2642,2646,2657,2661,2671,2675,2684,2690,2696,2702,2706,2735],[10,11,12,13,17,18,20,21,26],"p",{},"A workbook that points at other workbooks is fragile. Move the source file, rename a folder or email the report to someone outside the network, and every linked cell shows stale numbers or ",[14,15,16],"code",{},"#REF!",", while Excel greets the reader with a security prompt about links it cannot update. Broken references are the same problem from the inside: a deleted sheet leaves ",[14,19,16],{}," in every formula and named range that used it. Both are easy to miss by eye in a large file, and both are easy to find with Python. This guide, part of ",[22,23,25],"a",{"href":24},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002F","Working with Excel Formulas in Python",", builds an audit that looks everywhere links and broken references hide.",[28,29,37,38,37,42,37,46,37,54,37,64,37,75,37,82,37,87,37,95,37,102,37,108,37,113,37,116,37,121,37,124,37,130,37,134,37,138,37,141,37,143,37,146,37,152,37,155,37,159,37,162,37,164,37,167,37,173,37,177,37,181,37,184,37,186,37,191,37,198,37,203,37,207,37,210,37,212],"svg",{"viewBox":30,"role":31,"ariaLabelledBy":32,"xmlns":35,"style":36},"4 0 752 566","img",[33,34],"links-where-t","links-where-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  ",[39,40,41],"title",{"id":33},"The places external links and broken references hide in a workbook",[43,44,45],"desc",{"id":34},"A workbook can carry external links or",[47,48],"rect",{"x":49,"y":50,"width":51,"height":52,"fill":53},"4","0","752","566","#ffffff",[47,55],{"x":56,"y":57,"width":58,"height":59,"rx":60,"fill":61,"stroke":62,"style":63},"520.0","223.5","220.0","93.0","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[65,66,68],"g",{"transform":67},"translate(618.0,236.5) scale(1.000)",[69,70],"path",{"d":71,"fill":72,"stroke":73,"style":74},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[76,77,81],"text",{"x":78,"y":79,"style":80},"630.0","283.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Audit report",[76,83,86],{"x":78,"y":84,"style":85},"302.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","every link and #REF!",[47,88],{"x":89,"y":90,"width":91,"height":92,"rx":60,"fill":93,"stroke":94,"style":63},"20.0","16.0","250.0","92.0","#d9f4f1","var(--teal,#0f9488)",[65,96,98],{"transform":97},"translate(133.0,29.0) scale(1.000)",[69,99],{"d":100,"fill":72,"stroke":101,"style":74},"M5 9h14 M4 15h14 M10 4l-2 16 M16 4l-2 16","var(--teal-ink,#0b6157)",[76,103,107],{"x":104,"y":105,"style":106},"145.0","75.0","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Cell formulas",[76,109,112],{"x":104,"y":110,"style":111},"94.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","[1]Data!B2 and #REF!",[69,114],{"d":115,"fill":72,"stroke":62,"style":63},"M274.0,62.0 C394.0,62.0 394.0,270.0 510.0,270.0",[117,118],"polygon",{"points":119,"fill":120},"514.0,270.0 505.0,275.0 505.0,265.0","#5b5cf0",[47,122],{"x":89,"y":123,"width":91,"height":92,"rx":60,"fill":93,"stroke":94,"style":63},"120.0",[65,125,127],{"transform":126},"translate(133.0,133.0) scale(1.000)",[69,128],{"d":129,"fill":72,"stroke":101,"style":74},"M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1 M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1",[76,131,133],{"x":104,"y":132,"style":106},"179.0","Defined names",[76,135,137],{"x":104,"y":136,"style":111},"198.0","named ranges",[69,139],{"d":140,"fill":72,"stroke":62,"style":63},"M274.0,166.0 C394.0,166.0 394.0,270.0 510.0,270.0",[117,142],{"points":119,"fill":120},[47,144],{"x":89,"y":145,"width":91,"height":92,"rx":60,"fill":93,"stroke":94,"style":63},"224.0",[65,147,149],{"transform":148},"translate(133.0,237.0) scale(1.000)",[69,150],{"d":151,"fill":72,"stroke":101,"style":74},"M3 5h18l-7 8v6l-4 2v-8z",[76,153,154],{"x":104,"y":79,"style":106},"Data validation",[76,156,158],{"x":104,"y":157,"style":111},"302.0","list sources",[69,160],{"d":161,"fill":72,"stroke":62,"style":63},"M274.0,270.0 C394.0,270.0 394.0,270.0 510.0,270.0",[117,163],{"points":119,"fill":120},[47,165],{"x":89,"y":166,"width":91,"height":92,"rx":60,"fill":93,"stroke":94,"style":63},"328.0",[65,168,170],{"transform":169},"translate(133.0,341.0) scale(1.000)",[69,171],{"d":172,"fill":72,"stroke":101,"style":74},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5",[76,174,176],{"x":104,"y":175,"style":106},"387.0","Conditional formatting",[76,178,180],{"x":104,"y":179,"style":111},"406.0","rule formulas",[69,182],{"d":183,"fill":72,"stroke":62,"style":63},"M274.0,374.0 C394.0,374.0 394.0,270.0 510.0,270.0",[117,185],{"points":119,"fill":120},[47,187],{"x":89,"y":188,"width":91,"height":92,"rx":60,"fill":189,"stroke":190,"style":63},"432.0","#fdefd8","var(--gold,#b4740a)",[65,192,194],{"transform":193},"translate(133.0,445.0) scale(1.000)",[69,195],{"d":196,"fill":72,"stroke":197,"style":74},"M3 6h6l2 2h10v11H3z","var(--gold-ink,#7a4e06)",[76,199,202],{"x":104,"y":200,"style":201},"491.0","font-size:13px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","externalLink parts",[76,204,206],{"x":104,"y":205,"style":111},"510.0","file paths",[69,208],{"d":209,"fill":72,"stroke":62,"style":63},"M274.0,478.0 C394.0,478.0 394.0,270.0 510.0,270.0",[117,211],{"points":119,"fill":120},[76,213,217],{"x":214,"y":215,"style":216},"380.0","546.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","Excel's \"this workbook contains links\" prompt can be triggered by any of these, not just by cells",[219,220,222],"h2",{"id":221},"prerequisites","Prerequisites",[224,225,230],"pre",{"className":226,"code":227,"language":228,"meta":229,"style":229},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pandas\n","bash","",[14,231,232],{"__ignoreMap":229},[233,234,237,241,245,248],"span",{"class":235,"line":236},"line",1,[233,238,240],{"class":239},"sMTad","pip",[233,242,244],{"class":243},"srMev"," install",[233,246,247],{"class":243}," openpyxl",[233,249,250],{"class":243}," pandas\n",[10,252,253],{},"To have something realistic to audit, build a workbook with one external link, formulas that use it, a formula broken by a deleted sheet, a broken named range, and validation and formatting rules that reference both:",[224,255,259],{"className":256,"code":257,"language":258,"meta":229,"style":229},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import Workbook, load_workbook\nfrom openpyxl.formatting.rule import FormulaRule\nfrom openpyxl.packaging.relationship import Relationship\nfrom openpyxl.styles import Font\nfrom openpyxl.workbook.defined_name import DefinedName\nfrom openpyxl.workbook.external_link.external import ExternalBook, ExternalLink\nfrom openpyxl.worksheet.datavalidation import DataValidation\n\nwb = Workbook()\nws = wb.active\nws.title = \"Report\"\n\nlink = ExternalLink(externalBook=ExternalBook(id=\"rId1\"))\nlink.file_link = Relationship(type=\"externalLinkPath\", Target=\"Budget_2026.xlsx\",\n                              TargetMode=\"External\", Id=\"rId1\")\nwb._external_links.append(link)\n\nws[\"A1\"], ws[\"B1\"] = \"Budget\", \"=[1]Data!B2\"\nws[\"A2\"], ws[\"B2\"] = \"Budget total\", \"=SUM([1]Data!B2:B13)\"\nws[\"A3\"], ws[\"B3\"] = \"Variance\", \"=#REF!-B1\"\nws[\"A4\"], ws[\"B4\"] = \"Actual\", 1250\nwb.defined_names[\"OldRates\"] = DefinedName(\"OldRates\", attr_text=\"#REF!$B$2:$B$9\")\nwb.defined_names[\"BudgetLines\"] = DefinedName(\"BudgetLines\", attr_text=\"[1]Data!$A$2:$A$13\")\n\ndv = DataValidation(type=\"list\", formula1=\"=[1]Lists!$A$1:$A$5\")\nws.add_data_validation(dv)\ndv.add(\"C1\")\nws.conditional_formatting.add(\"B4\", FormulaRule(formula=[\"B4>#REF!\"], font=Font(bold=True)))\nwb.save(\"report.xlsx\")\n","python",[14,260,261,277,290,303,316,329,342,355,362,374,385,396,401,432,465,488,494,499,527,552,577,603,633,660,665,695,701,712,756],{"__ignoreMap":229},[233,262,263,267,271,274],{"class":235,"line":236},[233,264,266],{"class":265},"s-kum","from",[233,268,270],{"class":269},"skGVy"," openpyxl ",[233,272,273],{"class":265},"import",[233,275,276],{"class":269}," Workbook, load_workbook\n",[233,278,280,282,285,287],{"class":235,"line":279},2,[233,281,266],{"class":265},[233,283,284],{"class":269}," openpyxl.formatting.rule ",[233,286,273],{"class":265},[233,288,289],{"class":269}," FormulaRule\n",[233,291,293,295,298,300],{"class":235,"line":292},3,[233,294,266],{"class":265},[233,296,297],{"class":269}," openpyxl.packaging.relationship ",[233,299,273],{"class":265},[233,301,302],{"class":269}," Relationship\n",[233,304,306,308,311,313],{"class":235,"line":305},4,[233,307,266],{"class":265},[233,309,310],{"class":269}," openpyxl.styles ",[233,312,273],{"class":265},[233,314,315],{"class":269}," Font\n",[233,317,319,321,324,326],{"class":235,"line":318},5,[233,320,266],{"class":265},[233,322,323],{"class":269}," openpyxl.workbook.defined_name ",[233,325,273],{"class":265},[233,327,328],{"class":269}," DefinedName\n",[233,330,332,334,337,339],{"class":235,"line":331},6,[233,333,266],{"class":265},[233,335,336],{"class":269}," openpyxl.workbook.external_link.external ",[233,338,273],{"class":265},[233,340,341],{"class":269}," ExternalBook, ExternalLink\n",[233,343,345,347,350,352],{"class":235,"line":344},7,[233,346,266],{"class":265},[233,348,349],{"class":269}," openpyxl.worksheet.datavalidation ",[233,351,273],{"class":265},[233,353,354],{"class":269}," DataValidation\n",[233,356,358],{"class":235,"line":357},8,[233,359,361],{"emptyLinePlaceholder":360},true,"\n",[233,363,365,368,371],{"class":235,"line":364},9,[233,366,367],{"class":269},"wb ",[233,369,370],{"class":265},"=",[233,372,373],{"class":269}," Workbook()\n",[233,375,377,380,382],{"class":235,"line":376},10,[233,378,379],{"class":269},"ws ",[233,381,370],{"class":265},[233,383,384],{"class":269}," wb.active\n",[233,386,388,391,393],{"class":235,"line":387},11,[233,389,390],{"class":269},"ws.title ",[233,392,370],{"class":265},[233,394,395],{"class":243}," \"Report\"\n",[233,397,399],{"class":235,"line":398},12,[233,400,361],{"emptyLinePlaceholder":360},[233,402,404,407,409,412,416,418,421,424,426,429],{"class":235,"line":403},13,[233,405,406],{"class":269},"link ",[233,408,370],{"class":265},[233,410,411],{"class":269}," ExternalLink(",[233,413,415],{"class":414},"sa561","externalBook",[233,417,370],{"class":265},[233,419,420],{"class":269},"ExternalBook(",[233,422,423],{"class":414},"id",[233,425,370],{"class":265},[233,427,428],{"class":243},"\"rId1\"",[233,430,431],{"class":269},"))\n",[233,433,435,438,440,443,446,448,451,454,457,459,462],{"class":235,"line":434},14,[233,436,437],{"class":269},"link.file_link ",[233,439,370],{"class":265},[233,441,442],{"class":269}," Relationship(",[233,444,445],{"class":414},"type",[233,447,370],{"class":265},[233,449,450],{"class":243},"\"externalLinkPath\"",[233,452,453],{"class":269},", ",[233,455,456],{"class":414},"Target",[233,458,370],{"class":265},[233,460,461],{"class":243},"\"Budget_2026.xlsx\"",[233,463,464],{"class":269},",\n",[233,466,468,471,473,476,478,481,483,485],{"class":235,"line":467},15,[233,469,470],{"class":414},"                              TargetMode",[233,472,370],{"class":265},[233,474,475],{"class":243},"\"External\"",[233,477,453],{"class":269},[233,479,480],{"class":414},"Id",[233,482,370],{"class":265},[233,484,428],{"class":243},[233,486,487],{"class":269},")\n",[233,489,491],{"class":235,"line":490},16,[233,492,493],{"class":269},"wb._external_links.append(link)\n",[233,495,497],{"class":235,"line":496},17,[233,498,361],{"emptyLinePlaceholder":360},[233,500,502,505,508,511,514,517,519,522,524],{"class":235,"line":501},18,[233,503,504],{"class":269},"ws[",[233,506,507],{"class":243},"\"A1\"",[233,509,510],{"class":269},"], ws[",[233,512,513],{"class":243},"\"B1\"",[233,515,516],{"class":269},"] ",[233,518,370],{"class":265},[233,520,521],{"class":243}," \"Budget\"",[233,523,453],{"class":269},[233,525,526],{"class":243},"\"=[1]Data!B2\"\n",[233,528,530,532,535,537,540,542,544,547,549],{"class":235,"line":529},19,[233,531,504],{"class":269},[233,533,534],{"class":243},"\"A2\"",[233,536,510],{"class":269},[233,538,539],{"class":243},"\"B2\"",[233,541,516],{"class":269},[233,543,370],{"class":265},[233,545,546],{"class":243}," \"Budget total\"",[233,548,453],{"class":269},[233,550,551],{"class":243},"\"=SUM([1]Data!B2:B13)\"\n",[233,553,555,557,560,562,565,567,569,572,574],{"class":235,"line":554},20,[233,556,504],{"class":269},[233,558,559],{"class":243},"\"A3\"",[233,561,510],{"class":269},[233,563,564],{"class":243},"\"B3\"",[233,566,516],{"class":269},[233,568,370],{"class":265},[233,570,571],{"class":243}," \"Variance\"",[233,573,453],{"class":269},[233,575,576],{"class":243},"\"=#REF!-B1\"\n",[233,578,580,582,585,587,590,592,594,597,599],{"class":235,"line":579},21,[233,581,504],{"class":269},[233,583,584],{"class":243},"\"A4\"",[233,586,510],{"class":269},[233,588,589],{"class":243},"\"B4\"",[233,591,516],{"class":269},[233,593,370],{"class":265},[233,595,596],{"class":243}," \"Actual\"",[233,598,453],{"class":269},[233,600,602],{"class":601},"sP0c6","1250\n",[233,604,606,609,612,614,616,619,621,623,626,628,631],{"class":235,"line":605},22,[233,607,608],{"class":269},"wb.defined_names[",[233,610,611],{"class":243},"\"OldRates\"",[233,613,516],{"class":269},[233,615,370],{"class":265},[233,617,618],{"class":269}," DefinedName(",[233,620,611],{"class":243},[233,622,453],{"class":269},[233,624,625],{"class":414},"attr_text",[233,627,370],{"class":265},[233,629,630],{"class":243},"\"#REF!$B$2:$B$9\"",[233,632,487],{"class":269},[233,634,636,638,641,643,645,647,649,651,653,655,658],{"class":235,"line":635},23,[233,637,608],{"class":269},[233,639,640],{"class":243},"\"BudgetLines\"",[233,642,516],{"class":269},[233,644,370],{"class":265},[233,646,618],{"class":269},[233,648,640],{"class":243},[233,650,453],{"class":269},[233,652,625],{"class":414},[233,654,370],{"class":265},[233,656,657],{"class":243},"\"[1]Data!$A$2:$A$13\"",[233,659,487],{"class":269},[233,661,663],{"class":235,"line":662},24,[233,664,361],{"emptyLinePlaceholder":360},[233,666,668,671,673,676,678,680,683,685,688,690,693],{"class":235,"line":667},25,[233,669,670],{"class":269},"dv ",[233,672,370],{"class":265},[233,674,675],{"class":269}," DataValidation(",[233,677,445],{"class":414},[233,679,370],{"class":265},[233,681,682],{"class":243},"\"list\"",[233,684,453],{"class":269},[233,686,687],{"class":414},"formula1",[233,689,370],{"class":265},[233,691,692],{"class":243},"\"=[1]Lists!$A$1:$A$5\"",[233,694,487],{"class":269},[233,696,698],{"class":235,"line":697},26,[233,699,700],{"class":269},"ws.add_data_validation(dv)\n",[233,702,704,707,710],{"class":235,"line":703},27,[233,705,706],{"class":269},"dv.add(",[233,708,709],{"class":243},"\"C1\"",[233,711,487],{"class":269},[233,713,715,718,720,723,726,728,731,734,737,740,742,745,748,750,753],{"class":235,"line":714},28,[233,716,717],{"class":269},"ws.conditional_formatting.add(",[233,719,589],{"class":243},[233,721,722],{"class":269},", FormulaRule(",[233,724,725],{"class":414},"formula",[233,727,370],{"class":265},[233,729,730],{"class":269},"[",[233,732,733],{"class":243},"\"B4>#REF!\"",[233,735,736],{"class":269},"], ",[233,738,739],{"class":414},"font",[233,741,370],{"class":265},[233,743,744],{"class":269},"Font(",[233,746,747],{"class":414},"bold",[233,749,370],{"class":265},[233,751,752],{"class":601},"True",[233,754,755],{"class":269},")))\n",[233,757,759,762,765],{"class":235,"line":758},29,[233,760,761],{"class":269},"wb.save(",[233,763,764],{"class":243},"\"report.xlsx\"",[233,766,487],{"class":269},[219,768,770],{"id":769},"step-1-list-the-linked-workbooks","Step 1 — List the linked workbooks",[10,772,773],{},"External links live in their own parts of the package. openpyxl loads them into the workbook's external links list, each with the path of the linked file:",[224,775,777],{"className":256,"code":776,"language":258,"meta":229,"style":229},"wb = load_workbook(\"report.xlsx\")\ntargets = {i: ln.file_link.Target for i, ln in enumerate(wb._external_links, start=1)}\nprint(targets)          # {1: 'Budget_2026.xlsx'}\n",[14,778,779,792,828],{"__ignoreMap":229},[233,780,781,783,785,788,790],{"class":235,"line":236},[233,782,367],{"class":269},[233,784,370],{"class":265},[233,786,787],{"class":269}," load_workbook(",[233,789,764],{"class":243},[233,791,487],{"class":269},[233,793,794,797,799,802,805,808,811,814,817,820,822,825],{"class":235,"line":279},[233,795,796],{"class":269},"targets ",[233,798,370],{"class":265},[233,800,801],{"class":269}," {i: ln.file_link.Target ",[233,803,804],{"class":265},"for",[233,806,807],{"class":269}," i, ln ",[233,809,810],{"class":265},"in",[233,812,813],{"class":601}," enumerate",[233,815,816],{"class":269},"(wb._external_links, ",[233,818,819],{"class":414},"start",[233,821,370],{"class":265},[233,823,824],{"class":601},"1",[233,826,827],{"class":269},")}\n",[233,829,830,833,836],{"class":235,"line":292},[233,831,832],{"class":601},"print",[233,834,835],{"class":269},"(targets)          ",[233,837,839],{"class":838},"s-wDw","# {1: 'Budget_2026.xlsx'}\n",[10,841,842,843,846],{},"The number is what formulas use: ",[14,844,845],{},"[1]"," means the first entry. The attribute starts with an underscore — it is not part of openpyxl's documented API — so for a check that must not break on an upgrade, read the same information from the zip file directly:",[224,848,850],{"className":256,"code":849,"language":258,"meta":229,"style":229},"import re\nimport zipfile\n\ndef link_targets(path: str) -> dict[int, str]:\n    out = {}\n    with zipfile.ZipFile(path) as z:\n        for name in z.namelist():\n            m = re.match(r\"xl\u002FexternalLinks\u002F_rels\u002FexternalLink(\\d+)\\.xml\\.rels$\", name)\n            if m:\n                rels = z.read(name).decode()\n                out[int(m[1])] = re.search(r'Target=\"([^\"]+)\"', rels)[1]\n    return dict(sorted(out.items()))\n\nprint(link_targets(\"report.xlsx\"))   # {1: 'Budget_2026.xlsx'}\n",[14,851,852,859,866,870,898,908,922,935,981,989,999,1051,1068,1072],{"__ignoreMap":229},[233,853,854,856],{"class":235,"line":236},[233,855,273],{"class":265},[233,857,858],{"class":269}," re\n",[233,860,861,863],{"class":235,"line":279},[233,862,273],{"class":265},[233,864,865],{"class":269}," zipfile\n",[233,867,868],{"class":235,"line":292},[233,869,361],{"emptyLinePlaceholder":360},[233,871,872,875,879,882,885,888,891,893,895],{"class":235,"line":305},[233,873,874],{"class":265},"def",[233,876,878],{"class":877},"s_Opv"," link_targets",[233,880,881],{"class":269},"(path: ",[233,883,884],{"class":601},"str",[233,886,887],{"class":269},") -> dict[",[233,889,890],{"class":601},"int",[233,892,453],{"class":269},[233,894,884],{"class":601},[233,896,897],{"class":269},"]:\n",[233,899,900,903,905],{"class":235,"line":318},[233,901,902],{"class":269},"    out ",[233,904,370],{"class":265},[233,906,907],{"class":269}," {}\n",[233,909,910,913,916,919],{"class":235,"line":331},[233,911,912],{"class":265},"    with",[233,914,915],{"class":269}," zipfile.ZipFile(path) ",[233,917,918],{"class":265},"as",[233,920,921],{"class":269}," z:\n",[233,923,924,927,930,932],{"class":235,"line":344},[233,925,926],{"class":265},"        for",[233,928,929],{"class":269}," name ",[233,931,810],{"class":265},[233,933,934],{"class":269}," z.namelist():\n",[233,936,937,940,942,945,948,951,954,957,960,964,967,969,972,975,978],{"class":235,"line":357},[233,938,939],{"class":269},"            m ",[233,941,370],{"class":265},[233,943,944],{"class":269}," re.match(",[233,946,947],{"class":265},"r",[233,949,950],{"class":243},"\"xl\u002FexternalLinks\u002F_rels\u002FexternalLink",[233,952,953],{"class":601},"(\\d",[233,955,956],{"class":265},"+",[233,958,959],{"class":601},")",[233,961,963],{"class":962},"s_b0D","\\.",[233,965,966],{"class":243},"xml",[233,968,963],{"class":962},[233,970,971],{"class":243},"rels",[233,973,974],{"class":601},"$",[233,976,977],{"class":243},"\"",[233,979,980],{"class":269},", name)\n",[233,982,983,986],{"class":235,"line":364},[233,984,985],{"class":265},"            if",[233,987,988],{"class":269}," m:\n",[233,990,991,994,996],{"class":235,"line":376},[233,992,993],{"class":269},"                rels ",[233,995,370],{"class":265},[233,997,998],{"class":269}," z.read(name).decode()\n",[233,1000,1001,1004,1006,1009,1011,1014,1016,1019,1021,1024,1027,1030,1033,1036,1038,1040,1043,1046,1048],{"class":235,"line":387},[233,1002,1003],{"class":269},"                out[",[233,1005,890],{"class":601},[233,1007,1008],{"class":269},"(m[",[233,1010,824],{"class":601},[233,1012,1013],{"class":269},"])] ",[233,1015,370],{"class":265},[233,1017,1018],{"class":269}," re.search(",[233,1020,947],{"class":265},[233,1022,1023],{"class":243},"'Target=\"",[233,1025,1026],{"class":601},"([",[233,1028,1029],{"class":265},"^",[233,1031,977],{"class":1032},"sSjpA",[233,1034,1035],{"class":601},"]",[233,1037,956],{"class":265},[233,1039,959],{"class":601},[233,1041,1042],{"class":243},"\"'",[233,1044,1045],{"class":269},", rels)[",[233,1047,824],{"class":601},[233,1049,1050],{"class":269},"]\n",[233,1052,1053,1056,1059,1062,1065],{"class":235,"line":398},[233,1054,1055],{"class":265},"    return",[233,1057,1058],{"class":601}," dict",[233,1060,1061],{"class":269},"(",[233,1063,1064],{"class":601},"sorted",[233,1066,1067],{"class":269},"(out.items()))\n",[233,1069,1070],{"class":235,"line":403},[233,1071,361],{"emptyLinePlaceholder":360},[233,1073,1074,1076,1079,1081,1084],{"class":235,"line":434},[233,1075,832],{"class":601},[233,1077,1078],{"class":269},"(link_targets(",[233,1080,764],{"class":243},[233,1082,1083],{"class":269},"))   ",[233,1085,839],{"class":838},[219,1087,1089],{"id":1088},"step-2-scan-every-place-a-formula-can-live","Step 2 — Scan every place a formula can live",[10,1091,1092,1093,1095],{},"Cell formulas are only one place. Defined names, data validation and conditional formatting all hold formula text, and all of them can point outside the workbook or at ",[14,1094,16],{},". One scanner covers them, classifying each hit:",[224,1097,1099],{"className":256,"code":1098,"language":258,"meta":229,"style":229},"EXTERNAL = re.compile(r\"\\[(\\d+)\\]\")\n\ndef classify(text: str) -> list[str]:\n    kinds = []\n    if EXTERNAL.search(text):\n        kinds.append(\"external\")\n    if \"#REF!\" in text.upper():\n        kinds.append(\"broken\")\n    return kinds\n\ndef audit(path: str) -> list[dict]:\n    wb = load_workbook(path)\n    targets = link_targets(path)\n    found = []\n\n    def record(where, location, text):\n        for kind in classify(text):\n            ids = [int(i) for i in EXTERNAL.findall(text)]\n            found.append({\"where\": where, \"location\": location, \"kind\": kind, \"formula\": text,\n                          \"linked_file\": \", \".join(targets.get(i, f\"[{i}]?\") for i in ids)})\n\n    for ws in wb.worksheets:\n        for row in ws.iter_rows():\n            for cell in row:\n                if isinstance(cell.value, str) and cell.value.startswith(\"=\"):\n                    record(\"cell\", f\"{ws.title}!{cell.coordinate}\", cell.value)\n        for dv in ws.data_validations.dataValidation:\n            record(\"data validation\", f\"{ws.title}!{dv.sqref}\", str(dv.formula1 or \"\"))\n        for rng, rules in ws.conditional_formatting._cf_rules.items():\n            for rule in rules:\n                for f in rule.formula or []:\n                    record(\"conditional format\", f\"{ws.title}!{rng.sqref}\", f)\n    for name, dn in wb.defined_names.items():\n        record(\"defined name\", name, dn.attr_text or \"\")\n    return found\n\nimport pandas as pd\n\nreport = pd.DataFrame(audit(\"report.xlsx\"))\nprint(report[[\"where\", \"location\", \"kind\", \"linked_file\"]].to_string(index=False))\n",[14,1100,1101,1132,1136,1155,1165,1176,1186,1199,1208,1215,1219,1237,1247,1257,1266,1270,1281,1293,1320,1349,1393,1397,1410,1422,1435,1462,1498,1510,1556,1568,1581,1600,1634,1647,1665,1673,1678,1691,1696,1711],{"__ignoreMap":229},[233,1102,1103,1106,1109,1112,1114,1116,1119,1121,1123,1125,1128,1130],{"class":235,"line":236},[233,1104,1105],{"class":601},"EXTERNAL",[233,1107,1108],{"class":265}," =",[233,1110,1111],{"class":269}," re.compile(",[233,1113,947],{"class":265},[233,1115,977],{"class":243},[233,1117,1118],{"class":962},"\\[",[233,1120,953],{"class":601},[233,1122,956],{"class":265},[233,1124,959],{"class":601},[233,1126,1127],{"class":962},"\\]",[233,1129,977],{"class":243},[233,1131,487],{"class":269},[233,1133,1134],{"class":235,"line":279},[233,1135,361],{"emptyLinePlaceholder":360},[233,1137,1138,1140,1143,1146,1148,1151,1153],{"class":235,"line":292},[233,1139,874],{"class":265},[233,1141,1142],{"class":877}," classify",[233,1144,1145],{"class":269},"(text: ",[233,1147,884],{"class":601},[233,1149,1150],{"class":269},") -> list[",[233,1152,884],{"class":601},[233,1154,897],{"class":269},[233,1156,1157,1160,1162],{"class":235,"line":305},[233,1158,1159],{"class":269},"    kinds ",[233,1161,370],{"class":265},[233,1163,1164],{"class":269}," []\n",[233,1166,1167,1170,1173],{"class":235,"line":318},[233,1168,1169],{"class":265},"    if",[233,1171,1172],{"class":601}," EXTERNAL",[233,1174,1175],{"class":269},".search(text):\n",[233,1177,1178,1181,1184],{"class":235,"line":331},[233,1179,1180],{"class":269},"        kinds.append(",[233,1182,1183],{"class":243},"\"external\"",[233,1185,487],{"class":269},[233,1187,1188,1190,1193,1196],{"class":235,"line":344},[233,1189,1169],{"class":265},[233,1191,1192],{"class":243}," \"#REF!\"",[233,1194,1195],{"class":265}," in",[233,1197,1198],{"class":269}," text.upper():\n",[233,1200,1201,1203,1206],{"class":235,"line":357},[233,1202,1180],{"class":269},[233,1204,1205],{"class":243},"\"broken\"",[233,1207,487],{"class":269},[233,1209,1210,1212],{"class":235,"line":364},[233,1211,1055],{"class":265},[233,1213,1214],{"class":269}," kinds\n",[233,1216,1217],{"class":235,"line":376},[233,1218,361],{"emptyLinePlaceholder":360},[233,1220,1221,1223,1226,1228,1230,1232,1235],{"class":235,"line":387},[233,1222,874],{"class":265},[233,1224,1225],{"class":877}," audit",[233,1227,881],{"class":269},[233,1229,884],{"class":601},[233,1231,1150],{"class":269},[233,1233,1234],{"class":601},"dict",[233,1236,897],{"class":269},[233,1238,1239,1242,1244],{"class":235,"line":398},[233,1240,1241],{"class":269},"    wb ",[233,1243,370],{"class":265},[233,1245,1246],{"class":269}," load_workbook(path)\n",[233,1248,1249,1252,1254],{"class":235,"line":403},[233,1250,1251],{"class":269},"    targets ",[233,1253,370],{"class":265},[233,1255,1256],{"class":269}," link_targets(path)\n",[233,1258,1259,1262,1264],{"class":235,"line":434},[233,1260,1261],{"class":269},"    found ",[233,1263,370],{"class":265},[233,1265,1164],{"class":269},[233,1267,1268],{"class":235,"line":467},[233,1269,361],{"emptyLinePlaceholder":360},[233,1271,1272,1275,1278],{"class":235,"line":490},[233,1273,1274],{"class":265},"    def",[233,1276,1277],{"class":877}," record",[233,1279,1280],{"class":269},"(where, location, text):\n",[233,1282,1283,1285,1288,1290],{"class":235,"line":496},[233,1284,926],{"class":265},[233,1286,1287],{"class":269}," kind ",[233,1289,810],{"class":265},[233,1291,1292],{"class":269}," classify(text):\n",[233,1294,1295,1298,1300,1303,1305,1308,1310,1313,1315,1317],{"class":235,"line":501},[233,1296,1297],{"class":269},"            ids ",[233,1299,370],{"class":265},[233,1301,1302],{"class":269}," [",[233,1304,890],{"class":601},[233,1306,1307],{"class":269},"(i) ",[233,1309,804],{"class":265},[233,1311,1312],{"class":269}," i ",[233,1314,810],{"class":265},[233,1316,1172],{"class":601},[233,1318,1319],{"class":269},".findall(text)]\n",[233,1321,1322,1325,1328,1331,1334,1337,1340,1343,1346],{"class":235,"line":529},[233,1323,1324],{"class":269},"            found.append({",[233,1326,1327],{"class":243},"\"where\"",[233,1329,1330],{"class":269},": where, ",[233,1332,1333],{"class":243},"\"location\"",[233,1335,1336],{"class":269},": location, ",[233,1338,1339],{"class":243},"\"kind\"",[233,1341,1342],{"class":269},": kind, ",[233,1344,1345],{"class":243},"\"formula\"",[233,1347,1348],{"class":269},": text,\n",[233,1350,1351,1354,1357,1360,1363,1366,1369,1372,1375,1378,1381,1384,1386,1388,1390],{"class":235,"line":554},[233,1352,1353],{"class":243},"                          \"linked_file\"",[233,1355,1356],{"class":269},": ",[233,1358,1359],{"class":243},"\", \"",[233,1361,1362],{"class":269},".join(targets.get(i, ",[233,1364,1365],{"class":265},"f",[233,1367,1368],{"class":243},"\"[",[233,1370,1371],{"class":1032},"{",[233,1373,1374],{"class":269},"i",[233,1376,1377],{"class":1032},"}",[233,1379,1380],{"class":243},"]?\"",[233,1382,1383],{"class":269},") ",[233,1385,804],{"class":265},[233,1387,1312],{"class":269},[233,1389,810],{"class":265},[233,1391,1392],{"class":269}," ids)})\n",[233,1394,1395],{"class":235,"line":579},[233,1396,361],{"emptyLinePlaceholder":360},[233,1398,1399,1402,1405,1407],{"class":235,"line":605},[233,1400,1401],{"class":265},"    for",[233,1403,1404],{"class":269}," ws ",[233,1406,810],{"class":265},[233,1408,1409],{"class":269}," wb.worksheets:\n",[233,1411,1412,1414,1417,1419],{"class":235,"line":635},[233,1413,926],{"class":265},[233,1415,1416],{"class":269}," row ",[233,1418,810],{"class":265},[233,1420,1421],{"class":269}," ws.iter_rows():\n",[233,1423,1424,1427,1430,1432],{"class":235,"line":662},[233,1425,1426],{"class":265},"            for",[233,1428,1429],{"class":269}," cell ",[233,1431,810],{"class":265},[233,1433,1434],{"class":269}," row:\n",[233,1436,1437,1440,1443,1446,1448,1450,1453,1456,1459],{"class":235,"line":667},[233,1438,1439],{"class":265},"                if",[233,1441,1442],{"class":601}," isinstance",[233,1444,1445],{"class":269},"(cell.value, ",[233,1447,884],{"class":601},[233,1449,1383],{"class":269},[233,1451,1452],{"class":265},"and",[233,1454,1455],{"class":269}," cell.value.startswith(",[233,1457,1458],{"class":243},"\"=\"",[233,1460,1461],{"class":269},"):\n",[233,1463,1464,1467,1470,1472,1474,1476,1478,1481,1483,1486,1488,1491,1493,1495],{"class":235,"line":697},[233,1465,1466],{"class":269},"                    record(",[233,1468,1469],{"class":243},"\"cell\"",[233,1471,453],{"class":269},[233,1473,1365],{"class":265},[233,1475,977],{"class":243},[233,1477,1371],{"class":1032},[233,1479,1480],{"class":269},"ws.title",[233,1482,1377],{"class":1032},[233,1484,1485],{"class":243},"!",[233,1487,1371],{"class":1032},[233,1489,1490],{"class":269},"cell.coordinate",[233,1492,1377],{"class":1032},[233,1494,977],{"class":243},[233,1496,1497],{"class":269},", cell.value)\n",[233,1499,1500,1502,1505,1507],{"class":235,"line":703},[233,1501,926],{"class":265},[233,1503,1504],{"class":269}," dv ",[233,1506,810],{"class":265},[233,1508,1509],{"class":269}," ws.data_validations.dataValidation:\n",[233,1511,1512,1515,1518,1520,1522,1524,1526,1528,1530,1532,1534,1537,1539,1541,1543,1545,1548,1551,1554],{"class":235,"line":714},[233,1513,1514],{"class":269},"            record(",[233,1516,1517],{"class":243},"\"data validation\"",[233,1519,453],{"class":269},[233,1521,1365],{"class":265},[233,1523,977],{"class":243},[233,1525,1371],{"class":1032},[233,1527,1480],{"class":269},[233,1529,1377],{"class":1032},[233,1531,1485],{"class":243},[233,1533,1371],{"class":1032},[233,1535,1536],{"class":269},"dv.sqref",[233,1538,1377],{"class":1032},[233,1540,977],{"class":243},[233,1542,453],{"class":269},[233,1544,884],{"class":601},[233,1546,1547],{"class":269},"(dv.formula1 ",[233,1549,1550],{"class":265},"or",[233,1552,1553],{"class":243}," \"\"",[233,1555,431],{"class":269},[233,1557,1558,1560,1563,1565],{"class":235,"line":758},[233,1559,926],{"class":265},[233,1561,1562],{"class":269}," rng, rules ",[233,1564,810],{"class":265},[233,1566,1567],{"class":269}," ws.conditional_formatting._cf_rules.items():\n",[233,1569,1571,1573,1576,1578],{"class":235,"line":1570},30,[233,1572,1426],{"class":265},[233,1574,1575],{"class":269}," rule ",[233,1577,810],{"class":265},[233,1579,1580],{"class":269}," rules:\n",[233,1582,1584,1587,1590,1592,1595,1597],{"class":235,"line":1583},31,[233,1585,1586],{"class":265},"                for",[233,1588,1589],{"class":269}," f ",[233,1591,810],{"class":265},[233,1593,1594],{"class":269}," rule.formula ",[233,1596,1550],{"class":265},[233,1598,1599],{"class":269}," []:\n",[233,1601,1603,1605,1608,1610,1612,1614,1616,1618,1620,1622,1624,1627,1629,1631],{"class":235,"line":1602},32,[233,1604,1466],{"class":269},[233,1606,1607],{"class":243},"\"conditional format\"",[233,1609,453],{"class":269},[233,1611,1365],{"class":265},[233,1613,977],{"class":243},[233,1615,1371],{"class":1032},[233,1617,1480],{"class":269},[233,1619,1377],{"class":1032},[233,1621,1485],{"class":243},[233,1623,1371],{"class":1032},[233,1625,1626],{"class":269},"rng.sqref",[233,1628,1377],{"class":1032},[233,1630,977],{"class":243},[233,1632,1633],{"class":269},", f)\n",[233,1635,1637,1639,1642,1644],{"class":235,"line":1636},33,[233,1638,1401],{"class":265},[233,1640,1641],{"class":269}," name, dn ",[233,1643,810],{"class":265},[233,1645,1646],{"class":269}," wb.defined_names.items():\n",[233,1648,1650,1653,1656,1659,1661,1663],{"class":235,"line":1649},34,[233,1651,1652],{"class":269},"        record(",[233,1654,1655],{"class":243},"\"defined name\"",[233,1657,1658],{"class":269},", name, dn.attr_text ",[233,1660,1550],{"class":265},[233,1662,1553],{"class":243},[233,1664,487],{"class":269},[233,1666,1668,1670],{"class":235,"line":1667},35,[233,1669,1055],{"class":265},[233,1671,1672],{"class":269}," found\n",[233,1674,1676],{"class":235,"line":1675},36,[233,1677,361],{"emptyLinePlaceholder":360},[233,1679,1681,1683,1686,1688],{"class":235,"line":1680},37,[233,1682,273],{"class":265},[233,1684,1685],{"class":269}," pandas ",[233,1687,918],{"class":265},[233,1689,1690],{"class":269}," pd\n",[233,1692,1694],{"class":235,"line":1693},38,[233,1695,361],{"emptyLinePlaceholder":360},[233,1697,1699,1702,1704,1707,1709],{"class":235,"line":1698},39,[233,1700,1701],{"class":269},"report ",[233,1703,370],{"class":265},[233,1705,1706],{"class":269}," pd.DataFrame(audit(",[233,1708,764],{"class":243},[233,1710,431],{"class":269},[233,1712,1714,1716,1719,1721,1723,1725,1727,1729,1731,1734,1737,1740,1742,1745],{"class":235,"line":1713},40,[233,1715,832],{"class":601},[233,1717,1718],{"class":269},"(report[[",[233,1720,1327],{"class":243},[233,1722,453],{"class":269},[233,1724,1333],{"class":243},[233,1726,453],{"class":269},[233,1728,1339],{"class":243},[233,1730,453],{"class":269},[233,1732,1733],{"class":243},"\"linked_file\"",[233,1735,1736],{"class":269},"]].to_string(",[233,1738,1739],{"class":414},"index",[233,1741,370],{"class":265},[233,1743,1744],{"class":601},"False",[233,1746,431],{"class":269},[28,1748,37,1754,37,1757,37,1760,37,1765,37,1773,37,1777,37,1783,37,1787,37,1791,37,1795,37,1799,37,1803,37,1807,37,1811,37,1815,37,1818,37,1822,37,1824,37,1827,37,1829,37,1832,37,1834,37,1837,37,1840,37,1844,37,1846,37,1850,37,1852,37,1855,37,1857,37,1861,37,1863,37,1866,37,1869,37,1873,37,1875,37,1877,37,1879,37,1882,37,1884,37,1886,37,1888,37,1890,37,1893,37,1896,37,1898,37,1900,37,1902,37,1905,37,1909,37,1913,37,1915,37,1918,37,1922,37,1924,37,1927,37,1929,37,1932,37,1934,37,1936,37,1938,37,1940,37,1943,37,1947,37,1949,37,1952,37,1954,37,1957,37,1959,37,1961,37,1963,37,1966,37,1970,37,1972,37,1975,37,1977,37,1980,37,1982,37,1984,37,1986,37,1989,37,1993,37,1995,37,1997,37,1999,37,2002,37,2004,37,2006,37,2008,37,2010],{"viewBox":1749,"role":31,"ariaLabelledBy":1750,"xmlns":35,"style":1753},"123 0 513 306",[1751,1752],"links-report-t","links-report-d","width:100%;max-width:513px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[39,1755,1756],{"id":1751},"The audit report as a table",[43,1758,1759],{"id":1752},"The audit lists each finding with where it was found, its location, whether it is an external link or a broken reference, and the linked file. Two cell formulas and a defined name use Budget_2026.xlsx, the data validation list points at it too, and one cell, one defined name and one conditional format contain",[47,1761],{"x":1762,"y":50,"width":1763,"height":1764,"fill":53},"123","513","306",[47,1766],{"x":1767,"y":90,"width":1768,"height":1769,"rx":50,"fill":1770,"stroke":1771,"style":1772},"139.6","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[47,1774],{"x":1775,"y":90,"width":1776,"height":1769,"rx":50,"fill":1770,"stroke":1771,"style":1772},"171.6","137.0",[76,1778,1782],{"x":1779,"y":1780,"style":1781},"240.1","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[47,1784],{"x":1785,"y":90,"width":1786,"height":1769,"rx":50,"fill":1770,"stroke":1771,"style":1772},"308.5","101.2",[76,1788,1790],{"x":1789,"y":1780,"style":1781},"359.1","B",[47,1792],{"x":1793,"y":90,"width":1794,"height":1769,"rx":50,"fill":1770,"stroke":1771,"style":1772},"409.7","74.7",[76,1796,1798],{"x":1797,"y":1780,"style":1781},"447.0","C",[47,1800],{"x":1801,"y":90,"width":1802,"height":1769,"rx":50,"fill":1770,"stroke":1771,"style":1772},"484.4","136.1",[76,1804,1806],{"x":1805,"y":1780,"style":1781},"552.4","D",[47,1808],{"x":1767,"y":1809,"width":1768,"height":1810,"rx":50,"fill":1770,"stroke":1771,"style":1772},"40.0","28.0",[76,1812,824],{"x":1813,"y":1814,"style":1781},"155.6","59.0",[47,1816],{"x":1775,"y":1809,"width":1776,"height":1810,"rx":50,"fill":61,"stroke":62,"style":1817},"stroke-width:1.5px",[76,1819,1821],{"x":1779,"y":1814,"style":1820},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","where",[47,1823],{"x":1785,"y":1809,"width":1786,"height":1810,"rx":50,"fill":61,"stroke":62,"style":1817},[76,1825,1826],{"x":1789,"y":1814,"style":1820},"location",[47,1828],{"x":1793,"y":1809,"width":1794,"height":1810,"rx":50,"fill":61,"stroke":62,"style":1817},[76,1830,1831],{"x":1797,"y":1814,"style":1820},"kind",[47,1833],{"x":1801,"y":1809,"width":1802,"height":1810,"rx":50,"fill":61,"stroke":62,"style":1817},[76,1835,1836],{"x":1805,"y":1814,"style":1820},"linked file",[47,1838],{"x":1767,"y":1839,"width":1768,"height":1810,"rx":50,"fill":1770,"stroke":1771,"style":1772},"68.0",[76,1841,1843],{"x":1813,"y":1842,"style":1781},"87.0","2",[47,1845],{"x":1775,"y":1839,"width":1776,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,1847,1849],{"x":1779,"y":1842,"style":1848},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","cell",[47,1851],{"x":1785,"y":1839,"width":1786,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,1853,1854],{"x":1789,"y":1842,"style":1848},"Report!B1",[47,1856],{"x":1793,"y":1839,"width":1794,"height":1810,"rx":50,"fill":1770,"stroke":1771,"style":1817},[76,1858,1860],{"x":1797,"y":1842,"style":1859},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","external",[47,1862],{"x":1801,"y":1839,"width":1802,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,1864,1865],{"x":1805,"y":1842,"style":1848},"Budget_2026.xlsx",[47,1867],{"x":1767,"y":1868,"width":1768,"height":1810,"rx":50,"fill":1770,"stroke":1771,"style":1772},"96.0",[76,1870,1872],{"x":1813,"y":1871,"style":1781},"115.0","3",[47,1874],{"x":1775,"y":1868,"width":1776,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,1876,1849],{"x":1779,"y":1871,"style":1848},[47,1878],{"x":1785,"y":1868,"width":1786,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,1880,1881],{"x":1789,"y":1871,"style":1848},"Report!B2",[47,1883],{"x":1793,"y":1868,"width":1794,"height":1810,"rx":50,"fill":1770,"stroke":1771,"style":1817},[76,1885,1860],{"x":1797,"y":1871,"style":1859},[47,1887],{"x":1801,"y":1868,"width":1802,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,1889,1865],{"x":1805,"y":1871,"style":1848},[47,1891],{"x":1767,"y":1892,"width":1768,"height":1810,"rx":50,"fill":1770,"stroke":1771,"style":1772},"124.0",[76,1894,49],{"x":1813,"y":1895,"style":1781},"143.0",[47,1897],{"x":1775,"y":1892,"width":1776,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,1899,1849],{"x":1779,"y":1895,"style":1848},[47,1901],{"x":1785,"y":1892,"width":1786,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,1903,1904],{"x":1789,"y":1895,"style":1848},"Report!B3",[47,1906],{"x":1793,"y":1892,"width":1794,"height":1810,"rx":50,"fill":1907,"stroke":1908,"style":1817},"#fee8f2","var(--accent,#f43f8f)",[76,1910,1912],{"x":1797,"y":1895,"style":1911},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","broken",[47,1914],{"x":1801,"y":1892,"width":1802,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[47,1916],{"x":1767,"y":1917,"width":1768,"height":1810,"rx":50,"fill":1770,"stroke":1771,"style":1772},"152.0",[76,1919,1921],{"x":1813,"y":1920,"style":1781},"171.0","5",[47,1923],{"x":1775,"y":1917,"width":1776,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,1925,1926],{"x":1779,"y":1920,"style":1848},"data validation",[47,1928],{"x":1785,"y":1917,"width":1786,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,1930,1931],{"x":1789,"y":1920,"style":1848},"Report!C1",[47,1933],{"x":1793,"y":1917,"width":1794,"height":1810,"rx":50,"fill":1770,"stroke":1771,"style":1817},[76,1935,1860],{"x":1797,"y":1920,"style":1859},[47,1937],{"x":1801,"y":1917,"width":1802,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,1939,1865],{"x":1805,"y":1920,"style":1848},[47,1941],{"x":1767,"y":1942,"width":1768,"height":1810,"rx":50,"fill":1770,"stroke":1771,"style":1772},"180.0",[76,1944,1946],{"x":1813,"y":1945,"style":1781},"199.0","6",[47,1948],{"x":1775,"y":1942,"width":1776,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,1950,1951],{"x":1779,"y":1945,"style":1848},"conditional format",[47,1953],{"x":1785,"y":1942,"width":1786,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,1955,1956],{"x":1789,"y":1945,"style":1848},"Report!B4",[47,1958],{"x":1793,"y":1942,"width":1794,"height":1810,"rx":50,"fill":1907,"stroke":1908,"style":1817},[76,1960,1912],{"x":1797,"y":1945,"style":1911},[47,1962],{"x":1801,"y":1942,"width":1802,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[47,1964],{"x":1767,"y":1965,"width":1768,"height":1810,"rx":50,"fill":1770,"stroke":1771,"style":1772},"208.0",[76,1967,1969],{"x":1813,"y":1968,"style":1781},"227.0","7",[47,1971],{"x":1775,"y":1965,"width":1776,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,1973,1974],{"x":1779,"y":1968,"style":1848},"defined name",[47,1976],{"x":1785,"y":1965,"width":1786,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,1978,1979],{"x":1789,"y":1968,"style":1848},"OldRates",[47,1981],{"x":1793,"y":1965,"width":1794,"height":1810,"rx":50,"fill":1907,"stroke":1908,"style":1817},[76,1983,1912],{"x":1797,"y":1968,"style":1911},[47,1985],{"x":1801,"y":1965,"width":1802,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[47,1987],{"x":1767,"y":1988,"width":1768,"height":1810,"rx":50,"fill":1770,"stroke":1771,"style":1772},"236.0",[76,1990,1992],{"x":1813,"y":1991,"style":1781},"255.0","8",[47,1994],{"x":1775,"y":1988,"width":1776,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,1996,1974],{"x":1779,"y":1991,"style":1848},[47,1998],{"x":1785,"y":1988,"width":1786,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,2000,2001],{"x":1789,"y":1991,"style":1848},"BudgetLines",[47,2003],{"x":1793,"y":1988,"width":1794,"height":1810,"rx":50,"fill":1770,"stroke":1771,"style":1817},[76,2005,1860],{"x":1797,"y":1991,"style":1859},[47,2007],{"x":1801,"y":1988,"width":1802,"height":1810,"rx":50,"fill":53,"stroke":1771,"style":1772},[76,2009,1865],{"x":1805,"y":1991,"style":1848},[76,2011,2013],{"x":214,"y":2012,"style":216},"286.0","seven findings — only three of them are in ordinary cells",[10,2015,2016],{},"The report makes the point of the exercise: of seven findings, three are in ordinary cells. Somebody searching the sheet with Find would never see the validation rule, the formatting rule or the two named ranges, and those are exactly the leftovers that keep Excel's link warning alive after the \"last\" link has been removed.",[219,2018,2020],{"id":2019},"step-3-write-the-audit-where-people-will-see-it","Step 3 — Write the audit where people will see it",[10,2022,2023],{},"An audit is only useful if the owner of the workbook reads it. Write the findings to a new workbook alongside the original, sorted with broken references first, since those are already producing wrong numbers:",[224,2025,2027],{"className":256,"code":2026,"language":258,"meta":229,"style":229},"report[\"kind\"] = pd.Categorical(report[\"kind\"], [\"broken\", \"external\"], ordered=True)\nreport = report.sort_values([\"kind\", \"where\", \"location\"])\nwith pd.ExcelWriter(\"report_audit.xlsx\", engine=\"openpyxl\") as writer:\n    report.to_excel(writer, sheet_name=\"Findings\", index=False)\n    writer.sheets[\"Findings\"].freeze_panes = \"A2\"\nprint(len(report), \"findings written\")    # 7 findings written\n",[14,2028,2029,2065,2087,2115,2138,2153],{"__ignoreMap":229},[233,2030,2031,2034,2036,2038,2040,2043,2045,2048,2050,2052,2054,2056,2059,2061,2063],{"class":235,"line":236},[233,2032,2033],{"class":269},"report[",[233,2035,1339],{"class":243},[233,2037,516],{"class":269},[233,2039,370],{"class":265},[233,2041,2042],{"class":269}," pd.Categorical(report[",[233,2044,1339],{"class":243},[233,2046,2047],{"class":269},"], [",[233,2049,1205],{"class":243},[233,2051,453],{"class":269},[233,2053,1183],{"class":243},[233,2055,736],{"class":269},[233,2057,2058],{"class":414},"ordered",[233,2060,370],{"class":265},[233,2062,752],{"class":601},[233,2064,487],{"class":269},[233,2066,2067,2069,2071,2074,2076,2078,2080,2082,2084],{"class":235,"line":279},[233,2068,1701],{"class":269},[233,2070,370],{"class":265},[233,2072,2073],{"class":269}," report.sort_values([",[233,2075,1339],{"class":243},[233,2077,453],{"class":269},[233,2079,1327],{"class":243},[233,2081,453],{"class":269},[233,2083,1333],{"class":243},[233,2085,2086],{"class":269},"])\n",[233,2088,2089,2092,2095,2098,2100,2103,2105,2108,2110,2112],{"class":235,"line":292},[233,2090,2091],{"class":265},"with",[233,2093,2094],{"class":269}," pd.ExcelWriter(",[233,2096,2097],{"class":243},"\"report_audit.xlsx\"",[233,2099,453],{"class":269},[233,2101,2102],{"class":414},"engine",[233,2104,370],{"class":265},[233,2106,2107],{"class":243},"\"openpyxl\"",[233,2109,1383],{"class":269},[233,2111,918],{"class":265},[233,2113,2114],{"class":269}," writer:\n",[233,2116,2117,2120,2123,2125,2128,2130,2132,2134,2136],{"class":235,"line":305},[233,2118,2119],{"class":269},"    report.to_excel(writer, ",[233,2121,2122],{"class":414},"sheet_name",[233,2124,370],{"class":265},[233,2126,2127],{"class":243},"\"Findings\"",[233,2129,453],{"class":269},[233,2131,1739],{"class":414},[233,2133,370],{"class":265},[233,2135,1744],{"class":601},[233,2137,487],{"class":269},[233,2139,2140,2143,2145,2148,2150],{"class":235,"line":318},[233,2141,2142],{"class":269},"    writer.sheets[",[233,2144,2127],{"class":243},[233,2146,2147],{"class":269},"].freeze_panes ",[233,2149,370],{"class":265},[233,2151,2152],{"class":243}," \"A2\"\n",[233,2154,2155,2157,2159,2162,2165,2168,2171],{"class":235,"line":331},[233,2156,832],{"class":601},[233,2158,1061],{"class":269},[233,2160,2161],{"class":601},"len",[233,2163,2164],{"class":269},"(report), ",[233,2166,2167],{"class":243},"\"findings written\"",[233,2169,2170],{"class":269},")    ",[233,2172,2173],{"class":838},"# 7 findings written\n",[219,2175,2177],{"id":2176},"step-4-repoint-a-link-after-a-file-moves","Step 4 — Repoint a link after a file moves",[10,2179,2180],{},"When the linked workbook has simply moved or been renamed, the fix is to change the path rather than the formulas. Every formula refers to the link by its number, so updating the one relationship target repairs all of them at once:",[224,2182,2184],{"className":256,"code":2183,"language":258,"meta":229,"style":229},"wb = load_workbook(\"report.xlsx\")\nwb._external_links[0].file_link.Target = \"Budget_2026_final.xlsx\"\nwb.save(\"report_repointed.xlsx\")\nprint(link_targets(\"report_repointed.xlsx\"))   # {1: 'Budget_2026_final.xlsx'}\n",[14,2185,2186,2198,2213,2222],{"__ignoreMap":229},[233,2187,2188,2190,2192,2194,2196],{"class":235,"line":236},[233,2189,367],{"class":269},[233,2191,370],{"class":265},[233,2193,787],{"class":269},[233,2195,764],{"class":243},[233,2197,487],{"class":269},[233,2199,2200,2203,2205,2208,2210],{"class":235,"line":279},[233,2201,2202],{"class":269},"wb._external_links[",[233,2204,50],{"class":601},[233,2206,2207],{"class":269},"].file_link.Target ",[233,2209,370],{"class":265},[233,2211,2212],{"class":243}," \"Budget_2026_final.xlsx\"\n",[233,2214,2215,2217,2220],{"class":235,"line":292},[233,2216,761],{"class":269},[233,2218,2219],{"class":243},"\"report_repointed.xlsx\"",[233,2221,487],{"class":269},[233,2223,2224,2226,2228,2230,2232],{"class":235,"line":305},[233,2225,832],{"class":601},[233,2227,1078],{"class":269},[233,2229,2219],{"class":243},[233,2231,1083],{"class":269},[233,2233,2234],{"class":838},"# {1: 'Budget_2026_final.xlsx'}\n",[10,2236,2237,2238,2241],{},"Relative paths like ",[14,2239,2240],{},"Budget_2026_final.xlsx"," resolve against the folder of the workbook that holds the link, which is usually what you want when both files travel together. Use an absolute or UNC path only when the source lives in a fixed shared location.",[28,2243,37,2248,37,2251,37,2254,37,2257,37,2262,37,2267,37,2270,37,2274,37,2278,37,2281,37,2284,37,2292,37,2297,37,2301,37,2306,37,2311,37,2315,37,2317,37,2320,37,2324,37,2327,37,2330,37,2336,37,2340,37,2343,37,2345,37,2348,37,2352,37,2354,37,2357,37,2364,37,2368],{"viewBox":2244,"role":31,"ariaLabelledBy":2245,"xmlns":35,"style":36},"4 0 752 272",[2246,2247],"links-fix-t","links-fix-d",[39,2249,2250],{"id":2246},"Choosing a fix for each finding in the audit",[43,2252,2253],{"id":2247},"If an external link points at a file that has moved, repoint the link target. If the report must stand alone, break the link by replacing formulas with their cached values. If a reference is broken with",[47,2255],{"x":49,"y":50,"width":51,"height":2256,"fill":53},"272",[47,2258],{"x":2259,"y":90,"width":2260,"height":2261,"rx":60,"fill":61,"stroke":62,"style":63},"240.0","280.0","75.5",[65,2263,2265],{"transform":2264},"translate(368.0,29.0) scale(1.000)",[69,2266],{"d":71,"fill":72,"stroke":73,"style":74},[76,2268,2269],{"x":214,"y":2261,"style":80},"What kind of finding is it?",[235,2271],{"x1":214,"y1":2272,"x2":214,"y2":2273,"stroke":62,"style":63},"91.5","111.5",[235,2275],{"x1":2276,"y1":2273,"x2":2277,"y2":2273,"stroke":62,"style":63},"134.7","625.3",[235,2279],{"x1":2276,"y1":2273,"x2":2276,"y2":2280,"stroke":62,"style":63},"157.5",[117,2282],{"points":2283,"fill":120},"134.7,161.5 129.7,152.5 139.7,152.5",[47,2285],{"x":2286,"y":2287,"width":2288,"height":2289,"rx":2290,"fill":53,"stroke":1771,"style":2291},"80.7","121.5","107.9","21.0","10","stroke-width:1.2px",[76,2293,2296],{"x":2276,"y":2294,"style":2295},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","link, file moved",[47,2298],{"x":89,"y":2299,"width":2300,"height":59,"rx":60,"fill":93,"stroke":94,"style":63},"163.5","229.3",[65,2302,2304],{"transform":2303},"translate(122.7,176.5) scale(1.000)",[69,2305],{"d":129,"fill":72,"stroke":101,"style":74},[76,2307,2310],{"x":2276,"y":2308,"style":2309},"223.0","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Repoint",[76,2312,2314],{"x":2276,"y":2313,"style":85},"242.5","change one Target path",[235,2316],{"x1":214,"y1":2273,"x2":214,"y2":2280,"stroke":62,"style":63},[117,2318],{"points":2319,"fill":120},"380.0,161.5 375.0,152.5 385.0,152.5",[47,2321],{"x":2322,"y":2287,"width":2323,"height":2289,"rx":2290,"fill":53,"stroke":1771,"style":2291},"306.4","147.2",[76,2325,2326],{"x":214,"y":2294,"style":2295},"link, must stand alone",[47,2328],{"x":2329,"y":2299,"width":2300,"height":59,"rx":60,"fill":189,"stroke":190,"style":63},"265.3",[65,2331,2333],{"transform":2332},"translate(368.0,176.5) scale(1.000)",[69,2334],{"d":2335,"fill":72,"stroke":197,"style":74},"M6 3h9l4 4v14H6z M15 3v4h4",[76,2337,2339],{"x":214,"y":2308,"style":2338},"font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Break the link",[76,2341,2342],{"x":214,"y":2313,"style":85},"formulas become values",[235,2344],{"x1":2277,"y1":2273,"x2":2277,"y2":2280,"stroke":62,"style":63},[117,2346],{"points":2347,"fill":120},"625.3,161.5 620.3,152.5 630.3,152.5",[47,2349],{"x":2350,"y":2287,"width":2351,"height":2289,"rx":2290,"fill":53,"stroke":1771,"style":2291},"599.2","52.3",[76,2353,16],{"x":2277,"y":2294,"style":2295},[47,2355],{"x":2356,"y":2299,"width":2300,"height":59,"rx":60,"fill":1907,"stroke":1908,"style":63},"510.7",[65,2358,2360],{"transform":2359},"translate(613.3,176.5) scale(1.000)",[69,2361],{"d":2362,"fill":72,"stroke":2363,"style":74},"M4 7h16 M10 11v6 M14 11v6 M6 7l1 13h10l1-13 M9 7V4h6v3","var(--accent-ink,#be185d)",[76,2365,2367],{"x":2277,"y":2308,"style":2366},"font-size:13.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Rebuild or delete",[76,2369,2370],{"x":2277,"y":2313,"style":85},"the target no longer exists",[10,2372,2373,2374,2376],{},"A ",[14,2375,16],{}," cannot be repointed automatically: the reference that used to be there was destroyed when its sheet or range was deleted, and nothing in the file records what it pointed at. Those findings need a person — the audit's job is to make sure somebody knows they exist.",[219,2378,2380],{"id":2379},"step-5-break-links-by-converting-to-values","Step 5 — Break links by converting to values",[10,2382,2383,2384,2387],{},"When the report should stand alone — before emailing it outside the organisation, for example — replace every formula that uses an external link with the value Excel last calculated for it. Load the workbook twice: once for formulas, once with ",[14,2385,2386],{},"data_only=True"," for the cached values:",[224,2389,2391],{"className":256,"code":2390,"language":258,"meta":229,"style":229},"def break_links(path: str, out: str) -> int:\n    formulas = load_workbook(path)\n    values = load_workbook(path, data_only=True)\n    changed = 0\n    for ws in formulas.worksheets:\n        cached = values[ws.title]\n        for row in ws.iter_rows():\n            for cell in row:\n                if isinstance(cell.value, str) and EXTERNAL.search(cell.value):\n                    cell.value = cached[cell.coordinate].value\n                    changed += 1\n    formulas._external_links = []\n    formulas.save(out)\n    return changed\n\nprint(break_links(\"report.xlsx\", \"report_standalone.xlsx\"))   # 2\n",[14,2392,2393,2417,2426,2445,2455,2466,2476,2486,2496,2515,2525,2536,2545,2550,2557,2561],{"__ignoreMap":229},[233,2394,2395,2397,2400,2402,2404,2407,2409,2412,2414],{"class":235,"line":236},[233,2396,874],{"class":265},[233,2398,2399],{"class":877}," break_links",[233,2401,881],{"class":269},[233,2403,884],{"class":601},[233,2405,2406],{"class":269},", out: ",[233,2408,884],{"class":601},[233,2410,2411],{"class":269},") -> ",[233,2413,890],{"class":601},[233,2415,2416],{"class":269},":\n",[233,2418,2419,2422,2424],{"class":235,"line":279},[233,2420,2421],{"class":269},"    formulas ",[233,2423,370],{"class":265},[233,2425,1246],{"class":269},[233,2427,2428,2431,2433,2436,2439,2441,2443],{"class":235,"line":292},[233,2429,2430],{"class":269},"    values ",[233,2432,370],{"class":265},[233,2434,2435],{"class":269}," load_workbook(path, ",[233,2437,2438],{"class":414},"data_only",[233,2440,370],{"class":265},[233,2442,752],{"class":601},[233,2444,487],{"class":269},[233,2446,2447,2450,2452],{"class":235,"line":305},[233,2448,2449],{"class":269},"    changed ",[233,2451,370],{"class":265},[233,2453,2454],{"class":601}," 0\n",[233,2456,2457,2459,2461,2463],{"class":235,"line":318},[233,2458,1401],{"class":265},[233,2460,1404],{"class":269},[233,2462,810],{"class":265},[233,2464,2465],{"class":269}," formulas.worksheets:\n",[233,2467,2468,2471,2473],{"class":235,"line":331},[233,2469,2470],{"class":269},"        cached ",[233,2472,370],{"class":265},[233,2474,2475],{"class":269}," values[ws.title]\n",[233,2477,2478,2480,2482,2484],{"class":235,"line":344},[233,2479,926],{"class":265},[233,2481,1416],{"class":269},[233,2483,810],{"class":265},[233,2485,1421],{"class":269},[233,2487,2488,2490,2492,2494],{"class":235,"line":357},[233,2489,1426],{"class":265},[233,2491,1429],{"class":269},[233,2493,810],{"class":265},[233,2495,1434],{"class":269},[233,2497,2498,2500,2502,2504,2506,2508,2510,2512],{"class":235,"line":364},[233,2499,1439],{"class":265},[233,2501,1442],{"class":601},[233,2503,1445],{"class":269},[233,2505,884],{"class":601},[233,2507,1383],{"class":269},[233,2509,1452],{"class":265},[233,2511,1172],{"class":601},[233,2513,2514],{"class":269},".search(cell.value):\n",[233,2516,2517,2520,2522],{"class":235,"line":376},[233,2518,2519],{"class":269},"                    cell.value ",[233,2521,370],{"class":265},[233,2523,2524],{"class":269}," cached[cell.coordinate].value\n",[233,2526,2527,2530,2533],{"class":235,"line":387},[233,2528,2529],{"class":269},"                    changed ",[233,2531,2532],{"class":265},"+=",[233,2534,2535],{"class":601}," 1\n",[233,2537,2538,2541,2543],{"class":235,"line":398},[233,2539,2540],{"class":269},"    formulas._external_links ",[233,2542,370],{"class":265},[233,2544,1164],{"class":269},[233,2546,2547],{"class":235,"line":403},[233,2548,2549],{"class":269},"    formulas.save(out)\n",[233,2551,2552,2554],{"class":235,"line":434},[233,2553,1055],{"class":265},[233,2555,2556],{"class":269}," changed\n",[233,2558,2559],{"class":235,"line":467},[233,2560,361],{"emptyLinePlaceholder":360},[233,2562,2563,2565,2568,2570,2572,2575,2577],{"class":235,"line":490},[233,2564,832],{"class":601},[233,2566,2567],{"class":269},"(break_links(",[233,2569,764],{"class":243},[233,2571,453],{"class":269},[233,2573,2574],{"class":243},"\"report_standalone.xlsx\"",[233,2576,1083],{"class":269},[233,2578,2579],{"class":838},"# 2\n",[10,2581,2582,2583,2587],{},"A file written by Python has no cached values, so on this example the two broken-out cells become empty; on a workbook last saved by Excel they get the numbers Excel displayed. Clear or fix the link-carrying defined names, validation rules and formatting rules as well — the audit from Step 2 lists them — or Excel will keep asking about links. The general technique is covered in ",[22,2584,2586],{"href":2585},"\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",".",[219,2589,2591],{"id":2590},"common-pitfalls","Common pitfalls",[2593,2594,2595,2603,2619,2632],"ul",{},[2596,2597,2598,2602],"li",{},[2599,2600,2601],"strong",{},"Checking cells only."," Named ranges, validation and conditional formats are where most orphaned links survive.",[2596,2604,2605,2610,2611,2614,2615,2618],{},[2599,2606,2607,2608,2587],{},"Case of ",[14,2609,16],{}," Files saved by some applications store it as ",[14,2612,2613],{},"#ref!",". Compare case-insensitively, as ",[14,2616,2617],{},"classify"," does.",[2596,2620,2621,2624,2625,2628,2629,2631],{},[2599,2622,2623],{},"Chart series."," Charts can reference other workbooks too. openpyxl does not expose chart references reliably; for a complete audit, search the ",[14,2626,2627],{},"xl\u002Fcharts\u002F"," XML in the zip for ",[14,2630,730],{}," followed by a number.",[2596,2633,2634,2637,2638,2641],{},[2599,2635,2636],{},"Hidden defined names."," Some add-ins create hidden names that hold links. ",[14,2639,2640],{},"wb.defined_names"," includes them; Excel's Name Manager does not show them.",[219,2643,2645],{"id":2644},"performance-and-scale-notes","Performance and scale notes",[10,2647,2648,2649,2652,2653,2587],{},"The scan visits every cell once and is fast for typical reports — a few seconds for a workbook of a few hundred thousand formulas. For a folder of files, run it in a loop and concatenate the reports, adding the file name as a column, to produce one inventory of every link in a shared drive; the zip-level ",[14,2650,2651],{},"link_targets"," function alone is enough for a quick first pass, because it does not parse any cells. The folder loop pattern is shown in ",[22,2654,2656],{"href":2655},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fprocess-multiple-excel-files-in-parallel-with-python\u002F","Process Multiple Excel Files in Parallel with Python",[219,2658,2660],{"id":2659},"conclusion","Conclusion",[10,2662,2663,2664,2667,2668,2670],{},"External links are numbered parts of the package that formulas reference as ",[14,2665,2666],{},"[1]Sheet!A1","; broken references show up as ",[14,2669,16],{},". Both can hide in cells, defined names, data validation and conditional formatting, so audit all four, map link numbers to file paths, and write the findings where the owner will see them. Repoint a moved source by changing one relationship target, and break links for standalone copies by replacing linked formulas with their cached values and clearing the other places links live.",[219,2672,2674],{"id":2673},"frequently-asked-questions","Frequently asked questions",[10,2676,2677,2680,2681,2683],{},[2599,2678,2679],{},"How are external links stored in an xlsx file?","\nEach linked workbook gets an externalLink part inside the package, and formulas refer to it by number — ",[233,2682,824],{},"Data!B2 means cell B2 on sheet Data of the first linked workbook. The file path lives in the relationship file for that part.",[10,2685,2686,2689],{},[2599,2687,2688],{},"Why does Excel say the workbook contains links when I cannot find any?","\nLinks often hide outside cell formulas — in defined names, data validation lists, conditional formatting rules, chart series or leftover named ranges from copied sheets. Scan all of those, not just the cells.",[10,2691,2692,2695],{},[2599,2693,2694],{},"Can openpyxl update the path of an external link?","\nYes. Each entry in the workbook's external links list has a file_link relationship whose Target is the path; change it and save. It is an undocumented attribute, so test after upgrading openpyxl.",[10,2697,2698,2701],{},[2599,2699,2700],{},"What does it mean to break a link?","\nReplacing each formula that uses the link with the value it last calculated. The workbook then no longer depends on the other file, at the cost of no longer updating.",[219,2703,2705],{"id":2704},"related","Related",[2593,2707,2708,2714,2721,2728],{},[2596,2709,2710,2711,2713],{},"Up one level: ",[22,2712,25],{"href":24}," — formulas from Python.",[2596,2715,2716,2720],{},[22,2717,2719],{"href":2718},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fcreate-named-range-in-excel-with-openpyxl\u002F","Create a Named Range in Excel with openpyxl"," — defined names in depth.",[2596,2722,2723,2727],{},[22,2724,2726],{"href":2725},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002F","Troubleshooting Common Python Excel Errors"," — other ways workbooks break.",[2596,2729,2730,2734],{},[22,2731,2733],{"href":2732},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fwhat-openpyxl-loses-when-saving-an-existing-workbook\u002F","What openpyxl Loses When You Save an Existing Workbook"," — check before saving an audited file.",[2736,2737,2738],"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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}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 .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}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}",{"title":229,"searchDepth":279,"depth":279,"links":2740},[2741,2742,2743,2744,2745,2746,2747,2748,2749,2750,2751],{"id":221,"depth":279,"text":222},{"id":769,"depth":279,"text":770},{"id":1088,"depth":279,"text":1089},{"id":2019,"depth":279,"text":2020},{"id":2176,"depth":279,"text":2177},{"id":2379,"depth":279,"text":2380},{"id":2590,"depth":279,"text":2591},{"id":2644,"depth":279,"text":2645},{"id":2659,"depth":279,"text":2660},{"id":2673,"depth":279,"text":2674},{"id":2704,"depth":279,"text":2705},"2026-09-23","Audit a workbook for links to other files and for #REF! errors — in cell formulas, defined names, data validation and conditional formatting — with openpyxl and the zip file itself, then report them or replace them with values.","md",[2756,2758,2760,2762],{"q":2679,"a":2757},"Each linked workbook gets an externalLink part inside the package, and formulas refer to it by number — [1]Data!B2 means cell B2 on sheet Data of the first linked workbook. The file path lives in the relationship file for that part.",{"q":2688,"a":2759},"Links often hide outside cell formulas — in defined names, data validation lists, conditional formatting rules, chart series or leftover named ranges from copied sheets. Scan all of those, not just the cells.",{"q":2694,"a":2761},"Yes. Each entry in the workbook's external links list has a file_link relationship whose Target is the path; change it and save. It is an undocumented attribute, so test after upgrading openpyxl.",{"q":2700,"a":2763},"Replacing each formula that uses the link with the value it last calculated. The workbook then no longer depends on the other file, at the cost of no longer updating.","find external links and broken references in a workbook with python",null,{"breadcrumb":2767},[2768,2771,2774],{"name":2769,"item":2770},"Home","\u002F",{"name":2772,"item":2773},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":25,"item":24},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-external-links-and-broken-references-in-a-workbook",{"title":2777,"description":2778},"Find External Links and #REF! Errors in Excel Files with Python","Scan Excel workbooks for external links ([1]Sheet!A1 references), broken #REF! formulas, bad named ranges and validation rules with openpyxl, write an audit report, and break links by converting to values.","find-external-links-and-broken-references-in-a-workbook","getting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-external-links-and-broken-references-in-a-workbook\u002Findex",[2782,2783,2784],"excel-formulas","troubleshooting","openpyxl","how-to","5q3XOiGsOOGpMsyfIR_OrJjxFz66oyfOvzPNyuxSb2Q",[2788,2791],{"title":2719,"path":2789,"stem":2790,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fcreate-named-range-in-excel-with-openpyxl","getting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fcreate-named-range-in-excel-with-openpyxl\u002Findex",{"title":2792,"path":2793,"stem":2794,"children":-1},"Find #REF!, #DIV\u002F0! and Other Formula Errors in a Workbook","\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-formula-errors-in-a-workbook-with-python","getting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-formula-errors-in-a-workbook-with-python\u002Findex",[2796,2798],{"path":2797,"title":2772},"\u002Fgetting-started-with-python-excel-automation",{"path":2799,"title":25},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python",[],[2802,2805,2809,2812,2815,2818],{"path":2803,"title":2804,"kind":2785},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fstore-formula-results-with-xlsxwriter","Store Formula Results with xlsxwriter So Every Viewer Shows Values",{"path":2806,"title":2807,"kind":2808},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-cells-with-formula-errors-using-conditional-formatting","Highlight Cells with Formula Errors Using Conditional Formatting","fix",{"path":2810,"title":2811,"kind":2785},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgoal-seek-in-python-instead-of-excel","Goal Seek in Python Instead of Excel",{"path":2813,"title":2814,"kind":2785},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcheck-that-totals-in-a-report-add-up","Check That Totals in a Report Add Up",{"path":2816,"title":2817,"kind":2785},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcall-excel-worksheet-functions-through-microsoft-graph","Call Excel Worksheet Functions Through Microsoft Graph",{"path":2819,"title":2820,"kind":2785},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frecalculate-excel-online-workbooks-with-graph-sessions","Recalculate Excel Online Workbooks with Graph Sessions",1790972075649]