💡 Повний робочий приклад доступний на 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‑документів: зберігайте описові метадані, доки файл у обігу, стирайте все, коли він виходить за межі, і доводьте результат у будь‑якому випадку. Клонуйте приклад, запустіть його на документі, який пройшов реальну ревізію, і перегляньте кількість змінених елементів. Зазвичай їх більше, ніж очікувалося — і в цьому весь сенс.