Teknisk artikel

HotXLS Delphi Component: ODS open, save, and round-trip in Delphi

En Delphi-rapportbackend som i åratal har skrivit ut .xlsx får ett nytt krav: en kund inom offentlig sektor har upphandlingsregler som föreskriver OpenDocument Spreadsheet-utdata, och analytikerna på det kontot skickar tillbaka sina redigeringar som .ods-filer sparade från LibreOffice. Så nu måste samma kod skriva ODS och läsa den. HotXLS, losLabs nativa Object Pascal-kalkylbibliotek för Delphi och C++Builder, hanterar båda riktningarna utan att vare sig Excel eller LibreOffice är installerat någonstans. Vad det inte gör är att göra de två riktningarna symmetriska. Export bär med sig långt mer än vad import återvinner, och ett team som antar motsatsen kommer att se formler och formatering avdunsta någonstans mellan kundens revidering och nästa rapport, utan något fel att peka på

ODS-stöd bor på XLSX-fasaden, inte på XLS-fasaden

HotXLS levererar två oberoende klasshierarkier i ett enda paket: TXLSWorkbook i enheten lxHandle för binära BIFF8 .xls-filer, och TXLSXWorkbook i enheten lxHandleX för OOXML .xlsx-paket. Varje OpenDocument-ingångspunkt - OpenODS, SaveAsODS, GetODSSheetNames - hänger på TXLSXWorkbook. Placeringen är inte godtycklig. Ett ODS-paket, som specificerat i OASIS ODF 1.3, är ett zip-arkiv som bär en mimetype-medlem, en manifest och en content.xml-kropp, vilket gör det till en strukturell kusin till OOXML-zippen; BIFF8 är en binär postström från 1990-talet utan något gemensamt

Den placeringen har en praktisk baksida: en äldre .xls-arbetsbok kan inte bli .ods i ett enda anrop. Du bygger en bro för BIFF-innehållet in i XLSX-modellen först, med SaveXLSWorkbookAsXLSX från enheten lxXlsxExport, öppnar resultatet igen via TXLSXWorkbook, och exporterar sedan därifrån. Bron är inte förlustfri, och det är värt att känna till glappen innan du bygger på den. Den kopierar värden, formler, talformat, typsnitt, fyllningar och kolumnbredder. Den släpper ramlinjer, sammanslagna områden, kommentarer, diagram och villkorlig formatering. En .xls-källa med tung formatering kommer att nå ODS och se mer avskalad ut än när den lämnade, och det är en egenskap hos bron, inte hos ODS-skrivaren

Detektering på importsidan är automatisk. Den enkla metoden Open känner igen ett ODS-paket via dess mimetype-medlem, och faller tillbaka till en kontroll av en content.xml på toppnivå när den medlemmen saknas, så en generisk "öppna vad användaren än laddade upp"-kodväg behöver ingen egen filändelsedetektering. Efter öppningen rapporterar egenskapen SourceFormat vilken gren som utlöstes

Diagram över HotXLS-klasslayouten i Delphi där varje ODS-ingångspunkt bor på TXLSXWorkbook, och en SaveXLSWorkbookAsXLSX-brygga bär BIFF8 .xls-innehåll över
Varje OpenDocument-ingång hänger på TXLSXWorkbook, och en äldre .xls når ODS endast via den förlustbringande BIFF-till-XLSX-bryggan

Att exportera till ODS med TODSExportOptions

Själva exportanropet är en rad; options-objektet runt det bär besluten en granskare kommer att fråga om senare:

var
  Book: TXLSXWorkbook;
  Opts: TODSExportOptions;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('quarterly-report.xlsx');
    Opts := TODSExportOptions.Create;        // anroparen äger och frigör detta
    try
      Opts.Generator := 'ReportService 4.2'; // meta:generator-override
      Opts.IncludeCharts := True;
      Opts.IncludeImages := True;
      Book.SaveAsODS('quarterly-report.ods', Opts);
    finally
      Opts.Free;
    end;
  finally
    Book.Free;
  end;
end;

Options-objektet ägs av anroparen. HotXLS kommer inte att frigöra det, vilket är varför den inre try..finally finns och inte är valfri. De två egenskaperna som ändrar utdatan, snarare än att bara märka den, förtjänar en närmare titt. Att sätta IncludeCharts := False gör mer än att dölja diagram: det tar bort diagramunderdokumenten och deras manifestposter ur paketet, vilket är precis vad du vill när mottagaren är en datapipeline som skulle snubbla på dem. Generator åsidosätter ODF-strängen meta:generator, som annars lyder HotXLS/<version>; åsidosätt den när nedströmsverktyg fingeravtrycksidentifierar filproducenter för att styra support. Om inget av det gäller, hoppa över options-objektet helt. Att anropa SaveAs(FileName, xlsxOpenDocumentSpreadsheet) är samma sak som SaveAsODS med standardvärden, och strömöverlagringar på båda låter dig skriva paketet direkt in i ett HTTP-svar utan någon temporär fil

Vad importvägen läser - och vad den avsiktligt hoppar över

Läs den här delen noga innan du lovar någon fidelitet för tur och retur. ODS-import i HotXLS är avsiktligt en lättviktig väg. Den bevarar skalära cellvärden och det cachade resultatet varje formel bar vid sparningstillfället, och den expanderar upprepade rader och kolumner ut i rutnätet. Den för inte över stilar, ODS-formeluttryck eller ritningar

