مقاله فنی

کامنت‌های رشته‌ای در فایل‌های اکسل با دلفی و HotXLS

یک کارپوشه را از دلفی تولید کنید، یک سلول را با API کلاسیک کامنت‌گذاری یادداشت کنید، و اکسل ۳۶۵ متن شما را در بخش یادداشت‌ها (Notes) فایل می‌کند: یک جعبه زرد رنگ به سبک قدیمی بدون فیلد پاسخ (Reply)، بدون آواتار نویسنده، بدون دکمه حل (resolve). کامپوننت HotXLS کامنت‌های رشته‌ای واقعی Excel 365 را از طریق AddThreadedComment و AddThreadedReply در موتور XLSX می‌نویسد، بخش اختصاصی xl/threadedComments/threadedCommentN.xml را برای هر برگ صادر می‌کند و هر نویسنده را در بخش person در سطح کارپوشه ثبت می‌کند، به طوری که گفتگویی که کد شما می‌نویسد همان شیئی است که یک بازبین در اکسل به آن پاسخ می‌دهد

این تمایز اهمیت دارد زیرا این دو ویژگی فقط شبیه به هم هستند. یک یادداشت (note) یک جعبه متنی شناور است؛ یک کامنت رشته‌ای (threaded comment) یک رکورد گفتگو با شناسه‌های پایدار، یک درخت پاسخ، یک ثبت نویسنده و یک پرچم حل‌شده است — چیزهایی که یک جریان کاری بررسی در واقع روی آنها اجرا می‌شود. این مقاله ابتدا ساختار OOXML را بررسی می‌کند، زیرا وقتی دو مدل ذخیره‌سازی را در کنار هم ببینید، هرگونه تفاوت رفتاری در اکسل دیگر مرموز نخواهد بود

چرا کامنت‌های من در اکسل ۳۶۵ به عنوان یادداشت نشان داده می‌شوند؟

پاسخ کوتاه: دو نسل از کامنت‌ها یک شبکه را به اشتراک می‌گذارند و آنها در بخش‌های کاملاً متفاوتی از پکیج زندگی می‌کنند. مدل قدیمی — مدلی که متد AddComment هدف قرار می‌دهد — متن را در xl/comments1.xml (با نوع محتوای application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml) ذخیره می‌کند و بالن را از طریق یک بخش ترسیم VML همراه، یعنی xl/drawings/vmlDrawing1.vml موقعیت‌دهی می‌کند. آن جفت قبل از ریبون ایجاد شده‌اند؛ اکسل ۳۶۵ همچنان آن را به وفاداری می‌خواند، اما نتیجه را به عنوان یک "یادداشت" رندر می‌کند و هیچ رابط کاربری برای رشته کامنت روی آن ارائه می‌دهد، زیرا چیزی در ذخیره‌سازی برای رشته‌ای شدن وجود ندارد. نویسنده یک رشته نمایشی است، موقعیت یک شکل VML است و این کل مدل است

کامنت‌های رشته‌ای که با اکسل ۳۶۵ معرفی شدند، لایه VML را کاملاً نادیده می‌گیرند. هر برگ دارای گفتگو، بخش xl/threadedComments/threadedCommentN.xml خود را در زیر نوع محتوای application/vnd.openxmlformats-officedocument.spreadsheetml.threadedComments+xml حمل می‌کند و کارپوشه یک بخش واحد xl/person/person.xml (با نوع محتوای ...spreadsheetml.people+xml) دارد که هر شرکت‌کننده را یک بار فهرست می‌کند. HotXLS هر دو مدل را در یک کاربرگ ارائه می‌دهد: متد AddComment نوشتن جفت قدیمی را ادامه می‌دهد، متد AddThreadedComment بخش‌های جدید را می‌نویسد. اگر خروجی شما به صورت یادداشت ظاهر می‌شود، شما API اول را صدا زده‌اید و دومی را می‌خواسته‌اید. مدل قدیمی هنوز ابزار مناسبی برای برخی از کارها است — مقاله قبلی در مورد کامنت‌های سلول و هایپرلینک‌ها در جریان‌های کاری بررسی آن را به طور عمیق، از جمله بخش XLS پوشش می‌دهد — اما هیچ چیز در آن به یک رشته گفتگو ارتقا نمی‌یابد

