Teknisk artikel

Skabelonbaseret Excel-rapportgenerering i Delphi med HotXLS

Den pålidelige måde at producere en stilsat Excel-rapport fra Delphi på er at starte fra en arbejdsbog, en designer allerede har bygget. Nogen i økonomiafdelingen lægger fakturaen op i Excel: logoet, kolonneoverskrifterne, kanterne på detaljebåndet, den fede totalrække, valutaformaterne. Din kode åbner den fil, dropper levende data ind i de celler, designeren reserverede til det, og gemmer resultatet. Udseendet er deres; tallene er dine. HotXLS, et indfødt Delphi- og C++Builder-bibliotek, der læser og skriver XLS- og XLSX-arbejdsbøger uden at styre Excel, giver dig de tre operationer, denne tilgang har brug for: søge efter en celle ud fra dens tekst, kopiere et område med dets stile og formler intakte, og indsætte rækker, så alt nedenunder rykker ned sammen med dataene

Den ene regel, der adskiller en generator, der overlever skabelonredigeringer, fra én, der går i stykker ved den første, er aldrig at adressere celler med bogstavelige række- og kolonnetal. En skabelon er et dokument, andre redigerer. Økonomiteamet tilføjer en momslinje, hæver logorækkens højde, omrokerer adresseblokken, og filformatet hjælper dig overhovedet ikke: en BIFF- eller OOXML-gemning lykkes, uanset om række 10 stadig betyder, hvad den betød sidste kvartal. En generator, der skriver den første detaljelinje til en hårdkodet række 10, vil, første gang nogen indsætter en blok over detaljesektionen, stemple linjeposter over de forkerte celler og summe et totalområde, der ikke længere dækker dataene. Intet fejler, hver gemning returnerer succes, og det eneste signal er en kunde, der bemærker en forkert faktura

Diagram over HotXLS skabelonpipelinen i Delphi: forankr tokens med FindText, udvid detaljebåndet, verificér den beregnede total, gem derefter
Skabelon-rapportgenerering i Delphi kører som fire HotXLS-faser: forankr tokens, udvid detaljebåndet, verificér den beregnede total, og levér derefter

Forankr hver koordinat til et pladsholder-token

Løsningen er at lade skabelonen bære sine egne koordinater. Designeren skriver tokens som {{CUSTOMER}}, {{DATE}} og {{DETAIL_START}} ind i de celler, generatoren skal røre, og generatoren udregner hver position ved kørselstid ud fra, hvor den finder de tokens. Layoutredigeringer betyder ikke længere noget, fordi token'et flytter med den celle, det sidder i. Anden halvdel af kontrakten er fejlreglen: hvis et påkrævet token mangler, stopper jobbet, før nogen kundedata når filen. En skabelon, der er drevet ud af kurs, bør producere en mislykket jobbillet, ikke et leveret dokument

Find tokens: FindText og ReplaceText

Begge HotXLS-klassefamilier eksponerer søgning på regnearksniveau. FindText returnerer rækken og kolonnen for den første celle, hvis tekst matcher, med en overload, der tilføjer forskel på store og små bogstaver. ReplaceText udskifter hver forekomst og returnerer, hvor mange den ændrede. De to dækker de to slags tokens, du typisk har. Et enkelt anker som kundenavnet lokaliserer du én gang og skriver ved siden af; et token, der skal optræde præcis én gang, som rapportdatoen, udskifter du og tjekker antallet på. På XLSX-siden ser en udfyldning, der forankrer sig selv på denne måde, sådan ud:

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-baseret

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

To detaljer betyder noget. For det første matcher FindText og ReplaceText tekstværdien af en celle; et token indlejret inde i en formelstreng er usynligt for dem, så pladsholder-tokens hører hjemme i almindelige celler, aldrig inde i formler. For det andet er udskiftningsantallet din drift-detektor. En skabelon, der burde indeholde præcis ét {{DATE}}-token, men rapporterer nul udskiftninger, er blevet redigeret, og at rejse en undtagelse i det øjeblik er præcis det, der forvandler stille layoutdrift til en synlig fejl

Kloning af detaljerækken uden at miste stile eller formler

Detaljesektionen i en faktura vokser med dataene. At skrive værdier direkte ind i tomme rækker under prøvelinjen smider alt det, designeren forberedte, væk: kanterne, talformaterne, formlerne pr. række. Mønstret, der bevarer alt det, er at efterlade én fuldt formateret prøverække i skabelonen og klone den for hver post. CopyRange duplikerer stile og formler i ét enkelt kald, hvorefter generatoren kun overskriver værdicellerne

Diagram over tokenankre i en HotXLS Delphi-skabelon, hvor en manglende placeholder fejler jobbet, før nogen data skrives
Skabelon-tokens bærer deres egne koordinater, og en manglende token stopper jobbet, før nogen data skrives
const
  DetailRow = 10;            // den formaterede prøverække i skabelonen
var
  I: Integer;
begin
  // Åbn plads foran totalblokken først, så SUM-området
  // under detaljebåndet strækker sig 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 stile + formler fra prøverækken
      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]);  // intet '='-præfiks
  end;
end;

Hold godt øje med formeltildelingen. XLSX'ens Formula-egenskab tager udtrykket uden et indledende lighedstegn, mens XLS-facaden forventer '=B10*C10' tildelt via Value. At blande de to konventioner er den mest almindelige portningsfejl mellem klassefamilierne, og den fejler uden at klage: cellen holder blot en bogstavelig streng, som Excel viser som tekst. Hvis skabelonen udsmykker detaljebåndet med sammenlagte titelrækker, skal du huske, at kun den øverste venstre celle i et sammenlagt område bærer en værdi. Layoutreglerne i følgeartiklen om sammenlagte celler i layoutdrevne rapportskabeloner forklarer, hvorfor sammenlægningsområder hører hjemme helt uden for databåndet

