Teknisk artikel

Eksport af Excel-projektmapper til CSV, TSV, HTML og RTF fra Delphi med HotXLS

Forestil dig et natligt job, der bygger en fakturaprojektmappe i kode og skriver den ud som CSV, så et downstream-system kan importere den. Tallene ser rigtige ud i Excel. CSV-filen åbner problemfrit i en teksteditor. Så går importøren i stå på totalkolonnen, fordi beløbsfeltet for række 42 lyder =SUM(D2:D41), formlen som bogstavelig tekst, ikke det tal, den skulle beregne sig til. Intet er i stykker. Dette er dokumenteret adfærd, og det er det første, man skal forstå om eksport fra HotXLS: skriveren serialiserer cellemodellen præcis som den står, og en formelcelle, hvis værdi aldrig blev beregnet, har kun sin formeltekst at aflevere

Hvorfor din CSV indeholder formler i stedet for tal

HotXLS gemmer formeltekst og beregnet værdi som to adskilte ting. SaveAsCSV kører ikke beregningsmotoren på vej ud, med vilje: en eksport bør ikke ændre projektmappen, og den bør ikke risikere at gå i stå på en patologisk formelkæde. Filer, som Excel selv har gemt, bærer cachede resultater ved siden af formlerne, så det at genexportere dem opfører sig, som man forventer. Fælden er specifik for projektmapper, din egen kode har genereret, hvor formler blev skrevet, men aldrig evalueret. Løsningen er at få værdierne til at eksistere, før du eksporterer, ved brug af den samme Calculate-motor, der løser referencer på tværs af ark og brugerdefinerede funktioner:

Diagram, der viser en Delphi HotXLS projektmappcelle, der kun rummer formeltekst, indtil Book.Calculate beregner værdien, så CSV-eksporten udsender et tal i stedet for =SUM-tekst
SaveAsCSV serialiserer cellemodellen, som den står — uden Calculate bærer beløbfeltet bogstavelig formeltekst, og importeren afviser den
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  R: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('invoice-run.xlsx');
    Sheet := Book.Sheets[0];

    // Materialiser formelresultater, så CSV'en bærer tal, ikke '=...'-tekst
    for R := 2 to 41 do
      if Sheet.Cells[R, 4].Formula <> '' then
        Sheet.Cells[R, 4].Value := Book.Calculate(Sheet.Cells[R, 4].Formula);

    Book.SaveAsCSV('feed.csv', 0, ',');    // ark 0, komma
    Book.SaveAsCSV('feed.tsv', 0, #9);     // samme ark som TSV
  finally
    Book.Free;
  end;
end;

Læg mærke til, hvad løkken faktisk gør: den overskriver formelcellerne med deres beregnede værdier. Det er helt rigtigt til en engangseksport og forkert, hvis du har tænkt dig at gemme projektmappen som .xlsx bagefter, fordi du netop har erstattet levende formler med frosne tal. Eksportér fra en kopi, eller afgræns tilbageskrivningen, så den kun rammer eksportkørslen. Motoren bag Calculate rækker videre end dette, herunder registrering af dine egne funktioner, hvilket er emnet for HotXLS' formelmotor og brugerdefinerede funktioner

Hvad den afgrænsede skriver garanterer

CSV-stien producerer UTF-8 med en byte order mark, CRLF-linjeskift og RFC 4180-anførsel. Ethvert felt, der indeholder afgrænseren, et anførselstegn eller et linjeskift, bliver pakket ind, og indlejrede anførselstegn fordobles. Datoer gengives som yyyy-mm-dd hh:nn:ss uanset cellens visningsformat. Det er det rigtige valg til en maskinel forbruger, selvom det overrasker enhver, der forventede, at skærmformateringen skulle følge med. Rich text-celler flades ud ved at sammenkæde deres runs

Diagram over den enkelte HotXLS afgrænsede writer i Delphi, der producerer CSV med komma og TSV med #9, mens begge outputs deler UTF-8 BOM, CRLF-afslutninger og RFC 4180-citering
CSV og TSV kommer fra samme skriver, så UTF-8 BOM, CRLF-afslutninger og RFC 4180-citater gælder for begge uændret

De standardværdier afgør de fleste diskussioner med en importør, før de overhovedet opstår, men to af dem hører alligevel hjemme i din grænsefladekontrakt. Den første er BOM'en. Det er den, der lader Excel åbne filen med accenttegn intakte, men en håndfuld strikse parsere behandler de tre bytes som data; hvis din er en af dem, skal du fjerne dem i overdragelsen. Den anden er TSV. Det er slet ikke en separat funktion, bare den samme skriver kaldt med #9 som afgrænser, så alt ovenfor gælder uændret for den. Det ark, der skal eksporteres, vælges via et 0-baseret indeks i multi-argument-overloadet, mens en-argument-genvejen SaveAsCSV(FileName) tager det aktive ark

HTML-eksport er et øjebliksbillede, ikke et udvekslingsformat

Hvor CSV kasserer alt undtagen værdier, forsøger SaveAsHTML at bevare udseendet: én <table> pr. ark, sammenlagte områder udtrykt som colspan og rowspan, grundlæggende cellestyling indlejret som CSS. Temarelative farver springes over i stedet for at blive opløst, så en skabelon, der læner sig op ad tema-slots, kommer ud mere enkel, end den ser ud i Excel. Sæt eksplicitte RGB-farver på alt, der skal overleve turen. Options-objektet styrer konvolutten:

var
  Opts: TXLSXHtmlExportOptions;
begin
  Opts := TXLSXHtmlExportOptions.Create;
  try
    Opts.Title := 'Weekly settlement';
    Opts.TableClass := 'report-grid';     // krog til værtssidens stylesheet
    Opts.WriteDocument := True;           // fuld side, ikke et fragment
    if Book.SaveAsHTML('settlement.html', 0, Opts) <> 0 then
      raise Exception.Create('Sheet index out of range');
  finally
    Opts.Free;
  end;
end;

To detaljer i det udsnit er værd at bemærke. Sæt WriteDocument til False, og output bliver et nøgent tabelfragment i stedet for en hel side, hvilket er det, du vil have, når du indsætter en forhåndsvisning i et eksisterende layout: sæt TableClass, og lad værtsstylesheetet stå for temaet. Returkonventionen er også modsat de fleste HotXLS-kald. SaveAsHTML returnerer 0 ved succes og -1 ved et ugyldigt arkindeks, så et vanetjek for = 1 vil rapportere hver eneste vellykkede eksport som en fejl. Når du har brug for et område frem for et helt ark, måske for at maile eller indlejre en enkelt blok, eksporterer TXLSXRange.SaveAsHTML ethvert rektangulært område under de samme renderingsregler

RTF-output, og hvor det stadig gør fyldest

Det fjerde mål skriver RTF 1.6-tabeller, ét ark pr. kald via SaveAsRTF. Kolonnebredder tilnærmes til cirka 96 twips pr. tegn kolonnebredde. Den strukturelle begrænsning, man skal kende, er, at sammenlagte celler ikke spænder over i output: kun ankercellen bærer sit indhold, og de dækkede celler udskrives som tomme. Det udelukker RTF til layouttunge skabeloner. Det gør stadig fyldest som vejen med mindst modstand til at droppe tabelresultater ind i en tekstbehandler eller i et gammelt dokumenthåndteringssystem, der går forud for HTML-indlæsning

Rundtur: import af CSV er destruktiv med vilje

At læse CSV tilbage ind har sin egen kontrakt. OpenCSV rydder hele projektmappen og genopbygger den som ét enkelt ark ved navn Sheet1. Den er i ånden en konstruktør, ikke en sammenlægning, så kald den aldrig på en projektmappe, der stadig indeholder ugemt indhold. At angive #0 som separator udløser automatisk registrering af afgrænser. ADetectTypes-flaget styrer typeforfremmelse: med det slået til bliver numeriske strenge til tal, ISO-8601-strenge bliver til datoer, og true/false bliver til booleans. Slå det fra, når feedet bærer identifikatorer med foranstillede nuller, postnumre eller produktkoder, som forfremmelsen alle sammen lydløst forvansker til tal (et foranstillet nul er simpelthen væk, i det øjeblik 00123 bliver til 123). Begge facader eksponerer den samme import. Kombinér den med eksportkaldene ovenfor, og du har en formatbro, der ikke kræver Excel installeret noget sted i pipelinen, scenariet dækket i databaseeksport til Excel-rapporter med HotXLS

Eksport direkte til en stream

Hver skriver her har et stream-overload liggende ved siden af filnavn-versionen: CSV, HTML, RTF og selve projektmappeformaterne. I serverkode er det de overloads, man skal gribe efter. Et webendpoint, der leverer et CSV-download, kan skrive ind i en TMemoryStream og aflevere den direkte til response-objektet, uden midlertidig fil, uden oprydningsjob og uden kollision mellem to forespørgsler, der tilfældigvis valgte samme genererede navn. Det samme gælder, når man skubber eksporter ind i blob-lagring eller vedhæfter dem til udgående mail. Filsystemet forsvinder helt ud af billedet

Det mønster forstærkes af, hvordan biblioteket bliver udrullet. Begge facader er native Object Pascal-læsere og -skrivere, så der er ingen Excel-installation, ingen COM-automatisering og ingen per-proces-flaskehals, der serialiserer forespørgsler på serveren. Hver forespørgsel kan eje sit eget projektmappeobjekt, køre beregnings-tilbageskrivningen fra det første afsnit og streame sin eksport parallelt med sine naboer. Hukommelse er den ene ressource, man skal holde øje med. Projektmappemodellen lever i RAM under hele eksporten, så en tjeneste, der åbner meget store filer bare for at genudsende dem som CSV, bør sætte et loft over samtidige job eller sætte de oversize job i kø frem for at lade en trafikspids afgøre working set'et

En mindre knap: sæt IncludeBOM på HTML-indstillingerne, når fragmentet skal gemmes som en selvstændig fil, som et eller andet værktøj længere nede i kæden sniffer for kodning. Når du leverer HTML direkte over HTTP, skal du i stedet overlade charset-erklæringen til response-headerne

Når bytes stadig kommer forkert ud

Det mest almindelige supportspørgsmål om CSV-eksport er åbningsproblemet i et andet kostume: Excel viser mojibake i stedet for accenttegn. Instinktet er at give skriveren skylden, men den udsender en UTF-8 BOM netop af den grund, og filen er næsten altid korrekt, når den forlader din kode. Noget mellem dér og Excel har spist BOM'en. En FTP-overførsel i tekstmodus, en stream-kopi, der springer de første tre bytes over, en proxy, der genkoder undervejs: enhver af dem vil fjerne markøren og lade Excel gætte på kodningen, hvilket den gør dårligt. Diagnosticér dette ved grænsefladen, ikke i eksportkaldet. Åbn den leverede fil i en hex-viewer, og bekræft at EF BB BF stadig er det første i den

Diagram, der sporer, hvordan et korrekt UTF-8 BOM skrevet af HotXLS CSV-eksport i Delphi strippes af en FTP text-mode-overførsel eller re-encoding-proxy, så Excel viser mojibake
Skriveren udsender EF BB BF korrekt — mojibake viser sig først, efter en transport har stripset markøren, så diagnosér de leverede bytes i en hex-fremviser

Det er den røde tråd for alle fire formater. Eksportkaldet er den lette del, og HotXLS træffer et forsvarligt valg ved hver beslutning, skriveren står over for. Fejlene bor i samlingerne, hvor formeltekst møder en parser, der ville have et tal, hvor en BOM møder en transport, der ikke bevarer den, hvor en sammenlagt celle møder RTF's flade tabelmodel. Hver af dem er et faktum, der skal skrives ind i kontrakten mellem din eksportør og hvad end der forbruger den, fordi forbrugeren ikke kan læse dine hensigter ud af bytes. For den fulde metodeliste på tværs af begge projektmappefacader bærer produktsiden for HotXLS-Delphi-komponenten den fulde reference