آنچه HotXLS در پکیج می‌نویسد

HotXLS هر پاسخ را به عنوان یک شیء TXLSXThreadedComment مدل‌سازی می‌کند که لنگر سلول (Ref)، یک شناسه به سبک GUID (با نام Id)، یک ParentId اختیاری، یک AuthorId که از طریق لیست شخص حل می‌شود، یک آینه DisplayName، بدنه متن، یک پرچم Done و یک DateTime با فرمت ISO-8601 را حمل می‌کند. در هنگام ذخیره، هر ورودی به یک عنصر <threadedComment> تبدیل می‌شود که ویژگی t آن شناسه را نگه می‌دارد، ویژگی dT آن مهر زمان را نگه می‌دارد و ویژگی parentT آن — که فقط در پاسخ‌های فرزند وجود دارد — شناسه والد را نام می‌برد. اکسل درخت گفتگو را با مطابقت دادن parentT به t بازسازی می‌کند؛ هیچ تو در تویی در XML وجود ندارد، فقط یک لیست مسطح به علاوه مراجع است

بخش person تکه‌ای است که اکثر پیاده‌سازی‌های دستی آن را فراموش می‌کنند. هر authorId در یک کامنت رشته‌ای باید به یک ورودی <person> در xl/person/person.xml منتهی شود که در روابط کارپوشه متصل شده و نوع محتوای اختصاصی خود را دارد — در غیر این صورت اکسل هیچ نام نمایشی برای نشان دادن ندارد. HotXLS ثبت‌نام را به طور خودکار انجام می‌دهد: متد AddThreadedComment نویسنده را در لیست Persons کارپوشه بر اساس نام نمایشی جستجو می‌کند و در صورت عدم وجود، ورودی با یک GUID جدید ایجاد می‌کند، بنابراین کامنت‌های متوالی توسط یک نویسنده، یک هویت را به اشتراک می‌گذارند. روابط کاربرگ رابطه threadedComments را دریافت می‌کنند، روابط کارپوشه رابطه person را دریافت می‌کنند و مانیفست انواع محتوا هر دو را بدون اینکه هیچ کدام در کد شما ظاهر شوند، دریافت می‌کند

ساختن یک زنجیره پاسخ با AddThreadedComment و AddThreadedReply

متد AddThreadedComment ریشه گفتگو را ایجاد می‌کند: ویژگی ParentId آن خالی می‌ماند، که این همان چیزی است که آن را به عنوان بالای رشته علامت‌گذاری می‌کند. متد AddThreadedReply یک پارامتر ParentRef — مرجع سلولی که ریشه در آن زندگی می‌کند — را می‌گیرد، ریشه را در آن لنگر پیدا می‌کند و ParentId پاسخ جدید را با Id ریشه ثبت می‌کند. هر دو کلاس TXLSXThreadedComment جدید را برمی‌گردانند، بنابراین می‌توانید مهر زمان یا وضعیت حل‌شده را روی شیء که به تازگی ایجاد کرده‌اید تنظیم کنید:

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Root: TXLSXThreadedComment;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('forecast.xlsx');
    Sheet := Book.Sheets[0];

    // Conversation root; ParentId stays empty
    Root := Sheet.AddThreadedComment(8, 3,
      'Q3 figure looks low against the pipeline export', 'Maria Ortiz');
    Root.DateTime := '2026-07-06T09:12:00Z';

    // Replies anchor at the same cell; ParentRef names the root cell
    Sheet.AddThreadedReply(8, 3, Root.Ref,
      'Pipeline export missed the EMEA renewals, re-running', 'Jan Kowalski');
    Sheet.AddThreadedReply(8, 3, Root.Ref,
      'Confirmed, refreshed figure lands tomorrow', 'Maria Ortiz');

    Book.SaveAs('forecast-reviewed.xlsx');
  finally
    Book.Free;
  end;
end;

