Teknisk artikkel

Malbasert Excel-rapportgenerering i Delphi med HotXLS

Den pålitelige måten å produsere en stilsatt Excel-rapport fra Delphi på, er å starte fra en arbeidsbok en designer allerede har bygget. Noen i økonomiavdelingen legger opp fakturaen i Excel: logoen, kolonneoverskriftene, kantlinjene på detaljbåndet, den fete summeringsraden, valutaformatene. Koden din åpner den filen, dropper levende data inn i cellene designeren reserverte til det, og lagrer resultatet. Utseendet er deres; tallene er dine. HotXLS, et nativt bibliotek for Delphi og C++Builder som leser og skriver XLS- og XLSX-arbeidsbøker uten å styre Excel, gir deg de tre operasjonene denne tilnærmingen trenger: søke etter en celle ut fra teksten, kopiere et område med stiler og formler intakte, og sette inn rader slik at alt under skyves nedover sammen med dataene

Den ene regelen som skiller en generator som overlever malredigeringer fra en som bryter sammen ved den første, er å aldri adressere celler med bokstavelige rad- og kolonnenumre. En mal er et dokument andre mennesker redigerer. Økonomiteamet legger til en skattelinje, øker høyden på logoraden, omorganiserer adresseblokken, og filformatet hjelper deg ikke det minste: en BIFF- eller OOXML-lagring lykkes uansett om rad 10 fortsatt betyr det den betydde forrige kvartal. En generator som skriver den første detaljlinjen til en hardkodet rad 10, vil den første gangen noen setter inn en blokk over detaljseksjonen, stemple varelinjer over feil celler og summere et totalområde som ikke lenger dekker dataene. Ingenting kaster en feil, hver lagring returnerer suksess, og det eneste signalet er en kunde som legger merke til en feil faktura

Diagram over HotXLS malrørledningen i Delphi: forankre tokens med FindText, utvide detaljbåndet, verifisere beregnet total, deretter lagre
Malrapportgenerering i Delphi kjører som fire HotXLS-trinn: forankre tokenene, utvide detaljbåndet, verifisere den beregnede summen, og levere

Forankre hver koordinat til et plassholder-token

Løsningen er å la malen bære sine egne koordinater. Designeren skriver tokens som {{CUSTOMER}}, {{DATE}} og {{DETAIL_START}} inn i cellene generatoren må røre, og generatoren regner ut hver posisjon ved kjøretid ut fra hvor den finner disse tokenene. Layoutredigeringer spiller ikke lenger noen rolle, fordi tokenet flytter seg med cellen det sitter i. Den andre halvparten av kontrakten er feilregelen: hvis et påkrevd token mangler, stopper jobben før noen kundedata når filen. En mal som har driftet, bør produsere en mislykket jobbilett, ikke et levert dokument

Å finne tokenene: FindText og ReplaceText

Begge klassefamiliene i HotXLS eksponerer søk på arknivå. FindText returnerer raden og kolonnen til den første cellen hvis tekst matcher, med en overlasting som legger til skille mellom store og små bokstaver. ReplaceText bytter ut hver forekomst og returnerer hvor mange den endret. De to dekker de to typene tokens du som regel har. Et enkelt anker som kundenavnet lokaliserer du én gang og skriver ved siden av; et token som skal opptre nøyaktig én gang, som rapportdatoen, erstatter du og sjekker antallet. På XLSX-siden ser en utfylling som forankrer seg selv slik ut:

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 er 0-basert

    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');
    // detaljutvidelse og lagring følger nedenfor
  finally
    Book.Free;
  end;
end;

To detaljer betyr noe. For det første matcher FindText og ReplaceText tekstverdien til en celle; et token bygget inn i en formelstreng er usynlig for dem, så plassholder-tokens hører hjemme i vanlige celler, aldri inne i formler. For det andre er erstatningsantallet driftdetektoren din. En mal som skal inneholde nøyaktig ett {{DATE}}-token, men rapporterer null erstatninger, har blitt redigert, og å kaste et unntak i det øyeblikket er nettopp det som gjør stille layoutdrift om til en synlig feil

Å klone detaljraden uten å miste stiler eller formler

Detaljseksjonen på en faktura vokser med dataene. Å skrive verdier rett inn i tomme rader under eksempellinjen kaster bort alt designeren forberedte: kantlinjene, tallformatene, formlene per rad. Mønsteret som bevarer alt dette, er å la én fullt formatert eksempelrad stå igjen i malen og klone den for hvert element. CopyRange dupliserer stiler og formler i ett enkelt kall, hvoretter generatoren bare overskriver verdicellene

Diagram over token-forankringer i en HotXLS Delphi-mal der en manglende plassholder feiler jobben før noen data er skrevet
Maltokens bærer sine egne koordinater, og en manglende token stopper jobben før noen data er skrevet
const
  DetailRow = 10;            // den formaterte eksempelraden i malen
var
  I: Integer;
begin
  // Åpne plass foran summeringsblokken først, slik at SUM-området
  // under detaljbåndet strekker seg sammen med dataene.
  if Length(Items) > 1 then
    Sheet.InsertRows(DetailRow + 1, Length(Items) - 1);

  for I := 0 to High(Items) do
  begin
    if I > 0 then              // klon stiler + formler fra eksempelraden
      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]);  // ingen '='-prefiks
  end;
end;

