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ć

Przełączenie słownictwa StrictOOXML HotXLS w pakiecie XLSX: części skoroszytu, relacji i rozszerzonych właściwości przepisane z przestrzeni nazw przejściowych openxmlformats na ścisłe przestrzenie nazw purl.oclc.org
Każda część zmienia słownictwo naraz — od przestrzeni nazw korzenia przez typy relacji po typ extendedProperties zapisany camelCase; żadna przestrzeń nazw transitional nie może przeżyć gdziekolwiek w pakiecie

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:

Ścisła bramka zapisu HotXLS w Delphi: SaveAs z StrictOOXML podnosi wyjątek dla projektów VBA, kontrolek formularzy, komentarzy nitkowanych i metadanych tablic dynamicznych, a kończy się tylko wtedy, gdy żadna zakazana funkcja nie jest obecna
Głośna porażka bije po cichu porzucony projekt VBA, który nadal się otwiera; samoopisująca się zawartość rozszerzeń x14, xm i c15 nie ma postaci strict i jest zachowywana jak 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

Ścisła ścieżka wyjścia HotXLS dla zachowanych części XLSX: motyw, połączenia, łącza zewnętrzne, wykresy i pamięci podręczne przestawne przełączają się z dosłownego kopiowania bajtów na przebudowę albo tłumaczone odtwarzanie przez jedną rutynę zakotwiczoną w cytowanych atrybutach
Skopiowane skompresowane bajty przemyciłyby przestrzenie nazw transitional do pakietu strict, więc pięć zachowywanych ścieżek omija szybką drogę; tłumaczenie kotwiczy się na wartościach atrybutów w podwójnych cudzysłowach i rozcina strumieniowy szkielet na sheetData

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