دو جزئیات در آن لیست شایسته توجه است. اول اینکه Root.Ref به جای یک رشته دست‌ساز پاس داده شده است: مرجعی که کتابخانه ایجاد کرده تضمین می‌شود با آنچه AddThreadedReply جستجو می‌کند مطابقت داشته باشد، که یک کلاس کامل از باگ‌های لنگر را حذف می‌کند. دوم اینکه هر دو نویسنده به عنوان نام‌های نمایشی ساده پاس داده شدند — ثبت شخص، هویت‌های GUID و اتصال authorId همگی در پشت این فراخوانی‌ها اتفاق افتادند. فایل ذخیره شده را در اکسل ۳۶۵ باز کنید و لنگراندازی سلول، ترتیب پاسخ‌ها و دو شرکت‌کننده متمایز همگی زنده هستند: روی Reply کلیک کنید و اکسل به همان رشته‌ای که کد شما شروع کرده پاسخ اضافه می‌کند

خواندن مجدد یک گفتگو: رفتار رفت و برگشت

کامپوننت HotXLS کامنت‌های رشته‌ای را از نسخه v2.122.0 رفت و برگشت (round-trip) می‌کند: باز کردن مجدد پکیج ذخیره شده، لیست گفتگوی هر برگ و لیست شرکت‌کنندگان کارپوشه را از روی بخش‌ها بازسازی می‌کند، بنابراین شناسه‌ها، لینک‌های پاسخ و نام‌های نمایشی در یک چرخه کامل خواندن←نوشتن زنده می‌مانند. خواننده فایل person.xml را قبل از هرگونه محتوای برگ تجزیه می‌کند، که این همان چیزی است که اجازه می‌دهد هر authorId در زمان بارگذاری به یک نام نمایشی منتهی شود؛ شناسه‌های شخص تجزیه شده به همان صورت حفظ می‌شوند و دوباره تولید نمی‌گردند، بنابراین کارپوشه‌ای که بین کد شما و اکسل در حرکت است، هویت‌های نویسنده پایدار را در تعداد دلخواهی از رفت و برگشت‌ها حفظ می‌کند

ذخیره‌سازی به شکل لیست مسطح به علاوه مراجع به طور تعمدی در اینجا خود را نشان می‌دهد: ThreadedComments پاسخ‌ها را به ترتیب بخش شمارش می‌کند و یک ParentId خالی هر ریشه را شناسایی می‌کند. اگر به شکل درختی نیاز دارید – مثلاً برای صادر کردن لاگ بررسی – ابتدا بر اساس Ref گروه‌بندی کنید و سپس ParentId را به Id در گروه هر سلول زنجیره کنید. این رفتارِ حفظ-آنچه-تجزیه-شده یک نمونه از سیاست گسترده‌تر در کتابخانه است؛ مقاله در مورد رفت و برگشت بدون اتلاف تم‌ها، لیست‌های پسوند و زنجیره محاسبات چگونگی اعمال همین اصل را برای بقیه پکیج پوشش می‌دهد

var
  i: Integer;
  Cmt: TXLSXThreadedComment;
begin
  Book.Open('forecast-reviewed.xlsx');
  Sheet := Book.Sheets[0];
  for i := 0 to Sheet.ThreadedComments.Count - 1 do
  begin
    Cmt := Sheet.ThreadedComments[i];
    if Cmt.ParentId = '' then
      Writeln('Root at ', Cmt.Ref, ' by ', Cmt.DisplayName, ': ', Cmt.Text)
    else
      Writeln('  reply by ', Cmt.DisplayName, ': ', Cmt.Text);
  end;
  Writeln('Participants: ', Book.Persons.Count);
end;

وضعیت حل‌شده، مهر زمان و هویت نویسنده

سه ویژگی معنای بررسی را حمل می‌کنند و هر سه در اشیاء برگشتی قابل نوشتن هستند. ویژگی Done به ویژگی done نگاشت می‌شود — اکسل یک رشته حل‌شده را به صورت جمع‌شده با یک لینک Reopen نشان می‌دهد. ویژگی DateTime مهر زمان ایجاد UTC با استاندارد ISO-8601 است (dT)؛ HotXLS زمانی که آن را خالی بگذارید یک نگهدارنده ثابت را جایگزین می‌کند تا بخش همیشه معتبر بماند، اما یک مهر زمان واقعی همان چیزی است که تاریخچه رشته را خوانا می‌کند، بنابراین آن را تنظیم کنید. در سمت هویت، هر TXLSXPerson مقدار ProviderId — معمولاً None برای ورودی‌های نوشته شده به صورت محلی — و یک UserId را که می‌توانید با یک UPN یا ایمیل در زمانی که برنامه شما آن را می‌داند پر کنید، در دسترس قرار می‌دهد:

