Tekninen artikkeli

Työkirjojen auditointi- ja muuntotyöpöydän rakentaminen Delphissä HotXLS:llä

Laajamittainen laskentataulukoiden yhdenmukaistamistyö on kolme ongelmaa samassa paketissa. Arkistossa on sekalaisia muotoja: BIFF-ajan .xls-tiedostoja, nykyaikaisia .xlsx-tiedostoja, LibreOffice-kokeilusta jääneitä .ods-tiedostoja ja muutama tiedosto, jota kukaan ei voi avata, koska salasana lähti entisen työntekijän mukana. Tavoitteena on muuntaa kaikki XLSX- ja CSV-muotoon. Useimmat toteuttavat työn silmukkana, joka avaa jokaisen tiedoston ja tallentaa sen uudella tiedostopäätteellä, ja se toimii siihen asti, kun joku kysyy, mistä tiedostoista katosivat kaaviot, makrot tai jotka eivät koskaan avautuneet. Silmukalla ei ole vastausta, koska pelkkä muunnos ei pidä kirjaa. Työpöytä pitää: se inventoi ensin, muuntaa toiseksi ja tarkistaa kolmanneksi, ja kaikkien kolmen vaiheen on jaettava tietoa, jotta mikään niistä on luotettava

Tällaisen työpöydän kokoaminen Delphissä tai C++Builderissa tarkoittaa neljän HotXLS-ominaisuuden yhdistämistä, joista yksikään ei edellytä Excelin asennusta missään putken vaiheessa. Käytössä on kaksi natiivia moottoria: BIFF8-julkisivu .xls-muodolle sekä OOXML-julkisivu .xlsx- ja .ods-muodoille. Käytössä on edullisia luotauskutsuja, jotka lukevat metatietoja jäsentämättä koko tiedostoa. Käytössä on arkkikohtaisia auditointilaskureita, jotka kertovat, mitä työkirja todella sisältää. Lisäksi käytössä on muunnosmatriisi, jossa kunkin reitin säilyvyysprofiili on dokumentoitu. Työ on tietää, missä kussakin niistä on terävä reuna, sillä jokaisessa sellainen on, ja juuri nämä reunat muuttavat siistin yöajon maanantaiaamun häiriöksi

Putkikaavio HotXLS-auditointi ensin -muunnostyöpöydästä Delphissä: sekalainen xls-, xlsx- ja ods-tiedostoarkisto inventoidaan, muunnetaan reitillä ja varmennetaan inventoinnin aikana kirjattuja ennen-lukuja vasten
Työpöytä muuntaa kolmessa vaiheessa, ja inventoinnin aikana kirjatut auditointilaskurit muuttuvat ennen-numeroiksi, joita varmennus vertailee

Luotaa ennen lataamista: arkkien nimet ja salauksen tunnistus

200 Mt:n työkirjan avaaminen vain salauksen havaitsemiseksi tuhlaa minuutteja tiedostoa kohti ja suuren arkiston mittakaavassa päiviä. Molemmat julkisivut tarjoavat GetSheetNames-kutsun, joka lukee arkkien metatiedot täyttämättä työkirjaa. BIFF-toteutus skannaa vain virran alussa olevat BoundSheet-tietueet; OOXML-toteutus lukee vain zip-paketin sisällä olevan workbook.xml-tiedoston. Sen rinnalla CanReadEncrypted tunnistaa salauskontin yrittämättä purkaa salausta:

var
  Probe: TXLSXWorkbook;
  Names: TStringList;
begin
  Names := TStringList.Create;
  Probe := TXLSXWorkbook.Create;
  try
    if Probe.CanReadEncrypted(FileName) then
    begin
      Writeln(FileName + ': encrypted container - route to manual handling');
      Exit;
    end;
    if Probe.GetSheetNames(FileName, Names) <= 0 then
      Writeln(FileName + ': unreadable - quarantine')
    else
      Writeln(Format('%s: %d sheet(s), first "%s"',
        [FileName, Names.Count, Names[0]]));
  finally
    Probe.Free;
    Names.Free;
  end;
