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

Патчинг одного аркуша у великому XLSX з Delphi

HotXLS може перезаписати один аркуш усередині наявного пакета XLSX без розбору чи повторного стиснення решти файлу. TXLSDirectWriter.BeginPatch відкриває вихідний пакет, копіює кожен запис, крім цільового аркуша, зі своїми стиснутими байтами дослівно і дозволяє вам заново створити саме цей аркуш через звичайні виклики AddSheet, AddRow і Write*. Діаграми, зведені кеші, теми, стилі та спільні рядки взагалі ніколи не розпаковуються

Робочий процес, який це вирішує, з'являється у звітності та оновленні даних. Книга надходить від бізнес-команди, несучи зведені таблиці, зрізи, умовне форматування та десятиліття накопиченого форматування. Щоночі один аркуш даних треба замінити свіжими числами. Завантаження й повторне збереження всієї книги коштує хвилини на файл і, що важливіше, ризикує точністю відтворення можливостей, які рушію завантаження доводиться реконструювати. Патчинг обходить обидві проблеми, не торкаючись того, чого не потрібно торкатися

Чому копіювання стиснутих байтів — цікава частина?

Запис zip, скопійований на рівні стиснутих даних, коштує потокового копіювання. Той самий запис, пропущений через звичайний шлях запису, коштує розпакування на вході й запакування на виході, а запакування — дорожча половина. У книзі з великим зведеним кешем і кількома десятками вбудованих зображень ця різниця — це різниця між патчем, що завершується за час, потрібний для запису нового аркуша, і патчем, що витрачає більшість часу на повторне стискання байтів, які він жодного разу не оглянув

Для цього HotXLS використовує CopyCompressedFrom, який записує стиснуті байти вихідного запису безпосередньо в цільовий архів. Коли запис не можна скопіювати так — через інший метод стиснення або слабке шифрування — записувач відкочується до розпакованого потокового копіювання замість збою. Записи-маркери каталогів пропускаються, оскільки записувач формує власні

Заміна на місці або запис у новий файл

Два перевантаження покривають дві форми, яких набуває це завдання. Форма на місці готує результат у тимчасовому файлі поруч з оригіналом, закриває вихідний дескриптор, а потім видаляє й перейменовує, тож збій посеред запису лишає оригінал неушкодженим. Форма з явною ціллю лишає джерело недоторканим і може або замінити аркуш, або додати новий:

var
  W: TXLSDirectWriter;
begin
  W := TXLSDirectWriter.Create;
  try
    W.BeginPatch('monthly-dashboard.xlsx', 'Data');   // на місці
    W.AddSheet('Data');
    W.AddRow(1);
    W.WriteString(1, 'Region');
    W.WriteString(2, 'Revenue');
    W.AddRow(2);
    W.WriteString(1, 'North');
    W.WriteNumber(2, 184320.55);
    W.AddRow(3);
    W.WriteFormula(1, '=SUM(B2:B2)');
    W.Close;
  finally
    W.Free;
  end;
end;

Варіант вставки приймає вихідний і цільовий шлях плюс InsertSheet:

  // Джерело лишається недоторканим; ціль отримує додатковий аркуш з ім'ям Extra
  W.BeginPatch('template.xlsx', 'output.xlsx', 'Extra', True);
  W.AddSheet('Extra');
  W.AddRow(1);
  W.WriteString(1, 'appended by the nightly job');
  W.Close;

Вставка — та частина, яка потребує справжньої хірургії обліку. Записувач розбирає реєстр аркушів у xl/workbook.xml і карту зв'язків, що прив'язує кожен аркуш до його частини, а потім обирає наступний вільний номер частини, ідентифікатор аркуша та ідентифікатор зв'язку. Типи зв'язків слідують угодам вихідного пакета, тож патчинг строгої книги ISO 29500 видає строгі типи зв'язків, а патчинг перехідної видає перехідні типи

Що патч свідомо відкидає й обмежує

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

Усередині патча змінюються дві семантики створення, і обидві випливають з одного принципу: патч не повинен турбувати частини, яких він не переписував. Рядки записуються прямо в аркуш, а не додаються до таблиці спільних рядків, бо вихідна таблиця переходить далі недоторканою. А StyleIndex посилається на записи в cellXfs вихідного пакета, а не на таблицю стилів, яку будує записувач. Це означає, що ви можете посилатися на формати, які оригінальна книга вже визначає, що зазвичай саме те, чого хоче оновлення даних, але також означає, що вам треба знати, який індекс несе який формат

// Усередині патча StyleIndex індексує cellXfs ВИХІДНОГО пакета.
// Дата потребує явного індексу, що зіставляється там з форматом дати:
W.WriteDateTime(3, EncodeDate(2026, 8, 22), DateStyleIndexFromTemplate);

// Перевантаження WriteDateTime без стилю відхиляється в режимі
// патча, бо воно передбачає власну таблицю стилів записувача,
// яку патч ніколи не створює

Шість точок входу для створення заблоковано: додавання таблиць, діаграм, зображень, коментарів, іменованих діапазонів і стилів клітинок — усе це порушує виняток у режимі патча, з другою страховкою на момент закриття, що провалюється, якщо будь-який з їхніх лічильників ненульовий. Кожна з цих можливостей потребувала б редагування частин, які патч копіює дослівно, а наполовину відредагований пакет гірший за відхилену операцію. За одну операцію можна пропатчити рівно один аркуш

Коли патчити, а коли завантажувати

Патчинг — правильний інструмент, коли книга велика, зміна обмежена одним аркушем, а решта файлу має вижити побітово. Це неправильний інструмент, коли зміна охоплює кілька аркушів, коли потрібне нове форматування чи нові об'єкти, або коли файл достатньо малий, щоб звичайне завантаження й збереження нічого не коштувало. Для масового генерування з нуля потоковий шлях, описаний у статті потоковий прямий записувач, лишається кращим вибором, і він поділяє той самий API AddRow і Write*, тож перехід між ними механічний

Маніпуляції на рівні аркуша всередині завантаженої книги, коли вам справді потрібна повна об'єктна модель, описано у статті дублювання аркушів у пакетах XLSX. А якщо причина, з якої ви розглядаєте патчинг, — це те, що обробка всієї книги стала повільною, вимірювання й поведінку пам'яті у статті продуктивність великих книг варто прочитати перед вибором підходу

Перевірка, що патч справді зробив те, що ви думаєте

Три перевірки виловлюють майже кожну помилку. Переконайтеся, що частини, які ви очікували побачити цілими, все ще в архіві, що xl/calcChain.xml зник, і що повторне відкриття файлу через TXLSXWorkbook повідомляє очікувану кількість аркушів — незмінну для заміни та збільшену на один для вставки. Зчитування пропатченого аркуша назад і порівняння кількох значень і формул завершує цикл перевірки

Одну деталь реалізації з розробки цієї можливості варто повторити, бо вона може вкусити будь-кого, хто пише подібний код на рівні zip. Імена частин аркушів зіставляються за префіксом, і помилка на одиницю в довжині префікса означає, що предикат ніколи не збігається, тож новозаписана частина конфліктує з наявним ім'ям, а читачі, що беруть останній запис з даним ім'ям, мовчки обирають неправильний аркуш. Якщо патч виглядає так, ніби переставив місцями вміст двох аркушів, спершу подивіться на зіставлення імен, а вже потім на XML

Патчинг на місці, потоковий запис і повна об'єктна модель книги постачаються в одній бібліотеці для Delphi та C++Builder; перелік можливостей на сторінці компонента електронних таблиць HotXLS для Delphi