[{"data":1,"prerenderedAt":3212},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-only-selected-sheets-of-a-workbook-to-pdf":3,"surround:\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-only-selected-sheets-of-a-workbook-to-pdf":3204},{"id":4,"title":5,"body":6,"dateModified":3181,"datePublished":3181,"description":3182,"extension":3183,"faq":3184,"meta":3195,"navigation":261,"path":3196,"seo":3197,"slug":3200,"stem":3201,"type":3202,"__hash__":3203},"docs\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-only-selected-sheets-of-a-workbook-to-pdf\u002Findex.md","Convert Only Selected Sheets of a Workbook to PDF",{"type":7,"value":8,"toc":3168},"minimark",[9,19,190,195,223,230,233,646,650,653,1050,1073,1079,1083,1086,1260,1267,1271,1274,1352,1355,1476,1483,1487,1490,1964,1971,1974,2156,2160,2270,2276,2857,2869,2873,3003,3007,3010,3028,3042,3049,3077,3080,3084,3087,3091,3097,3103,3109,3115,3124,3128,3164],[10,11,12,13,18],"p",{},"A working workbook has more in it than the reader needs: a summary, a detail sheet, a lookup table, a raw extract nobody should see. Converting the whole thing to PDF publishes all of it, at forty pages instead of three. LibreOffice has no straightforward \"just this sheet\" flag, so the reliable approach is to shape a copy of the workbook first and convert that. This guide covers the trimmed-copy method, the hidden-sheet shortcut, and the chart-preservation trap that decides which one you can use. It extends ",[14,15,17],"a",{"href":16},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002F","Exporting Excel Reports to PDF",".",[20,21,30,31,30,35,30,39,30,46,30,53,30,62,30,67,30,70,30,74,30,78,30,83,30,86,30,90,30,93,30,97,30,102,30,109,30,114,30,120,30,127,30,133,30,139,30,143,30,146,30,149,30,152,30,156,30,160,30,164,30,168,30,175,30,181,30,186],"svg",{"viewBox":22,"role":23,"ariaLabel":24,"ariaLabelledBy":25,"xmlns":28,"style":29},"0 0 800 254","img","A five-sheet workbook trimmed to two sheets in a temporary copy, which is then converted to a short PDF while the original workbook is left untouched.",[26,27],"trim-t","trim-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:800px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[32,33,34],"title",{"id":26},"Trim a copy, convert the copy, keep the original",[36,37,38],"desc",{"id":27},"The source workbook holds five sheets: Summary, Regional, Detail, Lookups and an underscore-prefixed raw extract. A temporary copy is made and the three sheets not wanted in the document are deleted from it, leaving Summary and Regional in that order. LibreOffice converts the copy to a three-page PDF. The original workbook is never modified, and the temporary copy is discarded afterwards.",[40,41],"rect",{"x":42,"y":42,"width":43,"height":44,"fill":45},"0","800","254","#ffffff",[47,48,52],"text",{"x":49,"y":50,"style":51},"110","28","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","source workbook",[40,54],{"x":55,"y":56,"width":57,"height":50,"rx":58,"fill":59,"stroke":60,"style":61},"20","42","180","5","#d9f4f1","var(--teal,#0f9488)","stroke-width:2px",[47,63,66],{"x":49,"y":64,"style":65},"61","font-size:10.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Summary",[40,68],{"x":55,"y":69,"width":57,"height":50,"rx":58,"fill":59,"stroke":60,"style":61},"76",[47,71,73],{"x":49,"y":72,"style":65},"95","Regional",[40,75],{"x":55,"y":49,"width":57,"height":50,"rx":58,"fill":76,"stroke":77},"#f0f2f5","var(--line,#cdd5e6)",[47,79,82],{"x":49,"y":80,"style":81},"129","font-size:10.5px;fill:var(--muted,#5b6780);text-anchor:middle","Detail",[40,84],{"x":55,"y":85,"width":57,"height":50,"rx":58,"fill":76,"stroke":77},"144",[47,87,89],{"x":49,"y":88,"style":81},"163","Lookups",[40,91],{"x":55,"y":92,"width":57,"height":50,"rx":58,"fill":76,"stroke":77},"178",[47,94,96],{"x":49,"y":95,"style":81},"197","_raw",[47,98,101],{"x":49,"y":99,"style":100},"230","font-size:10px;fill:var(--muted,#5b6780);text-anchor:middle","never modified",[103,104],"line",{"x1":105,"y1":106,"x2":107,"y2":106,"stroke":108,"style":61},"208","120","248","var(--brand,#5b5cf0)",[110,111],"polygon",{"points":112,"fill":113},"256,120 244,114 244,126","#5b5cf0",[47,115,119],{"x":116,"y":117,"style":118},"232","106","font-size:9.5px;fill:var(--muted,#5b6780);text-anchor:middle","copy",[40,121],{"x":122,"y":56,"width":99,"height":123,"rx":124,"fill":125,"stroke":126,"style":61},"264","164","12","#fdefd8","var(--gold,#b4740a)",[47,128,132],{"x":129,"y":130,"style":131},"379","68","font-size:11.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","temporary copy",[40,134],{"x":135,"y":136,"width":137,"height":138,"rx":58,"fill":45,"stroke":126},"288","82","182","26",[47,140,66],{"x":129,"y":141,"style":142},"100","font-size:10px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[40,144],{"x":135,"y":145,"width":137,"height":138,"rx":58,"fill":45,"stroke":126},"114",[47,147,73],{"x":129,"y":148,"style":142},"132",[47,150,151],{"x":129,"y":123,"style":100},"the other three deleted",[47,153,155],{"x":129,"y":154,"style":100},"188","order controlled here",[103,157],{"x1":158,"y1":106,"x2":159,"y2":106,"stroke":126,"style":61},"502","542",[110,161],{"points":162,"fill":163},"550,120 538,114 538,126","#b4740a",[47,165,167],{"x":166,"y":117,"style":118},"526","soffice",[40,169],{"x":170,"y":171,"width":172,"height":173,"rx":124,"fill":174,"stroke":108,"style":61},"558","62","226","124","#ebebfd",[47,176,180],{"x":177,"y":178,"style":179},"671","94","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","report.pdf",[47,182,185],{"x":177,"y":183,"style":184},"122","font-size:10.5px;fill:var(--text,#172033);text-anchor:middle","3 pages, not 40",[47,187,189],{"x":177,"y":188,"style":81},"148","no lookups, no raw extract",[191,192,194],"h2",{"id":193},"prerequisites","Prerequisites",[196,197,202],"pre",{"className":198,"code":199,"language":200,"meta":201,"style":201},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[203,204,205],"code",{"__ignoreMap":201},[206,207,209,213,217,220],"span",{"class":103,"line":208},1,[206,210,212],{"class":211},"sMTad","pip",[206,214,216],{"class":215},"srMev"," install",[206,218,219],{"class":215}," pandas",[206,221,222],{"class":215}," openpyxl\n",[10,224,225,226,18],{},"Plus LibreOffice installed, as in ",[14,227,229],{"href":228},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-excel-file-to-pdf-with-python\u002F","converting an Excel file to PDF",[10,231,232],{},"A workbook with more sheets than the reader needs:",[196,234,238],{"className":235,"code":236,"language":237,"meta":201,"style":201},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nsummary = pd.DataFrame({\"region\": [\"North\", \"South\"], \"revenue\": [5150.0, 4268.5]})\nregional = pd.DataFrame({\"branch\": [f\"Branch {i}\" for i in range(1, 21)],\n                         \"revenue\": [100.0 * i for i in range(1, 21)]})\ndetail = pd.DataFrame({\"order\": range(1, 501), \"amount\": range(1, 501)})\nlookups = pd.DataFrame({\"code\": [\"N\", \"S\"], \"region\": [\"North\", \"South\"]})\n\nwith pd.ExcelWriter(\"workbook.xlsx\", engine=\"xlsxwriter\") as writer:\n    summary.to_excel(writer, sheet_name=\"Summary\", index=False)\n    regional.to_excel(writer, sheet_name=\"Regional\", index=False)\n    detail.to_excel(writer, sheet_name=\"Detail\", index=False)\n    lookups.to_excel(writer, sheet_name=\"Lookups\", index=False)\n    detail.to_excel(writer, sheet_name=\"_raw\", index=False)\n","python",[203,239,240,256,263,310,370,406,455,492,497,528,555,578,601,624],{"__ignoreMap":201},[206,241,242,246,250,253],{"class":103,"line":208},[206,243,245],{"class":244},"s-kum","import",[206,247,249],{"class":248},"skGVy"," pandas ",[206,251,252],{"class":244},"as",[206,254,255],{"class":248}," pd\n",[206,257,259],{"class":103,"line":258},2,[206,260,262],{"emptyLinePlaceholder":261},true,"\n",[206,264,266,269,272,275,278,281,284,287,290,293,296,298,302,304,307],{"class":103,"line":265},3,[206,267,268],{"class":248},"summary ",[206,270,271],{"class":244},"=",[206,273,274],{"class":248}," pd.DataFrame({",[206,276,277],{"class":215},"\"region\"",[206,279,280],{"class":248},": [",[206,282,283],{"class":215},"\"North\"",[206,285,286],{"class":248},", ",[206,288,289],{"class":215},"\"South\"",[206,291,292],{"class":248},"], ",[206,294,295],{"class":215},"\"revenue\"",[206,297,280],{"class":248},[206,299,301],{"class":300},"sP0c6","5150.0",[206,303,286],{"class":248},[206,305,306],{"class":300},"4268.5",[206,308,309],{"class":248},"]})\n",[206,311,313,316,318,320,323,325,328,331,335,338,341,344,347,350,353,356,359,362,364,367],{"class":103,"line":312},4,[206,314,315],{"class":248},"regional ",[206,317,271],{"class":244},[206,319,274],{"class":248},[206,321,322],{"class":215},"\"branch\"",[206,324,280],{"class":248},[206,326,327],{"class":244},"f",[206,329,330],{"class":215},"\"Branch ",[206,332,334],{"class":333},"sSjpA","{",[206,336,337],{"class":248},"i",[206,339,340],{"class":333},"}",[206,342,343],{"class":215},"\"",[206,345,346],{"class":244}," for",[206,348,349],{"class":248}," i ",[206,351,352],{"class":244},"in",[206,354,355],{"class":300}," range",[206,357,358],{"class":248},"(",[206,360,361],{"class":300},"1",[206,363,286],{"class":248},[206,365,366],{"class":300},"21",[206,368,369],{"class":248},")],\n",[206,371,373,376,378,381,384,386,389,391,393,395,397,399,401,403],{"class":103,"line":372},5,[206,374,375],{"class":215},"                         \"revenue\"",[206,377,280],{"class":248},[206,379,380],{"class":300},"100.0",[206,382,383],{"class":244}," *",[206,385,349],{"class":248},[206,387,388],{"class":244},"for",[206,390,349],{"class":248},[206,392,352],{"class":244},[206,394,355],{"class":300},[206,396,358],{"class":248},[206,398,361],{"class":300},[206,400,286],{"class":248},[206,402,366],{"class":300},[206,404,405],{"class":248},")]})\n",[206,407,409,412,414,416,419,422,425,427,429,431,434,437,440,442,444,446,448,450,452],{"class":103,"line":408},6,[206,410,411],{"class":248},"detail ",[206,413,271],{"class":244},[206,415,274],{"class":248},[206,417,418],{"class":215},"\"order\"",[206,420,421],{"class":248},": ",[206,423,424],{"class":300},"range",[206,426,358],{"class":248},[206,428,361],{"class":300},[206,430,286],{"class":248},[206,432,433],{"class":300},"501",[206,435,436],{"class":248},"), ",[206,438,439],{"class":215},"\"amount\"",[206,441,421],{"class":248},[206,443,424],{"class":300},[206,445,358],{"class":248},[206,447,361],{"class":300},[206,449,286],{"class":248},[206,451,433],{"class":300},[206,453,454],{"class":248},")})\n",[206,456,458,461,463,465,468,470,473,475,478,480,482,484,486,488,490],{"class":103,"line":457},7,[206,459,460],{"class":248},"lookups ",[206,462,271],{"class":244},[206,464,274],{"class":248},[206,466,467],{"class":215},"\"code\"",[206,469,280],{"class":248},[206,471,472],{"class":215},"\"N\"",[206,474,286],{"class":248},[206,476,477],{"class":215},"\"S\"",[206,479,292],{"class":248},[206,481,277],{"class":215},[206,483,280],{"class":248},[206,485,283],{"class":215},[206,487,286],{"class":248},[206,489,289],{"class":215},[206,491,309],{"class":248},[206,493,495],{"class":103,"line":494},8,[206,496,262],{"emptyLinePlaceholder":261},[206,498,500,503,506,509,511,515,517,520,523,525],{"class":103,"line":499},9,[206,501,502],{"class":244},"with",[206,504,505],{"class":248}," pd.ExcelWriter(",[206,507,508],{"class":215},"\"workbook.xlsx\"",[206,510,286],{"class":248},[206,512,514],{"class":513},"sa561","engine",[206,516,271],{"class":244},[206,518,519],{"class":215},"\"xlsxwriter\"",[206,521,522],{"class":248},") ",[206,524,252],{"class":244},[206,526,527],{"class":248}," writer:\n",[206,529,531,534,537,539,542,544,547,549,552],{"class":103,"line":530},10,[206,532,533],{"class":248},"    summary.to_excel(writer, ",[206,535,536],{"class":513},"sheet_name",[206,538,271],{"class":244},[206,540,541],{"class":215},"\"Summary\"",[206,543,286],{"class":248},[206,545,546],{"class":513},"index",[206,548,271],{"class":244},[206,550,551],{"class":300},"False",[206,553,554],{"class":248},")\n",[206,556,558,561,563,565,568,570,572,574,576],{"class":103,"line":557},11,[206,559,560],{"class":248},"    regional.to_excel(writer, ",[206,562,536],{"class":513},[206,564,271],{"class":244},[206,566,567],{"class":215},"\"Regional\"",[206,569,286],{"class":248},[206,571,546],{"class":513},[206,573,271],{"class":244},[206,575,551],{"class":300},[206,577,554],{"class":248},[206,579,581,584,586,588,591,593,595,597,599],{"class":103,"line":580},12,[206,582,583],{"class":248},"    detail.to_excel(writer, ",[206,585,536],{"class":513},[206,587,271],{"class":244},[206,589,590],{"class":215},"\"Detail\"",[206,592,286],{"class":248},[206,594,546],{"class":513},[206,596,271],{"class":244},[206,598,551],{"class":300},[206,600,554],{"class":248},[206,602,604,607,609,611,614,616,618,620,622],{"class":103,"line":603},13,[206,605,606],{"class":248},"    lookups.to_excel(writer, ",[206,608,536],{"class":513},[206,610,271],{"class":244},[206,612,613],{"class":215},"\"Lookups\"",[206,615,286],{"class":248},[206,617,546],{"class":513},[206,619,271],{"class":244},[206,621,551],{"class":300},[206,623,554],{"class":248},[206,625,627,629,631,633,636,638,640,642,644],{"class":103,"line":626},14,[206,628,583],{"class":248},[206,630,536],{"class":513},[206,632,271],{"class":244},[206,634,635],{"class":215},"\"_raw\"",[206,637,286],{"class":248},[206,639,546],{"class":513},[206,641,271],{"class":244},[206,643,551],{"class":300},[206,645,554],{"class":248},[191,647,649],{"id":648},"step-1-build-a-trimmed-copy","Step 1 — Build a trimmed copy",[10,651,652],{},"Copy the file, delete the unwanted sheets from the copy, convert that. The original is never touched:",[196,654,656],{"className":235,"code":655,"language":237,"meta":201,"style":201},"import shutil\nimport tempfile\nfrom pathlib import Path\nfrom openpyxl import load_workbook\n\ndef trimmed_copy(src, keep, dest=None):\n    \"\"\"A copy of the workbook containing only `keep`, in that order.\"\"\"\n    src = Path(src)\n    dest = Path(dest) if dest else Path(tempfile.mkdtemp()) \u002F src.name\n    dest.parent.mkdir(parents=True, exist_ok=True)\n    shutil.copyfile(src, dest)\n\n    wb = load_workbook(dest)\n\n    missing = [name for name in keep if name not in wb.sheetnames]\n    if missing:\n        raise KeyError(f\"sheets not in {src.name}: {', '.join(missing)}\")\n\n    for name in [n for n in wb.sheetnames if n not in keep]:\n        del wb[name]\n\n    # Reorder to match `keep`, so the PDF pages follow that sequence.\n    wb._sheets = [wb[name] for name in keep]\n    wb.active = 0\n\n    wb.save(dest)\n    return dest\n\ncopy = trimmed_copy(\"workbook.xlsx\", keep=[\"Summary\", \"Regional\"])\nprint(copy)\n",[203,657,658,665,672,685,697,701,721,726,736,764,788,793,797,807,811,845,854,893,898,932,941,946,953,973,984,989,995,1004,1009,1041],{"__ignoreMap":201},[206,659,660,662],{"class":103,"line":208},[206,661,245],{"class":244},[206,663,664],{"class":248}," shutil\n",[206,666,667,669],{"class":103,"line":258},[206,668,245],{"class":244},[206,670,671],{"class":248}," tempfile\n",[206,673,674,677,680,682],{"class":103,"line":265},[206,675,676],{"class":244},"from",[206,678,679],{"class":248}," pathlib ",[206,681,245],{"class":244},[206,683,684],{"class":248}," Path\n",[206,686,687,689,692,694],{"class":103,"line":312},[206,688,676],{"class":244},[206,690,691],{"class":248}," openpyxl ",[206,693,245],{"class":244},[206,695,696],{"class":248}," load_workbook\n",[206,698,699],{"class":103,"line":372},[206,700,262],{"emptyLinePlaceholder":261},[206,702,703,706,710,713,715,718],{"class":103,"line":408},[206,704,705],{"class":244},"def",[206,707,709],{"class":708},"s_Opv"," trimmed_copy",[206,711,712],{"class":248},"(src, keep, dest",[206,714,271],{"class":244},[206,716,717],{"class":300},"None",[206,719,720],{"class":248},"):\n",[206,722,723],{"class":103,"line":457},[206,724,725],{"class":215},"    \"\"\"A copy of the workbook containing only `keep`, in that order.\"\"\"\n",[206,727,728,731,733],{"class":103,"line":494},[206,729,730],{"class":248},"    src ",[206,732,271],{"class":244},[206,734,735],{"class":248}," Path(src)\n",[206,737,738,741,743,746,749,752,755,758,761],{"class":103,"line":499},[206,739,740],{"class":248},"    dest ",[206,742,271],{"class":244},[206,744,745],{"class":248}," Path(dest) ",[206,747,748],{"class":244},"if",[206,750,751],{"class":248}," dest ",[206,753,754],{"class":244},"else",[206,756,757],{"class":248}," Path(tempfile.mkdtemp()) ",[206,759,760],{"class":244},"\u002F",[206,762,763],{"class":248}," src.name\n",[206,765,766,769,772,774,777,779,782,784,786],{"class":103,"line":530},[206,767,768],{"class":248},"    dest.parent.mkdir(",[206,770,771],{"class":513},"parents",[206,773,271],{"class":244},[206,775,776],{"class":300},"True",[206,778,286],{"class":248},[206,780,781],{"class":513},"exist_ok",[206,783,271],{"class":244},[206,785,776],{"class":300},[206,787,554],{"class":248},[206,789,790],{"class":103,"line":557},[206,791,792],{"class":248},"    shutil.copyfile(src, dest)\n",[206,794,795],{"class":103,"line":580},[206,796,262],{"emptyLinePlaceholder":261},[206,798,799,802,804],{"class":103,"line":603},[206,800,801],{"class":248},"    wb ",[206,803,271],{"class":244},[206,805,806],{"class":248}," load_workbook(dest)\n",[206,808,809],{"class":103,"line":626},[206,810,262],{"emptyLinePlaceholder":261},[206,812,814,817,819,822,824,827,829,832,834,836,839,842],{"class":103,"line":813},15,[206,815,816],{"class":248},"    missing ",[206,818,271],{"class":244},[206,820,821],{"class":248}," [name ",[206,823,388],{"class":244},[206,825,826],{"class":248}," name ",[206,828,352],{"class":244},[206,830,831],{"class":248}," keep ",[206,833,748],{"class":244},[206,835,826],{"class":248},[206,837,838],{"class":244},"not",[206,840,841],{"class":244}," in",[206,843,844],{"class":248}," wb.sheetnames]\n",[206,846,848,851],{"class":103,"line":847},16,[206,849,850],{"class":244},"    if",[206,852,853],{"class":248}," missing:\n",[206,855,857,860,863,865,867,870,872,875,877,879,881,884,887,889,891],{"class":103,"line":856},17,[206,858,859],{"class":244},"        raise",[206,861,862],{"class":300}," KeyError",[206,864,358],{"class":248},[206,866,327],{"class":244},[206,868,869],{"class":215},"\"sheets not in ",[206,871,334],{"class":333},[206,873,874],{"class":248},"src.name",[206,876,340],{"class":333},[206,878,421],{"class":215},[206,880,334],{"class":333},[206,882,883],{"class":215},"', '",[206,885,886],{"class":248},".join(missing)",[206,888,340],{"class":333},[206,890,343],{"class":215},[206,892,554],{"class":248},[206,894,896],{"class":103,"line":895},18,[206,897,262],{"emptyLinePlaceholder":261},[206,899,901,904,906,908,911,913,916,918,921,923,925,927,929],{"class":103,"line":900},19,[206,902,903],{"class":244},"    for",[206,905,826],{"class":248},[206,907,352],{"class":244},[206,909,910],{"class":248}," [n ",[206,912,388],{"class":244},[206,914,915],{"class":248}," n ",[206,917,352],{"class":244},[206,919,920],{"class":248}," wb.sheetnames ",[206,922,748],{"class":244},[206,924,915],{"class":248},[206,926,838],{"class":244},[206,928,841],{"class":244},[206,930,931],{"class":248}," keep]:\n",[206,933,935,938],{"class":103,"line":934},20,[206,936,937],{"class":244},"        del",[206,939,940],{"class":248}," wb[name]\n",[206,942,944],{"class":103,"line":943},21,[206,945,262],{"emptyLinePlaceholder":261},[206,947,949],{"class":103,"line":948},22,[206,950,952],{"class":951},"s-wDw","    # Reorder to match `keep`, so the PDF pages follow that sequence.\n",[206,954,956,959,961,964,966,968,970],{"class":103,"line":955},23,[206,957,958],{"class":248},"    wb._sheets ",[206,960,271],{"class":244},[206,962,963],{"class":248}," [wb[name] ",[206,965,388],{"class":244},[206,967,826],{"class":248},[206,969,352],{"class":244},[206,971,972],{"class":248}," keep]\n",[206,974,976,979,981],{"class":103,"line":975},24,[206,977,978],{"class":248},"    wb.active ",[206,980,271],{"class":244},[206,982,983],{"class":300}," 0\n",[206,985,987],{"class":103,"line":986},25,[206,988,262],{"emptyLinePlaceholder":261},[206,990,992],{"class":103,"line":991},26,[206,993,994],{"class":248},"    wb.save(dest)\n",[206,996,998,1001],{"class":103,"line":997},27,[206,999,1000],{"class":244},"    return",[206,1002,1003],{"class":248}," dest\n",[206,1005,1007],{"class":103,"line":1006},28,[206,1008,262],{"emptyLinePlaceholder":261},[206,1010,1012,1015,1017,1020,1022,1024,1027,1029,1032,1034,1036,1038],{"class":103,"line":1011},29,[206,1013,1014],{"class":248},"copy ",[206,1016,271],{"class":244},[206,1018,1019],{"class":248}," trimmed_copy(",[206,1021,508],{"class":215},[206,1023,286],{"class":248},[206,1025,1026],{"class":513},"keep",[206,1028,271],{"class":244},[206,1030,1031],{"class":248},"[",[206,1033,541],{"class":215},[206,1035,286],{"class":248},[206,1037,567],{"class":215},[206,1039,1040],{"class":248},"])\n",[206,1042,1044,1047],{"class":103,"line":1043},30,[206,1045,1046],{"class":300},"print",[206,1048,1049],{"class":248},"(copy)\n",[10,1051,1052,1053,1057,1058,1061,1062,1064,1065,1068,1069,1072],{},"Two details. ",[1054,1055,1056],"strong",{},"Excel requires at least one visible sheet",", so deleting everything produces a file the renderer rejects — the ",[203,1059,1060],{},"missing"," check plus a non-empty ",[203,1063,1026],{}," list covers it. And ",[203,1066,1067],{},"wb._sheets"," is the practical way to reorder; openpyxl's public ",[203,1070,1071],{},"move_sheet"," works one sheet at a time and is fiddlier for a full reordering.",[10,1074,1075,1076,1078],{},"Order matters more than it sounds. Sheets render in workbook order, so putting ",[203,1077,66],{}," first is what makes it page one rather than page thirty-eight.",[191,1080,1082],{"id":1081},"step-2-or-just-hide-what-you-do-not-want","Step 2 — Or just hide what you do not want",[10,1084,1085],{},"LibreOffice does not render hidden sheets, so hiding is the least invasive option — the data stays in the workbook and nothing is deleted:",[196,1087,1089],{"className":235,"code":1088,"language":237,"meta":201,"style":201},"from openpyxl import load_workbook\n\ndef hide_except(src, dest, keep):\n    \"\"\"Hide every sheet except `keep`, so the PDF contains only those.\"\"\"\n    shutil.copyfile(src, dest)\n    wb = load_workbook(dest)\n\n    for ws in wb.worksheets:\n        ws.sheet_state = \"visible\" if ws.title in keep else \"hidden\"\n\n    visible = [ws for ws in wb.worksheets if ws.sheet_state == \"visible\"]\n    if not visible:\n        raise ValueError(\"at least one sheet must stay visible\")\n    wb.active = wb.index(visible[0])\n\n    wb.save(dest)\n    return dest\n",[203,1090,1091,1101,1105,1115,1120,1124,1132,1136,1148,1173,1177,1209,1219,1233,1246,1250,1254],{"__ignoreMap":201},[206,1092,1093,1095,1097,1099],{"class":103,"line":208},[206,1094,676],{"class":244},[206,1096,691],{"class":248},[206,1098,245],{"class":244},[206,1100,696],{"class":248},[206,1102,1103],{"class":103,"line":258},[206,1104,262],{"emptyLinePlaceholder":261},[206,1106,1107,1109,1112],{"class":103,"line":265},[206,1108,705],{"class":244},[206,1110,1111],{"class":708}," hide_except",[206,1113,1114],{"class":248},"(src, dest, keep):\n",[206,1116,1117],{"class":103,"line":312},[206,1118,1119],{"class":215},"    \"\"\"Hide every sheet except `keep`, so the PDF contains only those.\"\"\"\n",[206,1121,1122],{"class":103,"line":372},[206,1123,792],{"class":248},[206,1125,1126,1128,1130],{"class":103,"line":408},[206,1127,801],{"class":248},[206,1129,271],{"class":244},[206,1131,806],{"class":248},[206,1133,1134],{"class":103,"line":457},[206,1135,262],{"emptyLinePlaceholder":261},[206,1137,1138,1140,1143,1145],{"class":103,"line":494},[206,1139,903],{"class":244},[206,1141,1142],{"class":248}," ws ",[206,1144,352],{"class":244},[206,1146,1147],{"class":248}," wb.worksheets:\n",[206,1149,1150,1153,1155,1158,1161,1164,1166,1168,1170],{"class":103,"line":499},[206,1151,1152],{"class":248},"        ws.sheet_state ",[206,1154,271],{"class":244},[206,1156,1157],{"class":215}," \"visible\"",[206,1159,1160],{"class":244}," if",[206,1162,1163],{"class":248}," ws.title ",[206,1165,352],{"class":244},[206,1167,831],{"class":248},[206,1169,754],{"class":244},[206,1171,1172],{"class":215}," \"hidden\"\n",[206,1174,1175],{"class":103,"line":530},[206,1176,262],{"emptyLinePlaceholder":261},[206,1178,1179,1182,1184,1187,1189,1191,1193,1196,1198,1201,1204,1206],{"class":103,"line":557},[206,1180,1181],{"class":248},"    visible ",[206,1183,271],{"class":244},[206,1185,1186],{"class":248}," [ws ",[206,1188,388],{"class":244},[206,1190,1142],{"class":248},[206,1192,352],{"class":244},[206,1194,1195],{"class":248}," wb.worksheets ",[206,1197,748],{"class":244},[206,1199,1200],{"class":248}," ws.sheet_state ",[206,1202,1203],{"class":244},"==",[206,1205,1157],{"class":215},[206,1207,1208],{"class":248},"]\n",[206,1210,1211,1213,1216],{"class":103,"line":580},[206,1212,850],{"class":244},[206,1214,1215],{"class":244}," not",[206,1217,1218],{"class":248}," visible:\n",[206,1220,1221,1223,1226,1228,1231],{"class":103,"line":603},[206,1222,859],{"class":244},[206,1224,1225],{"class":300}," ValueError",[206,1227,358],{"class":248},[206,1229,1230],{"class":215},"\"at least one sheet must stay visible\"",[206,1232,554],{"class":248},[206,1234,1235,1237,1239,1242,1244],{"class":103,"line":626},[206,1236,978],{"class":248},[206,1238,271],{"class":244},[206,1240,1241],{"class":248}," wb.index(visible[",[206,1243,42],{"class":300},[206,1245,1040],{"class":248},[206,1247,1248],{"class":103,"line":813},[206,1249,262],{"emptyLinePlaceholder":261},[206,1251,1252],{"class":103,"line":847},[206,1253,994],{"class":248},[206,1255,1256,1258],{"class":103,"line":856},[206,1257,1000],{"class":244},[206,1259,1003],{"class":248},[10,1261,1262,1263,18],{},"The trade-off is honesty about what you are relying on. Deleting is an explicit instruction; hiding depends on the renderer's behaviour, which is stable in LibreOffice but is not something the file format guarantees. Prefer deletion when the output must be reproducible across tools, and hiding when you also want the trimmed workbook to remain usable — the visibility mechanics are covered in ",[14,1264,1266],{"href":1265},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fhide-sheets-rows-and-columns-with-openpyxl\u002F","hiding sheets, rows and columns",[191,1268,1270],{"id":1269},"step-3-watch-what-openpyxl-drops","Step 3 — Watch what openpyxl drops",[10,1272,1273],{},"Both approaches above load and re-save the workbook through openpyxl, and that is lossy in ways that matter for a document.",[20,1275,30,1281,30,1284,30,1287,30,1290,30,1297,30,1303,30,1308,30,1312,30,1317,30,1322,30,1327,30,1330,30,1335,30,1338,30,1341,30,1345,30,1348],{"viewBox":1276,"role":23,"ariaLabel":1277,"ariaLabelledBy":1278,"xmlns":28,"style":29},"0 0 800 238","Two trimming routes: an openpyxl round trip loses charts and images, while deleting sheets from a copy with LibreOffice preserves everything but costs a process start.",[1279,1280],"lossy-t","lossy-d",[32,1282,1283],{"id":1279},"Which trimming route preserves the visuals",[36,1285,1286],{"id":1280},"Loading a workbook with openpyxl and saving it back drops charts and images that openpyxl did not itself create, so a summary sheet with a chart converts to a PDF with an empty space where the chart was. Having LibreOffice do the work instead — converting the whole workbook and then extracting the pages, or scripting the sheet removal inside LibreOffice — preserves every visual, at the cost of a slower process. The choice depends entirely on whether the sheets being kept contain charts.",[40,1288],{"x":42,"y":42,"width":43,"height":1289,"fill":45},"238",[40,1291],{"x":1292,"y":138,"width":1293,"height":1294,"rx":1292,"fill":1295,"stroke":1296,"style":61},"14","368","192","#fee8f2","var(--accent,#f43f8f)",[47,1298,1302],{"x":1299,"y":1300,"style":1301},"198","54","font-size:12.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","openpyxl round trip",[47,1304,1307],{"x":1299,"y":1305,"style":1306},"86","font-size:11.5px;fill:var(--text,#172033);text-anchor:middle","fast, pure Python",[47,1309,1311],{"x":1299,"y":1310,"style":1306},"112","full control of order",[47,1313,1316],{"x":1299,"y":1314,"style":1315},"146","font-size:11.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","drops charts and images",[47,1318,1321],{"x":1299,"y":1319,"style":1320},"170","font-size:11px;fill:var(--muted,#5b6780);text-anchor:middle","also drops pivot caches",[47,1323,1326],{"x":1299,"y":1324,"style":1325},"200","font-size:11px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","fine for tables of numbers",[40,1328],{"x":1329,"y":138,"width":1293,"height":1294,"rx":1292,"fill":59,"stroke":60,"style":61},"418",[47,1331,1334],{"x":1332,"y":1300,"style":1333},"602","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","let LibreOffice do it",[47,1336,1337],{"x":1332,"y":1305,"style":1306},"convert the whole workbook",[47,1339,1340],{"x":1332,"y":1310,"style":1306},"or hide sheets, do not delete",[47,1342,1344],{"x":1332,"y":1314,"style":1343},"font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","every visual survives",[47,1346,1347],{"x":1332,"y":1319,"style":1320},"costs a process start",[47,1349,1351],{"x":1332,"y":1324,"style":1350},"font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","required when charts matter",[10,1353,1354],{},"openpyxl drops charts and images it did not create, so a summary sheet carrying a chart converts to a PDF with a blank space. Check before choosing a route:",[196,1356,1358],{"className":235,"code":1357,"language":237,"meta":201,"style":201},"import zipfile\nfrom pathlib import Path\n\ndef has_visuals(path):\n    \"\"\"True if the workbook contains charts or images openpyxl would drop.\"\"\"\n    with zipfile.ZipFile(path) as zf:\n        names = zf.namelist()\n    return any(n.startswith((\"xl\u002Fcharts\u002F\", \"xl\u002Fmedia\u002F\")) for n in names)\n\nif has_visuals(\"workbook.xlsx\"):\n    print(\"charts or images present — hide sheets rather than deleting them\")\n",[203,1359,1360,1367,1377,1381,1391,1396,1409,1419,1449,1453,1464],{"__ignoreMap":201},[206,1361,1362,1364],{"class":103,"line":208},[206,1363,245],{"class":244},[206,1365,1366],{"class":248}," zipfile\n",[206,1368,1369,1371,1373,1375],{"class":103,"line":258},[206,1370,676],{"class":244},[206,1372,679],{"class":248},[206,1374,245],{"class":244},[206,1376,684],{"class":248},[206,1378,1379],{"class":103,"line":265},[206,1380,262],{"emptyLinePlaceholder":261},[206,1382,1383,1385,1388],{"class":103,"line":312},[206,1384,705],{"class":244},[206,1386,1387],{"class":708}," has_visuals",[206,1389,1390],{"class":248},"(path):\n",[206,1392,1393],{"class":103,"line":372},[206,1394,1395],{"class":215},"    \"\"\"True if the workbook contains charts or images openpyxl would drop.\"\"\"\n",[206,1397,1398,1401,1404,1406],{"class":103,"line":408},[206,1399,1400],{"class":244},"    with",[206,1402,1403],{"class":248}," zipfile.ZipFile(path) ",[206,1405,252],{"class":244},[206,1407,1408],{"class":248}," zf:\n",[206,1410,1411,1414,1416],{"class":103,"line":457},[206,1412,1413],{"class":248},"        names ",[206,1415,271],{"class":244},[206,1417,1418],{"class":248}," zf.namelist()\n",[206,1420,1421,1423,1426,1429,1432,1434,1437,1440,1442,1444,1446],{"class":103,"line":494},[206,1422,1000],{"class":244},[206,1424,1425],{"class":300}," any",[206,1427,1428],{"class":248},"(n.startswith((",[206,1430,1431],{"class":215},"\"xl\u002Fcharts\u002F\"",[206,1433,286],{"class":248},[206,1435,1436],{"class":215},"\"xl\u002Fmedia\u002F\"",[206,1438,1439],{"class":248},")) ",[206,1441,388],{"class":244},[206,1443,915],{"class":248},[206,1445,352],{"class":244},[206,1447,1448],{"class":248}," names)\n",[206,1450,1451],{"class":103,"line":499},[206,1452,262],{"emptyLinePlaceholder":261},[206,1454,1455,1457,1460,1462],{"class":103,"line":530},[206,1456,748],{"class":244},[206,1458,1459],{"class":248}," has_visuals(",[206,1461,508],{"class":215},[206,1463,720],{"class":248},[206,1465,1466,1469,1471,1474],{"class":103,"line":557},[206,1467,1468],{"class":300},"    print",[206,1470,358],{"class":248},[206,1472,1473],{"class":215},"\"charts or images present — hide sheets rather than deleting them\"",[206,1475,554],{"class":248},[10,1477,1478,1479,18],{},"Hiding is the safer of the two here as well, because it still round-trips through openpyxl and therefore still drops visuals. When the kept sheets genuinely contain charts, the honest options are to convert the whole workbook and accept the extra pages, or to rebuild the charts in the trimmed copy with openpyxl — the approach in ",[14,1480,1482],{"href":1481},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fadd-a-combo-chart-with-a-secondary-axis-in-openpyxl\u002F","adding a combo chart with a secondary axis",[191,1484,1486],{"id":1485},"step-4-convert-and-clean-up","Step 4 — Convert and clean up",[10,1488,1489],{},"Wrap the whole thing so the temporary copy never outlives the call:",[196,1491,1493],{"className":235,"code":1492,"language":237,"meta":201,"style":201},"import shutil\nimport subprocess\nimport tempfile\nfrom pathlib import Path\n\ndef sheets_to_pdf(src, keep, out_dir=\"delivery\", timeout=300):\n    \"\"\"Convert only the named sheets, in that order, to a PDF.\"\"\"\n    soffice = shutil.which(\"soffice\") or shutil.which(\"libreoffice\")\n    if soffice is None:\n        raise RuntimeError(\"LibreOffice not found\")\n\n    src = Path(src)\n    out = Path(out_dir).resolve()\n    out.mkdir(parents=True, exist_ok=True)\n\n    with tempfile.TemporaryDirectory() as work:\n        copy = trimmed_copy(src, keep, dest=Path(work) \u002F src.name)\n\n        with tempfile.TemporaryDirectory() as profile:\n            result = subprocess.run(\n                [soffice, f\"-env:UserInstallation=file:\u002F\u002F{profile}\",\n                 \"--headless\", \"--convert-to\", \"pdf\",\n                 \"--outdir\", str(out), str(copy)],\n                capture_output=True, text=True, timeout=timeout,\n            )\n        if result.returncode != 0:\n            raise RuntimeError(result.stderr.strip())\n\n        pdf = out \u002F (copy.stem + \".pdf\")\n        if not pdf.is_file() or pdf.stat().st_size == 0:\n            raise RuntimeError(f\"no usable PDF at {pdf}\")\n        return pdf\n\npdf = sheets_to_pdf(\"workbook.xlsx\", keep=[\"Summary\", \"Regional\"])\nprint(\"wrote\", pdf)\n",[203,1494,1495,1501,1508,1514,1524,1528,1553,1558,1583,1599,1613,1617,1625,1635,1656,1660,1672,1695,1699,1711,1721,1743,1760,1778,1805,1810,1826,1836,1840,1863,1883,1908,1917,1922,1951],{"__ignoreMap":201},[206,1496,1497,1499],{"class":103,"line":208},[206,1498,245],{"class":244},[206,1500,664],{"class":248},[206,1502,1503,1505],{"class":103,"line":258},[206,1504,245],{"class":244},[206,1506,1507],{"class":248}," subprocess\n",[206,1509,1510,1512],{"class":103,"line":265},[206,1511,245],{"class":244},[206,1513,671],{"class":248},[206,1515,1516,1518,1520,1522],{"class":103,"line":312},[206,1517,676],{"class":244},[206,1519,679],{"class":248},[206,1521,245],{"class":244},[206,1523,684],{"class":248},[206,1525,1526],{"class":103,"line":372},[206,1527,262],{"emptyLinePlaceholder":261},[206,1529,1530,1532,1535,1538,1540,1543,1546,1548,1551],{"class":103,"line":408},[206,1531,705],{"class":244},[206,1533,1534],{"class":708}," sheets_to_pdf",[206,1536,1537],{"class":248},"(src, keep, out_dir",[206,1539,271],{"class":244},[206,1541,1542],{"class":215},"\"delivery\"",[206,1544,1545],{"class":248},", timeout",[206,1547,271],{"class":244},[206,1549,1550],{"class":300},"300",[206,1552,720],{"class":248},[206,1554,1555],{"class":103,"line":457},[206,1556,1557],{"class":215},"    \"\"\"Convert only the named sheets, in that order, to a PDF.\"\"\"\n",[206,1559,1560,1563,1565,1568,1571,1573,1576,1578,1581],{"class":103,"line":494},[206,1561,1562],{"class":248},"    soffice ",[206,1564,271],{"class":244},[206,1566,1567],{"class":248}," shutil.which(",[206,1569,1570],{"class":215},"\"soffice\"",[206,1572,522],{"class":248},[206,1574,1575],{"class":244},"or",[206,1577,1567],{"class":248},[206,1579,1580],{"class":215},"\"libreoffice\"",[206,1582,554],{"class":248},[206,1584,1585,1587,1590,1593,1596],{"class":103,"line":499},[206,1586,850],{"class":244},[206,1588,1589],{"class":248}," soffice ",[206,1591,1592],{"class":244},"is",[206,1594,1595],{"class":300}," None",[206,1597,1598],{"class":248},":\n",[206,1600,1601,1603,1606,1608,1611],{"class":103,"line":530},[206,1602,859],{"class":244},[206,1604,1605],{"class":300}," RuntimeError",[206,1607,358],{"class":248},[206,1609,1610],{"class":215},"\"LibreOffice not found\"",[206,1612,554],{"class":248},[206,1614,1615],{"class":103,"line":557},[206,1616,262],{"emptyLinePlaceholder":261},[206,1618,1619,1621,1623],{"class":103,"line":580},[206,1620,730],{"class":248},[206,1622,271],{"class":244},[206,1624,735],{"class":248},[206,1626,1627,1630,1632],{"class":103,"line":603},[206,1628,1629],{"class":248},"    out ",[206,1631,271],{"class":244},[206,1633,1634],{"class":248}," Path(out_dir).resolve()\n",[206,1636,1637,1640,1642,1644,1646,1648,1650,1652,1654],{"class":103,"line":626},[206,1638,1639],{"class":248},"    out.mkdir(",[206,1641,771],{"class":513},[206,1643,271],{"class":244},[206,1645,776],{"class":300},[206,1647,286],{"class":248},[206,1649,781],{"class":513},[206,1651,271],{"class":244},[206,1653,776],{"class":300},[206,1655,554],{"class":248},[206,1657,1658],{"class":103,"line":813},[206,1659,262],{"emptyLinePlaceholder":261},[206,1661,1662,1664,1667,1669],{"class":103,"line":847},[206,1663,1400],{"class":244},[206,1665,1666],{"class":248}," tempfile.TemporaryDirectory() ",[206,1668,252],{"class":244},[206,1670,1671],{"class":248}," work:\n",[206,1673,1674,1677,1679,1682,1685,1687,1690,1692],{"class":103,"line":856},[206,1675,1676],{"class":248},"        copy ",[206,1678,271],{"class":244},[206,1680,1681],{"class":248}," trimmed_copy(src, keep, ",[206,1683,1684],{"class":513},"dest",[206,1686,271],{"class":244},[206,1688,1689],{"class":248},"Path(work) ",[206,1691,760],{"class":244},[206,1693,1694],{"class":248}," src.name)\n",[206,1696,1697],{"class":103,"line":895},[206,1698,262],{"emptyLinePlaceholder":261},[206,1700,1701,1704,1706,1708],{"class":103,"line":900},[206,1702,1703],{"class":244},"        with",[206,1705,1666],{"class":248},[206,1707,252],{"class":244},[206,1709,1710],{"class":248}," profile:\n",[206,1712,1713,1716,1718],{"class":103,"line":934},[206,1714,1715],{"class":248},"            result ",[206,1717,271],{"class":244},[206,1719,1720],{"class":248}," subprocess.run(\n",[206,1722,1723,1726,1728,1731,1733,1736,1738,1740],{"class":103,"line":943},[206,1724,1725],{"class":248},"                [soffice, ",[206,1727,327],{"class":244},[206,1729,1730],{"class":215},"\"-env:UserInstallation=file:\u002F\u002F",[206,1732,334],{"class":333},[206,1734,1735],{"class":248},"profile",[206,1737,340],{"class":333},[206,1739,343],{"class":215},[206,1741,1742],{"class":248},",\n",[206,1744,1745,1748,1750,1753,1755,1758],{"class":103,"line":948},[206,1746,1747],{"class":215},"                 \"--headless\"",[206,1749,286],{"class":248},[206,1751,1752],{"class":215},"\"--convert-to\"",[206,1754,286],{"class":248},[206,1756,1757],{"class":215},"\"pdf\"",[206,1759,1742],{"class":248},[206,1761,1762,1765,1767,1770,1773,1775],{"class":103,"line":955},[206,1763,1764],{"class":215},"                 \"--outdir\"",[206,1766,286],{"class":248},[206,1768,1769],{"class":300},"str",[206,1771,1772],{"class":248},"(out), ",[206,1774,1769],{"class":300},[206,1776,1777],{"class":248},"(copy)],\n",[206,1779,1780,1783,1785,1787,1789,1791,1793,1795,1797,1800,1802],{"class":103,"line":975},[206,1781,1782],{"class":513},"                capture_output",[206,1784,271],{"class":244},[206,1786,776],{"class":300},[206,1788,286],{"class":248},[206,1790,47],{"class":513},[206,1792,271],{"class":244},[206,1794,776],{"class":300},[206,1796,286],{"class":248},[206,1798,1799],{"class":513},"timeout",[206,1801,271],{"class":244},[206,1803,1804],{"class":248},"timeout,\n",[206,1806,1807],{"class":103,"line":986},[206,1808,1809],{"class":248},"            )\n",[206,1811,1812,1815,1818,1821,1824],{"class":103,"line":991},[206,1813,1814],{"class":244},"        if",[206,1816,1817],{"class":248}," result.returncode ",[206,1819,1820],{"class":244},"!=",[206,1822,1823],{"class":300}," 0",[206,1825,1598],{"class":248},[206,1827,1828,1831,1833],{"class":103,"line":997},[206,1829,1830],{"class":244},"            raise",[206,1832,1605],{"class":300},[206,1834,1835],{"class":248},"(result.stderr.strip())\n",[206,1837,1838],{"class":103,"line":1006},[206,1839,262],{"emptyLinePlaceholder":261},[206,1841,1842,1845,1847,1850,1852,1855,1858,1861],{"class":103,"line":1011},[206,1843,1844],{"class":248},"        pdf ",[206,1846,271],{"class":244},[206,1848,1849],{"class":248}," out ",[206,1851,760],{"class":244},[206,1853,1854],{"class":248}," (copy.stem ",[206,1856,1857],{"class":244},"+",[206,1859,1860],{"class":215}," \".pdf\"",[206,1862,554],{"class":248},[206,1864,1865,1867,1869,1872,1874,1877,1879,1881],{"class":103,"line":1043},[206,1866,1814],{"class":244},[206,1868,1215],{"class":244},[206,1870,1871],{"class":248}," pdf.is_file() ",[206,1873,1575],{"class":244},[206,1875,1876],{"class":248}," pdf.stat().st_size ",[206,1878,1203],{"class":244},[206,1880,1823],{"class":300},[206,1882,1598],{"class":248},[206,1884,1886,1888,1890,1892,1894,1897,1899,1902,1904,1906],{"class":103,"line":1885},31,[206,1887,1830],{"class":244},[206,1889,1605],{"class":300},[206,1891,358],{"class":248},[206,1893,327],{"class":244},[206,1895,1896],{"class":215},"\"no usable PDF at ",[206,1898,334],{"class":333},[206,1900,1901],{"class":248},"pdf",[206,1903,340],{"class":333},[206,1905,343],{"class":215},[206,1907,554],{"class":248},[206,1909,1911,1914],{"class":103,"line":1910},32,[206,1912,1913],{"class":244},"        return",[206,1915,1916],{"class":248}," pdf\n",[206,1918,1920],{"class":103,"line":1919},33,[206,1921,262],{"emptyLinePlaceholder":261},[206,1923,1925,1928,1930,1933,1935,1937,1939,1941,1943,1945,1947,1949],{"class":103,"line":1924},34,[206,1926,1927],{"class":248},"pdf ",[206,1929,271],{"class":244},[206,1931,1932],{"class":248}," sheets_to_pdf(",[206,1934,508],{"class":215},[206,1936,286],{"class":248},[206,1938,1026],{"class":513},[206,1940,271],{"class":244},[206,1942,1031],{"class":248},[206,1944,541],{"class":215},[206,1946,286],{"class":248},[206,1948,567],{"class":215},[206,1950,1040],{"class":248},[206,1952,1954,1956,1958,1961],{"class":103,"line":1953},35,[206,1955,1046],{"class":300},[206,1957,358],{"class":248},[206,1959,1960],{"class":215},"\"wrote\"",[206,1962,1963],{"class":248},", pdf)\n",[10,1965,1966,1967,1970],{},"The nested ",[203,1968,1969],{},"TemporaryDirectory"," contexts do two jobs: the outer one holds the trimmed copy and removes it whatever happens, and the inner one gives LibreOffice a throwaway profile so concurrent runs cannot deadlock on the profile lock.",[10,1972,1973],{},"Verify the result is the size you expected — a PDF far longer than intended usually means a sheet you meant to exclude survived:",[196,1975,1977],{"className":235,"code":1976,"language":237,"meta":201,"style":201},"import re\n\ndef page_count(pdf_path):\n    data = Path(pdf_path).read_bytes()\n    counts = [int(m) for m in re.findall(rb\"\u002FCount\\s+(\\d+)\", data)]\n    return max(counts) if counts else len(re.findall(rb\"\u002FType\\s*\u002FPage[^s]\", data))\n\npages = page_count(pdf)\nassert pages \u003C= 10, f\"{pages} pages — did an unwanted sheet survive?\"\n",[203,1978,1979,1986,1990,2000,2010,2060,2112,2116,2126],{"__ignoreMap":201},[206,1980,1981,1983],{"class":103,"line":208},[206,1982,245],{"class":244},[206,1984,1985],{"class":248}," re\n",[206,1987,1988],{"class":103,"line":258},[206,1989,262],{"emptyLinePlaceholder":261},[206,1991,1992,1994,1997],{"class":103,"line":265},[206,1993,705],{"class":244},[206,1995,1996],{"class":708}," page_count",[206,1998,1999],{"class":248},"(pdf_path):\n",[206,2001,2002,2005,2007],{"class":103,"line":312},[206,2003,2004],{"class":248},"    data ",[206,2006,271],{"class":244},[206,2008,2009],{"class":248}," Path(pdf_path).read_bytes()\n",[206,2011,2012,2015,2017,2020,2023,2026,2028,2031,2033,2036,2039,2042,2045,2047,2050,2052,2055,2057],{"class":103,"line":372},[206,2013,2014],{"class":248},"    counts ",[206,2016,271],{"class":244},[206,2018,2019],{"class":248}," [",[206,2021,2022],{"class":300},"int",[206,2024,2025],{"class":248},"(m) ",[206,2027,388],{"class":244},[206,2029,2030],{"class":248}," m ",[206,2032,352],{"class":244},[206,2034,2035],{"class":248}," re.findall(",[206,2037,2038],{"class":244},"rb",[206,2040,2041],{"class":215},"\"\u002FCount",[206,2043,2044],{"class":300},"\\s",[206,2046,1857],{"class":244},[206,2048,2049],{"class":300},"(\\d",[206,2051,1857],{"class":244},[206,2053,2054],{"class":300},")",[206,2056,343],{"class":215},[206,2058,2059],{"class":248},", data)]\n",[206,2061,2062,2064,2067,2070,2072,2075,2077,2080,2083,2085,2088,2090,2093,2096,2098,2101,2104,2107,2109],{"class":103,"line":408},[206,2063,1000],{"class":244},[206,2065,2066],{"class":300}," max",[206,2068,2069],{"class":248},"(counts) ",[206,2071,748],{"class":244},[206,2073,2074],{"class":248}," counts ",[206,2076,754],{"class":244},[206,2078,2079],{"class":300}," len",[206,2081,2082],{"class":248},"(re.findall(",[206,2084,2038],{"class":244},[206,2086,2087],{"class":215},"\"\u002FType",[206,2089,2044],{"class":300},[206,2091,2092],{"class":244},"*",[206,2094,2095],{"class":215},"\u002FPage",[206,2097,1031],{"class":300},[206,2099,2100],{"class":244},"^",[206,2102,2103],{"class":333},"s",[206,2105,2106],{"class":300},"]",[206,2108,343],{"class":215},[206,2110,2111],{"class":248},", data))\n",[206,2113,2114],{"class":103,"line":457},[206,2115,262],{"emptyLinePlaceholder":261},[206,2117,2118,2121,2123],{"class":103,"line":494},[206,2119,2120],{"class":248},"pages ",[206,2122,271],{"class":244},[206,2124,2125],{"class":248}," page_count(pdf)\n",[206,2127,2128,2131,2134,2137,2140,2142,2144,2146,2148,2151,2153],{"class":103,"line":499},[206,2129,2130],{"class":244},"assert",[206,2132,2133],{"class":248}," pages ",[206,2135,2136],{"class":244},"\u003C=",[206,2138,2139],{"class":300}," 10",[206,2141,286],{"class":248},[206,2143,327],{"class":244},[206,2145,343],{"class":215},[206,2147,334],{"class":333},[206,2149,2150],{"class":248},"pages",[206,2152,340],{"class":333},[206,2154,2155],{"class":215}," pages — did an unwanted sheet survive?\"\n",[191,2157,2159],{"id":2158},"step-5-one-pdf-per-sheet","Step 5 — One PDF per sheet",[20,2161,30,2167,30,2170,30,2173,30,2175,30,2178,30,2183,30,2187,30,2191,30,2195,30,2200,30,2237,30,2239,30,2243,30,2261,30,2266],{"viewBox":2162,"role":23,"ariaLabel":2163,"ariaLabelledBy":2164,"xmlns":28,"style":29},"0 0 800 226","Batching conversions: one soffice invocation for many trimmed copies pays the start-up cost once, where a per-file loop pays it every time.",[2165,2166],"batch2-t","batch2-d",[32,2168,2169],{"id":2165},"One invocation for every sheet, not one per sheet",[36,2171,2172],{"id":2166},"Producing five per-sheet PDFs two ways. A loop calling soffice once per file repeats the one-to-two-second start-up five times, so the start-up dominates the total. Passing all five trimmed copies to a single invocation starts LibreOffice once and converts them in sequence inside that one process, finishing in a fraction of the time.",[40,2174],{"x":42,"y":42,"width":43,"height":172,"fill":45},[40,2176],{"x":116,"y":55,"width":1292,"height":1292,"rx":2177,"fill":113},"3",[47,2179,2182],{"x":44,"y":2180,"style":2181},"31","font-size:11px;fill:var(--muted,#5b6780)","LibreOffice start-up",[40,2184],{"x":2185,"y":55,"width":1292,"height":1292,"rx":2177,"fill":2186},"452","var(--teal,#14b8a6)",[47,2188,2190],{"x":2189,"y":2180,"style":2181},"474","converting one sheet",[40,2192],{"x":1292,"y":2193,"width":173,"height":50,"rx":2194,"fill":76,"stroke":77},"56","6",[47,2196,2199],{"x":69,"y":2197,"style":2198},"75","font-size:10.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","one call per file",[2201,2202,2203,2204,2203,2209,2203,2213,2203,2216,2203,2219,2203,2222,2203,2225,2203,2228,2203,2231,2203,2234,30],"g",{},"\n    ",[40,2205],{"x":2206,"y":2193,"width":2207,"height":50,"rx":2208,"fill":113},"152","98","4",[40,2210],{"x":2211,"y":2193,"width":2212,"height":50,"rx":2208,"fill":2186},"250","22",[40,2214],{"x":2215,"y":2193,"width":2207,"height":50,"rx":2208,"fill":113},"272",[40,2217],{"x":2218,"y":2193,"width":2212,"height":50,"rx":2208,"fill":2186},"370",[40,2220],{"x":2221,"y":2193,"width":2207,"height":50,"rx":2208,"fill":113},"392",[40,2223],{"x":2224,"y":2193,"width":2212,"height":50,"rx":2208,"fill":2186},"490",[40,2226],{"x":2227,"y":2193,"width":2207,"height":50,"rx":2208,"fill":113},"512",[40,2229],{"x":2230,"y":2193,"width":2212,"height":50,"rx":2208,"fill":2186},"610",[40,2232],{"x":2233,"y":2193,"width":2207,"height":50,"rx":2208,"fill":113},"632",[40,2235],{"x":2236,"y":2193,"width":2212,"height":50,"rx":2208,"fill":2186},"730",[40,2238],{"x":1292,"y":106,"width":173,"height":50,"rx":2194,"fill":76,"stroke":77},[47,2240,2242],{"x":69,"y":2241,"style":2198},"139","one call, five files",[2201,2244,2203,2245,2203,2247,2203,2249,2203,2252,2203,2255,2203,2258,30],{},[40,2246],{"x":2206,"y":106,"width":2207,"height":50,"rx":2208,"fill":113},[40,2248],{"x":2211,"y":106,"width":2212,"height":50,"rx":2208,"fill":2186},[40,2250],{"x":2251,"y":106,"width":2212,"height":50,"rx":2208,"fill":2186},"273",[40,2253],{"x":2254,"y":106,"width":2212,"height":50,"rx":2208,"fill":2186},"296",[40,2256],{"x":2257,"y":106,"width":2212,"height":50,"rx":2208,"fill":2186},"319",[40,2259],{"x":2260,"y":106,"width":2212,"height":50,"rx":2208,"fill":2186},"342",[47,2262,2265],{"x":2263,"y":2241,"style":2264},"376","font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157)","done here",[47,2267,2269],{"x":2268,"y":1294,"style":1320},"400","raise the timeout in proportion — the whole batch must finish inside one window",[10,2271,2272,2273,2275],{},"The same trimming, once per sheet, converted in a single ",[203,2274,167],{}," call so the start-up cost is paid once:",[196,2277,2279],{"className":235,"code":2278,"language":237,"meta":201,"style":201},"import shutil\nimport subprocess\nimport tempfile\nfrom pathlib import Path\nfrom openpyxl import load_workbook\n\ndef sheet_per_pdf(src, out_dir=\"delivery\", skip_prefixes=(\"_\",), timeout=900):\n    \"\"\"One PDF per sheet, converted in a single LibreOffice invocation.\"\"\"\n    soffice = shutil.which(\"soffice\") or shutil.which(\"libreoffice\")\n    if soffice is None:\n        raise RuntimeError(\"LibreOffice not found\")\n\n    src = Path(src)\n    out = Path(out_dir).resolve()\n    out.mkdir(parents=True, exist_ok=True)\n\n    names = [n for n in load_workbook(src, read_only=True).sheetnames\n             if not n.startswith(skip_prefixes)]\n\n    with tempfile.TemporaryDirectory() as work:\n        copies = []\n        for name in names:\n            safe = \"\".join(c if c.isalnum() or c in \"-_ \" else \"_\" for c in name)\n            copies.append(str(trimmed_copy(\n                src, [name], dest=Path(work) \u002F f\"{src.stem}-{safe}.xlsx\"\n            )))\n\n        with tempfile.TemporaryDirectory() as profile:\n            subprocess.run(\n                [soffice, f\"-env:UserInstallation=file:\u002F\u002F{profile}\",\n                 \"--headless\", \"--convert-to\", \"pdf\",\n                 \"--outdir\", str(out), *copies],\n                capture_output=True, text=True, timeout=timeout, check=True,\n            )\n\n    produced = [out \u002F (Path(c).stem + \".pdf\") for c in copies]\n    missing = [p.name for p in produced if not p.is_file()]\n    if missing:\n        raise RuntimeError(f\"no PDF for: {', '.join(missing)}\")\n    return produced\n\nfor path in sheet_per_pdf(\"workbook.xlsx\"):\n    print(path.name)\n",[203,2280,2281,2287,2293,2299,2309,2319,2323,2357,2362,2382,2394,2406,2410,2418,2426,2446,2450,2478,2488,2492,2502,2512,2524,2567,2577,2615,2620,2624,2634,2639,2657,2671,2686,2720,2724,2728,2759,2786,2793,2819,2827,2832,2849],{"__ignoreMap":201},[206,2282,2283,2285],{"class":103,"line":208},[206,2284,245],{"class":244},[206,2286,664],{"class":248},[206,2288,2289,2291],{"class":103,"line":258},[206,2290,245],{"class":244},[206,2292,1507],{"class":248},[206,2294,2295,2297],{"class":103,"line":265},[206,2296,245],{"class":244},[206,2298,671],{"class":248},[206,2300,2301,2303,2305,2307],{"class":103,"line":312},[206,2302,676],{"class":244},[206,2304,679],{"class":248},[206,2306,245],{"class":244},[206,2308,684],{"class":248},[206,2310,2311,2313,2315,2317],{"class":103,"line":372},[206,2312,676],{"class":244},[206,2314,691],{"class":248},[206,2316,245],{"class":244},[206,2318,696],{"class":248},[206,2320,2321],{"class":103,"line":408},[206,2322,262],{"emptyLinePlaceholder":261},[206,2324,2325,2327,2330,2333,2335,2337,2340,2342,2344,2347,2350,2352,2355],{"class":103,"line":457},[206,2326,705],{"class":244},[206,2328,2329],{"class":708}," sheet_per_pdf",[206,2331,2332],{"class":248},"(src, out_dir",[206,2334,271],{"class":244},[206,2336,1542],{"class":215},[206,2338,2339],{"class":248},", skip_prefixes",[206,2341,271],{"class":244},[206,2343,358],{"class":248},[206,2345,2346],{"class":215},"\"_\"",[206,2348,2349],{"class":248},",), timeout",[206,2351,271],{"class":244},[206,2353,2354],{"class":300},"900",[206,2356,720],{"class":248},[206,2358,2359],{"class":103,"line":494},[206,2360,2361],{"class":215},"    \"\"\"One PDF per sheet, converted in a single LibreOffice invocation.\"\"\"\n",[206,2363,2364,2366,2368,2370,2372,2374,2376,2378,2380],{"class":103,"line":499},[206,2365,1562],{"class":248},[206,2367,271],{"class":244},[206,2369,1567],{"class":248},[206,2371,1570],{"class":215},[206,2373,522],{"class":248},[206,2375,1575],{"class":244},[206,2377,1567],{"class":248},[206,2379,1580],{"class":215},[206,2381,554],{"class":248},[206,2383,2384,2386,2388,2390,2392],{"class":103,"line":530},[206,2385,850],{"class":244},[206,2387,1589],{"class":248},[206,2389,1592],{"class":244},[206,2391,1595],{"class":300},[206,2393,1598],{"class":248},[206,2395,2396,2398,2400,2402,2404],{"class":103,"line":557},[206,2397,859],{"class":244},[206,2399,1605],{"class":300},[206,2401,358],{"class":248},[206,2403,1610],{"class":215},[206,2405,554],{"class":248},[206,2407,2408],{"class":103,"line":580},[206,2409,262],{"emptyLinePlaceholder":261},[206,2411,2412,2414,2416],{"class":103,"line":603},[206,2413,730],{"class":248},[206,2415,271],{"class":244},[206,2417,735],{"class":248},[206,2419,2420,2422,2424],{"class":103,"line":626},[206,2421,1629],{"class":248},[206,2423,271],{"class":244},[206,2425,1634],{"class":248},[206,2427,2428,2430,2432,2434,2436,2438,2440,2442,2444],{"class":103,"line":813},[206,2429,1639],{"class":248},[206,2431,771],{"class":513},[206,2433,271],{"class":244},[206,2435,776],{"class":300},[206,2437,286],{"class":248},[206,2439,781],{"class":513},[206,2441,271],{"class":244},[206,2443,776],{"class":300},[206,2445,554],{"class":248},[206,2447,2448],{"class":103,"line":847},[206,2449,262],{"emptyLinePlaceholder":261},[206,2451,2452,2455,2457,2459,2461,2463,2465,2468,2471,2473,2475],{"class":103,"line":856},[206,2453,2454],{"class":248},"    names ",[206,2456,271],{"class":244},[206,2458,910],{"class":248},[206,2460,388],{"class":244},[206,2462,915],{"class":248},[206,2464,352],{"class":244},[206,2466,2467],{"class":248}," load_workbook(src, ",[206,2469,2470],{"class":513},"read_only",[206,2472,271],{"class":244},[206,2474,776],{"class":300},[206,2476,2477],{"class":248},").sheetnames\n",[206,2479,2480,2483,2485],{"class":103,"line":895},[206,2481,2482],{"class":244},"             if",[206,2484,1215],{"class":244},[206,2486,2487],{"class":248}," n.startswith(skip_prefixes)]\n",[206,2489,2490],{"class":103,"line":900},[206,2491,262],{"emptyLinePlaceholder":261},[206,2493,2494,2496,2498,2500],{"class":103,"line":934},[206,2495,1400],{"class":244},[206,2497,1666],{"class":248},[206,2499,252],{"class":244},[206,2501,1671],{"class":248},[206,2503,2504,2507,2509],{"class":103,"line":943},[206,2505,2506],{"class":248},"        copies ",[206,2508,271],{"class":244},[206,2510,2511],{"class":248}," []\n",[206,2513,2514,2517,2519,2521],{"class":103,"line":948},[206,2515,2516],{"class":244},"        for",[206,2518,826],{"class":248},[206,2520,352],{"class":244},[206,2522,2523],{"class":248}," names:\n",[206,2525,2526,2529,2531,2534,2537,2539,2542,2544,2547,2549,2552,2555,2558,2560,2562,2564],{"class":103,"line":955},[206,2527,2528],{"class":248},"            safe ",[206,2530,271],{"class":244},[206,2532,2533],{"class":215}," \"\"",[206,2535,2536],{"class":248},".join(c ",[206,2538,748],{"class":244},[206,2540,2541],{"class":248}," c.isalnum() ",[206,2543,1575],{"class":244},[206,2545,2546],{"class":248}," c ",[206,2548,352],{"class":244},[206,2550,2551],{"class":215}," \"-_ \"",[206,2553,2554],{"class":244}," else",[206,2556,2557],{"class":215}," \"_\"",[206,2559,346],{"class":244},[206,2561,2546],{"class":248},[206,2563,352],{"class":244},[206,2565,2566],{"class":248}," name)\n",[206,2568,2569,2572,2574],{"class":103,"line":975},[206,2570,2571],{"class":248},"            copies.append(",[206,2573,1769],{"class":300},[206,2575,2576],{"class":248},"(trimmed_copy(\n",[206,2578,2579,2582,2584,2586,2588,2590,2593,2595,2597,2600,2602,2605,2607,2610,2612],{"class":103,"line":986},[206,2580,2581],{"class":248},"                src, [name], ",[206,2583,1684],{"class":513},[206,2585,271],{"class":244},[206,2587,1689],{"class":248},[206,2589,760],{"class":244},[206,2591,2592],{"class":244}," f",[206,2594,343],{"class":215},[206,2596,334],{"class":333},[206,2598,2599],{"class":248},"src.stem",[206,2601,340],{"class":333},[206,2603,2604],{"class":215},"-",[206,2606,334],{"class":333},[206,2608,2609],{"class":248},"safe",[206,2611,340],{"class":333},[206,2613,2614],{"class":215},".xlsx\"\n",[206,2616,2617],{"class":103,"line":991},[206,2618,2619],{"class":248},"            )))\n",[206,2621,2622],{"class":103,"line":997},[206,2623,262],{"emptyLinePlaceholder":261},[206,2625,2626,2628,2630,2632],{"class":103,"line":1006},[206,2627,1703],{"class":244},[206,2629,1666],{"class":248},[206,2631,252],{"class":244},[206,2633,1710],{"class":248},[206,2635,2636],{"class":103,"line":1011},[206,2637,2638],{"class":248},"            subprocess.run(\n",[206,2640,2641,2643,2645,2647,2649,2651,2653,2655],{"class":103,"line":1043},[206,2642,1725],{"class":248},[206,2644,327],{"class":244},[206,2646,1730],{"class":215},[206,2648,334],{"class":333},[206,2650,1735],{"class":248},[206,2652,340],{"class":333},[206,2654,343],{"class":215},[206,2656,1742],{"class":248},[206,2658,2659,2661,2663,2665,2667,2669],{"class":103,"line":1885},[206,2660,1747],{"class":215},[206,2662,286],{"class":248},[206,2664,1752],{"class":215},[206,2666,286],{"class":248},[206,2668,1757],{"class":215},[206,2670,1742],{"class":248},[206,2672,2673,2675,2677,2679,2681,2683],{"class":103,"line":1910},[206,2674,1764],{"class":215},[206,2676,286],{"class":248},[206,2678,1769],{"class":300},[206,2680,1772],{"class":248},[206,2682,2092],{"class":244},[206,2684,2685],{"class":248},"copies],\n",[206,2687,2688,2690,2692,2694,2696,2698,2700,2702,2704,2706,2708,2711,2714,2716,2718],{"class":103,"line":1919},[206,2689,1782],{"class":513},[206,2691,271],{"class":244},[206,2693,776],{"class":300},[206,2695,286],{"class":248},[206,2697,47],{"class":513},[206,2699,271],{"class":244},[206,2701,776],{"class":300},[206,2703,286],{"class":248},[206,2705,1799],{"class":513},[206,2707,271],{"class":244},[206,2709,2710],{"class":248},"timeout, ",[206,2712,2713],{"class":513},"check",[206,2715,271],{"class":244},[206,2717,776],{"class":300},[206,2719,1742],{"class":248},[206,2721,2722],{"class":103,"line":1924},[206,2723,1809],{"class":248},[206,2725,2726],{"class":103,"line":1953},[206,2727,262],{"emptyLinePlaceholder":261},[206,2729,2731,2734,2736,2739,2741,2744,2746,2748,2750,2752,2754,2756],{"class":103,"line":2730},36,[206,2732,2733],{"class":248},"    produced ",[206,2735,271],{"class":244},[206,2737,2738],{"class":248}," [out ",[206,2740,760],{"class":244},[206,2742,2743],{"class":248}," (Path(c).stem ",[206,2745,1857],{"class":244},[206,2747,1860],{"class":215},[206,2749,522],{"class":248},[206,2751,388],{"class":244},[206,2753,2546],{"class":248},[206,2755,352],{"class":244},[206,2757,2758],{"class":248}," copies]\n",[206,2760,2762,2764,2766,2769,2771,2774,2776,2779,2781,2783],{"class":103,"line":2761},37,[206,2763,816],{"class":248},[206,2765,271],{"class":244},[206,2767,2768],{"class":248}," [p.name ",[206,2770,388],{"class":244},[206,2772,2773],{"class":248}," p ",[206,2775,352],{"class":244},[206,2777,2778],{"class":248}," produced ",[206,2780,748],{"class":244},[206,2782,1215],{"class":244},[206,2784,2785],{"class":248}," p.is_file()]\n",[206,2787,2789,2791],{"class":103,"line":2788},38,[206,2790,850],{"class":244},[206,2792,853],{"class":248},[206,2794,2796,2798,2800,2802,2804,2807,2809,2811,2813,2815,2817],{"class":103,"line":2795},39,[206,2797,859],{"class":244},[206,2799,1605],{"class":300},[206,2801,358],{"class":248},[206,2803,327],{"class":244},[206,2805,2806],{"class":215},"\"no PDF for: ",[206,2808,334],{"class":333},[206,2810,883],{"class":215},[206,2812,886],{"class":248},[206,2814,340],{"class":333},[206,2816,343],{"class":215},[206,2818,554],{"class":248},[206,2820,2822,2824],{"class":103,"line":2821},40,[206,2823,1000],{"class":244},[206,2825,2826],{"class":248}," produced\n",[206,2828,2830],{"class":103,"line":2829},41,[206,2831,262],{"emptyLinePlaceholder":261},[206,2833,2835,2837,2840,2842,2845,2847],{"class":103,"line":2834},42,[206,2836,388],{"class":244},[206,2838,2839],{"class":248}," path ",[206,2841,352],{"class":244},[206,2843,2844],{"class":248}," sheet_per_pdf(",[206,2846,508],{"class":215},[206,2848,720],{"class":248},[206,2850,2852,2854],{"class":103,"line":2851},43,[206,2853,1468],{"class":300},[206,2855,2856],{"class":248},"(path.name)\n",[10,2858,2859,2860,2864,2865,2868],{},"Sanitising the sheet name into a filename matters for the same reasons as in ",[14,2861,2863],{"href":2862},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fsplit-one-excel-sheet-into-multiple-files-by-value\u002F","splitting a sheet into multiple files"," — a sheet called ",[203,2866,2867],{},"Q1\u002FQ2"," produces an invalid path otherwise.",[191,2870,2872],{"id":2871},"common-pitfalls-and-fixes","Common pitfalls and fixes",[2874,2875,2876,2892],"table",{},[2877,2878,2879],"thead",{},[2880,2881,2882,2886,2889],"tr",{},[2883,2884,2885],"th",{},"Symptom",[2883,2887,2888],{},"Cause",[2883,2890,2891],{},"Fix",[2893,2894,2895,2907,2918,2929,2940,2958,2972,2986],"tbody",{},[2880,2896,2897,2901,2904],{},[2898,2899,2900],"td",{},"Whole workbook still converted",[2898,2902,2903],{},"Trimmed the wrong file",[2898,2905,2906],{},"Convert the copy, not the source.",[2880,2908,2909,2912,2915],{},[2898,2910,2911],{},"Renderer rejects the file",[2898,2913,2914],{},"Every sheet deleted or hidden",[2898,2916,2917],{},"Keep at least one visible sheet.",[2880,2919,2920,2923,2926],{},[2898,2921,2922],{},"Summary is on page 30",[2898,2924,2925],{},"Sheets render in workbook order",[2898,2927,2928],{},"Reorder the copy before converting.",[2880,2930,2931,2934,2937],{},[2898,2932,2933],{},"Chart missing from the PDF",[2898,2935,2936],{},"openpyxl dropped it on save",[2898,2938,2939],{},"Convert the whole workbook, or rebuild the chart.",[2880,2941,2942,2948,2951],{},[2898,2943,2944,2947],{},[203,2945,2946],{},"KeyError"," on a sheet name",[2898,2949,2950],{},"Name differs by case or spacing",[2898,2952,2953,2954,2957],{},"Check ",[203,2955,2956],{},"wb.sheetnames"," exactly.",[2880,2959,2960,2963,2966],{},[2898,2961,2962],{},"Temporary copies left behind",[2898,2964,2965],{},"No cleanup on failure",[2898,2967,2968,2969,2971],{},"Use a ",[203,2970,1969],{}," context.",[2880,2973,2974,2977,2980],{},[2898,2975,2976],{},"Conversion hangs under cron",[2898,2978,2979],{},"Profile lock contention",[2898,2981,2982,2983,18],{},"Pass a fresh ",[203,2984,2985],{},"-env:UserInstallation",[2880,2987,2988,2991,3000],{},[2898,2989,2990],{},"Invalid filename per sheet",[2898,2992,2993,2994,2996,2997],{},"Sheet name has ",[203,2995,760],{}," or ",[203,2998,2999],{},"\\",[2898,3001,3002],{},"Sanitise before using it as a path.",[191,3004,3006],{"id":3005},"performance-and-scale-notes","Performance and scale notes",[10,3008,3009],{},"The cost breaks into three parts: the file copy, the openpyxl round trip, and the LibreOffice conversion. The middle one is usually the surprise.",[10,3011,3012,3015,3016,3019,3020,3023,3024,3027],{},[203,3013,3014],{},"load_workbook"," parses the ",[1054,3017,3018],{},"entire"," workbook, including the sheets you are about to delete — so trimming a 60 MB file down to one small sheet still pays the full parse. Where the unwanted sheets are the bulk of the file, ",[203,3021,3022],{},"read_only=True"," for inspection and a direct zip-level manipulation avoid that, though deleting sheets correctly from the archive means fixing several relationship parts and is rarely worth the complexity. The pragmatic answer for a recurring job is to ",[1054,3025,3026],{},"produce the report and the working data as separate workbooks in the first place",", so no trimming is needed at all.",[10,3029,3030,3031,3037,3038,3041],{},"Two habits that always help. ",[1054,3032,3033,3034,3036],{},"Batch every conversion into one ",[203,3035,167],{}," call",", since the one-to-two-second start-up otherwise repeats per file — that is the dominant cost for a set of small sheets. And ",[1054,3039,3040],{},"skip the trim entirely when you want everything",", because a straight conversion of the original avoids both the copy and the parse.",[10,3043,3044,3045,3048],{},"For genuinely large workbooks, note that the trimmed copy is written in full before conversion, so peak disk usage is the source plus the copy. In a container with a small writable layer, point ",[203,3046,3047],{},"tempfile"," at a volume with room:",[196,3050,3052],{"className":235,"code":3051,"language":237,"meta":201,"style":201},"import tempfile\n\ntempfile.tempdir = \"\u002Fvar\u002Ftmp\u002Freports\"       # somewhere with space\n",[203,3053,3054,3060,3064],{"__ignoreMap":201},[206,3055,3056,3058],{"class":103,"line":208},[206,3057,245],{"class":244},[206,3059,671],{"class":248},[206,3061,3062],{"class":103,"line":258},[206,3063,262],{"emptyLinePlaceholder":261},[206,3065,3066,3069,3071,3074],{"class":103,"line":265},[206,3067,3068],{"class":248},"tempfile.tempdir ",[206,3070,271],{"class":244},[206,3072,3073],{"class":215}," \"\u002Fvar\u002Ftmp\u002Freports\"",[206,3075,3076],{"class":951},"       # somewhere with space\n",[10,3078,3079],{},"And where the goal is a short document from a large workbook, consider building the summary as its own small workbook rather than trimming a big one — that is both faster and avoids every chart-preservation question in this guide.",[191,3081,3083],{"id":3082},"conclusion","Conclusion",[10,3085,3086],{},"There is no flag that converts one sheet, so shape a copy and convert that. Copy the file, delete or hide what the reader should not see, reorder so the summary lands on page one, and convert the copy inside a temporary directory that cleans itself up. Check first whether the kept sheets carry charts or images — an openpyxl round trip drops them, and if they matter you must convert the whole workbook or rebuild the visuals. Then verify the page count, because a PDF much longer than expected is the clearest sign that a sheet you meant to exclude came along.",[191,3088,3090],{"id":3089},"frequently-asked-questions","Frequently asked questions",[10,3092,3093,3096],{},[1054,3094,3095],{},"Can LibreOffice convert just one sheet?","\nNot through a simple flag. The reliable approach is to copy the workbook, delete the sheets you do not want, and convert the copy — which also lets you control the order they appear in.",[10,3098,3099,3102],{},[1054,3100,3101],{},"Does hiding a sheet exclude it from the PDF?","\nYes, in practice — LibreOffice does not render hidden sheets. It is the least invasive method, since the data stays in the workbook, but it depends on renderer behaviour rather than an explicit instruction.",[10,3104,3105,3108],{},[1054,3106,3107],{},"How do I control the order sheets appear in the PDF?","\nReorder them in the workbook copy before converting. Sheets render in workbook order, so moving the summary to the front puts it on page one.",[10,3110,3111,3114],{},[1054,3112,3113],{},"Why does my trimmed copy lose its charts?","\nopenpyxl does not preserve charts it did not create, so loading and re-saving a workbook drops them. Convert the whole workbook, or rebuild the charts in the copy, when they must survive.",[10,3116,3117,3120,3121,3123],{},[1054,3118,3119],{},"Can I produce one PDF per sheet?","\nYes — make one trimmed copy per sheet and convert them all in a single ",[203,3122,167],{}," call. Passing every file to one invocation pays the start-up cost once rather than per sheet.",[191,3125,3127],{"id":3126},"related","Related",[3129,3130,3131,3138,3144,3151,3157],"ul",{},[3132,3133,3134,3135,3137],"li",{},"Up to the parent: ",[14,3136,17],{"href":16}," — the conversion methods compared.",[3132,3139,3140,3143],{},[14,3141,3142],{"href":228},"Convert an Excel File to PDF with Python"," — the headless LibreOffice recipe.",[3132,3145,3146,3150],{},[14,3147,3149],{"href":3148},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fadd-headers-footers-and-page-numbers-to-an-excel-pdf\u002F","Add Headers, Footers and Page Numbers to an Excel PDF"," — setting up the pages before converting.",[3132,3152,3153,3156],{},[14,3154,3155],{"href":1265},"Hide Sheets, Rows and Columns with openpyxl"," — the visibility method used here.",[3132,3158,3159,3163],{},[14,3160,3162],{"href":3161},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Frename-reorder-and-delete-excel-sheets-with-openpyxl\u002F","Rename, Reorder and Delete Excel Sheets with openpyxl"," — the sheet operations behind the trim.",[3165,3166,3167],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .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}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":201,"searchDepth":258,"depth":258,"links":3169},[3170,3171,3172,3173,3174,3175,3176,3177,3178,3179,3180],{"id":193,"depth":258,"text":194},{"id":648,"depth":258,"text":649},{"id":1081,"depth":258,"text":1082},{"id":1269,"depth":258,"text":1270},{"id":1485,"depth":258,"text":1486},{"id":2158,"depth":258,"text":2159},{"id":2871,"depth":258,"text":2872},{"id":3005,"depth":258,"text":3006},{"id":3082,"depth":258,"text":3083},{"id":3089,"depth":258,"text":3090},{"id":3126,"depth":258,"text":3127},"2026-08-15","Export the summary without the raw data — build a trimmed copy of the workbook, or use LibreOffice's export filter, and control the order sheets appear in the PDF.","md",[3185,3187,3189,3191,3193],{"q":3095,"a":3186},"Not through a simple flag. The reliable approach is to copy the workbook, delete the sheets you do not want, and convert the copy — which also lets you control the order they appear in.",{"q":3101,"a":3188},"Yes, in practice — LibreOffice does not render hidden sheets. It is the least invasive method, since the data stays in the workbook, but it depends on renderer behaviour rather than an explicit instruction.",{"q":3107,"a":3190},"Reorder them in the workbook copy before converting. Sheets render in workbook order, so moving the summary to the front puts it on page one.",{"q":3113,"a":3192},"openpyxl does not preserve charts it did not create, so loading and re-saving a workbook drops them. Copy the file on disk and delete sheets from the copy, rather than round-tripping through openpyxl, when charts must survive.",{"q":3119,"a":3194},"Yes — make one trimmed copy per sheet and convert them all in a single soffice call. Passing every file to one invocation pays the start-up cost once rather than per sheet.",{},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-only-selected-sheets-of-a-workbook-to-pdf",{"title":3198,"description":3199},"Export Selected Excel Sheets to PDF with Python","Convert some sheets and not others: a trimmed-copy approach with openpyxl, LibreOffice's selection filter, controlling sheet order, and verifying the resulting page count.","convert-only-selected-sheets-of-a-workbook-to-pdf","automating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-only-selected-sheets-of-a-workbook-to-pdf\u002Findex","how-to","fwxbmWfV-skUxRYxw4-W-9MYS4WlSlkCh3Qr-v7L2Ao",[3205,3208],{"title":3142,"path":3206,"stem":3207,"children":-1},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-excel-file-to-pdf-with-python","automating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-excel-file-to-pdf-with-python\u002Findex",{"title":3209,"path":3210,"stem":3211,"children":-1},"Generating Excel Reports from Templates","\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates","automating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Findex",1786800029060]