Teknisk artikel

XLSX pivot-felt-skemavaliditet i Delphi med HotXLS

HotXLS skriver XLSX-pivottabeldefinitioner, hvis pivotField- og cacheField-elementer validerer mod ECMA-376 Part 1 §18.10-skemaet: axis-attributter bruger ST_Axis-tokens axisRow, axisCol og axisPage, værdiområdefelter bærer dataField="1", item-lister er aldrig tomme, og cache-felter gemmer et numerisk numFmtId. Siden v2.384.33 respekterer readeren også de skemadefaults, den plejede at tage fejl af

Bugsene bag denne oprydning deler et ubehageligt træk: ingen af dem fejlede nogensinde en test. HotXLS skrev en pivot, HotXLS læste den tilbage, hvert felt landede på den rigtige akse, og round-trip-suiten forblev grøn i årevis. Problemet var, at writer og reader stille var blevet enige om en privat dialekt. En pivot bygget fra Delphi så fin ud for komponenten, der lavede den, mens et tjek mod CT_PivotField og CT_CacheField afslørede ugyldige enumereringstokens, et tomt element, skemaet forbyder, og flag, Excel forventer men aldrig fik. Genererer du pivots på en server og sender dem til folk, der åbner dem i Excel eller føder dem til deres egne parsere, er den eneste kontrakt, der tæller, skemaet — ikke hvad din egen reader tilfældigvis tilgiver

Hvorfor fangede HotXLS' round trips aldrig de forkerte axis-tokens?

HotXLS' round trips fangede aldrig de forkerte axis-tokens, fordi readeren accepterede begge stavemåder. Den gamle XlsxPivotAxisAttr udsendte axis="rowAxis", colAxis og pageAxis, som læser naturligt på engelsk, men ikke findes i skemaet; ST_Axis definerer præcis fire værdier, axisRow, axisCol, axisPage og axisValues. I mellemtiden matchede PivotAxisFromToken i lxPivotXml.pas både skema-tokenet og det opdigtede, så hver selvværditest bestod. Writeren udsender nu kun skema-tokens, og readeren fortsætter med at acceptere de gamle stavemåder, så filer gemt af tidligere HotXLS-versioner stadig indlæses med deres layout intakt

<!-- før v2.384.33: ugyldig ST_Axis-værdi, tomt CT_Items -->
<pivotField axis="rowAxis" defaultSubtotal="1"><items count="0"></items></pivotField>

<!-- siden v2.384.33 -->
<pivotField axis="axisRow" defaultSubtotal="1">
  <items count="4"><item x="0"/><item x="1"/><item x="2" h="1"/><item t="default"/></items>
</pivotField>
HotXLS pivotField XML før og efter v2.384.33, hvor den opdigtede axis-værdi rowAxis og et tomt items-element krænker CT_PivotField, indtil writeren udsender ST_Axis-tokens som axisRow med reelle item-entries, et bevaret hidden-flag og en afsluttende default-subtotal, som skemaet accepterer
Den alt for flinke reader accepterede begge stavemåder, så hver round trip bestod, mens filen brød enhver streng skemakontrol — skriv kun de fire ST_Axis-tokens og lad CT_Items bære mindst ét item

Hvad kræver CT_PivotField, som den gamle writer sprang over?

CT_PivotField kræver tre ting, som den gamle BuildPivotTableXml udelod eller fik forkert. For det første skal et felt, der aggregeres i værdiområdet, sige det på sin egen definition med dataField="1"; writeren sætter nu det flag på hvert felt, der refereres af en entry i DataFields, ikke kun i <dataFields>-listen. For det andet kræver CT_Items mindst ét item, så et felt uden items får ikke længere et tomt <items count="0">, og hele elementet udelades simpelthen. For det tredje beholdes hvert items tilstand: h="1" for et skjult item (TXLSPivotItem.IsHidden) og sd="0" for sammenklappede detaljer (IsDetailHidden), begge ting den gamle writer kasserede ved hver gemning

