การรวม DOCX, XLSX และ PDF เป็นไฟล์ binder เดียว — การสาธิต

สิ่งที่ทำให้วันศุกร์ของฉันหายไป

ทุกบ่ายวันศุกร์เป็นเวลาประมาณหนึ่งปี ฉันมีพิธีกรรมเล็ก ๆ ซ้ำ ๆ กัน สัญญาจะมาพร้อมกับไฟล์สามไฟล์ — สัญญาหลักใน Word, ภาคผนวกราคาใน Excel, และแผ่นเงื่อนไขของคู่ค้าเป็น PDF — แล้วฉันต้องรวมให้เป็น PDF เดียวที่สะอาด ไม่มีอะไรยาก เปิด Word แล้วส่งออกเป็น PDF เปิด Excel แล้วส่งออกเป็น PDF เปิดแอปฟรีสำหรับรวม PDF ลากไฟล์สามไฟล์เข้า ตรวจสอบลำดับแล้วบันทึก

ใช้เวลาประมาณแปดนาที คูณด้วยสัญญา 15 ฉบับต่อสัปดาห์ คุณก็เสียเวลาสองชั่วโมงกับการเคลื่อนเมาส์ แย่กว่านั้น ทุกสองสามสัปดาห์มีคนส่ง binder ที่ภาคผนวกอยู่หน้าแรกเพราะชื่อไฟล์ถูกจัดเรียงตามตัวอักษรในแอปรวมไฟล์

ถ้าคุณเคยเจอแบบนี้ ส่วนที่เหลือของโพสต์นี้คือบ่ายที่ฉันสุดท้ายเปลี่ยนพิธีกรรมเป็นโค้ด

ต้นทุนที่แท้จริงไม่ใช่เวลา — แต่คือสัญญาหนึ่งในห้าสิบที่หน้าติดกันผิดลำดับและไม่มีใครสังเกตจนกว่าลูกค้าจะเซ็นเวอร์ชันที่ผิด

สิ่งที่ฉันต้องการจริงๆ

ไม่ใช่ “pipeline เอกสารสุดหรู” แค่สามอย่าง:

  1. ให้เมธอดรับรายการไฟล์ (ผสม DOCX, XLSX, PDF ใดก็ได้) แล้วคืน PDF หนึ่งไฟล์
  2. ชี้ตรรกะเดียวกันไปที่โฟลเดอร์และให้มันหารายการไฟล์เอง
  3. ดึงช่วงหน้าจาก binder ที่เสร็จแล้วโดยไม่ต้องทำการรวมใหม่ทั้งหมด

นั่นคือทั้งหมด ถ้าไลบรารีทำไม่ได้สามอย่างนี้อย่างสะอาด ฉันก็ไม่ต้องการรู้อะไรเลย

การตั้งค่า

  • .NET 6.0 หรือใหม่กว่า
  • GroupDocs.Merger for .NET 24.10+ (รับไลเซนส์ชั่วคราว เพื่อไม่ให้มีลายน้ำประเมิน)
  • โฟลเดอร์ที่มีเอกสารผสมใดก็ได้ตามที่คุณมักจะรวมด้วยมือ
dotnet add package GroupDocs.Merger

แค่นี้พอสำหรับ dependencies ไม่ต้องใช้คอนเวอร์เตอร์ภายนอก ไม่ต้องติดตั้ง Office แบบ headless ไม่ต้องใช้ไลบรารีจัดการ PDF เพิ่มเติม

ขั้นตอนที่ 1 — ให้โฟลเดอร์เป็นอินพุต

ฉันเริ่มที่นี่เสมอเพราะเป็นจุดเริ่มต้นที่เป็นจริง ในการปฏิบัติ สิ่งอื่น (handler การอัปโหลด, งาน ingest อีเมล, dump ยามค่ำคืนจากฝ่ายการเงิน) จะวางไฟล์จำนวนมากลงในไดเรกทอรี และโค้ดของฉันต้องจัดการกับสิ่งที่พบ

