Tekninen artikkeli

ODS-laskentataulukoiden avaaminen ja tallentaminen Delphissä HotXLS:llä

Delphillä toteutettu raportointitaustajärjestelmä, joka on tuottanut .xlsx-tiedostoja vuosia, saa uuden vaatimuksen: julkishallinnon asiakkaan hankintasäännöt edellyttävät OpenDocument Spreadsheet -tulostetta, ja kyseisen asiakkaan analyytikot lähettävät muokkauksensa takaisin LibreOfficella tallennettuina .ods-tiedostoina. Nyt saman koodin on siis kirjoitettava ODS:ää ja luettava sitä. HotXLS, losLabin Delphille ja C++Builderille tarkoitettu natiivi Object Pascal -laskentataulukkojen kirjasto, hoitaa molemmat suunnat ilman, että Excel tai LibreOffice on asennettuna minnekään. Se ei kuitenkaan tee suunnista symmetrisiä. Vienti säilyttää paljon enemmän kuin tuonti palauttaa, ja toisin olettava tiimi näkee kaavojen ja muotoilujen haihtuvan jossakin asiakkaan muokkauksen ja seuraavan raportin välillä ilman virhettä, joka osoittaisi syyn

ODS-tuki kuuluu XLSX-julkisivuun, ei XLS-julkisivuun

HotXLS sisältää yhdessä paketissa kaksi itsenäistä luokkahierarkiaa: binäärisiä BIFF8 .xls -tiedostoja käsittelevän TXLSWorkbook-luokan yksikössä lxHandle sekä OOXML .xlsx -paketteja käsittelevän TXLSXWorkbook-luokan yksikössä lxHandleX. Jokainen OpenDocumentin aloituspiste — OpenODS, SaveAsODS, GetODSSheetNames — kuuluu TXLSXWorkbook-luokkaan. Sijoittelu ei ole mielivaltainen. OASIS ODF 1.3 -määrityksen mukainen ODS-paketti on zip-arkisto, jossa on mimetype-jäsen, manifesti ja content.xml-runko, joten se on rakenteellisesti OOXML-zipin sukulainen; BIFF8 taas on 1990-luvun binäärinen tietuevirta, jolla ei ole mitään yhteistä sen kanssa

Tällä sijoittelulla on käytännöllinen seuraus: vanhaa .xls-työkirjaa ei voi muuttaa .ods-tiedostoksi yhdellä kutsulla. Sinun on ensin siirrettävä BIFF-sisältö XLSX-malliin SaveXLSWorkbookAsXLSX-rutiinilla yksiköstä lxXlsxExport, avattava tulos uudelleen TXLSXWorkbook-luokan kautta ja vietävä se vasta sitten. Silta ei ole häviötön, ja puutteet kannattaa tuntea ennen kuin rakennat sen varaan. Se kopioi arvot, kaavat, lukumuotoilut, fontit, täytöt ja sarakeleveydet. Se pudottaa reunukset, yhdistetyt alueet, kommentit, kaaviot ja ehdollisen muotoilun. Runsaasti muotoiltu .xls-lähde saapuu ODS:ään pelkistetympänä kuin se lähti, ja tämä on sillan ominaisuus, ei ODS-kirjoittajan

Tuontipuolella tunnistus on automaattinen. Tavallinen Open-metodi tunnistaa ODS-paketin sen mimetype-jäsenestä ja palaa ylimmän tason content.xml-tarkistukseen, jos jäsentä ei ole, joten yleinen "avaa mikä tahansa käyttäjän lähettämä tiedosto" -koodipolku ei tarvitse omaa tiedostopäätteen tunnistusta. Avaamisen jälkeen SourceFormat-ominaisuus kertoo, mikä haara valittiin

Kaavio HotXLS-luokka-asettelusta Delphissä, jossa jokainen ODS-sisääntulopiste elää TXLSXWorkbookilla ja SaveXLSWorkbookAsXLSX-silta kuljettaa BIFF8 .xls -sisällön yli
Jokainen OpenDocument-aloituspiste roikkuu TXLSXWorkbookista, ja perinteinen .xls saavuttaa ODS:n vain häviöllisen BIFF–XLSX-sillan kautta

Vienti ODS:ään TODSExportOptions-luokalla

Vientikutsu itsessään on yksi rivi; sitä ympäröivä asetuskohde sisältää päätökset, joista tarkastaja kysyy myöhemmin:

var
  Book: TXLSXWorkbook;
  Opts: TODSExportOptions;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('quarterly-report.xlsx');
    Opts := TODSExportOptions.Create;        // kutsuja omistaa ja vapauttaa tämän
    try
      Opts.Generator := 'ReportService 4.2'; // meta:generator-ylikirjoitus
      Opts.IncludeCharts := True;
      Opts.IncludeImages := True;
      Book.SaveAsODS('quarterly-report.ods', Opts);
    finally
      Opts.Free;
    end;
  finally
    Book.Free;
  end;
end;

Asetuskohde on kutsujan omistuksessa. HotXLS ei vapauta sitä, minkä vuoksi sisempi try..finally on mukana eikä valinnainen. Kahta ominaisuutta, jotka muuttavat tulostetta pelkän nimeämisen sijaan, kannattaa tarkastella lähemmin. IncludeCharts := False tekee enemmän kuin piilottaa kaaviot: se poistaa kaavioiden alidokumentit ja niiden manifestimerkinnät paketista, mikä on juuri oikein, kun vastaanottaja on tietoputki, joka kompastuisi niihin. Generator korvaa ODF:n meta:generator-merkkijonon, joka olisi muuten HotXLS/<version>; korvaa se, kun jatkossa käytettävä työkalu tunnistaa tiedostotuottajat tukireititystä varten. Jos mikään tästä ei koske tapaustasi, jätä asetuskohde kokonaan pois. Kutsu SaveAs(FileName, xlsxOpenDocumentSpreadsheet) vastaa oletusasetuksilla tehtyä SaveAsODS-kutsua, ja kummankin stream-ylikuormitukset antavat kirjoittaa paketin suoraan HTTP-vastaukseen ilman väliaikaistiedostoa

Mitä tuontipolku lukee — ja mitä se tarkoituksella ohittaa

Lue tämä kohta huolellisesti ennen kuin lupaat kenellekään kierroksen säilyvän ennallaan. HotXLS:n ODS-tuonti on tarkoituksella kevyt polku. Se säilyttää skalaariarvot ja kunkin kaavan tallennushetkellä sisältämän välimuistituloksen sekä laajentaa toistetut rivit ja sarakkeet ruudukkoon. Se ei tuo tyylejä, ODS-kaavalausekkeita eikä piirroksia

Kaavoja koskeva valinta todennäköisimmin yllättää, ja se on tehty tarkoituksella. ODF-solu tallentaa rinnakkain kaksi asiaa: ODF 1.3:n osassa 4 määritellyllä OpenFormula-murteella kirjoitetun kaavalausekkeen sekä viimeisen arvon, jonka tuottava sovellus sille laski. OpenFormulan kääntäminen Excelin kaavasyntaksiin on oma murteiden muunnosongelmansa, jossa on todellisia reunatapauksia funktiosanastoissa, viittaussyntaksissa ja virhemalleissa. Välimuistiarvon lukeminen kiertää kokonaan tämän hiljaisen virhekäännöksen luokan, joten tuomasi luvut ovat täsmälleen ne luvut, jotka lähettäjä näki viimeksi. Vastineeksi ne saapuvat lukuina, eivät niitä tuottaneina elävinä kaavoina

Suunniteltava virhetilanne seuraa tästä suoraan: laskentataulukko, jonka loppusummat olivat oikein LibreOfficen viimeksi tallentaessa, tuodaan oikeine lukuineen, mutta luvut ovat nyt vakioita. Muokkaa syötesolua, laske uudelleen, eikä mikään muutu — kaava on poissa, jäljellä on vain sen lopputulos. Jos työnkulku tarvitsee tuonnin jälkeen eläviä kaavoja, luo ne uudelleen ohjelmallisesti omien liiketoimintasääntöjesi perusteella käyttämällä Cell.Formula-ominaisuutta, joka XLSX-julkisivussa vastaanottaa lausekkeen ilman alun yhtäsuuruusmerkkiä

Epäsymmetrisen kierroksen huomioiva suunnittelu

Vienti hahmontaa täydestä muistissa olevasta työkirjamallista: arvot, tyylit ja pyydettäessä myös kaaviot ja kuvat. Tuonti palauttaa vain arvot. Siksi .xlsx–.ods-vaihe on tarkka, kun taas .ods–.xlsx-vaihe tuo takaisin arvot ja välimuistitulokset mutta ei tyylejä eikä eläviä kaavoja. Kun ketjutat nämä kaksi, epäsymmetria kertautuu. Täysi .xlsx–.ods–.xlsx-kierto kirjoittaa kaiken uskollisesti ulospäin ja menettää tyylit sekä kaavat paluumatkalla, vaikka kummassakaan vaiheessa ei mennyt mitään vikaan

