Věc, která mi požíraly pátky
Každé odpoledne v pátek, asi rok, jsem měl stejný malý rituál. Smlouva přišla ve třech souborech — hlavní dohoda ve Wordu, cenový příloha v Excelu a podmínky partnera jako PDF — a já jsem je musel předat jako jeden čistý PDF. Nic složitého. Otevřít Word, exportovat do PDF. Otevřít Excel, exportovat do PDF. Otevřít nějakou bezplatnou aplikaci na slučování PDF, přetáhnout tři soubory, zkontrolovat pořadí, uložit.
Trvalo to asi osm minut. Vynásobte to patnácti smlouvami týdně a ztratíte dvě hodiny pohybem myši. Horší bylo, že každých pár týdnů někdo poslal svazek s přílohou na první stránce, protože názvy souborů se v aplikaci řadily abecedně.
Pokud vám to připadá povědomé, zbytek tohoto příspěvku je odpoledne, kdy jsem konečně nahradil rituál kódem.
Skutečný náklad není čas — je to jedna ze padesáti smluv, kde jsou stránky ve špatném pořadí a nikdo si toho nevšimne, dokud klient nepodepíše špatnou verzi.
Co jsem vlastně chtěl
Ne „nádherný dokumentový pipeline“. Jen tři věci:
- Předat metodě seznam souborů (libovolná kombinace DOCX, XLSX, PDF) a získat jeden PDF výstup.
- Nasměrovat stejnou logiku na složku a nechat ji sama zjistit seznam souborů.
- Vyjmout rozsah stránek z hotového svazku, aniž by se znovu provádělo celé sloučení.
To je celý úkol. Pokud knihovna nedokáže tyto tři věci čistě, nechci o tom vědět.
Nastavení
- .NET 6.0 nebo novější
- GroupDocs.Merger for .NET 24.10+ (získat dočasnou licenci, abyste neodesílali vodoznak z eval verze)
- Složka s libovolnou kombinací dokumentů, které byste normálně spojovali ručně
dotnet add package GroupDocs.Merger
To je vše, co potřebujete. Žádný externí konvertor, žádná headless instalace Office, žádná další knihovna pro manipulaci s PDF.
Krok 1 — Nechte složku být vstupem
Vždy začínám zde, protože je to realistický vstupní bod. V praxi něco jiného (handler pro nahrávání, úloha pro zpracování e‑mailů, noční dump z financí) uloží spoustu souborů do adresáře a můj kód se musí vypořádat s tím, co najde.
// Pick up every supported file in the drop folder; the PDF wins
// the tie-break for position 0 so the merger keeps the output
// as a PDF regardless of how files are named.
string[] extensions = { ".pdf", ".docx", ".xlsx" };
var files = Directory.EnumerateFiles(folderPath)
.Where(f => extensions.Contains(Path.GetExtension(f).ToLowerInvariant()))
.OrderBy(f => Path.GetExtension(f).ToLowerInvariant() == ".pdf" ? 0 : 1)
.ThenBy(f => f)
.ToArray();
if (files.Length == 0)
throw new InvalidOperationException(
$"No supported documents found in '{folderPath}'.");
Trik s OrderBy je zajímavý. GroupDocs.Merger si zvolí výstupní formát podle toho, který soubor otevřete jako první — pokud mu předáte DOCX jako primární dokument, výstup bude DOCX. Protože moje pipeline vždy chce PDF, zajistím, aby jakýkoli existující PDF ve složce dostal pozici 0.
Dvě věci, které stojí za zmínku:
ToLowerInvariant()protože partner vám jednou pošleREPORT.PDFa váš filtr fungující jen na malá písmena ho tiše zahodí.ThenBy(f)je zde jen proto, aby byl výstup deterministický. Bez něj se dva běhy ve stejné složce mohou lišit podle nálady souborového systému.
Krok 2 — Samotné sloučení
Jakmile mám seřazený seznam cest, sloučení je kratší než popis sloučení.
Console.WriteLine($"Primary source: {sourcePaths[0]}");
using var merger = new Merger(sourcePaths[0]);
var joinOptions = new JoinOptions();
for (int i = 1; i < sourcePaths.Length; i++)
{
Console.WriteLine($"Joining: {sourcePaths[i]}");
merger.Join(sourcePaths[i], joinOptions);
}
merger.Save(outputPath);
Console.WriteLine($"Unified PDF binder saved to: {Path.GetFullPath(outputPath)}");
Několik poznámek z používání v praxi:
usingmá význam.Mergerdrží souborové handly na zdrojích; pokud zapomenete uvolnit, váš worker pro složku nakonec nebude schopen smazat vstupní soubory.JoinOptionsje zde prázdný, protože výchozí hodnoty jsou to, co potřebuji 95 % času. Když ho potřebujete, najdete tam rozsahy stránek, rotaci a pozice vkládání.- Když Excel vstoupí do svazku, rozložení list‑na‑stránku určuje tisková oblast zdrojového sešitu. Pokud váš XLSX skončí na 38 stránkách a chtěli jste tři, oprava je v tabulce, ne v
JoinOptions.
Jedna kontrola, kterou vždy přidám hned po uložení:
using var verify = new Merger(outputPath);
Console.WriteLine($"Result pages: {verify.GetDocumentInfo().PageCount}");
Dvě sekundy kódu, který zachytil více „tiše zahodených příloh“ než jakýkoli test, který jsem napsal.
Krok 3 — Později extrahovat část
Požadavek, který dostávám pokaždé: „Můžete mi poslat jen úvodní stránku?“ nebo „Klient chce jen podpisy.“ Přestavovat celý svazek jen kvůli dvěma stránkám je hloupé — extrakce to udělá přímo.
using var merger = new Merger(binderPath);
merger.ExtractPages(new ExtractOptions(pages));
merger.Save(outputPath);
Console.WriteLine($"Extracted pages [{string.Join(",", pages)}] to " +
Path.GetFullPath(outputPath));
pages je int[] s čísly stránek (číslování od 1), které chcete zachovat. Všechno ostatní se zahodí. Je to rychlé, protože výsledek už je PDF — žádná konverzní smyčka.
Před a po, upřímně
| Co jsem dříve dělal | S Merger.Join |
|
|---|---|---|
| Čas na jeden kontrakt | 5–10 minut klikání | méně než 30 sekund od začátku do konce |
| Typické selhání | Stránky ve špatném pořadí, nikdo si nevšimne | Jakékoliv pořadí, které říká seznam souborů, opakovatelně |
| Škálování na 100/den | Nejde — najmete člověka | Jeden pracovník, většinu času znuděný |
| Kód, který udržujete | Stránka v Confluence s názvem „Binder Process v4“ | Jedna třída, ~70 řádků |
| Výstup | Tři PDF a modlitba | Jeden svazek s počtem stránek, který můžete zaznamenat |
Řádek, na který mi nejvíc záleží, je ten o „selhání“. Manuální slučování selhává tiše; kód, který loguje počet stránek, selže nahlas.
Skutečný příběh z malého týmu legal‑tech
Startup se dvěma lidmi, se kterým jsem pracoval, měl paralegála, jehož ráno začínalo sestavováním smluv. Wordová dohoda, Excelová cenová kalkulace, PDF dodatky, poskládané v aplikaci, nahrané do DocuSign. Asi osm minut na balíček, což při 30 balíčcích denně byl prakticky celý její den.
Zavlekli metodu pro skenování složky do backendové služby, která už sledovala jejich příchozí e‑mail. Dvacet sekund na balíček, plus řádek v logu s počtem stránek. Paralegál se přesunul k revizi smluv místo jejich sestavování. Nikdo už neodeslal špatně seřazený svazek — ne proto, že je knihovna kouzelná, ale protože seznam souborů je explicitní v kódu a můžete ho porovnat.
string folder = @"C:\IncomingContracts";
string output = @"C:\Processed\ContractPackage.pdf";
var files = CreatePdfBinderFromFolder(folder, output);
Console.WriteLine($"Package created: {files}");
To je celá integrace. Všechno předtím (listener e‑mailů, cesta úložiště) už bylo připravené.
Věci, které jsem dnes nepotřeboval, ale zítra budu potřebovat
Stejná knihovna dělá spoustu věcí, které jsem nezmínil, protože by článek byl příliš dlouhý. Přibližně v pořadí, v jakém jsem je použil:
- Watermarks on the output pro razítka „DRAFT“ na předpodepsaných kopiích.
- Page rotation pro skeny, které přicházejí šikmo.
- Custom page ordering když pořadí zdrojů není pořadím dodání.
- PDF encryption pro vše, co jde externímu protistraně.
Všechny tyto funkce jsou za stejným API Merger. docs mají kompletní seznam — chtěl jsem jen upozornit, že „merge“ je levný starter a zbytek je k dispozici, když to potřebujete.
Co bych řekl svému minulému já
Pokud se chystáte napsat vlastní krok DOCX‑to‑PDF, protože „je to jen jedna metoda“, zastavte se. Konverze je část, která tiše rezaví — nové funkce Office, zpracování skenovaných obrázků, vložená písma a tak dál. Nechte, aby to vlastnila jiná vrstva, a strávte páteční odpoledne něčím, co není řazení souborů.
Kam dál:
- Temporary license — vyžadováno pro výstup bez vodoznaku
- Advanced merging options — JoinOptions, možnosti uložení, komprese
- Supported formats — daleko za ty tři, které jsem zde ukázal
- Sample projects on GitHub — včetně tohoto