// 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 ออกมา เนื่องจาก pipeline ของฉันต้องการ PDF เสมอ ฉันจึงทำให้ PDF ใด ๆ ที่มีอยู่ในโฟลเดอร์ได้ตำแหน่ง 0

สองเรื่องที่ควรกล่าวถึง:

  • ToLowerInvariant() เพราะวันหนึ่งคู่ค้าจะส่ง REPORT.PDF มาและฟิลเตอร์ที่ตรวจเฉพาะตัวพิมพ์เล็กจะทิ้งไฟล์นั้นไปโดยเงียบ ๆ
  • ThenBy(f) มีไว้เพื่อทำให้ผลลัพธ์เป็นแบบ deterministic หากไม่มีมัน การรันสองครั้งบนโฟลเดอร์เดียวกันอาจต่างกันตามสภาพไฟล์ซิสเต็ม

ขั้นตอนที่ 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 ถือไฟล์แฮนด์เดิลของแหล่งข้อมูล; หากลืม dispose ตัวทำงานในโฟลเดอร์ดรอปจะล้มเหลวในการลบอินพุตของตนเองในที่สุด
  • JoinOptions ว่างเปล่าที่นี่เพราะค่าเริ่มต้นคือสิ่งที่ฉันต้องการ 95% ของเวลา หากคุณต้องการปรับค่า นั่นคือที่ที่ช่วงหน้า, การหมุน, และตำแหน่งแทรกอยู่
  • เมื่อ Excel เข้าสู่ binder การจัดหน้า‑ต่อ‑ชีตจะกำหนดโดยพื้นที่พิมพ์ของเวิร์กบุ๊กต้นทาง หาก XLSX ของคุณออกมาที่ 38 หน้าแต่คุณต้องการสามหน้า การแก้คือในสเปรดชีต ไม่ได้อยู่ใน JoinOptions

การตรวจสอบความสมเหตุสมผลที่ฉันใส่เสมอหลังการบันทึก:

using var verify = new Merger(outputPath);
Console.WriteLine($"Result pages: {verify.GetDocumentInfo().PageCount}");

สองวินาทีของโค้ดที่จับบั๊ก “ภาคผนวกที่ถูกทิ้งไปโดยเงียบ” มากกว่าการทดสอบใด ๆ ที่ฉันเขียน

ขั้นตอนที่ 3 — ดึงส่วนหนึ่งออกในภายหลัง

คำขอติดตามที่ฉันได้รับทุกครั้ง: “คุณส่งหน้าแรกให้ฉันได้ไหม?” หรือ “ลูกค้าแค่ต้องการลายเซ็น” การสร้าง binder ใหม่เพื่อส่งสองหน้าเป็นเรื่องโง่ — Extract ทำได้โดยตรง

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 อยู่แล้ว — ไม่ต้องทำรอบแปลง

ก่อน vs. หลัง, อย่างตรงไปตรงมา

สิ่งที่ฉันเคยทำ ด้วย Merger.Join
เวลาต่อสัญญา 5–10 นาทีของการคลิก น้อยกว่า 30 วินาทีตั้งแต่ต้นจนจบ
ความล้มเหลวทั่วไป หน้าตามลำดับผิด, ไม่มีใครสังเกต ตามลำดับที่ไฟล์ลิสต์บอก, ทำซ้ำได้เสมอ
การขยายเป็น 100/วัน ไม่ได้ — ต้องจ้างคน คนทำงานหนึ่งคน, เบื่อส่วนใหญ่ของเวลา
โค้ดที่ต้องดูแล หน้า Confluence ชื่อ “Binder Process v4” คลาสเดียว, ~70 บรรทัด
ผลลัพธ์ สาม PDF และคำอธิษฐาน Binder หนึ่งไฟล์, พร้อมจำนวนหน้าที่บันทึกได้

แถวที่ฉันใส่ใจที่สุดคือแถว “ความล้มเหลว” การรวมด้วยมือล้มเหลวโดยเงียบ; โค้ดที่บันทึกจำนวนหน้าล้มเหลวอย่างชัดเจน

