Іноді єдине запитання, на яке має відповісти процедура прийому файлів, — структурне: чи є в цій книзі аркуш під назвою «Mapping», або скільки вкладок вона несе. Відповідати на це викликом Open — дорогий спосіб. Повне відкриття роздуває таблицю спільних рядків, декодує кожен запис стилю й проходить клітинки кожного аркуша, бо не має способу дізнатися, що вам потрібен лише зміст. На великому файлі це сотні мегабайтів виділень пам’яті та кілька секунд процесорного часу, витрачені на читання списку, що займає кілька кілобайтів. HotXLS, нативна бібліотека електронних таблиць для Delphi від losLab, дає вам цей список окремо: GetSheetNames повертає назви аркушів у порядку книги, не матеріалізуючи жодної клітинки
Чому каталог дешево читати
Обидва формати електронних таблиць тримають свій зміст ближче до початку, і саме тому виклик перелічування швидкий, а не хитрий. Пакет OOXML тримає каталог аркушів у xl/workbook.xml — частині, що лишається малою, тримає книга десять рядків чи десять мільйонів. Файл BIFF8 .xls зберігає свої записи BoundSheet на початку потоку глобальних даних книги, попереду будь-яких даних клітинок. Тож робота, якої уникає виклик перелічування, — не похибка округлення порівняно з повним відкриттям. Це більшість файлу. Читання каталогу коштує ті самі кілька кілобайтів незалежно від кількості рядків, тоді як повне відкриття масштабується разом із даними, і на багатомегабайтній книзі цей розрив сягає кількох порядків величини як за обсягом торкнутих байтів, так і за виділеною пам’яттю
Ця стала вартість — саме та властивість, навколо якої варто проєктувати. Шлюз прийому, побудований на GetSheetNames, поводиться однаково і на файлі з 200 рядками, і на файлі в 200 МБ, тож найповільніший файл у пакеті більше не задає темп для рішення, чи взагалі варто обробляти файл
Один виклик для .xls, .xlsx і форматів шаблонів
У фасаді XLS TXLSWorkbook.GetSheetNames читає більше, ніж просто .xls. Він також приймає zip-базовані .xlsx, .xlsm, .xltx і .xltm, витягуючи з архіву лише workbook.xml. Для справжнього вводу .xls він сканує записи BoundSheet і зупиняється на першому записі EOF підпотоку глобальних даних, тож великий бінарний файл усе одно коштує лише свої початкові кілобайти. Фасад XLSX несе гарантію, яка для довготривалого сервісного коду важливіша, ніж здається на перший погляд: TXLSXWorkbook.GetSheetNames не скидає й не наповнює екземпляр книги, тож екземпляр, що вже тримає відкритий документ, може перевіряти інші файли, не турбуючи той, що вже в руках. GetODSSheetNames застосовує той самий підхід до пакетів OpenDocument, і кожен із цих викликів має перевантаження для потоку, що дозволяє перевіряти завантажений файл, який ніколи не потрапляє на диск
var
Book: TXLSXWorkbook;
Names: TStringList;
I: Integer;
begin
Names := TStringList.Create;
Book := TXLSXWorkbook.Create;
try
if Book.GetSheetNames('upload-7f3a.xlsx', Names) <= 0 then
raise Exception.Create('unreadable workbook package');
if Names.IndexOf('Mapping') < 0 then
raise Exception.Create('required Mapping sheet is missing');
for I := 0 to Names.Count - 1 do
Writeln(Format('sheet %d: %s', [I, Names[I]]));
finally
Book.Free;
Names.Free;
end;
end;
Той самий виклик робить хороший діалог імпорту в десктопному застосунку. Виведіть список аркушів, дайте користувачу обрати один, і платіть за повне відкриття лише після того, як вибір зроблено. На книзі з п’ятдесятьма аркушами різниця помітна: пікер, що з’являється миттєво, проти того, що зависає, поки весь файл завантажується позаду нього
Файли .xlsm з увімкненими макросами та формати шаблонів перелічуються точно так само, як звичайний .xlsx, бо каталог сидить у тому самому workbook.xml незалежно від того, чи їде разом у пакеті vbaProject.bin. Тож конвеєр прийому може перелічити аркуші макро-книги для маршрутизації, жодного разу не торкнувшись макро-навантаження і не роблячи нічого, що могло б його запустити, і лишити рішення щодо політики макросів етапу, який справді відкриває файл
Читання значення, що повертається, без самообману
Угоди про повернені значення в HotXLS не уніфіковані. Одні виклики повертають 1 при успіху, інші повертають кількість, тож для функцій перелічування єдина перевірка, що витримує критику, — вважати будь-яке значення нуль або менше збоєм, зі списком рядків, очищеним. Стримайте спокусу трактувати порожній список як «книгу без аркушів». І ECMA-376, і специфікація BIFF8 вимагають щонайменше одного аркуша у валідній книзі, тож нуль імен завжди означає, що читання не вдалося, а не те, що файл легітимно порожній
Невдале перелічування саме по собі — сигнал, який варто зберігати. Файл .xlsx, що провалює виклик, — одна з кількох конкретних речей: обрізаний, насправді не пакет OOXML взагалі (тут постійно трапляються неправильно позначені експорти CSV з інших систем) або зашифрований контейнер. Розрізняти їх — робота наступної перевірки. Логування перших байтів відхиленого файлу разом зі збоєм зазвичай перетворює тред підтримки на одне повідомлення
Виявлення зашифрованих контейнерів перед маршрутизацією
Зашифрований .xlsx — не zip. Це складений файл OLE, що обгортає потоки EncryptionInfo та EncryptedPackage, тож GetSheetNames не може зазирнути всередину і повертає збій, як і будь-який інший нечитний файл. CanReadEncrypted перевіряє саме на цю форму контейнера, що дозволяє прийому маршрутизувати зашифрований файл навмисно, а не проковтувати загальну помилку читання десь у глибині робочого процесу:
type
TIntakeRoute = (irNormal, irNeedsPassword, irUnreadable);
function ClassifyUpload(const FileName: string; Names: TStrings): TIntakeRoute;
var
Book: TXLSXWorkbook;
begin
Book := TXLSXWorkbook.Create;
try
// Зашифрований OOXML — це контейнер OLE, а не zip: перевіряти спочатку,
// бо виклики перелічування не можуть зазирнути всередину нього
if Book.CanReadEncrypted(FileName) then
Exit(irNeedsPassword);
if SameText(ExtractFileExt(FileName), '.ods') then
begin
if Book.GetODSSheetNames(FileName, Names) <= 0 then
Exit(irUnreadable);
end
else if Book.GetSheetNames(FileName, Names) <= 0 then
Exit(irUnreadable);
Result := irNormal;
finally
Book.Free;
end;
end;
Шифрування — це місце, де HotXLS навмисно асиметричний, тож маршрутизація має це враховувати. Застаріле шифрування .xls (RC4, RC4 CryptoAPI, XOR) читається: TXLSWorkbook.Open(FileName, Password) розшифровує зі збереженим паролем, і такі файли можуть лишатися на автоматизованому шляху. Зашифровані пакети OOXML йдуть іншим шляхом. HotXLS може записати такий пакет через SaveAsEncrypted, але не може прочитати його назад. OpenEncrypted викликає виняток EXlsxEncryptionNotImplemented, коли отримує зашифрований пакет, і саме тому чесний дизайн прийому надсилає зашифровані .xlsx людині з Excel, а .xls із паролем лишає в коді
Для пакетної роботи цей класифікатор виправдовує своє місце, пробігаючи по всьому вхідному каталогу до того, як будь-який робочий процес почне справжню обробку, бо кожна перевірка коштує приблизно одне відкриття файлу і кілька кілобайтів читань. Винесення її наперед змінює режим збою, який справді хвилює операційну команду. Замість завдання, що падає о третій ночі на файлі 412 з 600, ви отримуєте 412 файлів у черзі та 5 відхилених на етапі прийому з причиною, доданою до кожного. Ті самі виклики бібліотеки, набагато краща операційна історія
Питання, на які виклик перелічування відповісти не може
Назви й порядок — це все, що ви отримуєте. Виклики перелічування нічого не кажуть про видимість, тож приховані й дуже приховані аркуші потрапляють у список так само, як і будь-який інший. Вони не повідомляють ні розмірів використовуваного діапазону, ні кількості клітинок, ні властивостей документа. Частина docProps/core.xml теж мала, але сьогодні немає перевірки лише властивостей, тож метадані автора й заголовка все ще коштують повного Open. Чистий спосіб з цим жити — дозволити дешевим фактам маршрутизувати кожен файл і залишити дорогі для файлів, що пережили маршрутизацію. Для файлів, що таки переходять у глибоке читання, читання лише для читання великого .xls помітно швидше з _DisableGraphics := True, що пропускає розбір OfficeArt. Просто ніколи не зберігайте з цього екземпляра: шар малюнків, який він пропустив, зникає з моделі, і збереження прибрало б його з файлу
Файли, що пройшли тріаж, зазвичай прямують у глибший аналіз. Робоче місце аудиту й конвертації книг охоплює лічильники по аркушах, які варто збирати, щойно повне відкриття виправдане, а посібник з продуктивності великих книг охоплює те, як тримати те повне відкриття швидким
HotXLS — нативна бібліотека електронних таблиць Object Pascal для Delphi та C++Builder; повна поверхня API, включно з розглянутими тут викликами перевірки, задокументована на сторінці продукту HotXLS Delphi Component