HotXLS, нативна Excel-бібліотека для Delphi та C++Builder, читає значення, яке Excel уже зберіг поруч із формулою, через TryGetCachedFormulaValue та IXLSFormulaCacheReader. Жодна з точок входу не викликає калькулятор, не декомпілює токени формул, не оновлює брудний стан і не записує нічого назад у модель, тож книга, яку ви лише читаєте, залишається точно такою, як ви її відкрили
Сценарій, що рухає цим, нудний і надзвичайно поширений. Нічне завдання відкриває кілька сотень книжок, зроблених кимось іншим, витягує з кожної одну колонку підсумків і штовхає числа у сховище даних. Підсумки вже лежать у файлах — Excel їх обчислив і зберіг. Але щойно завдання питає клітинку з формулою про її значення, бібліотека, що має лише одну відповідь на це питання, будує граф залежностей і обчислює весь аркуш, і завдання, яке мало бути обмеженим вводом-виводом, перетворюється на бенчмарк обчислень
Чому читання клітинки з формулою коштує повний перерахунок?
Бо гетер значення на клітинці з формулою — це запит породити значення, а єдиний всезагально правильний спосіб породити його — обчислити формулу. Це правильна усталена поведінка для застосунку, що редагує книжки, і неправильна для конвеєра, що їх витягує. Гірше того, обчислення не вільне від побічних ефектів: воно записує результати назад у клітинки, перевертає брудні прапорці і може розвʼязатися інакше, ніж у застосунку-творці, коли функція не підтримується або зовнішнє посилання зламане. Завдання, яке ви описали своїй операційній команді як read-only, мовчки породжує книгу, що вже не збігається з файлом на диску, а якщо щось потім її збереже, файл на диску теж зміниться
Читання кешованих значень — друга половина контракту. Воно відповідає на вужче питання — що застосунок-творець тут зберіг? — і відмовляється відповідати на що-небудь інше. Коли ви справді хочете свіжі числа, HotXLS досі дає вам інкрементний перерахунок, керований графом залежностей; суть у тому, що витягування та обчислення мають бути двома різними викликами, а не одним викликом із двома настроями
Три ортогональні факти про одну клітинку
Спершу висновок: кешоване значення формули несе три незалежні факти, і злиття їх в один Variant втрачає потрібну вам інформацію. TXLSFormulaCacheInfo тримає їх окремо як State, Kind і Value. TXLSFormulaCacheState фіксує походження на пʼять випадків — xlfcsNotFormula, xlfcsMissing, xlfcsLoaded, xlfcsCalculated і xlfcsInvalidated — тоді як TXLSFormulaCacheValueKind класифікує корисне навантаження як xlfcvBlank, xlfcvNumber, xlfcvDateTime, xlfcvString, xlfcvBoolean або xlfcvError. Це розділення і дає можливість чесно звітувати про наявність: кешований бланк, кешований порожній рядок, кешований False, кешований нуль і кешована помилка — усе реальні значення, тож наявність ніколи не можна виводити з VarIsEmpty чи VarIsNull. TryGetCachedFormulaValue повертає True лише для xlfcsLoaded і xlfcsCalculated і все одно заповнює діагностований стан, коли повертає False
var
Book: TXLSXWorkbook;
Info: TXLSFormulaCacheInfo;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-model.xlsx');
// SheetIndex, Row і Col тут усі з одиниці
if Book.TryGetCachedFormulaValue(1, 12, 5, Info) then
Writeln('cached value: ', VarToStr(Info.Value))
else
Writeln('no usable cache, state ordinal ', Ord(Info.State));
finally
Book.Free;
end;
end;
Чому кешованого значення бракує?
Рівно чотири причини змушують TryGetCachedFormulaValue віддати False, і стан підказує, котра з них застосовується. xlfcsNotFormula означає, що клітинка тримає літерал або взагалі нічого, а координати поза діапазоном згортаються в ту саму відповідь. xlfcsMissing означає, що клітинка справді формула, але творець не зберіг для неї корисного навантаження значення — типовий наслідок, коли генератор пише формули й лишає Excel заповнити результати при першому відкритті. xlfcsInvalidated означає, що текст формули замінили після завантаження, тож значення, що було там, описує вираз, якого більше не існує. xlfcsCalculated, навпаки, — випадок успіху: він позначає значення, яке ваш власний код чи обчислювач HotXLS породив у цій сесії, на противагу xlfcsLoaded, що прийшов із файлу
Чесність щодо відсутнього кешу важить більше, ніж замазування. HotXLS відмовляється вигадувати значення, і при збереженні так само строгий — лише xlfcsLoaded і xlfcsCalculated видають кешоване значення, тоді як xlfcsMissing і xlfcsInvalidated записують саму формулу, замість заморожувати застаріле число у файл. Це лишає вам три здорові відповіді в конвеєрі: пропустити рядок і зафіксувати прогалину, навмисно перерахувати саме ту книгу і прийняти вартість, або обчислити і звірити. Якщо обчислене число розходиться з тим, що написав би застосунок-творець, трасувальник обчислення формул — інструмент, щоб знайти, де дві обчислювальні гілки розходяться, а не вгадувати з результату
Один читач для класичного, OOXML та ODF рушіїв
Конвеєр не мусить цікавитися, чи був щойно відкритий файл BIFF, OOXML чи ODF. IXLSFormulaCacheReader — єдина точка входу лише для читання для всіх трьох: і TXLSWorkbook.CreateFormulaCacheReader, і TXLSXWorkbook.CreateFormulaCacheReader повертають легку адаптацію над розрідженим пошуком клітинок, який кожен рушій уже використовує, з ідентичними координатами аркуша, рядка й колонки з одиниці. Класи книжок свідомо не реалізують інтерфейс самі — посилання на інтерфейс до книги змінило б її семантику володіння і дозволило б викликачам прослизнути повз оренду часу життя. Натомість знищення книги очищує сирий вказівник усередині тієї оренди, і будь-який читач, усе ще утримуваний вашим кодом, кидає EXLSFormulaCacheReaderInvalidated на наступному запиті замість розіменування звільненої памʼяті. Це fail-fast перевірка часу життя, а не гарантія конкурентності
var
Reader: IXLSFormulaCacheReader;
Info: TXLSFormulaCacheInfo;
Row, Missing, Errors: Integer;
Total: Double;
begin
Reader := Book.CreateFormulaCacheReader;
Total := 0;
Missing := 0;
Errors := 0;
for Row := 2 to LastRow do
if Reader.TryGetCachedFormulaValue(1, Row, 7, Info) then
begin
case Info.Kind of
xlfcvNumber: Total := Total + Double(Info.Value);
xlfcvError: Inc(Errors);
end;
end
else if Info.State = xlfcsMissing then
Inc(Missing);
// Жоден калькулятор не запускався, жоден брудний прапорець не зсунувся, Book не змінена
end;
Де кешовані байти насправді живуть
Для класичних .xls файлів кеш — це поле FormulaValue запису Formula, вісім байтів, описаних у [MS-XLS] §2.5.133. Коли старше слово дорівнює $FFFF, навантаження — не IEEE 754 double, а мічений варіант, і макет легко тонко зіпсувати: тип варіанта сидить у val[0], а навантаження boolean чи BErr — у val[2], з невизначеним val[1]. HotXLS раніше читав навантаження з val[1] — той рід зсуву на одиницю, що спливає лише на конкретних файлах, які кешують boolean чи помилку, а не число. Читач і записувач спільних формул тепер узгоджені на тих самих зсувах, тож кешований TRUE виживає в циклі завантаження й збереження цілим, замість гнити в шум
Вірність типів у пакетних форматах — окрема проблема з власною пасткою. В OOXML кешоване значення висить на елементі <c> як <v>, а атрибут t називає тип за ECMA-376 Part 1 §18.3.1.4. HotXLS читає t="e" просто в Variant varError і відображає його назад у стандартний текст помилки при збереженні, тож помилки ніколи не маскуються під звичайні цілі числа — але RTL Delphi тут не допоможе, бо VarAsType(Integer, varError) кидає виняток конверсії. Робоча конструкція встановлює TVarData.VType і TVarData.VError напряму. Дати дотримуються тієї ж дисципліни у протилежний бік: t="d" і тип значення дати ODF — явні декларації типу і стають varDate, тоді як числовий кеш BIFF узагалі не несе прапорця дати і тому лишається Double. HotXLS ніколи не вгадує дату з числового формату клітинки, бо числовий формат — це презентація, а кеш — дані. ODF додає ще один випадок, вартий знання — office:value-type="void" виражає кеш, що присутній, але не несе значення, а оскільки ODF не має типу значення помилки, схожий на помилку текст зберігається як текст, а не підвищується до помилки
function DescribeCache(const Info: TXLSFormulaCacheInfo): string;
begin
case Info.State of
xlfcsNotFormula: Result := 'not a formula cell';
xlfcsMissing: Result := 'formula stored with no cached value';
xlfcsInvalidated: Result := 'formula replaced since load';
else
case Info.Kind of
xlfcvError: Result := 'error code ' + IntToStr(TVarData(Info.Value).VError);
xlfcvDateTime: Result := DateTimeToStr(VarToDateTime(Info.Value));
xlfcvBoolean: Result := BoolToStr(Info.Value, True);
xlfcvNumber: Result := FloatToStr(Double(Info.Value));
xlfcvString: Result := VarToStr(Info.Value);
else
Result := 'present but blank';
end;
end;
end;
Чи діляться спільні формули своїми кешованими значеннями?
Ні, і припущення протилежного — це спосіб, яким масовий обхід закінчує звітом того самого числа для цілої колонки. Спільна формула OOXML ділиться лише виразом формули та оптимізацією зберігання; кожна клітинка-член досі володіє власним <v>. Тому HotXLS ніколи не розповсюджує кеш кореневого члена на послідовника, що прийшов без значення, і послідовник, завантажений як xlfcsMissing, досі звітує xlfcsMissing після збереження та повторного відкриття. Якщо ви розбираєтеся, як група взагалі зберігається й розгортається, механіка атрибута si спільної формули та її розгортання покрита окремо; для читання кешу правило зводиться до одного рядка — питайте кожну клітинку, не довіряйте нічому, про що не питали
Читання кешованих значень, уніфікований міжрушійний читач і рушій перерахунку, який ви можете обрати не викликати, усе постачається зі стандартним HotXLS Delphi Spreadsheet Component для Delphi та C++Builder, без залежності від Excel чи будь-якого OLE-сервера автоматизації; продуктова сторінка несе повний довідник API точок входу книги та читача, показаних тут