הדבר שהיה אוכל את ימי שישי שלי
כל אחר הצהריים של יום שישי, במשך כשנה, היה לי אותו טקס קטן. חוזה היה מגיע בשלושה קבצים — ההסכם הראשי ב‑Word, נספח תמחור ב‑Excel, וגיליון תנאי השותף כ‑PDF — והייתי צריך לשלב אותם לקובץ PDF אחד נקי. אין בזה קושי. פותחים את Word, מייצאים ל‑PDF. פותחים את Excel, מייצאים ל‑PDF. פותחים אפליקציית מיזוג PDF חינמית, גוררים את שלושת הקבצים, בודקים את הסדר, ושומרים.
זה לקח אולי שמונה דקות. אם נכפיל זאת בחמישה עשר חוזים בשבוע, מאבדים שעתיים בתזוזת העכבר. גרוע יותר, כל כמה שבועות מישהו שלח חוברת עם הנספח בעמוד הראשון מכיוון שהשמות ממוינים אלפביתית באפליקציית המיזוג.
אם זה נשמע מוכר, שאר הפוסט הוא האחה"צ שבו לבסוף החלפתי את הטקס בקוד.
העלות האמיתית איננה בזמן — אלא החוזה האחד מתוך חמישים שבו העמודים מגיעים בסדר הלא נכון ואף אחד לא שם לב עד שהלקוח חותם על הגרסה השגויה.
מה שרציתי באמת
לא “צינור מסמכים מתוחכם”. רק שלושה דברים:
- להעביר למתודה רשימת קבצים (כל שילוב של DOCX, XLSX, PDF) ולקבל חזרה קובץ PDF אחד.
- לכוון את הלוגיקה לאותה תיקייה ולתת לה לקבוע את רשימת הקבצים בעצמה.
- לחלץ טווח עמודים מהחוברת המוגמרת מבלי לבצע את המיזוג מחדש.
זה כל העבודה. אם הספרייה לא יכולה לבצע את שלושת המשימות הללו בצורה נקייה, איני רוצה לדעת על כך.
הגדרה
- .NET 6.0 או גרסה מאוחרת יותר
- GroupDocs.Merger for .NET 24.10+ (קבל רישיון זמני כדי שלא תשלח את סימן המים של ההערכה)
- תיקייה עם כל שילוב של מסמכים שהייתם ממזגים ידנית בדרך כלל
dotnet add package GroupDocs.Merger
זהו כל מה שתלוי. אין ממיר חיצוני, אין התקנת Office ללא ממשק, ואין ספריית מניפולציית PDF נוספת.
שלב 1 — לתת לתיקייה להיות הקלט
תמיד מתחילים כאן מכיוון שזה נקודת הכניסה הריאלית. בפועל, משהו אחר (מטפל העלאות, משימת קבלת אימיילים, ייצוא לילה מהמחלקה הפיננסית) מניח חבילה של קבצים בתיקייה, והקוד שלי צריך להתמודד עם מה שמוצא.
// 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}'.");
הטריק של OrderBy הוא החלק המעניין. GroupDocs.Merger בוחר את פורמט הפלט מהקובץ הראשון שנפתח — אם אני מעביר לו DOCX כמסמך ראשי, הוא מחזיר DOCX. מכיוון שהצינור שלי תמיד רוצה PDF, אני מבטיח שכל PDF קיים בתיקייה יקבל מיקום 0.
שני דברים שכדאי לציין:
ToLowerInvariant()מכיוון ששותף ביום מן הימים ישלח לךREPORT.PDFוהמסנן שמקבל רק אותיות קטנות יפיל אותו בשקט.ThenBy(f)קיים רק כדי להפוך את הפלט לדטרמיניסטי. ללא זה, שני ריצות על אותה תיקייה יכולות להשתנות בהתאם למצב מערכת הקבצים.
שלב 2 — המיזוג עצמו
לאחר שיש לי רשימת נתיבים מסודרת, המיזוג קצר יותר מהתיאור של המיזוג.
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)}");
כמה הערות משימוש קוד זה בתנאים קשים:
usingחשוב.Mergerמחזיק ידיות קבצים על המקורות; אם שוכחים לשחרר אותו, העובד בתיקיית ההפלה יכשל בסופו של דבר למחוק את הקבצים שלו.JoinOptionsריק כאן מכיוון שהברירות המחדל הן מה שאני רוצה ב‑95% מהמקרים. כשאתה צריך זאת, שם נמצאים טווחי העמודים, סיבוב ומיקומי ההוספה.- כאשר Excel נכנס לחוברת, פריסת הגיליון לעמוד נקבעת על ידי אזור ההדפסה של חוברת העבודה המקורית. אם ה‑XLSX שלך מסתיים ב‑38 עמודים ואתה רצית שלושה, התיקון נעשה בגיליון עצמו, לא ב‑
JoinOptions.
בדיקת תקינות אחת שאני תמיד מוסיף מיד אחרי השמירה:
using var verify = new Merger(outputPath);
Console.WriteLine($"Result pages: {verify.GetDocumentInfo().PageCount}");
שתי שניות קוד שתפסו יותר באגים של “נספח שנפל בשקט” מאשר כל מבחן שכתבתי.
שלב 3 — לחלץ חלק מאוחר יותר
הבקשה המשנית שאני מקבל בכל פעם: “אתה יכול לשלוח לי רק את דף הכיסוי?” או “הלקוח רוצה רק את החתימות.” לבנות מחדש את כל החוברת כדי להעביר שני עמודים זה טיפשי — החילוץ עושה זאת ישירות.
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 הוא int[] של מספרי עמודים מבוססי‑1 שאתה רוצה לשמור. כל השאר נופל. זה מהיר מכיוון שהתוצאה כבר PDF — ללא סיבוב המרה.
לפני מול אחרי, בכנות
| מה שהייתי עושה | עם Merger.Join |
|
|---|---|---|
| זמן לכל חוזה | 5–10 דקות של לחיצות | מתחת ל‑30 שניות מקצה לקצה |
| כשל טיפוסי | עמודים בסדר הלא נכון, אף אחד לא שם לב | כל סדר שהרשימה מציינת, באופן חוזר |
| הרחבה ל‑100 ביום | לא — אתה שוכר אדם | עובד אחד, משועמם רוב הזמן |
| קוד שאתה מתחזק | דף Confluence בשם “Binder Process v4” | מחלקה אחת, ~70 שורות |
| פלט | שלושה PDFs ותפילה | חוברת אחת, עם ספירת עמודים שניתן לתעד |
השורה שמעניינת אותי ביותר היא שורת ה"כשל". מיזוג ידני נכשל בשקט; קוד שרושם ספירת עמודים נכשל בקול רם.
סיפור אמיתי מצוות טכנולוגיה משפטית קטן
סטארט‑אפ של שני אנשים שעבדתי איתו היה עם פרלגאל שהבוקר שלו התחיל בהרכבת חוזים. הסכם Word, תמחור Excel, נספח PDF, משולבים באפליקציה, מועלים ל‑DocuSign. בערך שמונה דקות לחבילה, וב‑30 חבילות ביום זה היה כמעט כל הבוקר שלה.
הם הטמיעו את שיטת סריקת התיקייה בשירות האחורי שכבר ציפה למיילים הנכנסים. עשרים שניות לכל חבילה, ועוד שורת לוג עם ספירת העמודים. הפרלגאל עבר לבחינת חוזים במקום להרכיבם. אף אחד לא שלח חוברת מסודרת באופן שגוי שוב — לא בגלל שהספרייה קסומה, אלא מכיוון שרשימת הקבצים מפורשת בקוד וניתן לבצע diff.
string folder = @"C:\IncomingContracts";
string output = @"C:\Processed\ContractPackage.pdf";
var files = CreatePdfBinderFromFolder(folder, output);
Console.WriteLine($"Package created: {files}");
זה כל האינטגרציה. כל מה שמעל (מאזין האימייל, נתיב האחסון) כבר היה במקום.
דברים שלא הייתי צריך היום אבל אצטרך מחר
הספרייה הזו עושה ערימה של דברים שלא כיסיתי כי המאמר היה מתארך. בקירוב לפי הסדר שבו השתמשתי בהם:
- סימני מים על הפלט עבור חותמות “טיוטה” על עותקים לפני חתימה.
- סיבוב עמודים עבור סריקות שמגיעות בצד.
- סידור עמודים מותאם כאשר סדר המקור אינו סדר המסירה.
- הצפנת PDF לכל דבר שנשלח לצד חיצוני.
כל זה נמצא מאחורי אותו API של Merger. ה-תיעוד כולל את הרשימה המלאה — רק רציתי לציין ש"מיזוג" הוא הפתרון הבסיסי והשאר זמין כשצריך.
מה הייתי אומר לעצמי בעבר
אם אתה עומד לכתוב שלב DOCX‑ל‑PDF משלך כי “זה רק מתודה אחת”, עצור. ההמרה היא החלק שמחליד בשקט — תכונות Office חדשות, טיפול בתמונות סרוקות, גופנים משובצים, הכל. תן למשהו אחר לנהל את המשטח הזה, והקדש את אחר הצהריים של שישי שלך למשהו שלא קשור למיון שמות קבצים.
לאן ללכת הלאה:
- רישיון זמני — נדרש לפלט ללא סימן מים
- אפשרויות מיזוג מתקדמות — JoinOptions, אפשרויות שמירה, דחיסה
- פורמטים נתמכים — הרבה מעבר לשלושה שהצגתי כאן
- פרויקטים לדוגמה ב‑GitHub — כולל את זה