Kaavio epäsymmetrisestä HotXLS ODS -kiertomatkasta Delphistä: täysuskollinen vienti muistinvaraisesta työkirjamallista ja vain arvojen tuonti, joka jättää kaavat vakioiksi
Vienti renderöi koko muistimallin, kun taas tuonti palauttaa arvot ja välimuistitetut tulokset, joten täysi .xlsx–.ods–.xlsx-kierros pudottaa hiljaa tyylit ja elävät kaavat
Book := TXLSXWorkbook.Create;
try
  Book.Open('vendor-revision.ods');          // muoto tunnistettu automaattisesti
  if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
  begin
    // Arvot ja välimuistiin tallennetut kaavatulokset ovat läsnä ODS-
    // tuonnin jälkeen; tyylit ja elävät kaavat eivät ole. Rakenna uudelleen mitä
    // jatkoprosessi edellyttää ennen tallennusta.
    Book.Sheets[0].Cells[2, 5].Formula := 'SUM(B2:D2)';
    Book.SaveAs('vendor-revision.xlsx');
  end;
finally
  Book.Free;
end;

Tästä seuraava arkkitehtuurimalli on selvä: käsittele saapuvia .ods-tiedostoja tietosyötteinä, älä paikan päällä muokattavina dokumentteina. Säilytä kanoninen työkirja .xlsx-muodossa, lue arvot asiakkaan muokkauksista ja tuota tarvittaessa uusi ODS kanonisesta kopiosta. Varmennus kuuluu molempiin leireihin — avaa viedyt tiedostot LibreOffice Calcissa, ODF:n viitekuluttajassa, sekä Excelissä, joka on lukenut ODS:ää jo vuosia mutta eroaa LibreOfficesta kaavio- ja tyylituen reuna-alueilla. Laskentataulukoiden määrä, muutama avainsolu ja kaavioiden läsnäolo muodostavat riittävän savutestin kutakin vientiprofiilia varten

ODS-tiedoston arviointi ennen tuontiin sitoutumista

Kun päätepiste hyväksyy latauksia, laskentataulukoiden nimien luettelu on huomattavasti kevyempää kuin täysi jäsennys ja havaitsee rakenteelliset yllätykset varhain:

Kaavio HotXLS:n lähetystriagaportista Delphissä, jossa GetODSSheetNames hylkää lukukelvottomat ODS-paketit ja puuttuvat taulukot ennen kuin täysi tuonti ajaa
GetODSSheetNames-koe maksaa paljon vähemmän kuin täysi jäsennys ja havaitsee uudelleennimetyn taulukon epäonnistumisen, kun virhe voi vielä nimetä tiedoston
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;

Palautussopimus hämää ihmisiä: HotXLS-kutsut palauttavat yleensä positiivisen määrän tai arvon 1 onnistuessaan ja arvon -1 epäonnistuessaan sekä tyhjentävät luettelon epäonnistuessaan, joten testaa <= 0 sen sijaan, että vertaisit yhteen tiettyyn positiiviseen arvoon. GetODSSheetNames ei nollaa eikä täytä työkirjaesiintymää, joten yksi tarkistusobjekti voi arvioida kokonaisen saapuvien tiedostojen hakemiston. Tällaiset rakenteelliset tarkistukset havaitsevat tavallisimman todellisen virheen — analyytikko nimeää laskentataulukon uudelleen tai poistaa sen ennen muokkauksen palauttamista — jo portilla, jossa virheilmoitus voi vielä nimetä tiedoston ja puuttuvan laskentataulukon sen sijaan, että ongelma nousisi nil-viittauksena kolme kerrosta syvemmällä

Jos rakennat tämän ympärille laajempaa muunnosputkea, työkirjan auditointi- ja muunnostyöaseman malli näyttää, miten tiedoston ominaisuudet kartoitetaan ennen kohdemuodon valintaa, ja suurten työkirjojen suorituskykyopas pitää eräviennit kohtuullisissa muistirajoissa

HotXLS on natiivi Delphi- ja C++Builder-laskentataulukkokirjasto, jonka koko lähdekoodi toimitetaan mukana; täydellinen ominaisuusluettelo ja lisensointitiedot ovat HotXLS Delphi Componentin tuotesivulla