Den subtile del er de afsluttende subtotal-items. Når et felt har items, lister Excel ét ekstra item pr. subtotalfunktion efter data-items, typet med ST_ItemType: <item t="default"/> for den automatiske subtotal, derefter sum, countA, avg, max, min, product, count, stdDev, stdDevP, var og varP for de eksplicitte. HotXLS afleder de entries fra TXLSPivotField.Subtotals ved gemning og tæller dem med i items count. Felter oprettet af AddPivotTable starter med et tomt Subtotals-sæt, hvilket skriver defaultSubtotal="0" og intet afsluttende item, så bed om subtotals eksplicit, når rapporten har brug for dem. Bemærk navnefælden: xlpsCount mapper til countA (alle entries), og xlpsCountNums mapper til count (kun tal)

HotXLS anatomi af pivot-items-listen, hvor data-item-entries efterfølges af afsluttende subtotal-items afledt af TXLSPivotField.Subtotals som t=default og t=avg og talt med i items count, med navnefælden xlpsCount til countA og xlpsCountNums til count stavet ud
Felter fra AddPivotTable starter med et tomt Subtotals-sæt, hvilket skriver defaultSubtotal=0 og intet afsluttende item — bed om de funktioner, du vil have, og writeren afleder ét item pr. funktion ind i tællingen
uses
  lxHandleX, lxPivot;

var
  Book  : TXLSXWorkbook;
  Sheet : TXLSXWorksheet;
  Pivot : TXLSPivotTable;
  Region: TXLSPivotField;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('orders.xlsx');
    Sheet := Book.Sheets[1];                  // 1-baseret, som XLS-motoren
    Pivot := Sheet.AddPivotTable('Data!$A$1:$D$800', 3, 6, 'RegionTotals');
    if Pivot = nil then
      raise Exception.Create('Bad source range or anchor');

    Region := Pivot.AddRowField('Region');    // nil, hvis feltet ikke findes
    if Region <> nil then
      Region.Subtotals := [xlpsDefault, xlpsAverage];  // -> t="default", t="avg"
    Pivot.AddColumnField('Quarter');
    Pivot.AddDataFieldByName('Revenue', xlpaSum);      // markerer Revenue som dataField="1"

    Book.SaveAs('orders-pivot.xlsx');
  finally
    Book.Free;
  end;
end;

Hvordan læser HotXLS subtotal-items og skemadefaults nu?

HotXLS-readeren springer nu ethvert item over, hvis t-attribut er til stede og ikke er data, for subtotal-, grand total- og tomme-entries bærer intet cacheindeks. Før v2.384.34 blev de entries indlæst som ordinære items med CacheItemIndex sat til -1, så en Excel-lavet pivot kom tilbage med fantommedlemmer, der pegede ingen vegne, og al kode, der gik igennem Items, måtte filtrere dem fra i hånden. Da writeren genopbygger de afsluttende entries fra Subtotals, er readerens opgave at oversætte dem til det sæt, ikke at beholde dem som data

Det andet reader-fix handler om attributter, der er fraværende. I skemaet har defaultSubtotal på CT_PivotField og containsString på CT_SharedItems begge default true, og Excel udelader dem, når de holder den default. HotXLS læste tidligere en manglende attribut som false, hvilket betød, at hver pivot gemt af Excel stille mistede sin default-subtotal ved indlæsning, og et almindeligt tekst-cachefelt blev klassificeret som mixed i stedet for string. Dette er spejlbilledet af axis-bugen: en writer, der altid staver alle attributter ud, øver aldrig default-vejen, så kun filer fra en anden producent afslører den

Hvorfor var numFmtId="General" ugyldigt på cache-felter?

Værdien numFmtId="General" var ugyldig, fordi ST_NumFmtId er et unsigned integer, ikke et formatnavn. Den gamle cache-writer hardkodede den streng på hvert cacheField og lånte navnet, brugerne ser i Format Cells-dialogen. HotXLS skriver nu cache-feltets NumberFormat som et tal, hvilket er 0 (det indbyggede General-format), medmindre noget har sat det. En streng parser, der typer attributter fra skemaet, afviser den gamle værdi på stedet, og det er netop den fejlklasse, der ender som en reparationsdialog; artiklen om the OPC and markup rules behind the Excel repair prompt gennemgår, hvordan de dialoger udløses

Hvorfor blev pivottabeller under række 65535 klippet af?

