Tehnički članak

Generisanje Excel izveštaja na osnovu šablona u Delphi-ju pomoću HotXLS-a

Pouzdan način za kreiranje stilizovanog Excel izveštaja iz Delphi-ja je da počnete od radne sveske koju je dizajner već napravio. Neko u finansijama dizajnira izgled fakture u Excel-u: logotip, zaglavlja kolona, ivice na traci sa detaljima, podebljani red sa ukupnim vrednostima, formate valuta. Vaš kod otvara taj fajl, upisuje stvarne podatke u ćelije koje je dizajner rezervisao za njih i čuva rezultat. Izgled je njihov, a brojevi su vaši. HotXLS, izvorna biblioteka za Delphi i C++Builder koja čita i piše XLS i XLSX radne sveske bez pokretanja Excel-a, pruža vam tri operacije koje ovaj pristup zahteva: pretragu ćelije po njenom tekstu, kopiranje opsega sa netaknutim stilovima i formulama i ubacivanje redova tako da se sve ispod pomera nadole zajedno sa podacima

Jedino pravilo koje razlikuje generator koji preživljava izmene šablona od onog koji puca na prvoj promeni jeste da nikada ne pristupate ćelijama preko doslovnih brojeva redova i kolona. Šablon je dokument koji drugi ljudi uređuju. Finansijski tim doda red za porez, poveća visinu reda sa logotipom, promeni redosled bloka adrese, a format fajla vam uopšte ne pomaže: čuvanje u BIFF ili OOXML formatu uspeva bez obzira na to da li red 10 i dalje znači ono što je značio prošlog kvartala. Generator koji upisuje prvi red detalja u čvrsto kodirani red 10 će, kada neko prvi put ubaci blok iznad odeljka sa detaljima, upisati stavke preko pogrešnih ćelija i sabrati opseg ukupnih vrednosti koji više ne pokriva podatke. Ništa ne prijavljuje grešku, svako čuvanje vraća uspeh, a jedini signal je kada klijent primeti pogrešnu fakturu

Povežite (sidrite) svaku koordinatu za mesto za unos (token)

Rešenje je da učinite da šablon sam nosi svoje koordinate. Dizajner upisuje tokene kao što su {{CUSTOMER}}, {{DATE}} i {{DETAIL_START}} u ćelije koje generator mora da dotakne, a generator u vreme izvršavanja pronalazi te tokene i na osnovu njih određuje svaku poziciju. Izmene rasporeda više nisu važne, jer se token pomera zajedno sa ćelijom u kojoj se nalazi. Druga polovina ovog ugovora je pravilo o neuspehu: ako nedostaje zahtevani token, posao se zaustavlja pre nego što bilo kakvi korisnički podaci stignu u fajl. Šablon koji je odstupio od pravila trebalo bi da generiše izveštaj o neuspešnom poslu, a ne isporučeni dokument

Pronalaženje tokena: FindText i ReplaceText

Obe porodice klasa u HotXLS-u izlažu pretragu na nivou radnog lista. Metoda FindText vraća red i kolonu prve ćelije čiji se tekst podudara, sa preopterećenjem koje dodaje osetljivost na velika i mala slova. Metoda ReplaceText zamenjuje svako pojavljivanje i vraća broj promenjenih ćelija. Ove dve metode pokrivaju dve vrste tokena koje obično imate. Jedno sidro, kao što je ime klijenta, locirate jednom i pišete pored njega; token koji bi trebalo da se pojavi tačno jednom, kao što je datum izveštaja, zamenite i proverite broj zamena. Na XLSX strani, popunjavanje koje se sidri na ovaj način izgleda ovako:

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  R, C: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open('invoice-template.xlsx') <> 1 then
      raise Exception.Create('Cannot open invoice template');
    Sheet := Book.Sheets[0];               // TXLSXSheets.Items is 0-based

    if not Sheet.FindText('{{CUSTOMER}}', R, C) then
      raise Exception.Create('Template drift: {{CUSTOMER}} anchor missing');
    Sheet.Cells[R, C].Value := 'ACME Corp';

    if Sheet.ReplaceText('{{DATE}}',
         FormatDateTime('yyyy-mm-dd', Date)) = 0 then
      raise Exception.Create('Template drift: {{DATE}} token missing');
    // detail expansion and save follow below
  finally
    Book.Free;
  end;
end;