เรื่องจริงจากทีมเทคโนโลยีกฎหมายขนาดเล็ก

สตาร์ทอัพสองคนที่ฉันทำงานด้วยมีพนักงานพาราลีกัลที่เช้าเริ่มด้วยการประกอบสัญญา Word, Excel, PDF แล้วต่อในแอป แล้วอัปโหลดไปยัง DocuSign ใช้เวลาประมาณแปดนาทีต่อแพคเกจ ซึ่งที่ 30 แพคเกจต่อวัน คือเช้าวันของเธอทั้งหมด

พวกเขาใส่วิธีสแกนโฟลเดอร์ลงในบริการแบ็กเอนด์ที่กำลังดูอีเมลเข้าของพวกเขา 20 วินาทีต่อแพคเกจ พร้อมบรรทัดล็อกที่บอกจำนวนหน้า พาราลีกัลจึงย้ายไปตรวจสอบสัญญาแทนการประกอบใด ๆ อีกต่อไป ไม่มีใครส่ง binder ที่ลำดับผิดอีกแล้ว — ไม่ใช่เพราะไลบรารีเป็นเวทมนตร์ แต่เพราะรายการไฟล์ระบุอย่างชัดเจนในโค้ดและคุณสามารถ diff ได้

string folder = @"C:\IncomingContracts";
string output = @"C:\Processed\ContractPackage.pdf";

var files = CreatePdfBinderFromFolder(folder, output);
Console.WriteLine($"Package created: {files}");

นั่นคือการบูรณาการทั้งหมด ทุกอย่างที่อยู่ข้างต้น (listener อีเมล, เส้นทางจัดเก็บ) มีอยู่แล้ว

สิ่งที่ฉันไม่ได้ต้องการวันนี้แต่จะต้องใช้พรุ่งนี้

ไลบรารีเดียวกันทำหลายอย่างที่ฉันไม่ได้กล่าวถึงเพราะบทความจะยืดยาว ตามลำดับที่ฉันเคยใช้:

  • Watermarks on the output สำหรับสแตมป์ “DRAFT” บนสำเนาก่อนเซ็น
  • Page rotation สำหรับสแกนที่เข้ามาเป็นแนวข้าง
  • Custom page ordering เมื่อลำดับแหล่งไม่ตรงกับลำดับการส่งมอบ
  • PDF encryption สำหรับสิ่งที่ส่งให้คู่ค้าภายนอก

ทั้งหมดนั้นอยู่หลัง API Merger เดียวกัน เอกสาร docs มีรายการเต็ม — ฉันแค่ต้องการชี้ให้เห็นว่า “merge” เป็นตัวเริ่มต้นราคาถูกและส่วนอื่น ๆ พร้อมให้ใช้เมื่อคุณต้องการ

สิ่งที่ฉันจะบอกตัวเองในอดีต

ถ้าคุณกำลังจะเขียนขั้นตอน DOCX‑to‑PDF ของคุณเองเพราะ “แค่เมธอดเดียว” ให้หยุดก่อน การแปลงคือส่วนที่ค่อย ๆ เสื่อมสภาพ — ฟีเจอร์ใหม่ของ Office, การจัดการภาพสแกน, ฟอนต์ฝัง, ฯลฯ ให้สิ่งอื่นเป็นเจ้าของพื้นผิวนั้น แล้วใช้บ่ายวันศุกร์ของคุณทำอย่างที่ไม่ใช่การจัดเรียงชื่อไฟล์

ต่อไปควรทำอะไรต่อ

  • Temporary license — จำเป็นสำหรับเอาต์พุตไร้ลายน้ำ
  • Advanced merging options — JoinOptions, ตัวเลือกการบันทึก, การบีบอัด
  • Supported formats — มากกว่าสามรูปแบบที่แสดงที่นี่
  • Sample projects on GitHub — รวมถึงโปรเจกต์นี้

ลิงก์ที่เป็นประโยชน์