Technical Article

Izvoz Excela u CSV, TSV i HTML u Delphiju (HotXLS)

Zamislite noćni posao koji u kodu gradi radnu knjigu računa i zapisuje je kao CSV kako bi je uvezao neki kasniji sustav. Brojevi izgledaju ispravno u Excelu. CSV se čisto otvara u uređivaču teksta. Zatim se uvoznik zagrcne na stupcu s ukupnim iznosima jer polje iznosa u 42. retku glasi =SUM(D2:D41), što je formula kao doslovni tekst (literal), a ne broj u koji bi se trebala izračunati. Ništa nije pokvareno. To je dokumentirano ponašanje i to je prva stvar koju treba razumjeti o izvozu iz HotXLS-a: pisac serijalizira model ćelije točno onako kako on stoji, a ćelija s formulom čija vrijednost nikada nije izračunana ima samo tekst svoje formule za predati.

Zašto vaš CSV sadrži formule umjesto brojeva

HotXLS pohranjuje tekst formule i izračunanu vrijednost kao dvije zasebne stvari. Metoda SaveAsCSV po dizajnu ne pokreće mehanizam za izračun pri izvozu: izvoz ne bi trebao mijenjati radnu knjigu i ne bi trebao riskirati zastoj na patološkom lancu formula. Datoteke koje je sam Excel spremio nose predpredmemorirane rezultate pokraj formula, pa se ponovni izvoz tih datoteka ponaša onako kako očekujete. Zamka je specifična za radne knjige koje je generirao vaš kôd, gdje su formule zapisane, ali nikada procijenjene. Rješenje je učiniti da vrijednosti postoje prije izvoza, koristeći isti mehanizam Calculate koji rješava reference između listova i prilagođene funkcije:

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  R: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('invoice-run.xlsx');
    Sheet := Book.Sheets[0];

    // Materialize formula results so the CSV carries numbers, not '=...' text
    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, ',');    // sheet 0, comma
    Book.SaveAsCSV('feed.tsv', 0, #9);     // same sheet as TSV
  finally
    Book.Free;
  end;
end;

Pazite što petlja zapravo radi: ona prepisuje ćelije s formulama njihovim izračunanim vrijednostima. To je točno ono što trebate učiniti za jednokratni izvozni prolaz, ali pogrešno ako nakon toga namjeravate ponovno spremiti radnu knjigu kao .xlsx, jer ste upravo zamijenili aktivne formule zamrznutim brojevima. Izvezite iz kopije ili ograničite upisivanje natrag tako da dotiče samo izvozni rad. Mehanizam iza Calculate ide i dalje od ovoga, uključujući registraciju vaših vlastitih funkcija, što je tema članka HotXLS mehanizam formula i prilagođene funkcije.

Što jamči pisac s graničnicima

CSV staza proizvodi UTF-8 s oznakom redoslijeda bajtova (byte order mark - BOM), CRLF završetke redaka i navodnike prema standardu RFC 4180. Svako polje koje sadrži graničnik (delimiter), navodnik ili prijelom retka biva omotano, a ugrađeni navodnici se udvostručuju. Datumi se prikazuju kao yyyy-mm-dd hh:nn:ss bez obzira na format prikaza ćelije. To je ispravan izbor za strojne potrošače, iako iznenađuje svakoga tko je očekivao da će se oblikovanje sa zaslona prenijeti. Ćelije s bogatim tekstom izravnavaju se spajanjem njihovih nizova.

Te zadane vrijednosti rješavaju većinu sporova s uvoznikom prije nego što počnu, bi li dvije od njih svejedno pripadaju vašem ugovoru o sučelju. Prva je BOM. On omogućuje Excelu da otvori datoteku s netaknutim dijakritičkim znakovima, no nekolicina strogih analizatora (parsers) tretira ta tri bajta kao podatke; ako je vaš među njima, uklonite ih pri primopredaji. Druga je TSV. To uopće nije zasebna značajka, već isti pisac pozvan s #9 kao graničnikom, pa se sve gore navedeno na njega odnosi nepromijenjeno. List za izvoz bira se prema indeksu na bazi 0 u preopterećenju s više argumenata, dok skraćeni oblik s jednim argumentom SaveAsCSV(FileName) uzima aktivni list.

Izvoz u HTML je snimka, a ne format razmjene

Dok CSV odbacuje sve osim vrijednosti, SaveAsHTML pokušava zadržati izgled: jedna tablica <table> po listu, spojena područja izražena kao colspan i rowspan, te osnovni stilovi ćelija ugrađeni (inlined) kao CSS. Boje povezane s temama se preskaču radije nego da se razrješavaju, pa predložak koji se oslanja na utore tema izgleda jednostavnije nego u Excelu. Postavite eksplicitne RGB boje na sve što mora preživjeti to putovanje. Objekt opcija kontrolira omotnicu (envelope):

var
  Opts: TXLSXHtmlExportOptions;
begin
  Opts := TXLSXHtmlExportOptions.Create;
  try
    Opts.Title := 'Weekly settlement';
    Opts.TableClass := 'report-grid';     // hook for the host page stylesheet
    Opts.WriteDocument := True;           // full page, not a fragment
    if Book.SaveAsHTML('settlement.html', 0, Opts) <> 0 then
      raise Exception.Create('Sheet index out of range');
  finally
    Opts.Free;
  end;
end;

Dva detalja u tom isječku koda zaslužuju pažnju. Prebacite WriteDocument na False i izlaz postaje čisti fragment tablice umjesto cijele stranice, što je ono što želite kada ubacujete pregled u postojeći izgled: postavite TableClass i prepustite stiliziranje stilskoj tablici (stylesheet) domaćina. Konvencija o povratu također je suprotna većini HotXLS poziva. Metoda SaveAsHTML vraća 0 u slučaju uspjeha i -1 za neispravan indeks lista, pa će provjera = 1 vođena navikom prijaviti svaki uspješan izvoz kao neuspjeh. Kada trebate regiju radije nego cijeli list, možda za slanje e-poštom ili ugradnju jednog bloka, TXLSXRange.SaveAsHTML izvozi bilo koji pravokutni raspon pod istim pravilima renderiranja.

RTF izlaz i gdje još uvijek zaslužuje svoje mjesto

Četvrto odredište zapisuje RTF 1.6 tablice, jedan list po pozivu putem SaveAsRTF. Širine stupaca aproksimirane su na otprilike 96 twipa po znaku širine stupca. Strukturno ograničenje koje trebate znati jest da se spojene ćelije ne protežu u izlazu: samo sidrena ćelija nosi svoj sadržaj, dok pokrivene ćelije izlaze kao prazne. To isključuje RTF za predloške s teškim izgledom. Ipak, on i dalje opravdava svoje mjesto kao put najmanjeg otpora za umetanje tabličnih rezultata u tekstualni procesor ili u naslijeđeni sustav za upravljanje dokumentima koji je stariji od HTML uvoza.

Povratno putovanje: uvoz CSV-a je destruktivan po dizajnu

Uvoz CSV-a natrag ima vlastiti ugovor. OpenCSV čisti cijelu radnu knjigu i ponovno je gradi kao jedan list pod nazivom Sheet1. To je u biti konstruktor, a ne spajanje, stoga ga nikada ne pozivajte na radnoj knjizi koja još uvijek sadrži nespremljeni sadržaj. Prosljeđivanje #0 kao separatora pokreće automatsku detekciju graničnika. Zastavica ADetectTypes kontrolira promociju tipova: s njom uključenom, numerički nizovi postaju brojevi, ISO-8601 nizovi postaju datumi, a vrijednosti true/false postaju logičke vrijednosti (booleans). Isključite je kada tok podataka nosi identifikatore s vodećim nulama, poštanske brojeve ili kodove proizvoda, a koje promocija tiho pretvara u brojeve (vodeća nula jednostavno nestaje onog trenutka kada 00123 postane 123). Obje fasade izlažu isti uvoz. Uparite ga s gornjim pozivima za izvoz i dobit ćete formatni most koji ne zahtijeva instaliran Excel nigdje u cjevovodu, što je scenarij obrađen u članku generiranje Excel izvješća iz baze podataka s HotXLS-om.

Izvoz izravno u tok podataka (stream)

Svaki pisac ovdje ima preopterećenje toka (stream overload) koje se nalazi pokraj verzije s nazivom datoteke: CSV, HTML, RTF i sami formati radnih knjiga. U poslužiteljskom kodu ta su preopterećenja ona koja trebate koristiti. Web krajnja točka koja poslužuje preuzimanje CSV-a može pisati u TMemoryStream i to izravno predati objektu odgovora, bez privremene datoteke, bez posla čišćenja i bez sudara između dvaju zahtjeva koji su slučajno odabrali isto generirano ime. Isto vrijedi za guranje izvoza u pohranu objekata (blob storage) ili njihovo privitak odlaznoj pošti. Datotečni sustav u potpunosti ispada iz slike.

Taj se obrazac slaže s načinom na koji se knjižnica implementira. Obje su fasade izvorni Object Pascal čitači i pisci, tako da nema instalacije Excela, nema COM automatizacije i nema uskog grla po procesu koji bi serijalizirao zahtjeve na poslužitelju. Svaki zahtjev može posjedovati vlastiti objekt radne knjige, pokrenuti upisivanje izračuna iz prvog odjeljka i strujati svoj izvoz paralelno sa susjednima. Memorija je jedini resurs na koji treba paziti. Model radne knjige živi u RAM-u tijekom trajanja izvoza, pa bi servis koji otvara vrlo velike datoteke samo da bi ih ponovno emitirao kao CSV trebao ograničiti istodobne poslove, ili staviti prevelike u red čekanja, umjesto da dopusti da skok u prometu odluči o radnom skupu.

Jedan manji prekidač: postavite IncludeBOM u opcijama HTML-a kada će se fragment spremiti kao samostalna datoteka koju neki alat u nastavku provjerava radi kodiranja. Kada HTML poslužujete izravno preko HTTP-a, ostavite deklaraciju skupa znakova zaglavljima odgovora.

Kada bajtovi i dalje izlaze pogrešni

Najčešće pitanje podrške o izvozu CSV-a je problem otvaranja u drugom ruhu: Excel prikazuje nerazumljive znakove (mojibake) umjesto dijakritika. Instinkt je okriviti pisca, ali on emitira UTF-8 BOM upravo iz tog razloga i datoteka je gotovo uvijek ispravna kada napusti vaš kôd. Nešto između tog mjesta i Excela je pojelo BOM. FTP prijenos u tekstualnom načinu rada, kopiranje toka koje preskače prva tri bajta, proxy koji ponovno kodira na putu: bilo što od toga uklonit će marker i ostaviti Excel da nagađa o kodiranju, što on loše radi. Dijagnosticirajte to na granici, a ne u pozivu za izvoz. Otvorite isporučenu datoteku u heksadecimalnom pregledniku i potvrdite da je EF BB BF i dalje prva stvar u njoj.

To je zajednička crta za sva četiri formata. Poziv za izvoz je lakši dio, a HotXLS donosi obranjiv izbor pri svakoj odluci s kojom se pisac suočava. Problemi žive na spojevima, gdje se tekst formule susreće s analizatorom koji je želio broj, gdje se BOM susreće s transportom koji ga ne čuva, gdje se spojena ćelija susreće s RTF-ovim ravnim modelom tablice. Svaki od njih je činjenica koju treba unijeti u ugovor između vašeg izvoznika i onoga što ga konzumira, jer potrošač ne može pročitati vaše namjere iz bajtova. Za potpuni popis metoda na obje fasade radnih knjiga, stranica proizvoda HotXLS Component nosi cijelu referencu.