Технічна стаття

Розгалужені коментарі у файлах Excel з Delphi та HotXLS

Створіть робочу книгу з Delphi, додайте примітку до комірки за допомогою класичного API коментарів — і Excel 365 збереже ваш текст у розділі «Нотатки» (Notes): це стара жовта рамка без поля «Відповісти», без аватара автора та кнопки вирішення. HotXLS записує справжні розгалужені коментарі Excel 365 за допомогою методів AddThreadedComment та AddThreadedReply у движку XLSX, створюючи окремий файл xl/threadedComments/threadedCommentN.xml для кожного аркуша та реєструючи кожного автора у загальному списку користувачів на рівні книги, тож створене вашим кодом обговорення буде тим самим об'єктом, на який рецензент відповідатиме в Excel

Ця різниця є важливою, оскільки ці дві функції схожі лише зовні. Нотатка — це просто плаваюче текстове поле; розгалужений коментар — це запис розмови зі стабільними ідентифікаторами, деревом відповідей, реєстром авторів та прапорцем вирішення проблеми — тобто всім тим, на чому будується процес рецензування. У цій статті ми спочатку розглянемо структуру OOXML, оскільки порівняння двох моделей зберігання даних зніме будь-які питання щодо відмінностей у поведінці Excel

Чому мої коментарі відображаються як нотатки в Excel 365?

Коротка відповідь: два покоління коментарів використовують одну сітку, але зберігаються в абсолютно різних частинах файлу. Застаріла модель, на яку орієнтований метод AddComment, зберігає текст в xl/comments1.xml (тип вмісту application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml) і позиціонує спливаючу підказку за допомогою супутнього файлу малюнка VML — xl/drawings/vmlDrawing1.vml. Ця пара з'явилася ще до появи стрічкового інтерфейсу; Excel 365 досі зчитує її коректно, але відображає результат як «нотатку» (note) і не надає інтерфейсу для відповідей, оскільки в структурі збереження немає даних для зв'язування. Автор є просто текстовим рядком, позиція — фігурою VML, і це вся модель

Розгалужені коментарі, представлені в Excel 365, взагалі ігнорують рівень 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 (присутній лише у відповідях) вказує на ідентифікатор батьківського коментаря. Excel відновлює дерево обговорення шляхом зіставлення parentT та t; в XML немає вкладеності, лише плоский список та посилання

Розділ користувачів (person) — це частина, про яку часто забувають при ручній реалізації. Кожен authorId у розгалуженому коментарі повинен посилатися на запис <person> в xl/person/person.xml, зв'язаний із залежностями книги та власним типом вмісту — інакше Excel просто не зможе показати ім'я автора. HotXLS обробляє цю реєстрацію автоматично: метод AddThreadedComment шукає автора в списку Persons книги за відображуваним ім'ям і створює новий запис із новим GUID, якщо автора не знайдено, тому послідовні коментарі одного автора використовують один ідентифікатор. Зв'язки робочого аркуша отримують відношення threadedComments, зв'язки книги — відношення person, а маніфест типів вмісту оновлюється обома записами без необхідності писати для цього додатковий код

Створення ланцюжка відповідей за допомогою AddThreadedComment та AddThreadedReply

Метод AddThreadedComment створює корінь обговорення: його ParentId залишається порожнім, що визначає його як початок гілки. Метод AddThreadedReply приймає параметр ParentRef (адресу комірки, де знаходиться корінь), знаходить корінь за цією прив'язкою та записує Id кореня в поле ParentId нової відповіді. Обидва методи повертають новий об'єкт 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 відбулася автоматично «під капотом». Відкрийте збережений файл в Excel 365, і прив'язка до комірок, порядок відповідей та два різних учасники будуть активні: натисніть «Відповісти», і Excel додасть нове повідомлення в ту саму гілку, яку створив ваш код

Читання розмови назад: поведінка при круговому циклі (round-trip)

