Technical Article

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 jeste da počnete od radne sveske koju je dizajner već napravio. Neko u finansijama kreira izgled fakture u Excel-u: logo, zaglavlja kolona, ivice u opsegu detalja, podebljan red sa ukupnim vrednostima, formate valuta. Vaš kod otvara tu datoteku, upisuje stvarne podatke u ćelije koje je dizajner rezervisao za to i čuva rezultat. Izgled je njihov, a brojevi su vaši. HotXLS, matična Delphi i C++Builder biblioteka koja čita i upisuje XLS i XLSX radne sveske bez pokretanja Excel-a, pruža vam tri operacije koje su potrebne za ovaj pristup: pretragu ćelije po njenom tekstu, kopiranje opsega sa očuvanim stilovima i formulama, i umetanje redova tako da se sve ispod pomera nadole sa podacima.

Jedino pravilo koje razlikuje generator koji može da preživi izmene šablona od onog koji se kvari pri prvoj izmeni jeste da nikada ne pristupate ćelijama preko fiksnih brojeva redova i kolona. Šablon je dokument koji drugi ljudi uređuju. Finansijski tim dodaje poresku stavku, povećava visinu reda sa logotipom, menja redosled bloka adrese, a format datoteke vam uopšte ne pomaže: čuvanje BIFF ili OOXML formata uspeva bez obzira na to da li red 10 i dalje znači ono što je značio u prošlom kvartalu. Generator koji upisuje prvu stavku detalja u hardkodovani red 10 će, kada neko prvi put umetne blok iznad odeljka sa detaljima, prepisati 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 svaku koordinatu sa tokenom čuvara mesta (placeholder)

Rešenje je da šablon sam nosi svoje koordinate. Dizajner upisuje tokene kao što su {{CUSTOMER}}, {{DATE}} i {{DETAIL_START}} u ćelije koje generator mora da izmeni, a generator izračunava svaku poziciju tokom izvršavanja na osnovu mesta gde pronađe te tokene. Izmene izgleda više nisu bitne, jer se token pomera zajedno sa ćelijom u kojoj se nalazi. Druga polovina ovog pravila je pravilo o neuspehu: ako nedostaje potreban token, posao se zaustavlja pre nego što bilo kakvi podaci o klijentu stignu u datoteku. Šablon koji je promenjen treba da generiše prijavu o grešci, a ne isporučeni dokument.

Pronalaženje tokena: FindText i ReplaceText

Obe familije klasa u HotXLS-u omogućavaju pretragu na nivou radnog lista. Funkcija FindText vraća red i kolonu prve ćelije čiji se tekst podudara, sa preopterećenjem koje dodaje osetljivost na velika i mala slova. Funkcija ReplaceText zamenjuje svako pojavljivanje i vraća broj izmenjenih ćelija. Ove dve funkcije pokrivaju dve vrste tokena koje obično koristite. Jednostruki marker, poput imena klijenta, locirate jednom i pišete pored njega; token koji treba da se pojavi tačno jednom, poput datuma izveštaja, zamenjujete i proveravate broj uspešnih zamena. Na XLSX strani, popunjavanje koje se pozicionira 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, funkcije FindText i ReplaceText pretražuju tekstualnu vrednost ćelije; token koji je deo formule je nevidljiv za njih, pa stoga tokeni čuvari mesta pripadaju običnim ćelijama, a nikada formulama. Drugo, broj zamena je vaš detektor promena u šablonu. Šablon koji bi trebalo da sadrži tačno jedan token {{DATE}}, ali prijavljuje nula zamena, promenjen je, a podizanje izuzetka u tom trenutku je upravo ono što pretvara tihe promene izgleda u jasan neuspeh.

Kloniranje reda sa detaljima bez gubitka stilova ili formula

Odeljak sa detaljima na fakturi raste sa podacima. Upisivanje vrednosti direktno u prazne redove ispod uzorka briše sve što je dizajner pripremio: ivice, formate brojeva, formule po redovima. Obrazac koji sve to čuva jeste da ostavite jedan potpuno formatiran red uzorka u šablonu i klonirate ga za svaku stavku. Funkcija 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 dodelu formula. XLSX svojstvo Formula prima izraz bez vodećeg znaka jednakosti, dok XLS fasada očekuje '=B10*C10' dodeljen preko Value. Mešanje ove dve konvencije je najčešća greška pri prevođenju koda između ove dve familije klasa, i prolazi bez greške: ćelija jednostavno sadrži tekstualni niz koji Excel prikazuje kao običan tekst. Ako šablon ukrašava opseg detalja spojenim redovima naslova, zapamtite da samo gornja leva ćelija spojenog područja sadrži vrednost. Pravila rasporeda u pratećem članku o spojenim ćelijama u šablonima izveštaja vođenim rasporedom objašnjavaju zašto spojeni regioni treba da budu u potpunosti izvan opsega podataka.

Šta InsertRows pomera, a šta ostavlja iza sebe