end;

Kaksi toiminnallista yksityiskohtaa tekee tästä silmukasta edullisen. GetSheetNames ei nollaa eikä täytä työkirjaoliota, joten yksi luotausolio voi luokitella tuhansia tiedostoja ilman uudelleenluontia. Saman kutsun XLS-julkisivuversio ymmärtää myös .xlsx-paketteja, mikä tekee siitä kätevän yksittäisen luotaimen silloin, kun tiedostopäätteisiin ei voi luottaa, kuten näin vanhassa arkistossa harvoin voi. Lataamista edeltävä lajittelu ansaitsee oman käsittelynsä; kevyen tarkastuksen mekanismit esitetään arkkien luetteloa ja kevyttä työkirjatarkastusta käsittelevässä artikkelissamme

Triagavuokaavio HotXLS-työkirjaerille Delphissä: CanReadEncrypted reitittää salatut säiliöt manuaaliseen käsittelyyn, GetSheetNames asettaa karanteeniin lukukelvottomat tiedostot, ja läpäisevät tiedostot siirtyvät auditointikierrokseen, joka päättää muunnosreitin
CanReadEncrypted- ja GetSheetNames-tiedustelu luokittelee jokaisen tiedoston ennen latausta, joten salatut ja lukukelvottomat työkirjat eivät koskaan saavuta muunnossilmukkaa

Sen laskeminen, mitä työkirja todella sisältää

Kun tiedosto läpäisee lajittelun, auditointivaihe päättää sen muunnosreitin. XLSX-julkisivu tarjoaa laskurin jokaiselle ominaisuusperheelle, joka vaikuttaa säilyvyyspäätökseen: yhdistetyille soluille, kaavioille, kuville, ehdollisille muotoiluille, tietojen kelpoisuustarkistuksille, taulukoille, hyperlinkeille ja kommenteille sekä työkirjatason liput makroille, suojaukselle ja lähdemuodolle. Tiedoston muunnosreitti riippuu lähes kokonaan siitä, mitkä näistä palauttavat nollasta poikkeavan arvon

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  I: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open(FileName) <> 1 then Exit;
    for I := 0 to Book.Sheets.Count - 1 do
    begin
      Sheet := Book.Sheets[I];
      Writeln(Format('%s: cells=%d merges=%d charts=%d cf=%d dv=%d protected=%s',
        [Sheet.Name, Sheet.Cells.Count, Sheet.MergedCells.Count,
         Sheet.Charts.Count, Sheet.ConditionalFormats.Count,
         Sheet.DataValidations.Count, BoolToStr(Sheet.IsProtected, True)]));
    end;
    if Book.HasVbaProject then
      Writeln('  contains VBA project - macro policy applies');
    if Book.ExternalLinks.Count > 0 then
      Writeln(Format('  %d external link(s)', [Book.ExternalLinks.Count]));
  finally
    Book.Free;
  end;
end;

Lue Cells.Count yksi varaus mielessäsi. Soluvarasto on harva, joten luku laskee instanssoidut solut, ei käytetyn alueen suorakulmaista pinta-alaa. Arkki, jossa on yksi arvo solussa A1 ja toinen solussa ZZ9999, raportoi kaksi solua, ei niiden välissä olevia yli miljoonaa solua. BIFF-puolen vastaava läpikäynti käyttää UsedRange-rajoja yhdessä ForEachCell-kutsun kanssa, ja siinä on lähes jokaisen ensimmäisellä kerralla kompastuttava yhden virhe: UsedRange.FirstRow ja sen sisarominaisuudet ovat nollapohjaisia, kun taas Cells.Item[Row, Col] on yksipohjainen. Läpikäynti, joka unohtaa lisätä yhden jokaiseen rajaan, auditoi väärän suorakulmion eikä koskaan kerro siitä

