Техническа статия

Двупосочно запазване без загуби (Lossless Round-Trip) на XLSX в Delphi: Theme, extLst, calcChain

HotXLS, собствената библиотека за Excel за Delphi и C++Builder, е създадена за двупосочно запазване без загуби (lossless XLSX round-trip): отворете работна книга, променете една клетка, запазете я и персонализираната тема на клиента, външните разширителни блокове extLst и изчислителната верига ще се запазят; Три механизма правят това възможно — кеширането буква по буква на xl/theme/theme1.xml, базираното на събития повторно сериализиране на непознати блокове <ext> и свеж, съвместим със спецификацията xl/calcChain.xml при всяко запазване на работна книга с формули

Сценарият, който мотивира и трите, е отчайващо често срещан; Услуга за таксуване зарежда шаблон, който клиентът е разработил в Excel — корпоративна цветова тема, спарклайни (sparklines) в KPI колона, правило за условно форматиране, добавено от по-нова версия на Excel — записва една обща сума на фактура в клетка B3 и я запазва; Клиентът отваря резултата и корпоративните цветове са се върнали към стандартното синьо на Office, спарклайните са изчезнали и Excel предлага да „поправи“ файла; Нищо в кода не е докоснало тези функции; Библиотеката обаче ги е променила само чрез запазването

Защо Excel файловете губят форматирането си след редакции с библиотека?

Excel файловете губят форматирането си след редакции с библиотека, защото повечето библиотеки не редактират файла — те го изграждат наново; Пакетът .xlsx е ZIP от XML части: xl/workbook.xml, по един xl/worksheets/sheetN.xml на лист, xl/styles.xml, xl/theme/theme1.xml, xl/calcChain.xml и др.; Типичната библиотека анализира тези части в модел на обекти при отваряне и регенерира всяка част от този модел при запазване; Всяка функция, която моделът не представя — тема, която никога не е анализирал, или разширителен блок от по-нов Excel — няма къде да живее в паметта, така че регенерираната част тихомълком я пропуска

ECMA-376 предвиди половината от този проблем; SpreadsheetML дефинира extLst (ECMA-376 Част 1, „Бъдеща област за съхранение на данни за функции“, §18.2.10 за елемента на ниво работна книга) като определена точка за разширение: по-новите генератори поставят функции там, всяка обвита в елемент <ext> с атрибут uri, който идентифицира функцията, и от по-старите потребители се очаква да запазят това, което не разбират; Спарклайни, сегментатори (slicers) и по-нови типове условно форматиране се пренасят по този начин; Библиотека, която изхвърля непознати блокове <ext>, е не просто непълна — тя нарушава договора за съвместимост напред, около който е проектиран форматът; Въпросът към всяка библиотека за електронни таблици, която оценявате, е директен: ако променя една клетка, какво друго се променя?

Как HotXLS запазва персонализирана тема байт по байт?

HotXLS запазва темата на работната книга чрез кеширане на оригиналните байтове на xl/theme/theme1.xml при отваряне и записването им обратно буква по буква при запазване; Частта за тема (ECMA-376 Част 1, §14.2.7) е DrawingML, а не SpreadsheetML — цветови схеми, схеми за шрифтове, схеми за форматиране — и двигателят за електронни таблици няма причина да я моделира подробно; По-старите версии на HotXLS генерираха фиксирана Office тема при всяко запазване, което е точно гореописаният срив; от v2.89.46 темата на отворения пакет се съхранява в суров вид и се записва обратно недокосната, а вградената Office тема се генерира само за работни книги, създадени от нулата; Суровите байтове са най-силната възможна гаранция за точност: без анализ, без повторно сериализиране, без шанс за отклонение

Точното копие умишлено надделява над програмния достъп до темата; TXLSXWorkbook излага ThemeMajorFont и ThemeMinorFont, за да можете да избирате шрифтове за заглавия и тяло за нови работни книги, но когато при отваряне е заснето точно копие на темата, тези методи за задаване нямат ефект върху запазения файл — двупосочното запазване е с приоритет; Ако наистина трябва да промените темата на съществуваща работна книга, това е сигнал да редактирате шаблона в самия Excel, а не чрез ориентиран към данни API; Ежедневният случай не се нуждае от никакъв API:

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('branded-invoice.xlsx');
    Book.Sheets[0].Cells[3, 2].Value := 42750.00;  // единствената редакция
    Book.SaveAs('branded-invoice-out.xlsx');
    // theme1.xml в изхода е идентичен на ниво байтове с входа
  finally
    Book.Free;
  end;
end;

Какво се случва с непознатите extLst блокове при запазване?

HotXLS заснема всеки блок <ext> на ниво работен лист, който не моделира собственоръчно, и го възпроизвежда в extLst на запазения работен лист, така че функциите, записани от по-нови версии на Excel, оцеляват непокътнати; От v2.131.0 заснетите фрагменти са видими чрез свойството за четене RawWorksheetExtsTStringList на всеки XLSX работен лист, което прави гаранцията проверима от тестов код, а не въпрос на сляпа вяра:

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  i: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('from-newer-excel.xlsx');
    Sheet := Book.Sheets[0];
    WriteLn(Format('%d foreign ext block(s) captured',
      [Sheet.RawWorksheetExts.Count]));
    for i := 0 to Sheet.RawWorksheetExts.Count - 1 do
      WriteLn(Copy(Sheet.RawWorksheetExts[i], 1, 100)); // преглед на всяко uri
  finally
    Book.Free;
  end;
