Teknisk artikkel

Eksportere Excel-arbeidsbøker til CSV, TSV, HTML og RTF fra Delphi med HotXLS

Se for deg en nattlig jobb som bygger en fakturaarbeidsbok i kode og skriver den ut som CSV for at et nedstrøms system skal importere den. Tallene ser riktige ut i Excel. CSV-en åpner rent i en teksteditor. Deretter kveles importøren på totalkolonnen, fordi beløpsfeltet for rad 42 leser =SUM(D2:D41), formelen som bokstavelig tekst, ikke tallet den skulle beregne seg til. Ingenting er ødelagt. Dette er dokumentert oppførsel, og det er det første å forstå om eksport fra HotXLS: skriveren serialiserer cellemodellen nøyaktig slik den står, og en formelcelle hvis verdi aldri ble beregnet, har bare formelteksten sin å levere fra seg

Hvorfor CSV-en din inneholder formler i stedet for tall

HotXLS lagrer formeltekst og beregnet verdi som to atskilte ting. SaveAsCSV kjører ikke beregningsmotoren på vei ut, med hensikt: en eksport skal ikke mutere arbeidsboken, og den skal ikke risikere å henge seg opp i en patologisk formelkjede. Filer som Excel selv lagret, bærer bufrede resultater ved siden av formlene, så gjeneksport av dem oppfører seg slik du forventer. Fellen er spesifikk for arbeidsbøker koden din selv genererte, der formler ble skrevet, men aldri evaluert. Løsningen er å få verdiene til å eksistere før du eksporterer, ved å bruke den samme Calculate-motoren som løser arkkryssende referanser og egendefinerte funksjoner:

Diagram som viser en Delphi HotXLS arbeidsbokcelle som holder bare formeltekst til Book.Calculate beregner verdien, slik at CSV-eksporten sender ut et tall i stedet for =SUM-tekst
SaveAsCSV serialiserer cellemodellen som den står — uten Calculate bærer beløpsfeltet bokstavelig formulartekst og importøren avviser den
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  R: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('invoice-run.xlsx');
    Sheet := Book.Sheets[0];

    // Materialiser formelresultater slik at CSV-en bærer tall, 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;

Legg merke til hva løkken faktisk gjør: den overskriver formelcellene med sine beregnede verdier. Det er akkurat riktig for en engangs eksportrunde og feil hvis du har tenkt å lagre arbeidsboken igjen som .xlsx etterpå, fordi du nettopp har erstattet levende formler med fastfrosne tall. Eksporter fra en kopi, eller avgrens tilbakeskrivingen slik at den bare berører eksportkjøringen. Motoren bak Calculate går lenger enn dette, blant annet ved å registrere dine egne funksjoner, som er temaet for HotXLS-formelmotoren og egendefinerte funksjoner

Hva den avgrensede skriveren garanterer

CSV-stien produserer UTF-8 med en byte order mark, CRLF-linjeskift og RFC 4180-sitering. Ethvert felt som inneholder skilletegnet, et anførselstegn eller et linjeskift, blir pakket inn, og innebygde anførselstegn dobles. Datoer gjengis som yyyy-mm-dd hh:nn:ss uansett cellens visningsformat. Det er det riktige valget for en maskinkonsument, selv om det overrasker alle som forventet at skjermformateringen skulle bli med. Rich text-celler flates ut ved å slå sammen strekkene deres

Diagram over den eneste HotXLS avgrenset-skriveren i Delphi som produserer CSV med komma og TSV med #9, mens begge utdata deler UTF-8 BOM, CRLF-avslutninger og RFC 4180-sitering
CSV og TSV kommer fra samme skriver, så UTF-8 BOM, CRLF-avslutninger og RFC 4180-sitering gjelder for begge uendret