Kaksi vipua pienentää vain auditointiin tarkoitetun käsittelyn kustannusta suurissa vanhoissa tiedostoissa. Kun _DisableGraphics asetetaan todeksi ennen .xls-tiedoston avaamista, OfficeArt-piirtotason jäsentäminen ohitetaan kokonaan, mikä säästää todellista aikaa muotoja tiheästi sisältävissä työkirjoissa. Se on kuitenkin ehdottomasti vain luku -optimointi: tällaisesta instanssista tallentaminen pudottaisi piirtosisällön, jota se ei koskaan jäsentänyt, joten lippu kuuluu vain poluille, jotka eivät koskaan kirjoita tiedostoa takaisin. Kun auditointi tarvitsee laskureiden sijaan solukohtaista sisältöä, ForEachCell-takaisinkutsu käy suoraan läpi täytetyt solut ja välttää indeksoitujen soluominaisuuksien jokaisella luvulla maksaman Variant-kohtaisen käsittelykulun, joka kertyy nopeasti miljoonien solujen yli

Yhdenmukaista epäjohdonmukaiset paluukoodit varhain

HotXLS:n I/O-kutsut ilmoittavat virheistä kokonaislukutuloksilla poikkeusten sijaan, eivätkä käytännöt ole yhdenmukaisia kaikkialla API:ssa. Useimmat avaus- ja tallennuskutsut palauttavat onnistumisesta 1 ja epäonnistumisesta -1. GetSheetNames palauttaa arkkien lukumäärän tai -1 ja tyhjennetyn luettelon. XLSX:n SaveAsHTML rikkoo kaavan jälleen ja palauttaa onnistumisesta 0 sekä -1, kun arkin indeksi on alueen ulkopuolella. Työpöytä, joka testaa kaikkialla = 1, luokittelee hiljaisesti väärin kutsut, jotka ilmaisevat onnistumisen muulla tavalla, ja työpöytä, joka testaa <> -1, nielee kutsut, jotka epäonnistuvat toisella koodilla

Koko API:ssa toimiva sääntö on näennäistä kapeampi: käsittele <= 0 epäonnistumisena lukumäärän palauttaville kutsuille, tarkista jokaiselle todella käyttämällesi tallennusrutiinille dokumentoitu onnistumisarvo ja piilota molemmat yhden pienen tulostarkistusfunktion taakse, jotta käytäntö sijaitsee täsmälleen yhdessä paikassa. Eräputket epäonnistuvat paljon useammin tarkistamattomien paluukoodien hitaasti kasaantuvan joukon kuin eksoottisen jäsenninvirheen vuoksi, ja tämän virheen hinta näkyy neljäkymmentätuhatta tiedostoa myöhemmin, kun kukaan ei enää muista, mitkä muunnokset todella onnistuivat

Muunnosmatriisi ja kohdat, joissa kukin reitti menettää tietoja

Kaksi julkisivua jakaa muunnostyön keskenään. TXLSXWorkbook avaa XLSX-, ODS- ja CSV-tiedostoja sekä tallentaa XLSX-, ODS-, CSV-, HTML-, RTF- ja AES-salattua XLSX-muotoa. TXLSWorkbook avaa ja tallentaa BIFF-muotoa sekä vie HTML-, RTF- ja CSV-muotoon. Hyödyllistä on, että jokaisella reitillä on dokumentoitu säilyvyysprofiili, ei epämääräistä lupausta oikeellisuudesta, joten voit päättää etukäteen, mitkä reitit ovat turvallisia millekin tiedostoille