Dva detalja su važna. Prvo, metode FindText i ReplaceText traže podudaranje sa tekstualnom vrednošću ćelije; token ugrađen unutar stringa formule im je nevidljiv, pa mesta za unos (placeholders) pripadaju običnim ćelijama, nikada unutar formula. Drugo, broj zamena je vaš detektor odstupanja šablona. Šablon koji bi trebalo da sadrži tačno jedan token {{DATE}}, ali prijavljuje nula zamena, pretrpeo je izmene, a podizanje izuzetka u tom trenutku je upravo ono što tiho odstupanje rasporeda pretvara u vidljiv neuspeh

Kloniranje reda sa detaljima bez gubljenja stilova ili formula

Odeljak sa detaljima na fakturi raste sa podacima. Upisivanje vrednosti direktno u prazne redove ispod uzorka odbacuje sve što je dizajner pripremio: ivice, formate brojeva, formule po redovima. Šablon koji čuva sve to jeste da ostavi jedan potpuno formatiran uzorni red u šablonu i klonira ga za svaku stavku. Metoda CopyRange duplira stilove i formule u jednom pozivu, nakon čega generator prepisuje samo ćelije sa vrednostima

const
  DetailRow = 10;            // the formatted sample row in the template
var
  I: Integer;
begin
  // Open space before the totals block first, so the SUM range
  // below the detail band stretches together with the data.
  if Length(Items) > 1 then
    Sheet.InsertRows(DetailRow + 1, Length(Items) - 1);

  for I := 0 to High(Items) do
  begin
    if I > 0 then              // clone styles + formulas from the sample row
      Sheet.CopyRange(DetailRow, 1, DetailRow, 5, DetailRow + I, 1);
    Sheet.Cells[DetailRow + I, 1].Value := Items[I].Name;
    Sheet.Cells[DetailRow + I, 2].Value := Items[I].Qty;
    Sheet.Cells[DetailRow + I, 3].Value := Items[I].UnitPrice;
    Sheet.Cells[DetailRow + I, 4].Formula :=
      Format('B%d*C%d', [DetailRow + I, DetailRow + I]);  // no '=' prefix
  end;
end;

Pažljivo pratite dodeljivanje formule. Svojstvo Formula na XLSX strani uzima izraz bez vodećeg znaka jednakosti, dok XLS fasada očekuje '=B10*C10' dodeljeno kroz svojstvo Value. Mešanje ove dve konvencije je najčešća greška pri prenosu koda između porodica klasa, i to prolazi bez prijave greške: ćelija jednostavno drži doslovan string koji Excel prikazuje kao običan tekst. Ako šablon ukrašava traku sa detaljima spojenim redovima naslova, zapamtite da samo gornja leva ćelija spojenog opsega nosi vrednost. Pravila rasporeda u pratećem članku o spojenim ćelijama u šablonima izveštaja vođenim rasporedom objašnjavaju zašto oblasti spajanja u potpunosti pripadaju prostoru van trake sa podacima

Šta InsertRows pomera, a šta ostavlja iza sebe

Ubacivanje redova ispred bloka sa ukupnim vrednostima je ono što omogućava da se opseg SUM širi kako odeljak sa detaljima raste. Na XLSX strani, InsertRows povlači dugačku listu zavisnih struktura nadole zajedno sa ćelijama: spojene opsege, visine redova, hiperveze, komentare, zamrznuta polja (frozen panes), opsege auto-filtera, uslovne formate, validacije podataka, tabele, definisana imena, kao i sidra slika i grafikona. U toj listi postoji jedna granica koju vredi zapamtiti. Prepravljanje formula dotiče samo reference unutar istog lista. Formula na zbirnom listu koja pokazuje na pomerenu regiju zadržava svoje stare koordinate i tiho čita pogrešne ćelije, zbog čega je ukupne vrednosti povučene sa drugih listova sigurnije izraziti preko imena na nivou radne sveske. Prateći članak o definisanim imenima i međulistnim formulama detaljno opisuje ovaj šablon

Zastareli XLS format povlači granicu na još restriktivnijem mestu. HotXLS čuva pivot tabele, tabele upita (query tables) i spoljne veze sa podacima u BIFF datotekama kao sirove blokove bajtova. Oni preživljavaju otvaranje i čuvanje nepromenjeni, ali nisu modelovani, tako da ih ubacivanje redova nikada ne dotiče. Šablon koji postavlja pivot tabelu ispod odeljka sa detaljima koji se širi, čuva se bez ikakvog upozorenja dok pravougaonik izvora pivot tabele odstupa od podataka. Rešenje je strukturno, a ne defanzivno: držite sadržaj pivot tabela i upita na listovima na kojima generator nikada ne vrši ubacivanje redova, i problem zastarelosti se ne može desiti

