Перейменуйте аркуш зі «Summary» на «Overview» у згенерованій книзі — і кожне внутрішнє гіперпосилання, що вказувало на Summary!A1, перестає кудись вести. Жодного винятку ні під час збереження, ні під час відкриття. Посилання так само промальовується, так само має вигляд клікабельного і тихо не веде нікуди. Такий самий злам виринає після конвертації через save-as або після циклу .xls/.xlsx, коли примітка з’їжджає на стовпець убік чи відносне посилання губить ціль. Обидві можливості несуть стан рецензування, за яким діють живі люди, тож коли вони ламаються, збій лишається невидимим, доки рецензент не клацне й нічого не станеться
Саме тому примітки та гіперпосилання заслуговують на більшу увагу, ніж підказує їхній косметичний вигляд. HotXLS дає коду на Delphi та C++Builder прямий доступ на запис до обох, і в XLS, і в XLSX, без жодної автоматизації Excel у ланцюжку. Зворотний бік такого контролю — відповідальність: бібліотека записує рівно ті цілі, які ви їй передали, і не перевіряє жодної з них, тож утримати робочий процес рецензування цілим — завдання вашого коду, а не Excel
Примітки до клітинок як машинно записані нотатки рецензування
У класовій моделі XLSX примітка — це об’єкт рівня аркуша: вона знає свій рядок, свій стовпець, автора й текст. Поле автора недарма там стоїть. Коли згенерована вашим кодом книга йде ланцюжком рецензування, перше, що питає аудитор, — хто написав ту чи іншу нотатку, і нотатка без автора відповідає на це питання порожнечею. Штампуйте згенеровані примітки службовою ідентичністю, щоб походження ніколи не було неоднозначним
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Note: TXLSXComment;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('reconciliation.xlsx');
Sheet := Book.Sheets[0];
// Підписана нотатка до скоригованого показника
Sheet.AddComment(14, 4, 'Manual adjustment: late FX rate, see ticket FIN-2214',
'recon-service');
// Оновити наявну нотатку, а не накладати другу згори
Note := Sheet.Comments.FindAt(14, 4);
if Note <> nil then
Note.Text := Note.Text + ' [verified 2026-06-11]';
Book.SaveAs('reconciliation-reviewed.xlsx');
finally
Book.Free;
end;
end;
Зондування через FindAt важить більше, ніж здається. Пакетне завдання, що повторює спробу після тимчасового збою, залюбки викличе AddComment вдруге на клітинці, яку вже анотувало, і клітинка дістане дві накладені нотатки, яких ніхто не просив. Спершу зондуйте через FindAt, а тоді оновлюйте повернений об’єкт. Колекція Comments також відкриває DeleteAt і DeleteInRange. Саме варіант із діапазоном стає в пригоді, коли ви вичищаєте книгу перед тим, як вона піде назовні: прибрати внутрішні QA-анотації з цілої області — це один виклик, а не написаний руками цикл по клітинках
Зовнішні URL і переходи всередині книги — це різні API
OOXML тримає два види посилань у різних місцях. Зовнішній URL стає записом відношення в частині .rels аркуша, а клітинка вказує на це відношення за id. Внутрішній перехід не торкається шару відношень узагалі; це звичайний рядок розташування на кшталт Summary!A1, збережений просто в самому посиланні. HotXLS лишає цю відмінність видимою в API, замість перевантажувати один метод, тож правильний виклик ви обираєте, знаючи, де живе ціль:
Sheet.Cells[2, 1].Value := 'Source record';
Sheet.AddHyperlink(2, 1, 'https://intranet.example.com/records/2214',
'Open record 2214', 'ERP source entry');
Sheet.Cells[3, 1].Value := 'Totals';
Sheet.AddHyperlinkToCell(3, 1, 'Overview!B12', 'Jump to totals');
В отриманому об’єкті TXLSXHyperlink властивості Url і Location взаємно виключні, а IsInternal каже, яка з двох заповнена. Саме цей прапорець ви перевіряєте, коли інвентаризуєте посилання у відкритій книзі й маєте розвести «виходить за межі файлу» та «лишається у файлі» під різні правила: зовнішній хост може наразитися на список дозволених, тоді як внутрішня ціль мусить лише назвати аркуш, який існує. За внутрішніми посиланнями не стоїть жодних частин відношень, що робить їх ще й дешевшими для масового переписування
Злам, з якого починалася стаття, живе цілком на внутрішньому боці, і випливає він з одного факту: рядок розташування — це не розібране посилання. HotXLS записує рівно той текст, який ви передали, і ніщо не переспрямовує цей текст, коли аркуш згодом перейменують. На практиці тримаються два захисти. Перший — дисципліна порядку: перейменуйте всі аркуші, перш ніж згенерувати бодай одне посилання, а далі вважайте назви аркушів замороженими ідентифікаторами. Другий міцніший і переживає перейменування, зроблені постфактум. Наведіть посилання на визначене ім’я рівня книги, а не на сиру адресу Sheet!Cell, бо Excel переписує визначення імені, коли змінюється базовий аркуш, тож посилання їде разом із ним автоматично. Другий підхід природно поєднується з прийомами зі статті визначені імена та формули між аркушами в HotXLS
Бік XLS: ті самі поняття, старіша механіка
Фасад BIFF8 вішає примітки на діапазони, а не на колекцію рівня аркуша. Ви викликаєте AddComment на IXLSRange й отримуєте TXLSComment; властивість діапазону Comment читає наявну нотатку, а ClearComments їх стирає. Гостра грань тут позиційна. TXLSComment не відкриває публічно власні рядок і стовпець, тож природний цикл «обійти всі примітки й повідомити, де кожна сидить» іде проти шерсті цього API. Починати доводиться з клітинок. Або ведіть аудит зі списку адрес, які ви анотували, або тримайте власний журнал позицій під час запису, бо об’єкт примітки потім не скаже вам, де він живе
var
Book: IXLSWorkbook;
Sheet: IXLSWorksheet;
Remark: TXLSComment;
begin
Book := TXLSWorkbook.Create;
Sheet := Book.Sheets.Add;
Sheet.Name := 'Review';
Sheet.Cells.Item[5, 2].Value := 4821.50;
Remark := Sheet.Cells.Item[5, 2].AddComment('Awaiting sign-off from controller');
Remark.Visible := True; // розгорнути нотатку вже при першому перегляді
Sheet.AddHyperlink(7, 2, 'https://intranet.example.com/signoff/4821',
'Sign-off form', 'Opens the controller queue');
Book.SaveAs('review.xls');
end;
Встановлення Visible у True — спадковий спосіб зробити нотатку такою, що її неможливо проґавити: жовта рамка лишається розгорнутою на аркуші, замість чекати наведення. TXLSComment іде на крок далі за свого XLSX-відповідника, відкриваючи TextRuns, тож одна нотатка може нести жирне попередження поруч зі звичайним поясненням — форматування, якого API приміток XLSX у такий самий спосіб не надає. Гіперпосилання на цьому боці приходять через три наростальні перевантаження (лише адреса, потім із текстом для показу, потім із підказкою) і читаються назад через колекцію HyperLinks аркуша, де кожне посилання відкриває Address, SubAddress, DisplayText і ScreenTip
Аркуш-покажчик рецензування кращий за розкидані нотатки
Після десятка з гаком анотацій читання наведенням тихо перестає масштабуватися. Нотатки збираються на аркушах, які рецензент ніколи не відкриває, а найважливіші з них — саме ті, що їх найлегше проґавити. Найкраще тримається така структура: згенерований аркуш-покажчик, по рядку на кожне анотоване місце, із назвою аркуша, адресою клітинки, автором і коротким уривком нотатки. Останній стовпець несе внутрішнє гіперпосилання, побудоване через AddHyperlinkToCell, що стрибає прямо до анотованої клітинки. Тепер рецензент читає список згори вниз, замість полювати по сітці, а кількість рядків цього покажчика заразом слугує інвентарем приміток для аудиту, описаного нижче
Побудувати покажчик дешево, бо ваш генератор і так знає кожну позицію, якої торкнувся. Додавайте кортеж (аркуш, рядок, стовпець, автор, стислий опис) до списку в міру запису кожної примітки, а аркуш-покажчик видавайте останнім, щоб кількість його рядків була остаточною перед збереженням. Два уточнення себе виправдовують: упорядкуйте покажчик за критичністю або за аркушем, а не за порядком вставляння, і покладіть у заголовок покажчика зворотне посилання, щоб рецензент міг повертатися нагору після кожного пункту. Оскільки внутрішні посилання — це звичайні рядки розташування, за якими в шарі відношень немає нічого, навіть покажчик на тисячу рядків майже нічого не додає ні до розміру файлу, ні до часу збереження
Той самий аркуш окупається ще раз на зворотному шляху. Коли відрецензована книга повертається, ваш код читає значення статусу, вписані в клітинки поряд із рядками покажчика, а не перескановує кожен аркуш у пошуках приміток, що могли змінитися. Стовпець структурованих клітинок статусу розбирається чисто; розсип нотаток вільним текстом — ні
Аудит перед відправкою, який справді ловить злам
Жоден із цих API не перевіряє ціль. Посилання на аркуш, який ви видалили, помилка в написанні інтранет-хоста, файлова шара, виведена з експлуатації минулого кварталу, — усе це зберігається без жодного звуку. ECMA-376 визначає, як посилання зберігається, а не те, що воно кудись веде. Тож книга, яка несе метадані рецензування, заслуговує на короткий власний етап аудиту, запущений просто перед SaveAs:
- Зберіть усі внутрішні розташування, записані під час генерації, і переконайтеся, що назва аркуша перед знаком оклику досі є в колекції аркушів книги
- Звіряйте зовнішні URL зі списком дозволених схем і хостів. Голі
file://та UNC-шляхи розкривають деталі середовища й ламаються тієї ж миті, коли файл покидає вашу мережу - Рахуйте примітки на кожному аркуші й порівнюйте з тим, що ваш генератор мав намір записати. Повторна спроба, яка подвоїла нотатки, виринає тут, а не у скриньці рецензента
- Зчищайте суто внутрішні анотації через
DeleteInRangeщоразу, коли одержувач перебуває поза межами організації
Команди, що будують свої книги з шару даних, можуть вкласти цей етап у той самий крок конвеєра, який уже перевіряє дані, — тоді перевірка метаданих їде задарма. Механіка тут та сама, що описана в статті експорт результатів запитів до бази даних у звіти Excel, тільки повернена до посилань і приміток, а не до рядків
Одна деталь із лапками збиває людей з пантелику, коли вони будують рядки розташування руками. Аркуш, у назві якого є пробіл, треба брати в лапки всередині розташування — рівно так, як це робить рядок формул: 'Quarterly Totals'!A1, а не Quarterly Totals!A1. HotXLS застосовує ті самі правила, що й механізм формул для посилань між аркушами, тож якщо посилання працює у формулі аркуша, його лапки спрацюють і тут. Передайте назву з пробілом без лапок — і дістанете те саме тихе мертве посилання, про яке попереджав початок статті
Примітки та гіперпосилання — це ті частини згенерованої книги, за якими рецензенти діють не замислюючись, і саме тому ціль, що не веде нікуди, встигає завдати реальної шкоди, перш ніж хтось це помітить. Побудуйте перевірку один раз, запускайте її на кожній книзі перед відправкою — і робочий процес рецензування лишиться цілим попри перейменування й конвертації. Повна поверхня API для обох фасадів, XLS і XLSX, задокументована на сторінці продукту HotXLS Delphi Component