CSV-vienti kirjoittaa UTF-8:aa BOM-merkinnällä, CRLF-rivinvaihdoilla ja RFC 4180 -lainauksella. Se ei kuitenkaan laske kaavoja: =SUM(...)-kaavan sisältävä solu viedään kirjaimellisena kaavatekstinä, joten kaavoja sisältävästä arkista tulee merkkijonoja sisältävä arkki, ellet laske arvoja ensin. HTML-vienti tuottaa yhden taulukon, jossa colspan ja rowspan edustavat yhdistettyjä soluja ja perustyylit on upotettu. RTF-viennillä on terävämpi rajoitus: se ei voi venyttää yhdistettyjä soluja sarakkeiden yli, joten yhdistämisen jatkosolut tulevat tyhjinä. ODS-tuonti on tarkoituksella kevyt kirjaston oman dokumentaation mukaan. Skalaariarvot ja välimuistissa olevat kaavatulokset tulevat läpi; tyylit, elävät ODF-kaavalausekkeet ja piirrokset eivät. Tämä merkitsee heti, kun arkistossa on todellisia OpenDocument-tiedostoja, joita hallitsee OASIS ODF 1.3 ja joiden lähelläkään visuaalisesti uskollinen muunnos tarvitsee enemmän kuin tämä tuontipolku on rakennettu kuljettamaan. Auditointivaihe kertoo, että tällaisia tiedostoja on olemassa, ennen kuin eräajo latistaa ne hiljaisesti

SaveXLSWorkbookAsXLSX on tietosilta, ei asettelusilta

BIFF-julkisivu ei voi kirjoittaa OOXML:ää suoraan, joten siirtymä .xls-muodosta .xlsx-muotoon kulkee lxXlsxExport-yksikön SaveXLSWorkbookAsXLSX-funktion kautta. Tämän sillan säilyvyys kannattaa ilmaista suoraan, sillä nimi antaa ymmärtää enemmän kuin se tekee. Se kopioi arvot, kaavat, lukumuotoilut, täyttövärit, fontin ydinuominaisuudet, sarakeleveydet ja näkymäasetukset, kuten ruudukkoviivat. Se ei kopioi reunuksia, yhdistettyjä alueita, kommentteja, kaavioita eikä ehdollisia muotoiluja. Datatason yhdenmukaistamiseen, jossa jatkojärjestelmät jäsentävät tuloksen eikä kukaan katso muotoilua, tämä on täsmälleen riittävä eikä mitään tarpeellista katoa. Muotoillulle hallituksen raportille, jonka ihmisen on tarkoitus lukea, se ei riitä, ja juuri tässä auditointilaskurit ansaitsevat paikkansa: tiedosto, jonka auditointi merkitsi kaavioita ja ehdollisia muotoiluja sisältäväksi, on ohjattava manuaaliseen jonoon, ei sillan läpi, joka pudottaa molemmat sanomatta sanaakaan

Sillan uskollisuuskaavio HotXLS SaveXLSWorkbookAsXLSX:lle Delphissä: arvot, kaavat, numeromuodot, täyttövärit, perusfontin ominaisuudet, sarakeleveydet ja näkymäasetukset ylittävät BIFF xls:stä XLSX:ään, kun taas reunat, yhdistetyt alueet, kommentit, kaaviot ja ehdolliset muotoilut pudotetaan
SaveXLSWorkbookAsXLSX kantaa jäsentyjän tarvitseman datan BIFF–OOXML-sillan yli, ja auditointilaskurit ovat ne, jotka merkitsevät ne tiedostot, joiden kaaviot ja yhdistelyt putoaisivat
var
  Legacy: IXLSWorkbook;        // liittymäviittaus: älä kutsu Free
  Modern: TXLSXWorkbook;
begin
  if SameText(ExtractFileExt(FileName), '.xls') then
  begin
    Legacy := TXLSWorkbook.Create;
    if Legacy.Open(FileName) <= 0 then Exit;
    if SaveXLSWorkbookAsXLSX(Legacy,
         ChangeFileExt(FileName, '.xlsx')) <= 0 then
      Writeln('bridge failed: ' + FileName);
  end
  else
  begin
    Modern := TXLSXWorkbook.Create;
    try
      Modern.StreamingWrite := True;     // virtaa arkin XML zipiin
      if Modern.Open(FileName) = 1 then
        Modern.SaveAsCSV(ChangeFileExt(FileName, '.csv'), 0, ',');
    finally
      Modern.Free;
    end;
  end;
end;

