💡 דוגמה מלאה עובדת זמינה ב‑GitHub:
office-metadata-pii-cleanup-nodejs

Introduction

נקודת קצה להעלאה מקבלת קובץ DOCX מעובד ומאחסנת אותו בטיקיט של לקוח. הטקסט תקין. המאפיינים אינם: הקובץ מכיל את שם האדם שכתב אותו, את העמית ששמר אותו לאחרונה, את מנהל המחלקה מתבנית החברה, ובגלל שהקובץ הגיע מ‑SharePoint, את המאשר שהחתים עליו.

מתקן מטא‑נתונים הוא סקריפט קטן שמוחק את המאפיינים האלה לפני שהקובץ נשמר ולאחר מכן בודק את העבודה שלו. מדריך זה בונה מתקן כזה ב‑Node.js עם GroupDocs.Metadata, בארבעה שלבים: בחירת מאפיינים לפי תג, בחירתם לפי שם, מחיקת הכול כאשר הסלקטיביות מפסיקה לעזור, ואימות מה נשאר. כל שלב הוא כמה שורות, והסקריפט המוגמר הוא מתחת למאה שורות.

Why Metadata Sanitization Matters

הנתונים מצטברים ללא בחירה של אף אחד. Word כותב Author ו‑LastSavedBy מחשבון מערכת ההפעלה בכל שמירה, שומר מונה גרסאות, עוקב אחרי TotalEditingTime, ורושם LastPrinted. שרתי מסמכים מוסיפים נתיבי זרימת עבודה, מזהי מאשרים, ו‑URI של סוגי תוכן בעת הצ’ק‑אין. שום דבר מהזה אינו מופיע כשקוראים או מדפיסים את המסמך, ולכן ביקורת כתיב לא תופסת זאת.

היתרון ב‑Node.js במקום לעשות זאת ידנית הוא שהסקריפט מחזיר מספרים: כל קריאת הסרה מדווחת כמה מאפיינים נמחקו, והספירה הזו ניתנת לכתיבה ללוג, לאימות בבדיקה, או לצירוף לרשומה שהמסמך שייך אליה.

קיימת סיבה נוספת, פחות ברורה עד שמופעלת משימת אצווה. ניקוי ידני הוא החלטה שנעשית פעם אחת לכל קובץ על ידי מי שמטפל בו, ולכן שני אנשים שמנקים את אותו סוג מסמך יפיקו תוצאות שונות. סקריפט מתקן את הכלל במקום אחד: אותם ארבעה תגיות, אותן רשימות תתי‑מחרוזות, מיושמים באופן זהה בין אם התור מכיל שלושה קבצים או שלושה אלפים.

Prerequisites

החבילה היא Node.js דרך Java, ולכן המכונה צריכה סביבת זמן ריצה של Java לצד Node.

Installation

npm install @groupdocs/groupdocs.metadata

פרויקט הדוגמה קובע גרסה 26.7 ומוסיף ערך overrides שמגדיר nan ל‑^2.22.0, מה שמאפשר לבנייה של הקישוריות הטבעית על גרסאות Node נוכחיות. ללא קובץ רישיון הספרייה פועלת במצב הערכה, שהוא מספיק כדי לעקוב אחרי כל שלב כאן.

Step 1 - Select properties by what they mean

שמות המאפיינים שונים בין פורמטים וחבילות, ולכן הכלל הראשון תואם לפי תגיות במקום. ContainsTagSpecification מקבלת תגית ומתאימה לכל מאפיין שנושא אותה; .or() ממזג ספציפיקציות לאחת.

const T = groupdocs.Tags;
const spec = new groupdocs.ContainsTagSpecification(T.getPerson().getCreator())
  .or(new groupdocs.ContainsTagSpecification(T.getPerson().getEditor()))
  .or(new groupdocs.ContainsTagSpecification(T.getPerson().getManager()))
  .or(new groupdocs.ContainsTagSpecification(T.getCorporate().getCompany()));
