HotXLS, natiivi Delphin ja C++Builderin Excel-kirjasto, on rakennettu häviötöntä XLSX-edestakaisinmuokkausta (round-trip) varten: avaa työkirja, muuta yhtä solua, tallenna, ja asiakkaan mukautettu teema, vieraat extLst-laajennuslohkot sekä laskentaketju (calculation chain) säilyvät. Kolme mekanismia mahdollistaa tämän: tiedoston xl/theme/theme1.xml sanatarkka välimuisti tallennus, tuntemattomien <ext>-lohkojen tapahtumapohjainen uudelleensarjoitus sekä tuore, määrittelyjen mukainen xl/calcChain.xml jokaisessa kaavatyökirjan tallennuksessa
Kaikkia kolmea mekanismia motivoiva skenaario on harmillisen yleinen. Laskutuspalvelu lataa asiakkaan Excelillä suunnitteleman mallin — yrityksen väriteeman, pikakuvaajat (sparklines) KPI-sarakkeessa ja uudemman Excel-version lisäämän ehdollisen muotoilun säännön — kirjoittaa yhden laskun loppusumman soluun B3 ja tallentaa. Asiakas avaa lopputuloksen, ja yrityksen värit ovat palanneet tavalliseksi Officen siniseksi, pikakuvaajat ovat kadonneet ja Excel tarjoaa tiedoston "korjaamista". Koodissa mikään ei koskenut noihin ominaisuuksiin. Kirjasto teki sen pelkästään tallentamalla
Miksi Excel-tiedostot menettävät muotoilunsa kirjastoeditointien jälkeen?
Excel-tiedostot menettävät muotoilunsa kirjastomuokkausten jälkeen, koska useimmat kirjastot eivät muokkaa tiedostoa — ne rakentavat sen uudelleen. .xlsx-paketti on ZIP-arkisto XML-osista: xl/workbook.xml, yksi xl/worksheets/sheetN.xml sivua kohden, xl/styles.xml, xl/theme/theme1.xml, xl/calcChain.xml ja niin edelleen. Tyypillinen kirjasto jäsentää nämä osat objektimalliksi avattaessa ja luo jokaisen osan uudelleen tuosta mallista tallennettaessa. Mikä tahansa ominaisuus, jota malli ei edusta — teema, jota se ei koskaan jäsentänyt, tai uudemman Excelin laajennuslohko — ei voi säilyä muistissa, joten uudelleenluotu osa jättää sen hiljaisesti pois
Standardi ECMA-376 ennakoi puolet tästä ongelmasta. SpreadsheetML määrittelee extLst-elementin (ECMA-376 osa 1, "Future Feature Data Storage Area", §18.2.10 työkirjatason elementille) nimetyksi laajennuspisteeksi: uudemmat luontiohjelmat sijoittavat uudet ominaisuudet sinne, kunkin käärittynä <ext>-elementtiin, joka kantaa ominaisuuden tunnistavaa uri-attribuuttia. Vanhempien sovellusten odotetaan säilyttävän sen, mitä ne eivät ymmärrä. Pikakuvaajat, osittajat (slicers) ja uudemmat ehdollisen muotoilun tyypit kulkevat kaikki tällä tavalla. Kirjasto, joka pudottaa pois tuntemattomat <ext>-lohkot, ei ole vain häviöllinen — se rikkoo eteenpäin suuntautuvaa yhteensopivuussopimusta, jonka ympärille formaatti on suunniteltu. Kysymys, joka kannattaa esittää mille tahansa arvioitavalle taulukkolaskentakirjastolle, on tyly: jos muutan yhtä solua, mikä muu muuttuu
Miten HotXLS säilyttää mukautetun teeman tavulleen samanlaisena?
HotXLS säilyttää työkirjan teeman tallentamalla tiedoston xl/theme/theme1.xml alkuperäiset tavut välimuistiin avaushetkellä ja kirjoittamalla ne sanatarkasti takaisin tallennettaessa. Teema-osa (ECMA-376 osa 1, §14.2.7) on DrawingML-muotoa, ei SpreadsheetML:ää — väriskeemoja, fonttiskeemoja, muotoiluskeemoja — eikä taulukkolaskentamoottorilla ole syytä mallintaa sitä syvällisesti. Aiemmat HotXLS-versiot loivat kiinteän Office-teeman jokaisella tallennuksella, mikä aiheutti juuri edellä mainitun "brändivärien palautumisen". Versiosta v2.89.46 lähtien avatun paketin teema tallennetaan raakana ja kirjoitetaan takaisin koskemattomana, ja sisäänrakennettu Office-teema luodaan vain alusta alkaen tehdyille työkirjoille. Raakatavut ovat vahvin mahdollinen uskollisuustakuu: ei jäsentämistä, ei uudelleensarjoitusta, ei vaaraa muuttumisesta
Sanatarkka kopio syrjäyttää tarkoituksella ohjelmallisen teeman käytön. ThemeMajorFont ja ThemeMinorFont ovat käytettävissä, jotta voit valita otsikko- ja leipätekstin uusiin työkirjoihin, mutta kun sanatarkka teema kaapattiin avattaessa, näillä asetuksilla ei ole vaikutusta tallennettuun tiedostoon — round-trip on etusijalla. Jos sinun on todella muutettava olemassa olevan työkirjan teemaa, se on merkki siitä, että mallia tulisi muokata itse Excelissä eikä datakeskeisen sovellusliittymän kautta. Arkipäiväisessä käytössä ei tarvita API:a lainkaan
var
Book: TXLSXWorkbook;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('branded-invoice.xlsx');
Book.Sheets[0].Cells[3, 2].Value := 42750.00; // the one edit
Book.SaveAs('branded-invoice-out.xlsx');
// theme1.xml in the output is byte-identical to the input
finally
Book.Free;
end;
end;
Mitä tapahtuu tuntemattomille extLst-lohkoille tallennettaessa?
HotXLS kaappaa jokaisen laskentataulukon tason <ext>-lohkon, jota se ei natiivisti mallinna, ja toistaa sen tallennetun laskentataulukon extLst-osioon. Näin uudemmilla Excel-versioilla kirjoitetut ominaisuudet säilyvät ehjinä pyörähdyksessä. Versiosta v2.131.0 lähtien kaapatut fragmentit ovat nähtävissä vain-luku-tyyppisen RawWorksheetExts-ominaisuuden kautta, joka on TStringList jokaisella XLSX-laskentataulukolla. Tämä tekee takuusta auditoitavan testikoodista käsin pelkän uskon sijaan
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)); // peek at each uri
finally
Book.Free;
end;
end;
Toteutusyksityiskohta, joka on hyvä tietää, on se, että kaappaus on tapahtumatason uudelleensarjoitus, ei raaka tavukopio. HotXLS:n suoratoistava XML-lukija ei paljasta lähdekohdistuksia, joten tuntematon alipuu rakennetaan uudelleen Element-, Text- ja EndElement-tapahtumista sitä mukaa, kun ne virtaavat ohi. Tämä lähestymistapa piilottaa yhden perinteisen ansan: itsesulkeutuva elementti, kuten <a/>, laukaisee vain tyhjäksi merkityn Element-tapahtuman eikä koskaan EndElement-tapahtumaa. Näin ollen mikä tahansa syvyyslaskuri, joka vähenee vain EndElement-tapahtumasta, ei koskaan näe alipuun sulkeutuvan. Kun tämä käsitellään oikein, uudelleenrakennettu fragmentti on semanttisesti vastaava alkuperäisen kanssa — attribuuttien lainausmerkit ja itsesulkeutuvat muodot normalisoidaan. Se ei siis ole tavulleen identtinen, mutta Excel lukee merkitystä, ei tavuja. Kaksi Excelin oman tulosteen ominaisuutta tekee toistamisesta turvallista: Excel ilmoittaa tarvittavat xmlns-attribuutit <ext>-elementissä tai sen sisällä, joten kukin kaapattu fragmentti on nimiavaruudellisesti itsenäinen. Tämä sama itsenäisyys on syy siihen, miksi laskentataulukon kahdentaminen työkirjan sisällä tai niiden välillä voi siirtää vieraat lohkot mukanaan pelkällä merkkijonoluettelon sijoituksella
Tiedoston calcChain.xml kirjoittaminen, jotta Excel luettaa kaavoihisi
HotXLS kirjoittaa tiedoston xl/calcChain.xml (laskentaketjun osa, ECMA-376 osa 1, §12.3.1) aina, kun tallennettu työkirja sisältää kaavoja, ja se valitsee kahden järjestyksen väliltä. Jos kaavojen riippuvuuskaavio on jo rakennettu ja se on ajan tasalla — eli kutsuit Recalculate-metodia viimeisen muokkauksen jälkeen — ketju tulostetaan täydellisessä topologisessa järjestyksessä (riippuvuudet ennen niistä riippuvia) ja mahdolliset kehäviittauksen jäsenet liitetään loppuun. Muussa tapauksessa solut luetellaan dokumenttijärjestyksessä. Molemmat tavat ovat oikein: Microsoftin formaatin toteutushuomautukset, [MS-XLSX], käsittelevät laskentaketjua vihjeenä, jonka Excel varmistaa ja järjestää uudelleen latauksen aikana. Mikä tahansa kattava luettelo on siis laillinen, ja HotXLS kieltäytyy tarkoituksella pakottamasta kaavion rakentamista SaveAs-kutsun sisällä — reunaviivojen rakentaminen on neliöllistä solujen määrään nähden, mikä olisi mahdoton piilokustannus miljoonan solun tallennuksessa
Book.Open('model.xlsx');
Book.Sheets[0].Cells[10, 4].Formula := '=SUM(D2:D9)';
// Saved now, calcChain.xml lists formula cells in document order.
// After Recalculate the dependency graph exists, so the same save
// emits a full topological order instead:
Book.Recalculate;
Book.SaveAs('model-out.xlsx');
Mihin häviötön round-trip päättyy
Rehellisyys on tässä tärkeämpää kuin markkinointiväite, joten rajoitukset ansaitsevat saman huomion. HotXLS ei kopioi koko pakettia tavu tavulta: laskentataulukon XML, tyylit, jaetut merkkijonot (shared strings) ja työkirjan osat luodaan uudelleen jäsennetystä mallista. Tuloste on siis semanttisesti uskollinen, mutta ei binäärisesti identtinen — pelkästään ZIP-paikalliset otsikot kantavat uusia DOS-aikaleimoja. Kaapatut <ext>-fragmentit palautuvat normalisoituina, kuten edellä kuvattiin. Ohjelmalliset teeman fonttien ylikirjoitukset ohitetaan, kun sanatarkka teema on läsnä. Säilytysverkolla on määritelty meshinsä: ominaisuudet, joita HotXLS mallintaa natiivisti (esimerkiksi pikakuvaajat jäsennetään ja kirjoitetaan uudelleen sokean kopioinnin sijaan) plus vieras extLst-sisältö plus sanatarkasti välimuistiin tallennetut osat. Osa, jota ei mallinneta eikä se sijaitse laajennuspisteessä — kuten eksoottisen apuohjelman mukautettu osa — jää tämän artikkelin kattamien kolmen mekanismin ulkopuolelle. Testaa siis todellisia malleja arvaamisen sijaan
Oheinen säilytystyö täydentää kuvan. VBA-projektit ja ulkoiset työkirjaviittaukset säilyvät tallennuksessa saman "säilytä se mitä et mallinna" -filosofian mukaisesti, jota käsitellään kumppaniartikkelissa VBA- ja ulkoisten linkkien säilyttämisestä, ja tiedoston ominaisuuksilla docProps-osiossa on oma luku- ja kirjoitus-API:nsa hiljaisen poistamisen sijaan. Kun arvioit mitä tahansa taulukkolaskentakirjastoa, suorita yhden solun testi: avaa monipuolinen tuotantotyökirja, muuta yhtä arvoa, tallenna ja vertaa purettuja osia alkuperäiseen. Se, mikä muuttui muokkaamasi taulukon ulkopuolella, kertoo kirjastosta enemmän kuin mikään ominaisuusmatriisi
Tässä kuvatut round-trip-mekanismit — sanatarkka teeman säilyttäminen versiosta v2.89.46 lähtien, vieraiden extLst-lohkojen kaappaus ja calcChain.xml-tulostus versiosta v2.131.0 lähtien — toimitetaan nykyisessä HotXLS Delphi Excel Component -kirjastossa, jonka tuotesivu dokumentoi täydellisen XLSX-luku- ja kirjoitusominaisuuksien sarjan Delphille ja C++Builderille