Бекенд звітності на Delphi, що роками видавав .xlsx, дістає нову вимогу: правила закупівель замовника з державного сектору наказують видавати OpenDocument Spreadsheet, а аналітики на цьому рахунку надсилають свої правки назад як файли .ods, збережені з LibreOffice. Тож тепер той самий код мусить і писати ODS, і читати його. HotXLS, нативна бібліотека електронних таблиць на Object Pascal від losLab для Delphi та C++Builder, працює в обох напрямках, і ніде не потрібно ні Excel, ні LibreOffice. Чого вона не робить — так це не робить ці два напрямки симетричними. Експорт переносить набагато більше, ніж відновлює імпорт, і команда, яка припустить інакше, побачить, як формули та форматування випаровуються десь між правкою замовника й наступним звітом, і жодної помилки, на яку можна вказати
Підтримка ODS живе на фасаді XLSX, а не на XLS
HotXLS постачає дві незалежні ієрархії класів в одному пакеті: TXLSWorkbook у модулі lxHandle для двійкових файлів BIFF8 .xls і TXLSXWorkbook у модулі lxHandleX для пакетів OOXML .xlsx. Кожна точка входу OpenDocument — OpenODS, SaveAsODS, GetODSSheetNames — висить на TXLSXWorkbook. Це розміщення не довільне. Пакет ODS, як його визначає OASIS ODF 1.3, — це zip-архів, що несе член mimetype, маніфест і тіло content.xml, а це робить його структурним родичем zip із OOXML; BIFF8 — двійковий потік записів із 1990-х, який не має з ним нічого спільного
Це розміщення має практичний наслідок: спадкова книга .xls не може стати .ods одним викликом. Спершу ви перекидаєте вміст BIFF у модель XLSX через SaveXLSWorkbookAsXLSX із модуля lxXlsxExport, знову відкриваєте результат через TXLSXWorkbook, а тоді експортуєте вже звідти. Міст не є безвтратним, і про його прогалини варто знати, перш ніж на ньому будувати. Він копіює значення, формули, числові формати, шрифти, заливки й ширини стовпців. Він губить рамки, об’єднані діапазони, примітки, діаграми й умовне форматування. Джерело .xls із важким форматуванням дійде до ODS простішим на вигляд, ніж було на старті, і це властивість мосту, а не записувача ODS
Визначення формату з боку імпорту автоматичне. Звичайний метод Open упізнає пакет ODS за його членом mimetype, відкочуючись до перевірки content.xml на верхньому рівні, коли цього члена немає, тож узагальнений шлях «відкрий що завгодно, що завантажив користувач» не потребує власного винюхування розширень. Після відкриття властивість SourceFormat повідомляє, яка гілка спрацювала
Експорт в ODS через TODSExportOptions
Сам виклик експорту — це один рядок; об’єкт параметрів навколо нього несе ті рішення, про які рецензент запитає згодом:
var
Book: TXLSXWorkbook;
Opts: TODSExportOptions;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-report.xlsx');
Opts := TODSExportOptions.Create; // цим володіє і це звільняє викликач
try
Opts.Generator := 'ReportService 4.2'; // перевизначення meta:generator
Opts.IncludeCharts := True;
Opts.IncludeImages := True;
Book.SaveAsODS('quarterly-report.ods', Opts);
finally
Opts.Free;
end;
finally
Book.Free;
end;
end;
Об’єктом параметрів володіє викликач. HotXLS його не звільнить, і саме тому внутрішній try..finally там стоїть і не є необов’язковим. Дві властивості, що змінюють вивід, а не просто його підписують, варті пильнішого погляду. Задавання IncludeCharts := False робить більше, ніж просто ховає діаграми: воно вилучає піддокументи діаграм і їхні записи в маніфесті з пакета, а це рівно те, чого хочеться, коли споживач — конвеєр даних, який на них спіткнувся б. Generator перевизначає рядок ODF meta:generator, який інакше читається як HotXLS/<version>; перевизначайте його, коли інструменти нижче за течією знімають відбитки з виробників файлів, щоб маршрутизувати підтримку. Якщо ніщо з цього вас не стосується, пропустіть об’єкт параметрів цілком. Виклик SaveAs(FileName, xlsxOpenDocumentSpreadsheet) — це те саме, що SaveAsODS з усталеними значеннями, а перевантаження з потоками в обох дають записати пакет просто у відповідь HTTP без тимчасового файлу
Що читає шлях імпорту — і що він навмисно пропускає
Прочитайте цю частину уважно, перш ніж обіцяти комусь точність циклу читання-запису. Імпорт ODS у HotXLS навмисно є легким шляхом. Він зберігає скалярні значення клітинок і кешований результат, який кожна формула несла на момент збереження, а також розгортає повторювані рядки й стовпці в сітку. Він не переносить стилів, виразів формул ODS чи малюнків
Вибір щодо формул найімовірніше вкусить, і його зробили свідомо. Клітинка ODF зберігає дві речі поруч: вираз формули, записаний діалектом OpenFormula, який визначає ODF 1.3 Part 4, і останнє значення, що його обчислив застосунок-виробник. Переклад OpenFormula в синтаксис формул Excel — окрема задача конвертації діалектів із цілком реальними межовими випадками навколо словників функцій, синтаксису посилань і моделей помилок. Читання кешованого значення натомість оминає весь цей клас тихих хибних перекладів, тож числа, які ви імпортуєте, — це рівно ті числа, які відправник бачив востаннє. Ціна — те, що приходять вони як числа, а не як живі формули, що їх породили
Режим збою, навколо якого треба проєктувати, випливає прямо звідси: таблиця, чиї підсумки були правильними, коли LibreOffice зберігав її востаннє, імпортується з правильними числами, але ці числа тепер константи. Відредагуйте вхідну клітинку, перерахуйте — і ніщо не зрушить: формули немає, лишився тільки її кінцевий результат. Якщо робочому процесу потрібні живі формули після імпорту, відновіть їх програмно зі своїх власних ділових правил через Cell.Formula, який на фасаді XLSX бере вираз без початкового знака рівності
Проєктування навколо несиметричного циклу читання-запису
Експорт промальовує з повної моделі книги в пам’яті: значення, стилі і, якщо попросите, діаграми та зображення. Імпорт повертає лише значення. Тож відрізок .xlsx у .ods має високу точність, а відрізок .ods у .xlsx повертає значення й кешовані результати, але ні оформлення, ні живих формул. Зчепіть ці два — і несиметричність помножиться. Повний цикл .xlsx у .ods у .xlsx записує все сумлінно по дорозі назовні й губить стилі та формули по дорозі назад, дарма що на жодному з кроків нічого не пішло не так
Book := TXLSXWorkbook.Create;
try
Book.Open('vendor-revision.ods'); // формат визначено автоматично
if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
begin
// Після імпорту ODS присутні значення та кешовані результати
// формул; стилів і живих формул немає. Відбудуйте все, від чого
// залежить конвеєр нижче за течією, перед збереженням.
Book.Sheets[0].Cells[2, 5].Formula := 'SUM(B2:D2)';
Book.SaveAs('vendor-revision.xlsx');
end;
finally
Book.Free;
end;
Архітектурний підхід, що з цього випливає: ставтеся до вхідних файлів .ods як до стрічок даних, а не як до документів, які редагують на місці. Тримайте канонічну книгу в .xlsx, вичитуйте значення з правок замовника й видавайте свіжий ODS на вимогу з канонічної копії. Перевірка належить обом таборам — відкривайте експортовані файли в LibreOffice Calc, еталонному споживачі ODF, і в Excel, який читає ODS уже роками, але розходиться з LibreOffice на краях підтримки діаграм і стилів. Кількість аркушів, жменька ключових клітинок і наявність діаграм складають достатню димову перевірку на кожен профіль експорту
Сортування файлу ODS до того, як братися за імпорт
Коли точка приймає завантаження, перелічити назви аркушів набагато дешевше, ніж повністю розібрати файл, і це рано ловить структурні несподіванки:
Names := TStringList.Create;
Book := TXLSXWorkbook.Create;
try
if Book.GetODSSheetNames('incoming.ods', Names) <= 0 then
raise Exception.Create('not a readable ODS package');
if Names.IndexOf('Data') < 0 then
raise Exception.Create('revision is missing the Data sheet');
finally
Book.Free;
Names.Free;
end;
Домовленість про повернені значення збиває людей з пантелику: виклики HotXLS зазвичай повертають додатну кількість або 1 при успіху й -1 при невдачі, очищаючи список у разі провалу, тож перевіряйте <= 0, а не порівнюйте з одним конкретним додатним значенням. GetODSSheetNames ні скидає, ні наповнює екземпляр книги, тож один зондувальний об’єкт може перевірити цілий каталог вхідних файлів. Структурні перевірки на кшталт цієї ловлять найпоширеніший реальний збій — аналітик перейменував чи видалив аркуш, перш ніж надіслати правку назад, — на воротях, де повідомлення про помилку ще може назвати файл і відсутній аркуш, а не виринути nil-посиланням трьома шарами глибше
Якщо ви будуєте навколо цього ширший конвеєр конвертації, підхід із верстатом аудиту та конвертації книг показує, як інвентаризувати можливості файлу, перш ніж обирати цільовий формат, а посібник із продуктивності великих книг тримає пакетні експорти в притомних межах пам’яті
HotXLS — це нативна бібліотека електронних таблиць для Delphi та C++Builder з повним вихідним кодом; повний перелік можливостей і подробиці ліцензування — на сторінці продукту HotXLS Delphi Component