💡 דוגמה מלאה עובדת זמינה ב‑GitHub:
compare-encrypted-pdf-and-word-documents-dotnet
הדרך הישנה הייתה כואבת
שתי גרסאות של הסכם אספקה מגיעות לתיבת הדואר שלך. שתיהן מוגנות בסיסמה, שלכל אחת סיסמה שונה, ומישהו צריך עותק מסומן שמראה מה השתנה. ספריית ההשוואה שברשותך מצפה לקלט בטקסט רגיל, ולכן נדרש שלב נוסף בצינור העבודה: פענוח שני הקבצים לתיקייה זמנית, השוואת העותקים בטקסט רגיל, ואז לזכור למחוק אותם. תיקייה זמנית זו היא כעת הקשר החלש ביותר בתהליך שעוצב במיוחד מכיוון שהמסמכים רגישים.
קיימת גרסה שנייה של אותה בעיה שקל יותר לפספס. כמה צוותים מדלגים על תיקייה זמנית ומפענחים לזיכרון במקום, מה שמפתור את שאלת הניקוי אך לא את שאלת הפורמט: ממשק ה‑API לפענוח שונה לפי פורמט, ולכן תמיכה בגיליונות אלקטרוניים מוצפנים אחרי קבצי PDF מוצפנים דורשת אינטגרציה שנייה במקום שורת קוד שנייה.
העלות אינה נובעת בעיקר משיחת הפענוח – היא נובעת מכל מה שמסביב. יש לכתוב עותקים בטקסט רגיל במקום כלשהו, לנקות אותם בכל נתיב יציאה כולל נתיבי כשל, ולשמור אותם מחוץ לגיבויים ולדאמפים של קריסה. תוצאה שמופקת בדרך זו מגיעה גם היא ללא הגנה כברירת מחדל, ולכן הפלט של שני קבצים מוצפנים הופך לקובץ היחיד בשרשרת שכל אחד יכול לפתוח.
העלות האמיתית של מסלול הפענוח: תיקייה זמנית שמחזיקה עותקים בטקסט רגיל של מסמכים שהוצפנו מסיבה, עם ניקוי שחייב להיות נכון בכל נתיב שגיאה.
יש דרך טובה יותר
השוואה מוגנת בסיסמה היא יכולת של GroupDocs.Comparison עבור .NET שפותחת קבצי PDF, DOCX, XLSX ו‑PPTX מוצפנים במקום ומחליטה איזו סיסמה מגנה על תוצאת ההשוואה. אין שלב פענוח, אין קבצים ביניים בטקסט רגיל: הסיסמה נוסעת עם המסמך לתוך ההשוואה עצמה, כמאפיין ב‑LoadOptions.
לפני שנתחיל, תזדקק ל‑:
- .NET 8.0 SDK או גרסה מאוחרת יותר
- GroupDocs.Comparison 26.9.0 (רישיון זמני)
- שני מסמכים מוצפנים באותו פורמט, והסיסמאות שלהם
התקנה בפקודה אחת:
dotnet add package GroupDocs.Comparison
הדרך החדשה: מסמכים מוצפנים ישירות לתוך המשווה
הדוגמה למטה משווה שני קבצי PDF מוצפנים – המקור נפתח עם 1234, היעד עם 4321 – וכותבת קובץ תוצאה יחיד עם השינויים משולבים באופן פנימי. סיסמאות שונות במכוון, מכיוון שכאן מסתתר הטעות הראשונה.
שלב 1 – תן לכל מסמך את ה‑LoadOptions שלו
Comparer מחזיק מקור אחד וכל מספר של יעדים, וכל מסמך נושא את ההגנה שלו. סיסמת המקור מועברת לבנאי; סיסמת כל יעד מועברת לקריאה משלה של Add.
// LoadOptions אחד לכל מסמך – אפשרויות הבנאי פותחות רק
// את המקור, ולעולם אינן מגיעות ליעדים.
using var comparer = new Comparer("source.pdf",
new LoadOptions { Password = "1234" });
comparer.Add("target.pdf", new LoadOptions { Password = "4321" });
זהו הפרט שתופס אנשים. העברת LoadOptions יחיד לבנאי וציפייה שהוא יכסה גם את היעדים היא הדרך השכיחה ביותר שבה זה משתבש, ובגלל האופן שבו הכשל מתוזמן, הוא לא מודיע על עצמו במקום שבו היית מחפש אותו.
שלב 2 – החלט מה מגנה על התוצאה
CompareOptions.PasswordSaveOption בוחר את ההגנה של הפלט: None, Source, Target, או User. ברירת המחדל היא None, שמביאה בשקט שני קבצים מוצפנים לתוצאה אחת ללא הגנה.
// סימון פנימי, והתוצאה משתמשת בסיסמת המסמך המקור.
var options = new PdfCompareOptions
{
DisplayMode = PdfCompareOptions.ComparisonDisplayMode.Inline,
PasswordSaveOption = PasswordSaveOption.Source
};
comparer.Compare("Result/1-pdf-inline.pdf", options);
נקודות מפתח:
- PasswordSaveOption:
Sourceמשתמש בסיסמת המקור בפלט. בחרUserיחד עםSaveOptions.Passwordכדי להגדיר סיסמה חדשה במקום זאת. - ComparisonDisplayMode: נמצא בתוך
PdfCompareOptions, שמציע גםSideBySideו‑Interleaved.WordCompareOptionsמגדיר enum בעל שם זהה עם ערכים שונים, ולכן יש להשתמש בשם המלא.
שלב 3 – הגן על הפלט עם סיסמה משלו
כאשר ההבדל נשלח למבקרים שלא אמורים לדעת אף אחת מהסיסמאות המקוריות, PasswordSaveOption.User לוקח את הערך מ‑SaveOptions.Password במקום להשתמש באחת הקלטים.
var compareOptions = new PdfCompareOptions
{
DisplayMode = PdfCompareOptions.ComparisonDisplayMode.Inline,
PasswordSaveOption = PasswordSaveOption.User
};
var saveOptions = new SaveOptions { Password = "5678" };
comparer.Compare("Result/4-own-password.pdf", saveOptions, compareOptions);
שני האובייקטים מועברים ל‑overload של Compare עם שלושה פרמטרים. הגדרת SaveOptions.Password לבד לא משנה דבר – ערך ה‑enum הוא שמפעיל את סיסמת השמירה. התוצאה של קריאה זו נפתחת עם 5678 ומדחיקה את 1234.
למה ה‑try/catch סביב ה‑Comparer לא תופס סיסמה שגויה?
הבנאי לעולם לא פותח את המסמך. הוא רק רושם את הנתיב, וכך גם Add. שני המסמכים נקראים כאשר Compare רץ, ושם נזרקת PasswordProtectedFileException עם ההודעה Password is missing. סיסמה שגויה מתנהגת זהה: היא מתקבלת בשקט בזמן הבנייה, ואז נדחית מאוחר יותר ב‑Compare.
לכן יש להגן על קריאת ההשוואה, ולא על הבנאי. מצאתי זאת בדרך האיטית, עטיפת הבנייה ב‑try וצפייה בקובץ מוצפן שעובר דרכה לפני שהוא נכשל שלוש שורות לאחר מכן. המאגר מדפיס כל שלב, מה שמבהיר את הסדר בקריאה ראשונה:
using var comparer = new Comparer("source.pdf"); // מצליח
comparer.Add("target.pdf"); // מצליח
comparer.Compare("Result/unreachable.pdf"); // נזרק כאן
ציד‑בציד: לפני vs. אחרי
| לפני (פענוח קודם) | אחרי (GroupDocs.Comparison) | |
|---|---|---|
| שלבי צינור | פענוח שני הקבצים, השוואה, מחיקת העותקים הזמניים | השוואה |
| טקסט רגיל על הדיסק | שני עותקים, ניקוי בכל נתיב כשל | אין |
| הגנת תוצאה | שלב הצפנה נפרד | ערך אחד של PasswordSaveOption |
| כיסוי פורמטים | כלי פענוח לכל פורמט | LoadOptions.Password אחד ל‑PDF, DOCX, XLSX, PPTX |
| קוד נדרש | עוזר פענוח + השוואה | 4 שורות |
תכונות ההשוואה אינן משתנות בקלט מוצפן. מצבי תצוגה, דפי סיכום וזיהוי סגנון פועלים בדיוק כפי שהם פועלים עבור קבצים בטקסט רגיל, מכיוון שההגנה מטופלת במלואה בשכבת הטעינה.
השכבה הזו היא מה שהופכת את כיסוי הפורמטים לזול. LoadOptions.Password הוא מאפיין string פשוט, והמאפיין הזה פותח PDF, DOCX, XLSX ו‑PPTX – קוד הטעינה בדוגמת Word למטה הוא תו‑ב‑תו מהדוגמאות של PDF. רק מחלקת האפשרויות משתנה, ורק משום שכל פורמט מציג אפשרויות רינדור שונות. הוספת תמיכה בגיליונות אלקטרוניים מוצפנים לקוד שכבר משווה PDF מוצפנים אינה מוסיפה דבר לנתיב הטעינה.
דוגמה מהעולם האמיתי: עריכת חוזים בין משרדי עורכי דין
צוות משפטי מקבל כל גרסה של הסכם מוצפנת, עם סיבוב סיסמה בכל החלפה כדי שמקרה של דליפה לא יחשוף את כל ההיסטוריה. השותף המבצע ביקורת צריך מסמך מסומן אחד לכל סיבוב, ובתנאי שמירת נתונים העותק המסומן אינו יכול לשבת ללא הגנה על שיתוף קבצים.
שתי הגדרות מכסות זאת. כל מסמך נפתח עם LoadOptions משלו, ולכן סיבוב סיסמאות אינו דורש טיפול מיוחד, ו‑PasswordSaveOption.User נותן לכל diff שמופץ סיסמה משלו – סיסמה שמפענחת רק את ההשוואה ולא דבר אחר.
// גרסאות Word, כך שהשותף המבצע ביקורת יכול לקבל או לדחות כל עריכה.
var options = new WordCompareOptions
{
DisplayMode = WordCompareOptions.ComparisonDisplayMode.Revisions,
PasswordSaveOption = PasswordSaveOption.Source
};
using var comparer = new Comparer("round3.docx",
new LoadOptions { Password = "1234" });
comparer.Add("round4.docx", new LoadOptions { Password = "4321" });
comparer.Compare("Result/redline.docx", options);
מה עוד אפשר לעשות עם GroupDocs.Comparison?
- להשוות יותר משני מסמכים מוגנים: הוספת כמה יעדים מוצפנים להשוואה אחת, עבור פורמטים של Word והצגות.
- ליצור גרסאות Word מקומיות:
WordCompareOptions.ComparisonDisplayMode.Revisionsכותב שינויים שהמבקר יכול לקבל או לדחות ישירות ב‑Word. - לשלוט בטעינת משאבים חיצוניים: לחסום או לאפשר רשימות לבנות של הפניות מרוחקות שהמסמך נושא, באמצעות
LoadOptionsנוסף. - ליצור דף סיכום:
GenerateSummaryPageמוסיף סקירת שינויים למסמך הפלט.
סיכום
מסלול הפענוח מעולם לא היה קשור להשוואה – הוא היה קשור לספרייה שלא יכלה לקרוא את מה שהייתה ברשותך. הגדרת LoadOptions.Password לכל מסמך מסירה את תיקיית הזמנית, את נתיבי הניקוי, ואת ה‑diff הלא מוגן בסוף השרשרת. שלושה החלטות הן כל מה שנשאר: סיסמה לכל מסמך, PasswordSaveOption מפורש במקום ברירת המחדל None, וטיפול בשגיאות סביב Compare שבו הכשל מתרחש בפועל.
מוכן לאוטומציה של זרימת העבודה עם המסמכים שלך?
- נסה את גרסת ה‑API החינמית
- חקור את טעינת מסמכים מוגנים בסיסמה
- קרא את המדריך המלא להשוואת מסמכים מוגנים
- בדוק את פרויקט הדוגמה ב‑GitHub