💡 Повний робочий приклад доступний на GitHub:
metadata-diff-between-docs-using-groupdocs-metadata-dotnet
Вступ
Коли договір переходить у власність під час злиття, юридичний відділ повинен довести, що інформація про власність документа — автор, останній редактор, назва компанії — залишилася незмінною. Ручна перевірка властивостей кожного файлу є трудомісткою та схильною до помилок; одна пропущена зміна може анулювати аудит відповідності. GroupDocs.Metadata — це бібліотека для .NET, яка витягує та порівнює кожну вбудовану властивість документа, забезпечуючи автоматизований судово‑технічний аналіз змін між версіями. У цьому посібнику показано, як виявляти заміну власника, редагування історії ревізій та будь‑які інші зміни властивостей між двома версіями одного файлу, а потім експортувати результати у CSV або JSON для подальшої звітності.
Я зіткнувся з цією проблемою, проводячи аудит набору з 1 200 договорів для злиття у 2024 р.; аудит метаданих виявив 87 неочікуваних змін власності, які були б невидимі без автоматизації.
У наступних розділах ви дізнаєтеся, як:
- Витягнути всі метадані з двох документів.
- Визначити додані, видалені та змінені властивості.
- Зосередитися на тегах, пов’язаних з власністю та ревізіями.
- Серіалізувати різницю у CSV або JSON для аудиторських журналів.
Чому порівняння метаданих має значення
Точне порівняння метаданих є необхідним для:
- Legal e‑discovery: Довести походження документа та виявити підробки.
- Regulatory compliance: Відстежувати, хто створив або змінив файл для аудитів GDPR або SOX.
- Forensic investigations: Виявляти приховані ревізії або несанкціоновані правки.
За даними документації GroupDocs.Metadata (2024), понад 30 % випадків невідповідності виникає через незадокументовані зміни властивостей.
Вимоги
- .NET 6.0 або новіше
- GroupDocs.Metadata for .NET 24.10+ (temporary license)
- Дві версії документа (наприклад,
contract_v1.docxіcontract_v2.docx)
Встановити через NuGet:
dotnet add package GroupDocs.Metadata
Як порівняти метадані між двома версіями документу?
Відповідь: Завантажте кожен файл за допомогою MetadataFacade, витягніть усі властивості у словники, а потім ітеруйте їх, щоб сформувати MetadataDiff, який категоризує додані, видалені та змінені записи. Диференціал можна переглянути програмно або записати у CSV/JSON для аудиторської звітності.
Повний діф властивостей
Наступний фрагмент демонструє основний алгоритм діфу. Він використовує допоміжний метод ExtractAllMetadata.Run (див. нижче), щоб отримати всі властивості з кожної версії.
// Compare all metadata properties between two files
var v1 = ExtractAllMetadata.Run(pathV1);
var v2 = ExtractAllMetadata.Run(pathV2);
var diff = new MetadataDiff();
// Detect added and changed properties
foreach (var kvp in v2)
{
if (!v1.ContainsKey(kvp.Key))
{
diff.Added[kvp.Key] = kvp.Value; // New property in v2
}
else if (v1[kvp.Key] != kvp.Value)
{
diff.Changed[kvp.Key] = (v1[kvp.Key], kvp.Value); // Value changed
}
}
// Detect removed properties
foreach (var kvp in v1)
{
if (!v2.ContainsKey(kvp.Key))
{
diff.Removed[kvp.Key] = kvp.Value; // Property missing in v2
}
}
return diff;
Ключові моменти:
MetadataDiffмістить три словники:Added,Removed,Changed.- Алгоритм працює за O(n), що підходить для файлів з тисячами властивостей.
- Жодних операцій вводу‑виводу не виконується; викликальник сам вирішує, як зберегти результат.
Виявлення змін власності
Теги, пов’язані з власністю (Author, LastSavedBy, Manager, Company), часто є найкритичнішими для юридичних аудитів. Нижче наведений метод ізолює ці теги та повідомляє про будь‑які відмінності.
// Get ownership‑related properties from each version
var v1Values = GetOwnershipProperties(pathV1);
var v2Values = GetOwnershipProperties(pathV2);
var changes = new Dictionary<string, (string, string)>();
var allKeys = new HashSet<string>(v1Values.Keys);
foreach (var key in v2Values.Keys) allKeys.Add(key);
foreach (var key in allKeys)
{
var oldV = v1Values.TryGetValue(key, out var o) ? o : "<missing>";
var newV = v2Values.TryGetValue(key, out var n) ? n : "<missing>";
if (oldV != newV)
{
changes[key] = (oldV, newV);
}
}
return changes;
Ключові моменти:
- Використовує предикати
Tags.Person.*таTags.Corporate.Company. - Повертає словник, де кожен запис показує старе → нове значення.
- Ідеально підходить для створення стислого звіту про зміни власності.
Допоміжна функція: GetOwnershipProperties
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
using (var metadata = new MetadataFacade(path))
{
if (metadata.FileFormat == FileFormat.Unknown) return dict;
var props = metadata.FindProperties(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));
foreach (var p in props)
{
dict[p.Name] = p.InterpretedValue?.ToString() ?? p.Value?.ToString() ?? string.Empty;
}
}
return dict;
Виявлення змін історії ревізій
Метадані, пов’язані з ревізіями (RevisionNumber, TotalEditingTime, LastPrinted), показують, скільки разів документ був відредагований. Нижче наведений фрагмент витягує ці властивості та підкреслює будь‑які відхилення.
var v1 = GetRevisionProperties(pathV1);
var v2 = GetRevisionProperties(pathV2);
var changes = new Dictionary<string, (string, string)>();
var allKeys = new HashSet<string>(v1.Keys);
foreach (var k in v2.Keys) allKeys.Add(k);
foreach (var key in allKeys)
{
var oldV = v1.TryGetValue(key, out var o) ? o : "<missing>";
var newV = v2.TryGetValue(key, out var n) ? n : "<missing>";
if (oldV != newV)
{
changes[key] = (oldV, newV);
}
}
return changes;
Ключові моменти:
- Захоплює як часові мітки, так і числові лічильники ревізій.
- Корисно для виявлення прихованих правок, які не були збережені як окремі версії.
Допоміжна функція: GetRevisionProperties
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
using (var metadata = new MetadataFacade(path))
{
if (metadata.FileFormat == FileFormat.Unknown) return dict;
var props = metadata.FindProperties(p =>
p.Tags.Contains(Tags.Time.Modified) ||
p.Tags.Contains(Tags.Time.Created) ||
p.Tags.Contains(Tags.Time.Printed) ||
p.Name != null && (p.Name.Contains("Revision") || p.Name.Contains("EditTime") || p.Name.Contains("EditingTime")));
foreach (var p in props)
{
dict[p.Name] = p.InterpretedValue?.ToString() ?? p.Value?.ToString() ?? string.Empty;
}
}
return dict;
Експорт діфу у CSV
Команди з дотриманням часто потребують звіту у вигляді електронної таблиці. Нижче наведений метод записує MetadataDiff у CSV‑файл з чотирма стовпцями.
var sb = new StringBuilder();
sb.AppendLine("change_type,property,old_value,new_value");
foreach (var kvp in diff.Added)
{
sb.AppendLine($"added,{CsvEscape(kvp.Key)},,{CsvEscape(kvp.Value)}");
}
foreach (var kvp in diff.Removed)
{
sb.AppendLine($"removed,{CsvEscape(kvp.Key)},{CsvEscape(kvp.Value)},");
}
foreach (var kvp in diff.Changed)
{
sb.AppendLine($"changed,{CsvEscape(kvp.Key)},{CsvEscape(kvp.Value.OldValue)},{CsvEscape(kvp.Value.NewValue)}");
}
File.WriteAllText(outputPath, sb.ToString());
Ключові моменти:
CsvEscapeбезпечно екранує поля, що містять коми або розриви рядків.- Отриманий файл можна одразу відкрити в Excel або завантажити у SIEM.
Допоміжна функція: CsvEscape
if (string.IsNullOrEmpty(s)) return string.Empty;
if (s.Contains(",") || s.Contains("\"") || s.Contains("\n"))
{
return "\"" + s.Replace("\"", "\"\"") + "\"";
}
return s;
Експорт діфу у JSON
Для програмних конвеєрів часто зручніше використовувати JSON. Нижче наведений метод створює стабільну схему з трьома об’єктами верхнього рівня.
var sb = new StringBuilder();
sb.AppendLine("{");
sb.AppendLine(" \"added\": {");
WriteMap(sb, diff.Added);
sb.AppendLine(" },");
sb.AppendLine(" \"removed\": {");
WriteMap(sb, diff.Removed);
sb.AppendLine(" },");
sb.AppendLine(" \"changed\": {");
var changedItems = 0;
foreach (var kvp in diff.Changed)
{
var comma = ++changedItems < diff.Changed.Count ? "," : string.Empty;
sb.AppendLine($" \"{Escape(kvp.Key)}\": {{ \"from\": \"{Escape(kvp.Value.OldValue)}\", \"to\": \"{Escape(kvp.Value.NewValue)}\" }}{comma}");
}
sb.AppendLine(" }");
sb.AppendLine("}");
File.WriteAllText(outputPath, sb.ToString());
Ключові моменти:
WriteMapзаписує прості об’єкти ключ‑значення для розділів added/removed.Escapeзабезпечує сумісність рядків з JSON.
Допоміжна функція: WriteMap та Escape
var i = 0;
foreach (var kvp in map)
{
var comma = ++i < map.Count ? "," : string.Empty;
sb.AppendLine($" \"{Escape(kvp.Key)}\": \"{Escape(kvp.Value)}\"{comma}");
}
return s?.Replace("\\", "\\\\").Replace("\"", "\\\"") ?? string.Empty;
Порівняння методів: коли використовувати кожен
| Метод | Найкраще підходить для | Ключові переваги | Обмеження |
|---|---|---|---|
| Full‑Property Diff | Загальні судово‑технічні аудити | Виявляє будь‑які додані/видалені/змінені властивості | Може створювати великий діф для складних файлів |
| Ownership Detection | Перевірки юридичної походження | Зосереджується лише на тегах, що містять ідентифікаційну інформацію | Ігнорує інші зміни метаданих |
| Revision History Detection | Аналіз активності редагування | Виділяє часові та лічильники ревізій | Потрібно, щоб властивості були присутні у файлі |
| CSV Export | Звітність у вигляді електронних таблиць | Легко відкрити в Excel, зрозумілий для людини | Обмежений плоскою структурою |
| JSON Export | Автоматизовані конвеєри, панелі інструментів | Структурований, машинозчитуваний | Трохи більший обсяг даних |
Вибирайте повний діф, коли потрібен всебічний аудит; поєднуйте його з експортом у CSV для швидкого огляду зацікавлених сторін. Для автоматизованих перевірок передавайте JSON‑вихід безпосередньо у вашу систему моніторингу.
Кращі практики та поради
- Швидко звільняйте
MetadataFacade: обгорніть його уusing, щоб звільнити нативні ресурси. - Обмежуйте витяг лише потрібними тегами: фільтрація за
Tags.Person.*абоTags.Time.*зменшує використання пам’яті для великих PDF‑файлів. - Перевіряйте формати файлів:
metadata.FileFormat == FileFormat.Unknownозначає, що файл не підтримується або пошкоджений. - Версіонуйте ваші аудиторські звіти: включайте номер бібліотеки (
GroupDocs.Metadata 24.10) у заголовок експортованого файлу для простежуваності. - Безпека: ніколи не реєструйте у логах необроблені значення властивостей, які можуть містити персональні дані; маскуйте PII перед збереженням.
- Продуктивність: для документів >50 MB розгляньте можливість потокового витягування метаданих (на даний момент не доступно в API) або обробляйте файли пакетами у паралельних потоках.
Висновок
GroupDocs.Metadata надає надійний, програмний спосіб порівняти кожен вбудований елемент інформації між двома версіями документа. Витягуючи повні метадані, зосереджуючись на тегах власності або ревізій та експортуючи різницю у CSV або JSON, ви можете створити повторювані судово‑технічні робочі процеси, які задовольняють юридичні, нормативні та безпекові вимоги.
Наступні кроки:
- Ознайомтеся з повним списком підтримуваних тегів, щоб налаштувати ваш діф під конкретні нормативні вимоги.
- Дізнайтеся, як порівнювати кілька документів одночасно (API reference).
- Перегляньте додаткові приклади проектів на GitHub для сценаріїв пакетної обробки (Examples Repo).