Artykuł techniczny

Zapisywanie plików XLSX w wariancie ISO 29500 Strict z Delphi

HotXLS zapisuje z Delphi i C++Builder skoroszyty Open XML w wariancie ISO/IEC 29500 Strict, ustawiając przed zapisem jedną właściwość, StrictOOXML. Każda część pakietu, od xl/workbook.xml po pliki relacji i typy zawartości, jest zapisywana ze ścisłymi słownikami purl.oclc.org zamiast przejściowych schemas.openxmlformats.org, a funkcje, na które Strict nie zezwala, są odrzucane jawnym wyjątkiem, a nie mimo to zapisywane

Większość programistów spotyka się z tym wymogiem za sprawą dokumentu przetargowego. Przetargi sektora publicznego w kilku jurysdykcjach żądają znormalizowanej przez ISO postaci Open XML, a nie postaci przejściowej, którą domyślnie zapisuje Office, a archiwum wymagające ISO 29500 Strict odrzuci zwykły plik .xlsx, mimo że Excel otwiera go bez problemu. Przestrzenie nazw przejściowe istnieją po to, by uwzględnić dziedzictwo zachowań plików binarnych; ścisłe są właściwym standardem

Czym właściwie różnią się Strict i Transitional?

Widoczna różnica dotyczy słownictwa. Ścisła część skoroszytu deklaruje http://purl.oclc.org/ooxml/spreadsheetml/main jako główną przestrzeń nazw i http://purl.oclc.org/ooxml/officeDocument/relationships do odwołań relacji, a żadna przejściowa przestrzeń nazw nie może przetrwać w żadnym miejscu pakietu. Wraz z tym zmieniają się typy relacji, więc główna część relacji nazywa się .../ooxml/officeDocument/relationships/officeDocument, a nie znanym odpowiednikiem z openxmlformats, a typ rozszerzonych właściwości jest zapisany camelCase jako extendedProperties

Niewidoczna różnica dotyczy zakresu. Strict celowo pomija te części schematu przejściowego, które istniały wyłącznie po to, by bezstratnie odtwarzać stare pliki binarne, wraz z rozszerzeniami producenta dodanymi przez Office później. Dlatego konwersja nie jest zwykłym wyszukaj-i-zamień na ciągach znaków: niektóre funkcje po prostu nie mają ścisłego zapisu i w ogóle nie wolno ich zapisywać

Włączanie trybu

Zwykły kod tworzący dokument się nie zmienia. Buduj skoroszyt tak jak zawsze, ustaw flagę i zapisz:

var
  Wb: TXLSXWorkbook;
  Sh: TXLSXWorksheet;
begin
  Wb := TXLSXWorkbook.Create;
  try
    Sh := Wb.Sheets.Add('Data');
    Sh.Cells[1, 1].Value := 'Product';
    Sh.Cells[1, 2].Value := 'Amount';
    Sh.Cells[2, 1].Value := 'Widget';
    Sh.Cells[2, 2].Value := 17;
    Sh.Cells[3, 2].Formula := '=SUM(B2:B2)';

    Wb.StrictOOXML := True;          // wyjście w wariancie ISO/IEC 29500 Strict
    if Wb.SaveAs('archive-copy.xlsx') <> 1 then
      raise Exception.Create('strict save failed');
  finally
    Wb.Free;
  end;
end;

Flaga jest resetowana na początku każdej operacji zapisu i ponownie przypisywana z właściwości skoroszytu, więc wyjątek podczas jednego zapisu nie może przeciekać trybu strict do kolejnego zapisu. Ten szczegół ma znaczenie w procesach serwerowych, gdzie jeden obiekt skoroszytu obsługuje kilka żądań eksportu

Dlaczego zapis strict może odmówić wykonania?

Cztery rodziny funkcji to rozszerzenia Microsoftu bez odpowiednika w ISO 29500 Strict, a HotXLS zgłasza wyjątek w chwili zapisu zamiast wygenerować pakiet, który twierdzi, że jest zgodny ze strict, a nim nie jest:

// Wyjście strict nie może osadzić projektu VBA
//   -> zapisz skoroszyty z makrami jako przejściowy .xlsm
// Wyjście strict nie może nieść kontrolek formularza
//   -> przyciski, pola wyboru, listy rozwijane i ich ctrlProps
// Wyjście strict nie może nieść komentarzy wątkowych
//   -> nowoczesny model persons/threads, nie klasyczne notatki
// Wyjście strict nie może nieść metadanych tablic dynamicznych
//   -> zakresy rozlania zapisane przez część metadanych

Głośna awaria to tutaj właściwy kompromis. Po cichu porzucony projekt VBA zamienia działający skoroszyt w uszkodzony, który nadal się otwiera, a zgłoszenie awarii przychodzi od użytkownika dopiero tygodnie później. Wyjątek nazywa funkcję i właściwość do zmiany, dopóki kod wywołujący wciąż wie, co eksportował. Zachowanie makr i łączy zewnętrznych na ścieżce przejściowej jest opisane w zachowywaniu projektów VBA i łączy zewnętrznych