Formelvalet är det som mest sannolikt biter, och det gjordes med avsikt. En ODF-cell lagrar två saker sida vid sida: formeluttrycket, skrivet i OpenFormula-dialekten definierad i ODF 1.3 Part 4, och det senaste värdet det producerande programmet beräknade för den. Att översätta OpenFormula till Excels formelsyntax är sitt eget dialektkonverteringsproblem, med verkliga gränsfall kring funktionsvokabulärer, referenssyntax och felmodeller. Att läsa det cachade värdet i stället kringgår hela den klassen av tyst felöversättning, så talen du importerar är exakt de tal avsändaren senast såg. Kostnaden är att de anländer som tal, inte som de levande formlerna som producerade dem

Felbeteendet att designa kring följer direkt: ett kalkylblad vars summor var korrekta när LibreOffice senast sparade det importeras med korrekta tal, men de talen är nu konstanter. Redigera en indatacell, beräkna om, och ingenting rör sig - formeln är borta, bara dess slutresultat kvarstår. Om arbetsflödet behöver levande formler efter import, återupprätta dem programmatiskt utifrån dina egna affärsregler via Cell.Formula, som på XLSX-fasaden tar uttrycket utan ett inledande likhetstecken

Att designa kring den asymmetriska tur-och-retur-resan

Export renderar från den fullständiga arbetsboksmodellen i minnet: värden, stilar och, om du ber om dem, diagram och bilder. Import returnerar bara värden. Så steget .xlsx till .ods har hög fidelitet, och steget .ods till .xlsx tar med sig värden och cachade resultat men ingen styling och inga levande formler. Kedja ihop de två och asymmetrin förstärks. En fullständig cykel .xlsx till .ods till .xlsx skriver allt troget på vägen ut och förlorar stilarna och formlerna på vägen tillbaka in, trots att inget gick fel i något steg

Diagram över den asymmetriska HotXLS ODS tur och retur från Delphi: fullåtrohetsexport från arbetsboksmodellen i minnet, och en endast-värden-import som lämnar formler som konstanter
Export renderar hela minnesmodellen medan import returnerar värden och cachade resultat, så en full .xlsx-till-.ods-till-.xlsx-cykel tyst tappar stilar och levande formler
Book := TXLSXWorkbook.Create;
try
  Book.Open('vendor-revision.ods');          // format autodetekterat
  if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
  begin
    // Värden och cachade formelresultat finns efter en ODS-
    // import; stilar och levande formler gör det inte. Återbygg vad
    // nedströmspipelinen än beror på innan sparning.
    Book.Sheets[0].Cells[2, 5].Formula := 'SUM(B2:D2)';
    Book.SaveAs('vendor-revision.xlsx');
  end;
finally
  Book.Free;
end;

Arkitekturmönstret som faller ut av detta: behandla inkommande .ods-filer som dataflöden, inte som dokument att redigera på plats. Håll den kanoniska arbetsboken i .xlsx, läs ut värden ur kundrevideringar, och skriv ut färsk ODS på begäran från den kanoniska kopian. Verifiering hör hemma i båda lägren - öppna exporterade filer i LibreOffice Calc, referens-ODF-konsumenten, och i Excel, som har läst ODS i flera år men är oense med LibreOffice vid kanterna av diagram- och stilstöd. Bladantal, en handfull nyckelceller och diagramnärvaro utgör en tillräcklig rökkontroll per exportprofil

Att triagera en ODS-fil innan du åtar dig en import

När en ändpunkt tar emot uppladdningar är det att lista bladnamn långt billigare än en fullständig tolkning och fångar strukturella överraskningar tidigt:

Diagram över HotXLS uppladdningstriagegrind i Delphi där GetODSSheetNames avvisar oläsbara ODS-paket och saknade blad innan en fullständig import körs
En GetODSSheetNames-sondering kostar långt mindre än en full tolkning och fångar felet med omdöpt blad medan felet fortfarande kan namnge filen
Names := TStringList.Create;
Book := TXLSXWorkbook.Create;
try
  if Book.GetODSSheetNames('incoming.ods', Names) <= 0 then
    raise Exception.Create('not a readable ODS package');
  if Names.IndexOf('Data') < 0 then
    raise Exception.Create('revision is missing the Data sheet');
finally
  Book.Free;
  Names.Free;
end;

Returkonventionen får folk att snubbla: HotXLS-anrop returnerar generellt ett positivt antal eller 1 vid framgång och -1 vid misslyckande, och tömmer listan när de misslyckas, så testa <= 0 snarare än att jämföra mot ett specifikt positivt värde. GetODSSheetNames varken återställer eller fyller i arbetsboksinstansen, så ett enda testobjekt kan granska en hel katalog med inkommande filer. Strukturella kontroller som den här fångar det vanligaste verkliga felet - en analytiker som byter namn på eller tar bort ett blad innan de skickar tillbaka revideringen - vid grinden, där felmeddelandet fortfarande kan namnge filen och det saknade bladet i stället för att dyka upp som en nil-referens tre lager djupare

Om du bygger en bredare konverteringspipeline runt det här visar mönstret för arbetsboksgranskning och konverteringsverkstad hur man inventerar en fils funktioner innan man väljer ett målformat, och guiden om prestanda för stora arbetsböcker håller batchexporter inom förnuftiga minnesgränser

HotXLS är ett nativt kalkylbibliotek för Delphi och C++Builder med fullständig källkod; den fullständiga funktionslistan och licensdetaljerna finns på produktsidan för HotXLS Delphi Component