Disse standardvalgene avgjør de fleste stridighetene med en importør før de starter, men to av dem hører uansett hjemme i grensesnittkontrakten din. Den første er BOM-en. Det er den som lar Excel åpne filen med aksenttegn intakte, likevel behandler en håndfull strenge parsere de tre bytene som data; hvis din er en av dem, fjern dem i overleveringen. Den andre er TSV. Det er ikke en egen funksjon i det hele tatt, bare den samme skriveren kalt med #9 som skilletegn, så alt ovenfor gjelder uendret for den. Arket som eksporteres, velges med en 0-basert indeks i overbelastningen med flere argumenter, mens snarveien med ett argument, SaveAsCSV(FileName), tar det aktive arket

HTML-eksport er et øyeblikksbilde, ikke et utvekslingsformat

Der CSV kaster bort alt utenom verdier, prøver SaveAsHTML å beholde utseendet: én <table> per ark, sammenslåtte områder uttrykt som colspan og rowspan, grunnleggende cellestiling satt inline som CSS. Temarelative farger hoppes over i stedet for å løses, så en mal som lener seg på temaplasser, kommer ut mer nøkternt enn den ser ut i Excel. Sett eksplisitte RGB-farger på alt som må overleve turen. Options-objektet styrer konvolutten:

var
  Opts: TXLSXHtmlExportOptions;
begin
  Opts := TXLSXHtmlExportOptions.Create;
  try
    Opts.Title := 'Weekly settlement';
    Opts.TableClass := 'report-grid';     // krok for vertssidens stilark
    Opts.WriteDocument := True;           // hel 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 utdraget er verdt oppmerksomhet. Sett WriteDocument til False, og utdataene blir et bart tabellfragment i stedet for en hel side, som er det du vil ha når du injiserer en forhåndsvisning inn i et eksisterende oppsett: sett TableClass og la vertsstilarket gjøre temaarbeidet. Returkonvensjonen er også omvendt av de fleste HotXLS-kall. SaveAsHTML returnerer 0 ved suksess og -1 for en ugyldig arkindeks, så en vanebasert sjekk for = 1 vil rapportere hver vellykket eksport som en feil. Når du trenger et område i stedet for et helt ark, kanskje for å sende på e-post eller bygge inn en enkelt blokk, eksporterer TXLSXRange.SaveAsHTML et hvilket som helst rektangulært område under de samme rendringsreglene

RTF-utdata og hvor det fortsatt fortjener sin plass

Det fjerde målet skriver RTF 1.6-tabeller, ett ark per kall gjennom SaveAsRTF. Kolonnebredder tilnærmes til omtrent 96 twips per tegn kolonnebredde. Den strukturelle begrensningen å kjenne til, er at sammenslåtte celler ikke spenner over i utdataene: bare ankercellen bærer innholdet sitt, og de dekkede cellene sendes ut som tomme. Det utelukker RTF for oppsettstunge maler. Det fortjener likevel sin plass som veien med minst motstand for å slippe tabelldata inn i en tekstbehandler eller inn i et eldre dokumenthåndteringssystem som kom før HTML-inntak

Tur-retur: import av CSV er destruktiv med hensikt

Å lese CSV tilbake inn har sin egen kontrakt. OpenCSV tømmer hele arbeidsboken og bygger den opp igjen som ett enkelt ark ved navn Sheet1. Den er en konstruktør i ånden, ikke en sammenslåing, så kall den aldri på en arbeidsbok som fortsatt bærer ulagret innhold. Å sende #0 som skilletegn utløser automatisk gjenkjenning av skilletegn. ADetectTypes-flagget styrer typeopprykk: med det på blir numeriske strenger til tall, ISO-8601-strenger til datoer, og true/false til boolske verdier. Slå det av når kildedataene bærer identifikatorer med innledende nuller, postnumre eller produktkoder, som alle blir stille ødelagt til tall av opprykket (en innledende null er rett og slett borte i det øyeblikket 00123 blir 123). Begge fasadene eksponerer samme import. Kombiner den med eksportkallene ovenfor, og du har en formatbro som ikke trenger Excel installert noe sted i pipelinen, scenarioet dekket i databasetil-Excel-rapportgenerering med HotXLS

