💡 Повний робочий приклад доступний на GitHub:
scrub-office-document-pii-dotnet

Старий спосіб був болючим

Рутина виглядає так: відкриваєте документ, File → Info → Check for Issues → Inspect Document, ставите галочки, Remove All, зберігаєте під новим ім’ям, закриваєте, відкриваєте наступний. Через сорок файлів хтось помічає, що Inspect Document також видалив Title, який використовується індексом записів, а копія, відправлена годину тому, все ще містила ідентифікатор схвалювача SharePoint, бо файл був збережений іншим застосунком, який записав це поле назад.

Видалення PII‑метаданих — це можливість GroupDocs.Metadata для .NET, яка програмно стирає властивості, що містять ідентифікаційну інформацію, з Office‑документів і повідомляє, що залишилося. Ручна процедура провалюється за трьома пунктами: не масштабується понад кілька файлів, працює «все або нічого» щодо полів, і не залишає запису про те, що було видалено. У цій статті показано .NET‑версію тієї ж роботи, по одній групі властивостей за раз.

Корисно знати, що саме знаходиться всередині. Word‑файл, який пройшов ревізію, зазвичай містить Author і LastSavedBy з Windows‑акаунту того, хто його зберіг, Manager і Company з корпоративного шаблону, лічильник ревізій, TotalEditingTime, мітку часу LastPrinted та лічильники для гілок коментарів. Додаєте SharePoint — отримуєте ідентифікатори схвалювачів, шляхи робочих процесів, URI типу вмісту та шаблон, з якого створено документ. Усе це не видно на сторінці, а подорожує в одному файлі.

Є кращий спосіб

Все в GroupDocs.Metadata для .NET проходить через один пошуковий движок властивостей. RemoveProperties приймає лямбда‑вираз над MetadataProperty, видаляє кожну властивість, яку лямбда приймає, і повертає кількість видалених. FindProperties виконує ту ж лямбду без запису. Властивості також несуть теги, тому Tags.Person.Creator ідентифікує поля типу автора у різних форматах і пакетах, замість того щоб порівнювати буквальні назви, які різняться між застосунками.

Отже, є три варіанти очищення замість однієї кнопки: проходження за тегом для ідентифікаційних полів, проходження за іменами для сімей, таких як коментарі та ревізії, і Sanitize(), коли нічого не має залишитися. Усі три повертають числа, і саме ці числа роблять проходження аудиторським.

Новий спосіб: предикат для кожної групи властивостей

Крок 1 – Очистити імена

Чотири перевірки тегів охоплюють групу ідентифікації. Описові поля залишаються недоторканими — це відрізняє наш підхід від Document Inspector з опцією Remove All:

using (var metadata = new Metadata(inputPath))
{
    if (metadata.FileFormat == FileFormat.Unknown) return 0;
    var affected = metadata.RemoveProperties(p =>
        p.Tags.Contains(Tags.Person.Creator) ||
        p.Tags.Contains(Tags.Person.Editor) ||
        p.Tags.Contains(Tags.Person.Manager) ||
        p.Tags.Contains(Tags.Corporate.Company));
    metadata.Save(outputPath);
    return affected;
}

Перевірка FileFormat.Unknown — це захист, який зберігає нульовий результат чесним: без неї нечитаємий файл і чистий файл виглядатимуть однаково для викликаючого коду.

Крок 2 – Очистити сім’ї навколо них

Коментарі, ревізії та серверні поля не мають тегу, тому предикат порівнює імена. Час редагування — це група, яку найчастіше забувають, і саме вона вказує, як був створений документ:

using (var metadata = new Metadata(inputPath))
{
    if (metadata.FileFormat == FileFormat.Unknown) return 0;
    var affected = metadata.RemoveProperties(p =>
        p.Name != null && (
            p.Name.Contains("Revision") ||
            p.Name.Contains("TrackedChange") ||
            p.Name.Contains("LastPrinted") ||
            p.Name.Contains("TotalEditingTime") ||
            p.Name.Contains("EditTime")));
    metadata.Save(outputPath);
    return affected;
}

Пошук підрядка навмисний: він ловить CommentsCount разом з Comment і TotalEditingTime разом з EditTime, без потреби підтримувати точний список імен для кожного формату. Прохід для SharePoint — це той самий виклик, лише з додаванням Server, Workflow, Approver, ContentType і Template.

Крок 3 – Видалити все, а потім перевірити результат

На межі довіри один виклик замінює чотири проходження:

using (var metadata = new Metadata(inputPath))
{
    if (metadata.FileFormat == FileFormat.Unknown) return 0;
    var affected = metadata.Sanitize();
    metadata.Save(outputPath);
    return affected;
}

Потім йде частина, якої немає в ручній процедурі. Перевірка‑сканування повторно використовує предикати видалення через FindProperties і розподіляє залишки у два списки:

