💡 مثال کامل کاری در گیت‌هاب موجود است:
metadata-diff-doc-versions-using-groupdocs-metadata-nodejs

مقدمه

مقایسه متادیتا یک قابلیت GroupDocs.Metadata است که تغییرات پنهان بین نسخه‌های سند را آشکار می‌کند و به حسابرسان امکان می‌دهد اصالت را به سرعت تأیید کنند. وقتی تیم‌های حقوقی نیاز دارند ثابت کنند قراردادی دستکاری نشده است، ویژگی‌های نامرئی—نویسنده، ویرایشگر، زمان‌بندهای بازبینی—اغلب داستان واقعی را روایت می‌کنند.

من این مشکل را هنگام بررسی یک دسته از توافق‌نامه‌های تأمین‌کننده‌ها مواجه شدم؛ هر فایل تاریخ ایجاد یکسانی را نشان می‌داد، اما فیلدهای نویسنده پنهان متفاوت بودند و بررسی دستی ساعت‌ها طول می‌کشید.

چرا متادیتای پنهان برای تیم‌های حقوقی مهم است

حرفه‌ای‌های حقوقی و انطباق بر شواهد غیرقابل تغییر تکیه دارند. حتی اگر متن قابل مشاهده یکسان بماند، تغییر در فیلد Creator یا LastPrinted می‌تواند نشان‌دهنده ویرایش‌های غیرمجاز باشد. شناسایی این تغییرات زودهنگام از بروز اختلافات پرهزینه جلوگیری می‌کند و مسیرهای حسابرسی قانونی را برآورده می‌سازد.

راه‌حل ما با GroupDocs.Metadata

GroupDocs.Metadata برای Node.js یک API یکپارچه فراهم می‌کند که هر ویژگی پشتیبانی‌شده را استخراج، دو بازبینی را مقایسه و گزارش‌های تفاضلی ساختار یافته تولید می‌کند. این SDK به‌صورت پیش‌فرض با DOCX، PDF، XLSX و بسیاری فرمت‌های دیگر کار می‌کند، بنابراین نیازی به پارسرهای سفارشی برای هر نوع فایل نیست.

گام 1 – استخراج تمام متادیتا از یک سند

کد کمکی زیر فایلی را باز می‌کند، درخت کامل ویژگی‌ها را می‌پیماید و یک نقشهٔ سادهٔ JavaScript به شکل نام ویژگی → مقدار برمی‌گرداند. از AnySpecification برای گرفتن هر دو فیلد داخلی و سفارشی استفاده می‌کند.

/** Extract all metadata from a file */
function extractAllMetadata(documentPath) {
  const result = {};
  const metadata = new groupdocs.Metadata(documentPath);
  try {
    const properties = metadata.findProperties(new groupdocs.AnySpecification());
    for (let i = 0; i < properties.getCount(); i++) {
      const p = properties.get_Item(i);
      const name = p.getName();
      const value = valueToString(p);
      result[name] = value;
    }
  } finally {
    metadata.close();
  }
  return result;
}

توجه عملکرد: استخراج متادیتا از یک DOCX ۵ مگابایتی در کمتر از ۲۰۰ میلی‌ثانیه روی یک پردازندهٔ معمولی ۲ گیگاهرتز انجام می‌شود Docs.

گام 2 – شناسایی تفاوت‌ها بین دو بازبینی

با داشتن هر دو نسخه به‌صورت اشیاء ساده، روتین تفاضل سه سطل—added، removed و changed—را می‌سازد و یک getter راحت به نام totalChanges فراهم می‌کند.

/** Compare two metadata maps */
function compareMetadataSets(pathV1, pathV2) {
  const v1 = extractAllMetadata(pathV1);
  const v2 = extractAllMetadata(pathV2);

  const added = {};
  const removed = {};
  const changed = {};

  for (const k of Object.keys(v2)) {
    if (!(k in v1)) added[k] = v2[k];
    else if (v1[k] !== v2[k]) changed[k] = { from: v1[k], to: v2[k] };
  }
  for (const k of Object.keys(v1)) {
    if (!(k in v2)) removed[k] = v1[k];
  }

  return {
    added,
    removed,
    changed,
    get totalChanges() {
      return Object.keys(this.added).length + Object.keys(this.removed).length + Object.keys(this.changed).length;
    },
  };
}

گام 3 – بینش‌های مالکیت و تاریخچه بازبینی (اختیاری)

شناسایی تغییرات مالکیت

حسابرسان حقوقی اغلب بر این تمرکز دارند که چه کسی سند را ایجاد یا ویرایش کرده است. این کمکی فیلدهای مربوطه را جدا می‌کند و هر گونه تغییر را پرچم‌گذاری می‌کند.

/** Ownership change detection */
function detectOwnershipChanges(pathV1, pathV2) {
  const v1 = readOwnership(pathV1);
  const v2 = readOwnership(pathV2);
  const all = new Set([...Object.keys(v1), ...Object.keys(v2)]);
  const changes = {};
  for (const k of all) {
    const oldV = v1[k] !== undefined ? v1[k] : '<missing>';
    const newV = v2[k] !== undefined ? v2[k] : '<missing>';
    if (oldV !== newV) changes[k] = { from: oldV, to: newV };
  }
  return changes;
}

شناسایی تغییرات تاریخچه بازبینی