end;

Подробността за имплементацията, която си струва да знаете, е че заснемането е повторно сериализиране на ниво събития, а не копиране на сурови байтове; Поточният XML четец на HotXLS не излага изходни отмествания, така че непознатото поддърво се възстановява от събитията Element, Text и EndElement, докато преминават през потока; Този подход крие един класически капан: самозатварящ се елемент като <a/> задейства само събитие Element, маркирано като празно, и никога EndElement, така че всеки брояч на дълбочина, който намалява само при EndElement, никога няма да види затварянето на поддървото; Справете се с това и възстановеният фрагмент е семантично еквивалентен на оригинала — кавичките на атрибутите и самозатварящите се форми са нормализирани, така че той не е идентичен на ниво байтове, но Excel чете смисъл, а не байтове; Две свойства на собствения изход на Excel прават възпроизвеждането безопасно: Excel декларира необходимите атрибути xmlns в елемента <ext> или вътре в него, така че всеки заловен фрагмент е самостоятелен по отношение на пространството от имена, и точно тази самостоятелност е причината, поради която дублирането на работен лист в или между работни книги може да пренесе външните блокове заедно с просто присвояване на списък с низове

Записване на calcChain.xml, за да може Excel да се довери на формулите ви

HotXLS записва xl/calcChain.xml (частта Calculation Chain, ECMA-376 Част 1, §12.3.1) винаги, когато запазената работна книга съдържа формули, и избира между две подредби; Ако графът на зависимостите на формулите вече е изграден и е актуален — извикали сте Recalculate след последната си редакция — веригата се записва в пълен топологичен ред, зависимостите преди зависимите, като всички членове на циклични препратки се добавят в края; В противен случай клетките се изброяват по реда на документа; И двете са правилни: бележките за имплементация на Microsoft за формата, [MS-XLSX], третират изчислителната верига като подсказка, която Excel проверява и пренарежда по време на зареждане, така че всеки пълен списък е законен, а HotXLS умишлено отказва да форсира изграждане на граф в SaveAs — конструирането на ребра е квадратично спрямо броя на клетките, което е недопустима скрита цена при запазване на милион клетки

Book.Open('model.xlsx');
Book.Sheets[0].Cells[10, 4].Formula := '=SUM(D2:D9)';
// Запазен сега, calcChain.xml изброява формулните клетки по реда на документа.
// След Recalculate графът на зависимости съществува, така че същото запазване
// извежда вместо това пълен топологичен ред:
Book.Recalculate;
Book.SaveAs('model-out.xlsx');

Защо да ни е грижа за част, която Excel третира като препоръчителна? Защото нейното отсъствие е сигнал; Някои потребители — инструменти за възстановяване, визуализатори от трети страни, инструменти за сравнение (diff) — очакват работна книга с формули да носи изчислителна верига, а библиотека, която тихомълком изхвърля тази част при запазване, произвежда файлове, които са малко по-различни от всичко, което Excel записва; Записването на валидна верига държи изходния резултат в рамките на това, срещу което останалата част от екосистемата е тествана, което е тихата ядро на двупосочното инженерство

Къде свършва двупосочното запазване без загуби

Честността тук има по-голямо значение от маркетинговото тикче, така че границите заслужават еднакво внимание; HotXLS не копира целия пакет байт по байт: XML на работните листове, стиловете, споделените низове и частите на работната книга се регенерират от анализирания модел, така че изходът е семантично верен, но не и бинарно идентичен — само локалните ZIP хедъри носят нови DOS времеви клейма; Заловените фрагменти <ext> се връщат нормализирани, както е описано по-горе; Програмните промени на шрифтовете на темата се игнорират, когато присъства точно копие на темата; И мрежата за запазване има определен размер на окото: функциите, които HotXLS моделира собственоръчно (например спарклайните се анализират и пренаписват, вместо сляпо да се копират), плюс външното съдържание в extLst, плюс verbatim-кешираните части; Част, която не е моделирана, нито се намира в разширителна точка — например персонализирана част на екзотична добавка — попада извън трите механизма, разгледани в тази статия, така че тествайте реалните си шаблони, вместо да предполагате

Съпътстващата работа по запазване допълва картината; Проектите на VBA и външните препратки към работни книги се пренасят при запазване по същата философия за запазване на това, което не се моделира, описана в съпътстващата статия за запазване на VBA и външни препратки, а свойствата на документа в docProps имат свой собствен API за четене и запис, вместо да бъдат тихомълком изхвърляни; Когато оценявате дадена библиотека за електронни таблици, стартирайте теста с една клетка: отворете богата на функции производствена работна книга, променете една стойност, запазете я и сравнете разархивираните части с оригинала; Това, което се е променило извън листа, който сте докоснали, ви казва повече за библиотеката, отколкото всяка функционална матрица

Механизмите за двупосочно запазване, описани тук — запазване на темата verbatim от v2.89.46, заснемане на външни extLst и записване на calcChain.xml от v2.131.0 — се доставят в текущия HotXLS Delphi Excel Component, чиято продуктова страница документира пълния XLSX набор от функции за четене и писане за Delphi и C++Builder