const affected = metadata.removeProperties(spec);
metadata.save(outputPath);

נקודות מפתח:

  • ארבע תגיות מכסות את קבוצת הזהות: יוצר, עורך, מנהל, ושדה החברה של החברה.
  • כותרת, נושא ומילות מפתח נשארים ללא שינוי, כך שמאגר רישומים שמפתח עליהם ממשיך לעבוד.
  • removeProperties מחזיר את מספר הפריטים שהושפעו במקום ערך בוליאני.

עטפו את כל הקוד ב‑try/finally עם metadata.close() ב‑finally. הקישור מחזיק את הקובץ פתוח עד אז, ולולאה ללא סגירה תיגמר ממספר המופעים.

Step 2 - Select properties by name

שורות תגובה, מוני גרסאות ושדות שרת אינם נושאים תג. עבורם, WithNameSpecification(needle, false) מתאים לכל מאפיין שהשם שלו מכיל את המחרוזת, ובונה של ארבע שורות מחבר אחת לכל תת‑מחרוזת:

let spec = null;
for (const needle of needles) {
  const s = new groupdocs.WithNameSpecification(needle, false /* fullMatch */);
  spec = spec ? spec.or(s) : s;
}
return spec;

שלושה מעברים משתמשים בבונה הזה עם רשימות שונות. תחילה עוברים על תגובות:

const affected = metadata.removeProperties(
  nameContainsSpec(['Comment', 'Reviewer', 'Reviewed']));
metadata.save(outputPath);

קו זמן העריכה הוא הקבוצה שנוטה להישכח, והיא מתארת איך נוצר המסמך:

const affected = metadata.removeProperties(nameContainsSpec([
  'Revision', 'TrackedChange', 'LastPrinted', 'TotalEditingTime', 'EditTime',
]));
metadata.save(outputPath);

המעבר של SharePoint הוא אותו קריאה עם Server, Workflow, Approver, ContentType, ו‑Template. התאמה לפי תת‑מחרוזת היא מכוונת: היא תופסת CommentsCount יחד עם Comment בלי צורך ברשימת שמות מדויקת לכל פורמט.

Step 3 - Wipe everything when selectivity stops helping

לגרסה שמיועדת לצאת מהארגון, קריאה אחת מחליפה את ארבעת המעברים:

const affected = metadata.sanitize();
metadata.save(outputPath);

sanitize() מוחק כל חבילת מטא‑נתונים שהספרייה מזהה, כולל חלקי OOXML מותאמים, והספירה שלו בדרך כלל עולה על סכום המעברים הממוקדים. היא גם לוקחת את הכותרת והנושא, ולכן היא מתבצעת בגבול ולא בתוך לולאת ביקורת.

Step 4 - Verify, because a silent miss looks like success

הסריקה משתמשת באותן ספציפיקציות דרך findProperties, שקוראת ללא כתיבה. התוצאה היא אוסף Java, ולכן היא נבדקת לפי אינדקס:

const props = metadata.findProperties(tagSpec.or(nameSpec));
for (let i = 0; i < props.getCount(); i++) {
  const p = props.get_Item(i);
  const val = p.getValue && p.getValue();
  const value = val ? String(val.getRawValue ? val.getRawValue() : val) : '';
  if (!value || value === '0' || value === '0.0') continue;
  leaks.push(`${p.getName()}=${value}`);
}

הסינון של ריקים ואפסים מרוויח את מקומו. הוספתי אותו אחרי שריצה נכשלה על מונה גרסאות שנוקה ל‑0, והסריקה דיווחה במדויק על המאפיין שנשאר.

Complete Working Example