XLSX-pivottabeller placeret på eller under række 65536 blev klippet af, fordi den delte pivotmodel gemte FirstRow, LastRow, FirstHeaderRow, FirstDataRow og kolonnemodstykkerne som Word, og rækkeforskud-koden klemte dem med Min(.., High(Word)). Det er en rest af BIFF8 SxView-recorden, hvor 16 bit er nok, men et XLSX-ark løber til 1.048.576 rækker. Siden v2.384.37 er de egenskaber på TXLSPivotTable Integer, klemmerne er væk, og kun BIFF8-writeren indsnævrer værdierne. TXLSXWorksheet.AddPivotTable og AddPivotTableCopy returnerer nu nil for et anker uden for 1..1048576 gange 1..16384, eller for en kopi, hvis udstrækning ville løbe af gitteret

HotXLS pivot-anker på række 70001 mod 16-bit-loftet, hvor FirstRow og LastRow blev gemt som Word og klemt med Min mod High(Word) på 65535, hvilket klippede pivots af på eller under linjen, indtil v2.384.37 flyttede modellen til Integer-felter med nil-return uden for gitteret
Word-felterne var en BIFF8 SxView-rest i et format, hvis ark løber til 1048576 rækker — et anker forbi række 65536 wrap'ede tidligere ind i 16-bit-intervallet og mistede sin pivot ved gemning
var
  Pivot: TXLSPivotTable;
  Check: TXLSXWorkbook;
begin
  // Række 70001 wrap'ede tidligere ind i 16-bit-intervallet; nu overlever den gemning og indlæsning
  Pivot := Sheet.AddPivotTable('Data!$A$1:$D$800', 70001, 1, 'LateTotals');
  if Pivot = nil then
    Exit;  // anker uden for arket eller uopløseligt kildeområde
  Pivot.AddRowField('Region');
  Pivot.AddDataFieldByName('Revenue', xlpaSum);
  Book.SaveAs('late.xlsx');

  Check := TXLSXWorkbook.Create;
  try
    Check.Open('late.xlsx');
    Pivot := Check.Sheets[1].PivotTables.FindByName('LateTotals');
    Assert((Pivot <> nil) and (Pivot.FirstRow = 70001));
  finally
    Check.Free;
  end;
end;

Classic XLS-motoren fik det matchende fix i v2.384.38. Dens model gemte tidligere de rå 0-baserede SxView- og DConRef-værdier og lod AddPivotTable-ankere passere direkte igennem, mens dokumentationen, demoerne og XLSX-motoren alle brugte 1-baserede celler som Cells[Row, Col]. Begge motorer holder nu 1-baserede positioner i modellen, BIFF8-readeren lægger 1 til, og writeren trækker 1 fra ved record-grænsen, så kode, der ankrede på (0, 0), skal flytte til (1, 1), for classic AddPivotTable returnerer nu nil for et anker uden for 1..65536 gange 1..256; det nye kald skriver samme bytes som det gamle. Record-layoutet selv er uændret og er beskrevet i de BIFF8 SX-records, der ligger bag classic .xls-pivottabeller

Validér mod skemaet, ikke din egen reader

Lærdommen generaliserer ud over pivots: en flink reader skjuler writer-overtrædelser, så en round trip gennem din egen kode beviser konsistens, ikke korrekthed. Enhver bug her overlevede, fordi den tolerante side og den fejlbehæftede side boede i samme bibliotek. De kontroller, der faktisk fanger denne defectklasse, er en skemavalidering af de genererede parts, filer produceret af Excel ført igennem din reader med attributter udeladt ved deres defaults, og fixtures, der fastlåser den eksakte token frem for det parsede resultat. Pivots, du bygger gennem API'en, inklusive de beregnede felter, beregnede items og percent-of-total-layouts vist i building and refreshing XLSX pivot tables with calculated fields, får den rettede XML uden kodeændring, mens pivots indlæst fra Excel-filer fortsætter med at afspille deres originale parts, til du ændrer dem

Alle disse fixes skibes i den nuværende HotXLS Delphi spreadsheet component, som læser og skriver XLS, XLSX og pivottabeller fra Delphi og C++Builder uden Excel eller COM automation på maskinen