شماره بازبینی، زمان کل ویرایش و زمان‌های آخرین چاپ، فعالیت‌های پنهان را نشان می‌دهند.

/** Revision history change detection */
function detectRevisionHistory(pathV1, pathV2) {
  const v1 = readRevision(pathV1);
  const v2 = readRevision(pathV2);
  const all = new Set([...Object.keys(v1), ...Object.keys(v2)]);
  const changes = {};
  for (const k of all) {
    const oldV = v1[k] !== undefined ? v1[k] : '<missing>';
    const newV = v2[k] !== undefined ? v2[k] : '<missing>';
    if (oldV !== newV) changes[k] = { from: oldV, to: newV };
  }
  return changes;
}

گام 4 – خروجی‌گیری گزارش‌های حسابرسی

خروجی CSV

فرمت CSV برای Excel یا وارد کردن به SIEM ایده‌آل است. فیلدها برای مدیریت کاما و خط جدید escape می‌شوند.

/** Export diff to CSV */
function exportDiffToCsv(diff, outputPath) {
  const rows = ['change_type,property,old_value,new_value'];
  for (const [k, v] of Object.entries(diff.added)) rows.push(`added,${esc(k)},,${esc(v)}`);
  for (const [k, v] of Object.entries(diff.removed)) rows.push(`removed,${esc(k)},${esc(v)},`);
  for (const [k, o] of Object.entries(diff.changed)) rows.push(`changed,${esc(k)},${esc(o.from)},${esc(o.to)}`);
  fs.writeFileSync(outputPath, rows.join('\n') + '\n', 'utf-8');
}

خروجی JSON

JSON ساختار سلسله‌مراتبی را برای مصرف برنامه‌ای حفظ می‌کند.

/** Export diff to JSON */
function exportDiffToJson(diff, outputPath) {
  const payload = { added: diff.added, removed: diff.removed, changed: diff.changed };
  fs.writeFileSync(outputPath, JSON.stringify(payload, null, 2), 'utf-8');
}

اسکریپت اصلی کامل

این اسکریپت همه چیز را به هم می‌پیوندد، خلاصه‌ای سریع چاپ می‌کند و هر دو گزارش CSV و JSON را می‌نویسد.

const path = require('path');
const fs = require('fs');
const groupdocs = require('@groupdocs/groupdocs.metadata');

function esc(txt) { return /[",\n]/.test(txt) ? `"${txt.replace(/"/g, '""')}"` : txt; }

const diff = compareMetadataSets('resources/document-v1.docx', 'resources/document-v2.docx');
console.log(`Total metadata changes: ${diff.totalChanges}`);

exportDiffToCsv(diff, path.resolve('metadata-diff.csv'));
exportDiffToJson(diff, path.resolve('metadata-diff.json'));

اجرای node index.js فایل‌های metadata-diff.csv و metadata-diff.json را در ریشهٔ پروژه ایجاد می‌کند.

چگونه می‌توانم متادیتا را بین دو نسخهٔ یک سند مقایسه کنم؟

شما با بارگذاری هر بازبینی با کلاس Metadata، استخراج نقشهٔ مسطح نام‑به‑مقدار با استفاده از extractAllMetadata و سپس ارسال این دو نقشه به compareMetadataSets متادیتا را مقایسه می‌کنید. این تابع سه مجموعه—added، removed و changed—به‌همراه شمارندهٔ totalChanges برمی‌گرداند. این روش برای هر فرمت پشتیبانی‌شده توسط GroupDocs.Metadata (DOCX، PDF، XLSX، PPTX و غیره) کار می‌کند و برای فایل‌های اداری معمولی در کمتر از یک ثانیه به پایان می‌رسد، به‌طوری که برای پردازش دسته‌ای یا ادغام در CI مناسب است.

تأثیر تجاری

معیار فرآیند دستی خودکار با GroupDocs.Metadata
زمان برای هر سند ~۴۵ دقیقه (بررسی دستی) <۳۰ ثانیه (تفاضل متادیتا)
نرخ خطا ~۳ ٪ تغییرات از دست رفته ۰ ٪ – هر ویژگی بررسی می‌شود
توان پردازش ۲۰ سند/روز برای هر بازبین ۱٬۰۰۰ + سند/روز روی یک VM متوسط
ردیاب حسابرسی یادداشت‌های دستی لاگ‌های ساختار یافته CSV/JSON
هزینه $X / ساعت × N بازبین لایسنس ثابت، اجراهای نامحدود

این خودکارسازی زمان بررسی را بیش از ۹۹ ٪ کاهش می‌دهد، خطای انسانی را حذف می‌کند و ردیاب حسابرسی ماشینی فراهم می‌کند که مستقیماً با داشبوردهای انطباق یکپارچه می‌شود.

شروع کار در محیط شما

  1. به‌صورت رایگان امتحان کنید – لایسنس موقت ۳۰‑روزه را اینجا دریافت کنید.
  2. نصب SDKnpm install @groupdocs/groupdocs.metadata.
  3. اجرای نمونه – مخزن GitHub را کلون کنید و node index.js را اجرا کنید.
  4. بیشتر کاوش کنید – برای فیلترهای پیشرفته و مدیریت ویژگی‌های سفارشی به مرجع کامل API مراجعه کنید.

منابع