Yllä oleva silmukka näyttää myös OOXML-puolen läpimenon vivun. StreamingWrite-asetuksen asettaminen todeksi suoratoistaa laskentataulukon XML:n suoraan tulospakettiin sen sijaan, että se valmisteltaisiin yhtenä valtavana merkkijonona muistissa. Tämä ratkaisee, onko ajo mukava vai päättyykö se muistin loppumiseen, kun tiedostoissa on satojatuhansia rivejä. Tämän tilan kokoa ja muistinkäyttöä käsitellään erikseen palvelineräajojen suoratoistokirjoitusta käsittelevässä artikkelissamme. Vielä yksi ominaisuus merkitsee eräajolle, joka haluaa käyttää kaikki ytimet: kumpikaan julkisivu ei ole säieturvallinen, mutta kumpikaan ei jaa globaalia tilaa, joten tuettu malli rinnakkaiseen muunnokseen on yksi työkirjainstanssi työntekijäsäiettä kohti ilman lukitusta niiden välillä

Salasanatiedostot ja niiden käsittely

Arkiston lukitut tiedostot jakautuvat selvästi muodon mukaan, ja jako määrää niiden reitin. Vanhan .xls-muodon salaus, olipa se RC4, CryptoAPI:n päällä käytettävä RC4 tai vanha XOR-hämäys, on luettavissa: anna salasana Open-kutsulle ja tiedosto muuntuu kuten mikä tahansa muu. Salatut .xlsx-paketit ovat eri asia. HotXLS tunnistaa ne CanReadEncrypted-kutsulla mutta ei voi purkaa niiden salausta, joten ainoa rehellinen ratkaisu on ohjata ne jonoon, jossa ihminen avaa ja tallentaa jokaisen uudelleen Excelissä ennen kuin se palaa putkeen. Tämä epäsymmetria kannattaa suunnitella etukäteen, sillä salatut XLSX-tiedostot ovat todennäköisimmin juuri niitä tietueita, joista joku todella välittää

Sulje silmukka varmennuksella

Kolmas vaihe jätetään usein pois, ja sen pois jättäminen muuttaa massamuunnoksen vastuuksi. Mikään HotXLS:n tallennuspolku ei laske kaavoja. Excel laskee ne uudelleen, kun se avaa tiedoston, joten XLSX:stä XLSX:ään -muunnos säilyy oikeana, mutta CSV-kohde vastaanottaa kaavatekstin sellaisenaan, ellei putki ensin suorita Calculate-kutsua soluille ja kirjoita tuloksia takaisin. Tämän tietäminen etukäteen ratkaisee, saatko lukuja täynnä olevan CSV:n vai =SUM(...)-merkkijonoja täynnä olevan CSV:n, joita kukaan ei huomaa ennen kuin jatkojärjestelmän tuonti tukehtuu niihin

Varmennus on itsessään niin edullista, ettei sen pois jättämiseen ole tekosyytä. Avaa jokainen muunnettu tiedosto uudelleen samalla kirjastolla, suorita auditointilaskurit uudelleen ja vertaa niitä inventointivaiheen jo tallentamiin muunnosta edeltäviin lukuihin. Pudonnut arkkien lukumäärä, kaavioiden lukumäärä, joka putosi nollaan lähteen sisältäessä kolme, tai solumäärä, joka romahti: jokainen on hiljainen menetys, joka havaitaan toisen avauksen hinnalla. Tee lisäksi silmämääräinen pistokoe Excelissä tai LibreOfficessa, ja yhdistelmä havaitsee valtaosan muunnosvaurioista ennen toimitusta. Tästä syystä inventointivaihe syöttää varmennusvaihetta. Ilman ennen-lukuja jälkeen-luvut eivät todista mitään

Auditointi ensin -työpöytä muuttaa riskialttiin massamuunnoksen mitattavaksi prosessiksi, jossa puhtaasti läpäisemättömille tiedostoille on karanteenikaista. Kaikki tässä esitetyt luotaus-, laskenta- ja muunnoskutsut ovat osa HotXLS Delphi Component -komponenttia, joka suorittaa ne natiivisti samassa prosessissa ilman Excel-automatisointia