HotXLS підтримує повний цикл роботи (round-trip) з розгалуженими коментарями, починаючи з версії v2.122.0: повторне відкриття збереженого пакета відновлює списки розмов кожного аркуша та список учасників книги з відповідних файлів, тому ідентифікатори, зв'язки відповідей та імена авторів повністю зберігаються при зчитуванні та записі. Парсер зчитує person.xml перед завантаженням вмісту аркушів, що дозволяє зіставити кожен authorId з ім'ям користувача вже при відкритті; зчитані ідентифікатори авторів зберігаються без змін, тому при роботі з файлом між вашим кодом та Excel зберігаються стабільні профілі авторів незалежно від кількості циклів збереження

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;

Тут свідомо простежується плоска структура зберігання з посиланнями: колекція ThreadedComments перераховує відповіді в порядку їхнього розташування у файлі, а порожній ParentId визначає початок кожної гілки. Якщо вам потрібна саме деревоподібна форма — наприклад, для експорту логу рецензування — спочатку згрупуйте записи за посиланням Ref, а потім зв'яжіть ParentId з Id всередині групи кожної комірки. Така логіка збереження зчитаної структури є частиною загальної політики бібліотеки; стаття про збереження тем, списків розширень та ланцюжка обчислень без втрат описує, як цей принцип застосовується до всього архіву документа

Стан вирішення, мітки часу та профілі авторів

Смислове навантаження рецензування несуть три атрибути, і всі вони доступні для запису в об'єктах. Властивість Done мапується на атрибут done — Excel показує вирішену гілку згорнутою з посиланням «Повторно відкрити». Властивість DateTime є міткою часу створення в UTC за стандартом ISO-8601 (атрибут dT); HotXLS підставляє фіксоване значення, якщо поле залишене порожнім, щоб файл залишався валідним, але реальний час робить історію обговорення читаною, тому його варто вказувати. З боку профілю користувача кожен об'єкт TXLSXPerson містить ProviderId (зазвичай None для локально створених записів) та UserId, куди можна записати email або 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) не моделюються, тому текст із символом @ зберігається як звичайний текст. Для автоматично згенерованого звіту це рідко є проблемою, але якщо ваш процес критично залежить від сповіщень через згадування, плануйте додавання таких згадувань вручну в Excel. Ідентифікація авторів у розділі person гарно поєднується з полями походження на рівні книги, які описані в метаданих книги та властивостях документа, де зберігається решта історії аудиту файлу

Як старі версії Excel обробляють розгалужені коментарі?

Excel 2016 і старіші версії з'явилися раніше за розгалужені коментарі, тому вони просто ігнорують невідомі їм частини файлу. Колі сам Excel 365 зберігає розгалужену розмову, він також записує копію у вигляді застарілого коментаря (тіньову нотатку з текстом «[Threaded comment]...»), щоб старіші версії показували хоч щось у цільовій комірці. HotXLS створює лише нові розгалужені коментарі без застарілої тіні, тому файл, записаний вашим кодом, не матиме жодних коментарів при відкритті в Excel 2016. Якщо ваші клієнти використовують версії до Office 365 і коментарі мають бути видимими для них, сумісним варіантом залишається класичне API AddComment; уникайте одночасного використання обох моделей для однієї комірки без ретельного тестування у всіх версіях Excel, оскільки спосіб відображення нотатки та гілки відповідей на одній комірці залежить виключно від переглядача

Інше жорстке обмеження стосується самого формату файлу. Розгалужені коментарі є структурою OOXML і не мають аналогів у BIFF8, тому сторона .xls у HotXLS не підтримує розгалужені коментарі — книга у застарілому бінарному форматі може використовувати лише класичні нотатки. На практиці вибір простий: якщо ви створюєте файл .xlsx для Excel 365 чи веб/мобільних переглядачів Excel, використовуйте AddThreadedComment для отримання повноцінних обговорень; для орієнтації на Excel 2016 або формат .xls викликайте AddComment; а при аналізі отриманого файлу перевіряйте як колекцію ThreadedComments, так і Comments, оскільки книга могла зберегти обидва типи записів. Весь інтерфейс API для обох поколінь задокументований на сторінці продукту HotXLS Delphi Excel Component