המאגר מחבר את שש הפונקציות ל‑index.js, שמיישם את הרישיון, מריץ כל מעבר על resources/pii-sample.docx, מאמת שכל קובץ פלט קיים, ולבסוף מאמת שהרשימה leaks ריקה. כשל באימות יוצא עם קוד חזרה שונה מאפס, ולכן כל התהליך משמש כביקורת ב‑CI ולא כהדגמה לקריאה בלבד.

פרט שכדאי להעתיק לגרסה שלכם: כל מעבר קורא את קובץ המקור עצמו וכותב פלט נפרד, במקום לשרשר קובץ מנוקה לקובץ הבא. כך ספירות ההשפעה נשארות עצמאיות, וקו הלוג של מעבר התגובות מדווח מה מצא כלל התגובות ולא מה נשאר אחרי כלל הזהות.

When should I run a targeted pass instead of sanitize()?

בכל פעם שהמסמך עדיין נמצא בשימוש. קבצים שמסתובבים בין מבקרים מסתמכים על כותרת, נושא ומילות מפתח לחיפוש וסיווג, ו‑sanitize() מסיר את שלושתם יחד עם הנתונים האישיים. הריצו את מעברי הזהות והתגובות במהלך שיתוף פעולה, שמרו על השדות התיאוריים, והשתמשו במחיקה מלאה רק עבור העותק שעוזב את הארגון.

Real-World Applications

Upload handler

נתיב Express מנקה קובץ מצורף לפני כתיבתו לאחסון, רושם את ספירות ההשפעה בטיקיט, ודוחה את ההעלאה אם רשימת הדליפות אינה ריקה.

Nightly export job

עובד עובר על תיקיית יצוא, מפעיל את מעברי הזהות והשרת, ומפסיק את המשימה במקום לרשום אזהרה כאשר מסמך עדיין מדווח על PII שארית.

Pre‑publication gate

שלב בנייה מנקה קבצי תיעוד לפני שחרור, משתמש ב‑sanitize() מכיוון שאין צורך לשמר את המטא‑נתונים בקבצים אלה.

Best Practices and Tips

  • תמיד כתבו לנתיב חדש כך שהמקור יישאר לשימוש במקרים של מחלוקת.
  • סגרו את אובייקט המטא‑נתונים ב‑finally, במיוחד בלולאות.
  • מיזוג ספציפיקציות עם .or() בעבודות אצווה; פתיחה אחת ושמירה אחת עדיפות על ארבע.
  • רשמו את ספירת ההשפעה לכל מעבר, כולל אפסים, כך שמבנה פורמט לא מוכר יופיע.

Troubleshooting Common Issues

ספירת ההשפעה היא אפס על מסמך שאתה יודע שהוא מזוהם
ודאו שהפורמט של הקלט מזוהה לפני שמסיקים שהקובץ נקי; קובץ שלא ניתן לקרוא וקובץ נקי מחזירים אפס.

בדיקת הדליפה מדווחת על מאפיינים שהסרת זה עתה
כוונו אותה לנתיב הפלט השמור, לא לנתיב הקלט. הסריקה קוראת את הקובץ שהיא מקבלת.

בלוני תגובה עדיין נראים ב‑Word
טקסט התגובה נמצא בגוף המסמך, לא בחבילת מטא‑נתונים. GroupDocs.Metadata מנקה את המאפיינים הקשורים לתגובות; מחיקת הבלונים עצמה דורשת ספריית עריכת תוכן כגון Aspose.Words.

Conclusion

ארבעה שלבים, שש פונקציות, סקריפט אחד שמדווח מה הוא עשה. ספציפיקציות תגיות מטפלות בקבוצת הזהות בין פורמטים, ספציפיקציות שם מכסות את המשפחות שהתגיות אינן מסווגות, sanitize() מטפל בגבול, וסריקת הדליפות הופכת את הכול לבדיקה. שיבטו את המאגר, הריצו אותו על מסמך שעבר סבב ביקורת אמיתי, והסתכלו על הספירות לפני שתחליטו אילו מעברים נדרשים בצינור שלכם.

Additional Resources