Eksport rett inn i en strøm

Hver skriver her har en strømoverbelastning ved siden av filnavn-versjonen: CSV, HTML, RTF og selve arbeidsbokformatene. I serverkode er det de overbelastningene du skal gripe til. Et web-endepunkt som serverer en CSV-nedlasting, kan skrive inn i en TMemoryStream og gi den rett videre til responsobjektet, uten noen midlertidig fil, ingen opprydningsjobb, og ingen kollisjon mellom to forespørsler som tilfeldigvis valgte samme genererte navn. Det samme gjelder for å skyve eksporter inn i blob-lagring eller feste dem til utgående e-post. Filsystemet faller helt ut av bildet

Det mønsteret forsterkes av hvordan biblioteket driftsettes. Begge fasadene er native Object Pascal-lesere og -skrivere, så det er ingen Excel-installasjon, ingen COM-automatisering, og ingen per-prosess-flaskehals som serialiserer forespørsler på serveren. Hver forespørsel kan eie sitt eget arbeidsbokobjekt, kjøre beregnings-tilbakeskrivingen fra den første seksjonen, og strømme eksporten sin parallelt med naboene sine. Minne er den ene ressursen å holde øye med. Arbeidsbokmodellen lever i RAM for varigheten av eksporten, så en tjeneste som åpner svært store filer bare for å sende dem ut igjen som CSV, bør sette et tak på samtidige jobber, eller sette de for store i kø, i stedet for å la en trafikktopp bestemme arbeidssettet

Én mindre bryter: sett IncludeBOM på HTML-alternativene når fragmentet skal lagres som en frittstående fil som et verktøy nedstrøms snuser på for koding. Når du serverer HTML direkte over HTTP, la heller charset-deklarasjonen ligge til responshodene

Når bytene fortsatt kommer ut feil

Det vanligste supportspørsmålet om CSV-eksport er åpningsproblemet i en annen drakt: Excel viser tegnsøppel i stedet for aksenttegn. Instinktet er å skylde på skriveren, men den sender ut en UTF-8-BOM nettopp av denne grunnen, og filen er nesten alltid korrekt når den forlater koden din. Noe mellom der og Excel har spist BOM-en. En FTP-overføring i tekstmodus, en strømkopi som hopper over de tre første bytene, en proxy som koder om underveis: hvilken som helst av disse vil fjerne markøren og la Excel gjette på kodingen, noe den gjør dårlig. Diagnostiser dette ved grensesnittet, ikke i eksportkallet. Åpne den leverte filen i en hex-viewer og bekreft at EF BB BF fortsatt er det første i den

Diagram som sporer hvordan en korrekt UTF-8 BOM skrevet av HotXLS CSV-eksport i Delphi skrelles av av en FTP tekstmodus-overføring eller rekodingsproxy, og etterlater Excel å vise mojibake
Skriveren sender EF BB BF korrekt — mojibake viser seg bare etter at en transport har strippet markøren, så diagnostiser de leverte bytene i en hex-fremviser

Det er den røde tråden gjennom alle fire formatene. Eksportkallet er den enkle delen, og HotXLS tar et forsvarlig valg ved hver beslutning skriveren står overfor. Feilene bor i skjøtene, der formeltekst møter en parser som ønsket seg et tall, der en BOM møter en transport som ikke bevarer den, der en sammenslått celle møter RTF-ens flate tabellmodell. Hver av dem er et faktum å skrive inn i kontrakten mellom eksportøren din og det som konsumerer den, fordi konsumenten ikke kan lese intensjonene dine ut av bytene. For den fullstendige metodelisten på tvers av begge arbeidsbokfasadene, bærer produktsiden for HotXLS Delphi Component hele referansen