var
  Root: TXLSXThreadedComment;
  Person: TXLSXPerson;
begin
  Root := Sheet.ThreadedComments.FindAt('D9');
  if Root <> nil then
    Root.Done := True;   // saved as done="1"; Excel shows the thread resolved

  Person := Book.Persons.FindByDisplayName('Maria Ortiz');
  if Person <> nil then
    Person.UserId := 'maria.ortiz@example.com';
end;

یک مرز برای بیان صریح: HotXLS عنصر <mentions> را خالی می‌نویسد. رکوردهای @-mention — مکانیزمی که نام یک همکار را در متن کامنت روشن می‌کند و یک اعلان را در Microsoft 365 فعال می‌سازد — مدل‌سازی نشده‌اند، بنابراین متنی حاوی علامت @ به عنوان متن ساده ذخیره می‌شود. برای یک کارپوشه بررسی تولید شده، این به ندرت یک باخت است، اما اگر جریان کاری شما به اعلان‌های mention وابسته است، برنامه‌ریزی کنید که یک انسان آنها را در اکسل اضافه کند. هویت نویسنده در بخش شخص به طور طبیعی با فیلدهای منشأ در سطح کارپوشه که در متادیتای کارپوشه و ویژگی‌های سند پوشش داده شده‌اند جفت می‌شود، جایی که بقیه دنباله ممیزی یک فایل تولید شده در آن قرار دارد

نسخه‌های قدیمی‌تر اکسل با کامنت‌های رشته‌ای چه می‌کنند؟

اکسل ۲۰۱۶ و نسخه‌های قدیمی‌تر قبل از مدل رشته‌ای ایجاد شده‌اند و به سادگی بخش‌هایی را که متوجه نمی‌شوند نادیده می‌گیرند. وقتی خود اکسل ۳۶۵ یک گفتگوی رشته‌ای را ذخیره می‌کند، یک سایه کامنت قدیمی نیز می‌نویسد — یک یادداشت نگهدارنده با متن "[Threaded comment]..." — دقیقاً به این دلیل که نسخه‌های قدیمی‌تر چیزی را در سلول لنگر نشان دهند. HotXLS فقط بخش‌های رشته‌ای را بدون سایه قدیمی صادر می‌کند، بنابراین فایلی که توسط کد شما نوشته شده است، در هنگام باز شدن در اکسل ۲۰۱۶ هیچ نشانگر کامنتی را نشان نمی‌دهد. اگر مخاطبان شما شامل نسخه‌های قبل از ۳۶۵ هستند و یادداشت باید در آنجا قابل مشاهده باشد، API قدیمی AddComment همچنان کانال سازگار باقی می‌ماند؛ فقط از روی هم چیدن هر دو مدل روی یک سلول بدون تست کردن هر نسخه اکسلی که به آن ارسال می‌کنید خودداری کنید، زیرا نحوه تطبیق یک یادداشت و یک رشته کامنت روی یک سلول توسط خواننده، تصمیم خواننده است و نه شما

مرز سخت دیگر خود فرمت فایل است. کامنت‌های رشته‌ای یک ساختار OOXML بدون همتای BIFF8 هستند، بنابراین بخش .xls در HotXLS هیچ API رشته‌ای ندارد — کارپوشه‌ای که باید در فرمت باینری قدیمی باقی بماند به یادداشت‌های کلاسیک محدود است. در عمل، جدول تصمیم‌گیری کوتاه است: در هنگام نوشتن .xlsx برای اکسل ۳۶۵ یا خواننده‌های وب/موبایل اکسل، از AddThreadedComment استفاده کنید و گفتگوهای واقعی دریافت کنید؛ در هنگام هدف‌گذاری اکسل ۲۰۱۶ یا .xls، از AddComment استفاده کنید و مدل یادداشت را بپذیرید؛ در هنگام ممیزی فایلی که دریافت کرده‌اید، هر دو مورد ThreadedComments و Comments را بررسی کنید، زیرا کارپوشه‌ای که در هر دو جهان زندگی کرده است می‌تواند به طور قانونی هر دو را حمل کند. کل سطح API برای هر دو نسل در صفحه محصول HotXLS Delphi Excel Component مستند شده است