Umetanje redova ispred bloka sa ukupnim vrednostima je ono što omogućava da se opseg SUM širi kako raste odeljak sa detaljima. Na XLSX strani, InsertRows prenosi dugačku listu zavisnih struktura nadole sa ćelijama: spojene opsege, visine redova, hiperveze, komentare, zamrznuta okna, opsege autofiltera, uslovna formatiranja, validacije podataka, tabele, definisana imena, kao i sidra slika i grafikona. Postoji jedna granica na toj listi koju vredi zapamtiti. Ponovno upisivanje formula obuhvata samo reference unutar istog lista. Formula na zbirnom listu koja ukazuje na pomereni region zadržava svoje stare koordinate i tiho čita pogrešne ćelije, zbog čega je ukupne vrednosti koje se povlače sa različitih listova sigurnije izraziti kroz imena na nivou radne sveske. Prateći članak o definisanim imenima i formulama između listova detaljno opisuje ovaj obrazac.

Zastareli XLS format postavlja granicu na još zahtevnije mesto. HotXLS čuva izvedene tabele (pivot tables), upitne tabele (query tables) i eksterne veze sa podacima u BIFF datotekama kao sirove blokove bajtova. Oni preživljavaju otvaranje i čuvanje nepromenjeni, ali nisu modelirani, pa ih umetanje redova nikada ne menja. Šablon koji postavlja izvedenu tabelu ispod promenljivog bloka sa detaljima čuva se bez ikakvog upozorenja, dok izvor izvedene tabele odstupa od podataka. Rešenje je strukturno, a ne odbrambeno: držite izvedeni i upitni sadržaj na listovima u koje generator nikada ne umeće redove, pa se zastarevanje ne može dogoditi.

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

HotXLS ne izračunava formule tokom poziva SaveAs. Kada korisnik otvori datoteku, Excel preračunava sve (XLS fasada izlaže svojstva CalculationMode i RecalcOnSave ako je potrebno da time upravljate), tako da izveštaj namenjen osobi ne zahteva ništa više od vas. Slika se menja onog trenutka kada radna sveska služi kao ulaz za drugi program. CSV izvoz upisuje formule kao njihov doslovni tekst i nikada ih ne izračunava, pa će svaki parser koji veruje keširanim vrednostima pročitati zastarele brojeve ili prazna polja. Za te slučajeve, izračunajte vrednosti na serveru pomoću funkcije Calculate, koja procenjuje 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 porudžbine pre čuvanja je jeftino osiguranje sa dobrim ishodom. To pretvara pogrešnu fakturu u neuspeli posao. Operater može ponovo pokrenuti neuspeli posao za nekoliko sekundi, dok pogrešna faktura koja je već u poštanskom sandučetu klijenta košta menadžera naloga izvinjenja i ispravke.

Dve familije klasa, jedan algoritam

Ista logika se prenosi između formata, ali ne i isti kod. TXLSWorkbook za stariji .xls je zasnovan na interfejsima i koristi brojanje referenci, ima indeksiranje listova od 1 i nikada ga ne oslobađate ručno. TXLSXWorkbook za .xlsx je običan objekat koji morate osloboditi unutar bloka try..finally, ima indeksiranje listova od 0 i konvenciju o formulama koja je prikazana iznad. Funkcije FindText, ReplaceText, CopyRange i InsertRows postoje na obe strane, tako da se šablon "sidro-klon-preračunavanje" prenosi bez problema. Praktičan savet je da se opredelite za jedan format po toku obrade ili da sakrijete životne cikluse ova dva objekta iza jednostavnog adaptera, umesto da širite razlike kroz ceo generator.

Veličina retko predstavlja problem za vrstu izveštaja koju ovaj obrazac generiše. Kloniranje stilizovanog reda nekoliko hiljada puta nije nikakav napor za današnji hardver. Proces čuvanja postaje usko grlo tek kada sekcija sa detaljima dostigne šestocifren broj redova, a u tom trenutku podešavanje StreamingWrite šalje XML radnog lista direktno u izlazni paket umesto da ga baferuje; članak o upisivanju toka za pozadinske poslove na serveru objašnjava kada se taj kompromis isplati. Grafikoni se ponašaju isto kao i ostatak rasporeda: na XLSX strani i sidro grafikona i reference serija se pomeraju kada se InsertRows pokrene iznad njih, pa grafikon ispod reda sa ukupnim vrednostima ostaje povezan sa ispravnim podacima, dok na XLS strani grafikoni stoje na sopstvenim listovima sa grafikonima i, poput izvedenih tabela, nikada se ne pomeraju. To je još jedan razlog za držanje prezentacionih listova odvojeno od lista koji generator proširuje.

Ovaj pristup "sidro-klon-preračunavanje" omogućava dizajneru da kontroliše izgled radne sveske, dok vaš kod kontroliše njen sadržaj, što je obično ono zbog čega se generisani Excel izlaz isplati održavati. Pozivi za pretragu, kopiranje i umetanje prikazani ovde, zajedno sa mehanizmom za formule koji se koristi za proveru ukupne vrednosti pre isporuke, dolaze uz HotXLS komponentu za Delphi i C++Builder.