Dwie rodziny rozszerzeń są traktowane inaczej i warto wiedzieć dlaczego. Paski danych, wykresy przebiegu w czasie i podobne funkcje mieszkają w słownikach x14 i xm, a warianty SVG obrazów mieszkają w c15. To treść listy rozszerzeń, której przestrzenie nazw są samoopisujące, ogólne parsery arkuszy kalkulacyjnych je tolerują, a nie istnieje odpowiednik ISO, na który dałoby się je przetłumaczyć. HotXLS je zachowuje zamiast porzucać treść użytkownika. Jeśli walidator w twoim potoku jest ścisły również co do rozszerzeń, a nie tylko przestrzeni nazw, usuń te funkcje z arkusza źródłowego przed eksportem

Tłumaczenie musi dosięgnąć części, których zwykle nikt nie przepisuje

Interesujący problem inżynieryjny w wyjściu strict nie dotyczy XML-a arkusza. Dotyczy części, które szybki zapis wolałby po prostu skopiować dosłownie. HotXLS zachowuje motywy, połączenia, łącza zewnętrzne, wykresy i bloby tabel przestawnych, kopiując ich oryginalne skompresowane bajty wprost, co jest dokładnie właściwym podejściem dla wierności i dokładnie złym dla wyjścia strict, ponieważ skopiowane bajty niosą przejściowe przestrzenie nazw

Pod StrictOOXML tych pięć zachowywanych ścieżek przełącza się na przebudowę albo na tłumaczące odtworzenie, omijając szybką ścieżkę kopiowania bajtów. Cały XML przechodzi przez jedną procedurę tłumaczącą, zakotwiczoną na wartościach atrybutów w cudzysłowach, dzięki czemu ciąg znaków wyglądający jak URI wewnątrz komórki nigdy nie zostanie przypadkowo przepisany. Tekst komórki zawierający ten sam URI jest zapisany w XML jako encja, więc zakotwiczone zastąpienie go nie widzi. Zapis strumieniowy najpierw tłumaczy swój szkielet, a następnie dzieli się przy sheetData, ponieważ bloki wierszy w ogóle nie zawierają URI słownictwa. Powiązaną mechanikę ścieżki zachowywania opisano w bezstratnym odtwarzaniu motywów, list rozszerzeń i calcChain

Odczyt plików zapisanych przez Excela jako strict

Zapis to tylko połowa historii. Excel oferuje jako opcję zapisu „Strict Open XML Spreadsheet”, a pliki wytworzone w ten sposób muszą się poprawnie otwierać. HotXLS normalizuje typy relacji w każdym miejscu parsowania relacji w pakiecie: w korzeniu, łączach zewnętrznych, arkuszach, rysunkach i tabelach przestawnych, dzięki czemu ścisły typ relacji pasuje do tej samej wewnętrznej stałej co jego przejściowy odpowiednik

Odpowiednikiem po stronie odczytu jest normalizacja prefiksów przestrzeni nazw, która pozwala dowolnym prefiksom i obu słownikom rozwiązywać się do jednej kanonicznej tabeli nazw. Ta praca przynosi korzyść zarówno zwykłym plikom, jak i ścisłym, ponieważ generatory zewnętrzne wiążą prefiksy dowolnie, i jest to ten sam mechanizm opisany w rozwiązywaniu relacji OPC w pakietach XLSX

Krótka lista kontrolna przed wysłaniem wyjścia strict

Weryfikuj pakiet, nie Excela. Excel bez problemu otwiera obie postaci, więc udane otwarcie niczego nie dowodzi o zgodności. Rozpakuj wynik i potwierdź, że xl/workbook.xml deklaruje przestrzeń nazw purl, że żadna część nie zawiera schemas.openxmlformats.org/spreadsheetml oraz że typy relacji w _rels/.rels i xl/_rels/workbook.xml.rels używają ścisłych postaci

Następnie ponownie otwórz plik przez HotXLS i porównaj wartości, formuły, formaty i hiperłącza ze źródłem. Test odczytu zwrotnego to jedyny tani sposób, by udowodnić, że tłumaczenie nie uszkodziło treści, i jednocześnie ćwiczy normalizację po stronie odczytu. Jeśli twoje skoroszyty niosą wykresy, sprawdź również je, ponieważ część wykresu to jedna z zachowywanych części, która w trybie strict przełącza się na ścieżkę przebudowy

Wyjście strict, tolerancyjny odczyt i bezstratne zachowywanie to wszystko elementy tego samego silnika OOXML dla Delphi i C++Builder; pełna lista funkcji znajduje się na stronie komponentu arkusza kalkulacyjnego HotXLS dla Delphi