Hvad InsertRows flytter, og hvad det efterlader

At indsætte rækker foran totalblokken er det, der holder et SUM-område strakt, efterhånden som detaljesektionen vokser. På XLSX-siden fører InsertRows en lang liste af afhængige strukturer med sig ned sammen med cellerne: sammenlagte områder, rækkehøjder, hyperlinks, kommentarer, frosne ruder, autofilter-områder, betinget formatering, datavalidering, tabeller, definerede navne samt billed- og diagramankre. Der er én grænse i den liste, det er værd at huske. Formelomskrivning når kun referencer inden for det samme ark. En formel på et opsummeringsark, der peger ind i det flyttede område, beholder sine gamle koordinater og læser stiltiende de forkerte celler, hvilket er grunden til, at totaler hentet på tværs af ark er sikrere at udtrykke gennem navne på arbejdsbogniveau. Følgeartiklen om definerede navne og formler på tværs af ark gennemgår det mønster

Det ældre XLS-format trækker grænsen et hårdere sted. HotXLS bevarer pivottabeller, forespørgselstabeller og eksterne dataforbindelser i BIFF-filer som rå byteblokke. De overlever åbning og gemning uændret, men de er ikke modelleret, så rækkeindsættelse rører dem aldrig. En skabelon, der parkerer en pivottabel under en ekspanderende detaljeblok, gemmer uden nogen advarsel overhovedet, mens pivotkildens rektangel driver væk fra dataene. Vejen ud er strukturel, ikke defensiv: hold pivot- og forespørgselsindhold på ark, generatoren aldrig indsætter i, så kan forældelsen ikke ske

Diagram over hvad HotXLS InsertRows flytter i XLSX, og krydsarkformel- og BIFF pivot-grænserne, Delphi-generatorer må respektere
InsertRows bærer afhængige strukturer ned på XLSX, mens krydsarkformler og BIFF rå blokke markerer grænserne

Genberegn før levering, eller vid hvorfor du sprang det over

HotXLS evaluerer ikke formler under SaveAs. Når en person åbner filen, genberegner Excel alt (XLS-facaden eksponerer CalculationMode og RecalcOnSave, hvis du har brug for at styre det), så en rapport bestemt til en menneskelig indbakke har ikke brug for mere fra dig. Billedet ændrer sig i det øjeblik, arbejdsbogen fodrer et andet program. CSV-eksport skriver formler ud som deres bogstavelige tekst og beregner dem aldrig, og enhver downstream-parser, der stoler på cachede værdier, vil læse forældede tal eller tomme felter. For de veje beregner du på serveren med Calculate, som evaluerer et vilkårligt udtryk mod den indlæste arbejdsbog og afleverer resultatet tilbage:

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;

At tjekke det beregnede total op mod ordreregistreringen før gemningen er billig forsikring med god gevinst. Det forvandler en forkert faktura til et mislykket job. En operatør kan prøve et mislykket job igen på sekunder; en forkert faktura, der allerede ligger i en kundes indbakke, koster en account manager en undskyldning og en rettelse

To klassefamilier, én algoritme

Den samme logik porter mellem formaterne, men ikke den samme kode. TXLSWorkbook til ældre .xls er grænsefladebaseret og reference-tællet, med 1-baseret arkindeksering, og du frigør den aldrig i hånden. TXLSXWorkbook til .xlsx er et almindeligt objekt, du skal frigøre i en try..finally, med 0-baseret arkindeksering og den formelkonvention, der er vist ovenfor. FindText, ReplaceText, CopyRange og InsertRows findes alle på begge sider, så anker-klon-genberegn-formen overføres uden problemer. Det praktiske råd er at forpligte sig til ét format pr. pipeline, eller at skjule de to objektlivscyklusser bag din egen tynde adapter i stedet for at sprede forskellen ud gennem generatoren

Størrelse betyder sjældent noget for den slags rapport, dette mønster producerer. At klone en stilsat række et par tusind gange er ingenting for nutidens hardware. Gemmestien bliver først flaskehalsen, når et detaljebånd løber op i seks cifre af rækker, og på det tidspunkt sender indstillingen StreamingWrite regnearkets XML direkte ind i outputpakken i stedet for at bufre den; artiklen om streaming-skrivninger til server-batchjob dækker, hvornår den afvejning er værd at tage. Diagrammer opfører sig, som resten af layoutet gør: på XLSX-siden flytter både diagramankeret og dets serie-referencer, når InsertRows kører over dem, så et diagram under totalrækken forbliver bundet til de rigtige data, mens diagrammer på XLS-siden sidder på deres egne diagramark og, ligesom pivottabeller, aldrig flytter sig. Det er endnu et argument for at holde præsentationsark fri af det ark, generatoren udvider

Denne anker-klon-genberegn-tilgang lader en designer eje, hvordan en arbejdsbog ser ud, mens din kode ejer, hvad den siger, hvilket som regel er det, der gør genereret Excel-output værd at vedligeholde. Søge-, kopi- og indsætningskaldene, der er vist her, sammen med formelmotoren, der bruges til totaltjekket før levering, følger med HotXLS Delphi Component til Delphi og C++Builder