Teknisk artikel

HotXLS Delphi Component: CSV, TSV, HTML, and RTF export in Delphi

Föreställ dig ett nattligt jobb som bygger en fakturaarbetsbok i kod och skriver ut den som CSV för att importeras av ett nedströmssystem. Talen ser korrekta ut i Excel. CSV-filen öppnas felfritt i en textredigerare. Sedan fastnar importören på totalkolumnen, eftersom beloppsfältet för rad 42 lyder =SUM(D2:D41), formeln som bokstavlig text, inte den siffra den borde beräkna fram. Inget är trasigt. Det här är dokumenterat beteende, och det är det första man behöver förstå om export från HotXLS: skrivaren serialiserar cellmodellen exakt som den står, och en formelcell vars värde aldrig beräknades har bara sin formeltext att lämna ifrån sig

Varför din CSV-fil innehåller formler i stället för tal

HotXLS lagrar formeltext och beräknat värde som två separata saker. SaveAsCSV kör inte beräkningsmotorn på vägen ut, med avsikt: en export ska inte mutera arbetsboken, och den ska inte riskera att hänga sig på en patologisk formelkedja. Filer som Excel själv har sparat bär cachade resultat bredvid formlerna, så att exportera dem igen beter sig som förväntat. Fällan gäller specifikt arbetsböcker som din egen kod har genererat, där formler skrevs men aldrig utvärderades. Lösningen är att se till att värdena finns innan du exporterar, med hjälp av samma Calculate-motor som löser upp korsbladsreferenser och anpassade funktioner:

Diagram som visar en Delphi HotXLS-arbetsbokscell som håller bara formeltext tills Book.Calculate beräknar värdet, så att CSV-exporten sänder ut ett tal i stället för =SUM-text
SaveAsCSV serialiserar cellmodellen som den står — utan Calculate bär beloppsfältet bokstavlig formeltext och importern avvisar den
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  R: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('invoice-run.xlsx');
    Sheet := Book.Sheets[0];

    // Materialisera formelresultat så att CSV-filen innehåller tal, inte text i stil med '=...'
    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, ',');    // blad 0, komma
    Book.SaveAsCSV('feed.tsv', 0, #9);     // samma blad som TSV
  finally
    Book.Free;
  end;
end;

Lägg märke till vad loopen faktiskt gör: den skriver över formelcellerna med sina beräknade värden. Det är precis rätt för en engångsexport och fel om du tänker spara arbetsboken igen som .xlsx efteråt, eftersom du just har ersatt levande formler med frusna tal. Exportera från en kopia, eller begränsa återskrivningen så att den bara påverkar exportkörningen. Motorn bakom Calculate går längre än så här, bland annat genom att registrera egna funktioner, vilket är ämnet för artikeln om HotXLS formelmotor och anpassade funktioner

Vad den avgränsade skrivaren garanterar

CSV-vägen producerar UTF-8 med en byteordningsmarkering, CRLF-radslut och RFC 4180-citering. Alla fält som innehåller avgränsaren, ett citattecken eller en radbrytning omsluts, och inbäddade citattecken dubbleras. Datum renderas som yyyy-mm-dd hh:nn:ss oavsett cellens visningsformat. Det är rätt val för en maskinell mottagare, även om det överraskar den som förväntade sig att skärmformateringen skulle följa med. Rich text-celler plattas ut genom att deras textsegment sammanfogas

Diagram över den enda avgränsade skrivaren i HotXLS i Delphi som producerar CSV med kommatecken och TSV med #9, medan båda utdata delar UTF-8 BOM, CRLF-ändelser och RFC 4180-citering
CSV och TSV kommer från samma skrivare, så UTF-8-BOM, CRLF-radslut och RFC 4180-citering gäller båda oförändrat

De där standardvärdena löser de flesta tvister med en importör innan de ens uppstår, men två av dem hör ändå hemma i ditt gränssnittskontrakt. Den första är BOM:en. Det är den som gör att Excel kan öppna filen med accenttecken intakta, ändå behandlar en handfull strikta parsrar dessa tre byte som data; om din tillhör dem, ta bort dem i överlämningen. Den andra är TSV. Det är inte alls en separat funktion, bara samma skrivare anropad med #9 som avgränsare, så allt ovan gäller den oförändrat. Bladet som ska exporteras väljs via ett 0-baserat index i den flerargumentsöverlagringen, medan enargumentsgenvägen SaveAsCSV(FileName) tar det aktiva bladet

HTML-export är en ögonblicksbild, inte ett utbytesformat

Där CSV kastar bort allt utom värdena, försöker SaveAsHTML behålla utseendet: en <table> per blad, sammanslagna områden uttryckta som colspan och rowspan, grundläggande cellstil inbäddad som CSS. Temarelativa färger hoppas över i stället för att lösas upp, så en mall som lutar sig mot temaplatser kommer ut mer avskalad än den ser ut i Excel. Sätt explicita RGB-färger på allt som måste överleva resan. Options-objektet styr kuvertet:

var
  Opts: TXLSXHtmlExportOptions;
begin
  Opts := TXLSXHtmlExportOptions.Create;
  try
    Opts.Title := 'Weekly settlement';
    Opts.TableClass := 'report-grid';     // krok för värdsidans stilmall
    Opts.WriteDocument := True;           // hel sida, inte ett fragment
    if Book.SaveAsHTML('settlement.html', 0, Opts) <> 0 then
      raise Exception.Create('Sheet index out of range');
  finally
    Opts.Free;
  end;
end;

Två detaljer i det utdraget förtjänar uppmärksamhet. Slå om WriteDocument till False och utdatan blir ett rent tabellfragment i stället för en hel sida, vilket är vad du vill ha när du injicerar en förhandsgranskning i en befintlig layout: sätt TableClass och låt värdsidans stilmall sköta temat. Returkonventionen är också omvänd jämfört med de flesta HotXLS-anrop. SaveAsHTML returnerar 0 vid lyckad export och -1 för ett ogiltigt bladindex, så en vaneartad kontroll mot = 1 kommer att rapportera varje lyckad export som ett misslyckande. När du behöver ett område i stället för ett helt blad, kanske för att mejla eller bädda in ett enda block, exporterar TXLSXRange.SaveAsHTML vilket rektangulärt område som helst enligt samma renderingsregler

RTF-utdata och var den fortfarande har sin plats

Det fjärde målet skriver RTF 1.6-tabeller, ett blad per anrop via SaveAsRTF. Kolumnbredder approximeras till ungefär 96 twip per tecken kolumnbredd. Den strukturella begränsningen att känna till är att sammanslagna celler inte sträcker sig över flera i utdatan: bara ankarcellen bär sitt innehåll, och de täckta cellerna kommer ut tomma. Det utesluter RTF för layouttunga mallar. Den har ändå sin plats som vägen med minst motstånd för att släppa in tabellresultat i en ordbehandlare eller i ett äldre dokumenthanteringssystem som föregår HTML-inmatning

Tur och retur: att importera CSV är destruktivt med avsikt

Att läsa in CSV igen har sitt eget kontrakt. OpenCSV tömmer hela arbetsboken och bygger om den som ett enda blad med namnet Sheet1. Den är till sin natur en konstruktor, inte en sammanslagning, så anropa den aldrig på en arbetsbok som fortfarande innehåller osparat innehåll. Att skicka #0 som avgränsare utlöser automatisk avgränsardetektering. Flaggan ADetectTypes styr typuppgradering: med den påslagen blir numeriska strängar tal, ISO-8601-strängar blir datum, och true/false blir booleaner. Slå av den när flödet bär identifierare med inledande nollor, postnummer eller produktkoder, vilka alla tyst förvanskas till tal av uppgraderingen (en inledande nolla försvinner helt enkelt i samma stund som 00123 blir 123). Båda fasaderna exponerar samma import. Kombinera den med exportanropen ovan och du har en formatbro som inte behöver någon Excel-installation någonstans i pipelinen, scenariot som beskrivs i artikeln om databas-till-Excel-rapportgenerering med HotXLS

Exportera direkt till en ström

Varje skrivare här har en strömöverlagring som sitter bredvid filnamnsversionen: CSV, HTML, RTF och själva arbetsboksformaten. I serverkod är det dessa överlagringar man ska ta till. En webbändpunkt som servar en CSV-nedladdning kan skriva in i en TMemoryStream och lämna över den direkt till svarsobjektet, utan temporär fil, inget städjobb och ingen kollision mellan två förfrågningar som råkade välja samma genererade namn. Samma gäller för att skjuta exporter till blob-lagring eller bifoga dem till utgående e-post. Filsystemet försvinner helt ur bilden

Det mönstret förstärks av hur biblioteket distribueras. Båda fasaderna är nativa Object Pascal-läsare och -skrivare, så det finns ingen Excel-installation, ingen COM-automation och ingen per-process-flaskhals som serialiserar förfrågningar på servern. Varje förfrågan kan äga sitt eget arbetsboksobjekt, köra beräknings-återskrivningen från det första avsnittet, och strömma sin export parallellt med sina grannar. Minne är den enda resurs att hålla ett öga på. Arbetsboksmodellen lever i RAM under hela exporten, så en tjänst som öppnar mycket stora filer bara för att åter-emittera dem som CSV bör begränsa antalet samtidiga jobb, eller köa de överdimensionerade, i stället för att låta en trafiktopp bestämma arbetsmängden

En mindre inställning: sätt IncludeBOM i HTML-alternativen när fragmentet ska sparas som en fristående fil som något verktyg i efterföljande led känner av för kodning. När du servar HTML direkt över HTTP, lämna i stället teckenkodningsdeklarationen till svarshuvudena

När byten ändå kommer ut fel

Den vanligaste supportfrågan om CSV-export är öppningsproblemet i en annan skepnad: Excel visar mojibake i stället för accenttecken. Instinkten är att skylla på skrivaren, men den skriver ut en UTF-8-BOM av precis den anledningen, och filen är nästan alltid korrekt när den lämnar din kod. Något mellan där och Excel har ätit upp BOM:en. En FTP-överföring i textläge, en strömkopiering som hoppar över de tre första byten, en proxy som omkodar på vägen igenom: vilken som helst av dessa kommer att ta bort markören och lämna Excel att gissa på kodningen, vilket den gör dåligt. Diagnostisera det här vid gränsen, inte i exportanropet. Öppna den levererade filen i en hexvisare och bekräfta att EF BB BF fortfarande är det första i den

Diagram som spårar hur en korrekt UTF-8-BOM skriven av HotXLS CSV-export i Delphi strippas av en FTP-textlägesöverföring eller omkodningsproxy, och lämnar Excel att visa mojibake
Skrivaren emitterar EF BB BF korrekt — mojibake dyker upp först efter att en transport strukit markören, så diagnostisera de levererade bytena i en hexvisare

Det är den röda tråden för alla fyra formaten. Exportanropet är den enkla delen, och HotXLS gör ett försvarbart val vid varje beslut skrivaren ställs inför. Felen bor i sömmarna, där formeltext möter en parser som ville ha ett tal, där en BOM möter en transport som inte bevarar den, där en sammanslagen cell möter RTF:s platta tabellmodell. Vart och ett av dessa är ett faktum att skriva in i kontraktet mellan din exportör och vad som än konsumerar den, eftersom konsumenten inte kan läsa dina avsikter ur byten. För den fullständiga metodlistan över båda arbetsboksfasaderna har produktsidan för HotXLS Delphi Component den fullständiga referensen