Se nøye på formeltilordningen. XLSX-Formula-egenskapen tar imot uttrykket uten et innledende likhetstegn, mens XLS-fasaden forventer '=B10*C10' tilordnet gjennom Value. Å blande de to konvensjonene er den vanligste porteringsfeilen mellom klassefamiliene, og den feiler uten å klage: cellen holder bare en bokstavelig streng som Excel viser som tekst. Hvis malen dekorerer detaljbåndet med sammenslåtte titelrader, husk at bare den øverste venstre cellen i et sammenslått område bærer en verdi. Layoutreglene i følgeartikkelen om sammenslåtte celler i layoutdrevne rapportmaler forklarer hvorfor sammenslåingsområder hører hjemme helt utenfor databåndet

Hva InsertRows flytter, og hva den lar bli igjen

Å sette inn rader foran summeringsblokken er det som holder et SUM-område strukket etter hvert som detaljseksjonen vokser. På XLSX-siden bærer InsertRows en lang liste av avhengige strukturer med seg ned sammen med cellene: sammenslåtte områder, radhøyder, hyperkoblinger, kommentarer, fryste ruter, autofilterområder, betinget formatering, datavalidering, tabeller, definerte navn, og bilde- og diagramforankringer. Det finnes én grense i den listen verdt å lære utenat. Formelomskrivning når bare referanser innenfor det samme arket. En formel på et sammendragsark som peker inn i det flyttede området, beholder sine gamle koordinater og leser stille feil celler, og det er derfor summer hentet på tvers av ark er tryggere å uttrykke gjennom navn på arbeidsboknivå. Følgeartikkelen om definerte navn og formler på tvers av ark går gjennom det mønsteret

Det gamle XLS-formatet trekker linjen et hardere sted. HotXLS holder pivottabeller, spørringstabeller og eksterne datatilkoblinger i BIFF-filer som rå bytebokser. De overlever åpning og lagring uendret, men de er ikke modellert, så radinnsetting rører dem aldri. En mal som parkerer en pivottabell under en detaljblokk som utvider seg, lagres uten noen advarsel i det hele tatt mens pivotkildens rektangel driver bort fra dataene. Utveien er strukturell, ikke defensiv: hold pivot- og spørringsinnhold på ark generatoren aldri setter inn i, så kan foreldelsen aldri skje

Diagram over hva HotXLS InsertRows flytter i XLSX, og kryssark-formel- og BIFF pivot-grensene Delphi generatorer må respektere
InsertRows bærer avhengige strukturer nedover på XLSX, mens kryssarkformler og BIFF råblokker markerer grensene

Regn om før levering, eller vit hvorfor du hoppet over det

HotXLS evaluerer ikke formler under SaveAs. Når et menneske åpner filen, regner Excel om alt (XLS-fasaden eksponerer CalculationMode og RecalcOnSave hvis du trenger å styre det), så en rapport på vei til en menneskelig innboks trenger ingenting mer fra deg. Bildet endrer seg i det øyeblikket arbeidsboken mater et annet program. CSV-eksport skriver ut formler som sin bokstavelige tekst og beregner dem aldri, og enhver nedstrøms parser som stoler på bufrede verdier, vil lese foreldede tall eller blanke felt. For de stiene, beregn på serveren med Calculate, som evaluerer et vilkårlig uttrykk mot den innlastede arbeidsboken og gir tilbake resultatet:

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;

Å sjekke det beregnede totalbeløpet mot ordrejournalen før lagringen er billig forsikring med god avkastning. Det gjør en feil faktura om til en mislykket jobb. En operatør kan kjøre en mislykket jobb på nytt på sekunder; en feil faktura som allerede ligger i en kundes innboks, koster en kundeansvarlig en unnskyldning og en korrigering

To klassefamilier, én algoritme

Den samme logikken porteres mellom formatene, men ikke den samme koden. TXLSWorkbook for gammel .xls er grensesnittbasert og referansetalt, med 1-basert arkindeksering, og du frigjør den aldri for hånd. TXLSXWorkbook for .xlsx er et vanlig objekt du må frigjøre i en try..finally, med 0-basert arkindeksering og formelkonvensjonen vist ovenfor. FindText, ReplaceText, CopyRange og InsertRows lever alle på begge sider, så anker-klone-regn-om-formen bæres rent over. Det praktiske rådet er å forplikte seg til ett format per pipeline, eller å skjule de to objektlivssyklusene bak en tynn egen adapter fremfor å spre forskjellen gjennom generatoren

Størrelse spiller sjelden noen rolle for den typen rapport dette mønsteret produserer. Å klone en stilsatt rad noen tusen ganger er ingenting for dagens maskinvare. Lagringsstien blir bare flaskehalsen når et detaljbånd løper opp i seks sifre av rader, og på det punktet sender innstillingen StreamingWrite arkets XML rett inn i utdatapakken i stedet for å bufre den; artikkelen om strømmende skriving for serverbatchjobber dekker når den avveiningen er verdt å ta. Diagrammer oppfører seg som resten av layouten gjør: på XLSX-siden flytter både diagramforankringen og seriereferansene når InsertRows kjøres over dem, slik at et diagram under summeringsraden holder seg bundet til riktige data, mens diagrammer på XLS-siden sitter på sine egne diagramark og, i likhet med pivottabeller, aldri flytter seg. Det er enda et argument for å holde presentasjonsark unna arket generatoren utvider

Denne anker-klone-regn-om-tilnærmingen lar en designer eie hvordan en arbeidsbok ser ut, mens koden din eier hva den sier, noe som vanligvis er det som gjør generert Excel-utdata verdt å vedlikeholde. Søke-, kopi- og innsettingskallene vist her, sammen med formelmotoren brukt for kontrollen av totalbeløpet før levering, følger med HotXLS Delphi Component for Delphi og C++Builder