foreach (var p in props)
{
    var value = p.InterpretedValue?.ToString() ?? p.Value?.ToString() ?? string.Empty;
    if (string.IsNullOrWhiteSpace(value)) continue;
    if (value == "0" || value == "0.0") continue;

    var entry = $"{p.Name}={value}";
    var name = p.Name ?? string.Empty;
    if (name.StartsWith("Comment") || name.StartsWith("Revision"))
        report.ContentLevelLeaks.Add(entry);
    else
        report.MetadataLeaks.Add(entry);
}

MetadataLeaks має бути порожнім, щоб файл вважався «очищеним». ContentLevelLeaks — це лише інформація: коментарі Word і автори відстежуваних змін знаходяться в word/document.xml, що є вмістом тіла, і їхнє очищення вимагає бібліотеки для редагування вмісту, наприклад Aspose.Words, а не API метаданих.

Чому не просто викликати Sanitize() для всього?

Тому що більшість документів ще використовується. Sanitize() стирає кожен виявлений пакет, включаючи Title, Subject і Keywords — поля, від яких залежать системи записів і пошукові індекси. Використовуйте цільові проходження, доки файл циркулює всередині організації, залишайте описові метадані активними, а повне стирання залишайте для копії, що виходить за межі компанії.

Порівняння: до і після

Ручна інспекція GroupDocs.Metadata для .NET
Вибірковість Remove All, включаючи описові поля один предикат на групу властивостей
Покриття лише поля, які показує діалог всі пакети, які виявляє бібліотека, включаючи кастомні OOXML‑частини
Запис відсутній кількість змінених елементів, повернена кожною операцією
Перевірка відкриття файлу та візуальний огляд сканування FindProperties з метаданими та списками рівня вмісту
Пакет з 200 файлів 200 кліків один цикл, п’ять операцій, один рядок логу на файл

Рядок, що змінює поведінку, — це запис. Коли кожен проход повертає кількість, процес очищення перестає бути «кроком, який хтось пам’ятає виконати», а стає даними, які можна перевіряти в конвеєрі: поріг у тесті, поле в аудиторській таблиці, умова, що зупиняє нічну задачу. Це саме той рядок, який ручна процедура не може забезпечити за будь‑якої дисципліни.

Реальний приклад: хук перед відправкою

Портал підтримки дозволяє співробітникам прикріплювати документи до заявок клієнтів. Тепер обробник вкладень спочатку виконує проходження ідентифікації та серверне проходження, записує обидві кількості у заявку і запускає перевірку витоку на збереженій копії. Якщо список витоків метаданих не порожній, завантаження відхиляється з повідомленням, що вказує проблемне поле, тож користувач дізнається про це одразу, а не після того, як документ потрапить до клієнта.

Два нюанси роблять такий хук практичним. Проходження записують у новий шлях, тому оригінал залишається в особистому сховищі співробітника і нічого не знищується автоматично. А кількості зберігаються в записі заявки поруч із вкладенням, що означає: відповідь на питання «що було видалено з цього документа» — це збережене число, а не припущення про типову поведінку конвеєра.

Коли я вперше застосував цю перевірку до реального шаблону, вона повернула значення Manager, яке проходження ідентифікації видалило секунди раніше, а корпоративний шаблон записав назад під час збереження. Виклик видалення працював точно за документацією; проблема була в оточуючому конвеєрі, і лише повторне зчитування це показало.

Що ще можна робити з GroupDocs.Metadata?

Той самий предикат‑движок використовується для читання. Порівняння властивостей між двома версіями документа виявляє зміни власника та повторне авторство поза процесом ревізії, а огляд очищення метаданих розповідає, де інтерактивний інструмент ще може доповнювати API‑підхід. Оскільки система тегів охоплює формати, предикат ідентифікації, написаний тут, працює і з PDF, і з зображеннями, і з аудіофайлами без змін.

Ця портативність варто планувати. Правило очищення, написане як лямбда над MetadataProperty, — це звичайний C#, тому його можна розмістити в спільній бібліотеці, протестувати юніт‑тестами на тестових документах і застосовувати в будь‑якому сервісі: кінцевій точці експорту, запланованій задачі записів або кроці збірки, що очищає вкладення документації перед випуском. Правила залишаються в одному місці; змінюються лише місця виклику.

Висновок

Чотири цільових проходження, одне повне Sanitize(), одне сканування‑перевірка. Такий набір покриває практичний діапазон для Office‑документів: зберігайте описові метадані, доки файл у обігу, стирайте все, коли він виходить за межі, і доводьте результат у будь‑якому випадку. Клонуйте приклад, запустіть його на документі, який пройшов реальну ревізію, і перегляньте кількість змінених елементів. Зазвичай їх більше, ніж очікувалося — і в цьому весь сенс.

Додаткові ресурси