Ponovo proračunajte pre isporuke, ili znajte zašto ste to preskočili

HotXLS ne evaluira formule tokom izvršavanja SaveAs. Kada čovek otvori fajl, Excel ponovo izračunava sve (XLS fasada izlaže svojstva CalculationMode i RecalcOnSave ako to želite da kontrolišete), tako da izveštaju namenjenom za prijemno sanduče čoveka nije potrebno ništa više od vas. Slika se menja onog trenutka kada radna sveska hrani neki drugi program. Izvoz u CSV upisuje formule kao njihov doslovan tekst i nikada ih ne izračunava, a bilo koji nizvodni parser koji veruje keširanim vrednostima čitaće zastarele brojeve ili prazna polja. Za te putanje, izvršite proračun na serveru pomoću metode Calculate, koja evaluira proizvoljan izraz u odnosu na učitanu radnu svesku i vraća rezultat:

var
  Total: Variant;
  LastDetail: Integer;
begin
  LastDetail := DetailRow + Length(Items) - 1;
  Total := Book.Calculate(Format('SUM(Invoice!D%d:D%d)',
    [DetailRow, LastDetail]));
  if (not VarIsNumeric(Total)) or
     (Abs(Total - ExpectedTotal) > 0.005) then
    raise Exception.Create('Invoice total does not match the order record');

  if Book.SaveAs('invoice-2026-0611.xlsx') <> 1 then
    raise Exception.Create('Save failed: check output path and permissions');
end;

Provera izračunate ukupne vrednosti u odnosu na zapis o porudžbini pre čuvanja je jeftino osiguranje koje se višestruko isplati. To pretvara pogrešnu fakturu u neuspešan posao. Operater može ponovo pokrenuti neuspešan posao za nekoliko sekundi, dok pogrešna faktura koja je već stigla u sanduče klijenta košta menadžera naloga izvinjenja i ispravke

Dve porodice klasa, jedan algoritam

Ista logika se prenosi između formata, ali ne i isti kod. Klasa TXLSWorkbook za zastareli .xls je bazirana na interfejsima i ima brojanje referenci (reference-counted), sa indeksiranjem listova koje počinje od 1, i nikada je ne oslobađate ručno. Klasa TXLSXWorkbook za .xlsx je običan objekat koji morate osloboditi u bloku try..finally, sa indeksiranjem listova koje počinje od 0 i konvencijom o formulama koja je prikazana iznad. Metode FindText, ReplaceText, CopyRange i InsertRows postoje na obe strane, tako da se obrazac sidrenja, kloniranja i ponovnog proračuna prenosi čisto. Praktičan savet je da se opredelite za jedan format po cevovodu, ili da sakrijete ova dva životna ciklusa objekata iza sopstvenog tankog adaptera, umesto da širite razlike kroz čitav generator

Veličina retko predstavlja problem za vrstu izveštaja koju ovaj obrazac proizvodi. Kloniranje stilizovanog reda nekoliko hiljada puta nije ništa za današnji hardver. Putanja čuvanja postaje usko grlo tek kada traka sa detaljima dostigne šestocifren broj redova, a u tom trenutku podešavanje svojstva StreamingWrite šalje XML radnog lista direktno u izlazni paket umesto da ga baferuje; članak o strimovanju upisa za serverske batch poslove pokriva situacije u kojima se isplati napraviti taj kompromis. Grafikoni se ponašaju onako kako se ponaša i ostatak rasporeda: na XLSX strani i sidro grafikona i reference njegove serije se pomeraju kada se InsertRows pokrene iznad njih, tako da grafikon ispod reda sa ukupnim vrednostima ostaje povezan sa pravim podacima, dok na XLS strani grafikoni stoje na sopstvenim listovima sa grafikonima i, poput pivot tabela, nikada se ne pomeraju. To je još jedan razlog za držanje listova za prezentaciju odvojeno od lista koji generator proširuje

Ovaj pristup sidrenja, kloniranja i ponovnog proračuna omogućava dizajneru da kontroliše kako radna sveska izgleda, dok vaš kod kontroliše šta ona kaže, što je obično ono što generisani Excel izlaz čini lakim za održavanje. Pozivi za pretragu, kopiranje i ubacivanje koji su ovde prikazani, zajedno sa mehanizmom formula koji se koristi za proveru ukupne vrednosti pre isporuke, isporučuju se sa HotXLS komponentom za Delphi i C++Builder