HotXLS skriver XLSX-pivottabellsdefinitioner vars pivotField- och cacheField-element validerar mot schemat ECMA-376 Part 1 §18.10: axis-attribut använder ST_Axis-token axisRow, axisCol och axisPage, värdeområdesfält bär dataField="1", itemlistor är aldrig tomma, och cachefält lagrar ett numeriskt numFmtId. Sedan v2.384.33 hedrar läsaren också de schemadefault den brukade ha fel
Buggarna bakom denna städning delar ett osmickrande drag: ingen av dem underkändes någonsin av ett test. HotXLS skrev en pivot, HotXLS läste tillbaka den, varje fält landade på rätt axis, och rundturssviten förblev grön i åratal. Problemet var att skrivaren och läsaren hade tyst kommit överens om en privat dialekt. En pivot byggd från Delphi såg fin ut för komponenten som gjorde den, medan en kontroll mot CT_PivotField och CT_CacheField påvisade ogiltiga uppräkningstoken, ett tomt element som schemat förbjuder och flaggor Excel förväntade sig men aldrig fick. Om du genererar pivots på en server och skickar dem till personer som öppnar dem i Excel eller matar dem till sina egna tolkar är det enda kontrakt som gäller schemat, inte vad din egen läsare råkar förlåta
Varför fångade HotXLS rundturer aldrig de felaktiga axis-tokenen?
HotXLS rundturer fångade aldrig de felaktiga axis-tokenen eftersom läsaren accepterade båda stavningarna. Den gamla XlsxPivotAxisAttr emitterade axis="rowAxis", colAxis och pageAxis, som läses naturligt på engelska men inte finns i schemat; ST_Axis definierar exakt fyra värden, axisRow, axisCol, axisPage och axisValues. Under tiden matchade PivotAxisFromToken i lxPivotXml.pas både schematoken och den påhittade, så att varje självtest gick igenom. Skrivaren emitterar nu bara schematoken, och läsaren fortsätter acceptera de gamla stavningarna så att filer sparade av tidigare HotXLS-versioner fortfarande läses in med sin layout intakt
<!-- före v2.384.33: ogiltigt ST_Axis-värde, tomt CT_Items -->
<pivotField axis="rowAxis" defaultSubtotal="1"><items count="0"></items></pivotField>
<!-- sedan 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>
Vad kräver CT_PivotField som den gamla skrivaren skippade?
CT_PivotField kräver tre saker som den gamla BuildPivotTableXml utelämnade eller hade fel. För det första måste ett fält som aggregeras i värdeområdet säga det i sin egen definition med dataField="1"; skrivaren sätter nu den flaggan på varje fält som refereras av en post i DataFields, inte bara i listan <dataFields>. För det andra kräver CT_Items minst ett item, så ett fält utan items får inte längre ett tomt <items count="0"> utan hela elementet utelämnas helt enkelt. För det tredje bevarar varje item sitt tillstånd: h="1" för ett dolt item (TXLSPivotItem.IsHidden) och sd="0" för hopfällda detaljer (IsDetailHidden), vilka båda den gamla skrivaren tappade vid varje sparning
Den subtila delen är de avslutande subtotalitemen. När ett fält har items listar Excel ett extra item per subtotalfunktion efter dataitemen, typade med ST_ItemType: <item t="default"/> för den automatiska subtotalen, sedan sum, countA, avg, max, min, product, count, stdDev, stdDevP, var och varP för explicita. HotXLS härleder de posterna från TXLSPivotField.Subtotals vid sparningen och räknar dem in i items count. Fält skapade av AddPivotTable börjar med en tom Subtotals-mängd, vilket skriver defaultSubtotal="0" och inget avslutande item, så begär subtotals explicit när rapporten behöver dem. Notera namnfällan: xlpsCount mappas till countA (alla poster) och xlpsCountNums mappas till count (endast tal)
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-baserat, som XLS-motorn
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 om inget sådant fält finns
if Region <> nil then
Region.Subtotals := [xlpsDefault, xlpsAverage]; // -> t="default", t="avg"
Pivot.AddColumnField('Quarter');
Pivot.AddDataFieldByName('Revenue', xlpaSum); // flaggar Revenue som dataField="1"
Book.SaveAs('orders-pivot.xlsx');
finally
Book.Free;
end;
end;
Hur läser HotXLS subtotal-items och schemadefault nu?
HotXLS-läsaren hoppar nu över varje item vars t-attribut finns och inte är data, eftersom subtotal-, grand total- och tomma-poster inte bär något cacheindex. Före v2.384.34 lästes de posterna som vanliga items med CacheItemIndex satt till -1, så att en Excel-skapad pivot kom tillbaka med spökdeltagare som pekade ingenstans, och all kod som gick igenom Items fick filtrera bort dem för hand. Eftersom skrivaren bygger upp de avslutande posterna från Subtotals är läsarens uppgift att översätta dem till den mängden, inte att behålla dem som data
Den andra läsfixen handlar om attribut som saknas. I schemat är defaultSubtotal på CT_PivotField och containsString på CT_SharedItems båda default true, och Excel utelämnar dem när de håller det värdet. HotXLS läste förr ett saknat attribut som false, vilket betydde att varje pivot sparad av Excel tyst förlorade sin default-subtotal vid inläsning, och ett vanligt text-cachefält klassades som blandat i stället för sträng. Det här är spegelbilden av axis-buggen: en skrivare som alltid stavar ut varje attribut övar aldrig på default-vägen, så bara filer från en annan producent exponerar den
Varför var numFmtId="General" ogiltigt på cachefält?
Värdet numFmtId="General" var ogiltigt eftersom ST_NumFmtId är ett teckenlöst heltal, inte ett formatnamn. Den gamla cacheskrivaren hårdkodade den strängen på varje cacheField, lånat av namnet användare ser i dialogen Formatera celler. HotXLS skriver nu cachefältets NumberFormat som ett tal, vilket är 0 (det inbyggda General-formatet) om inget satt det. En strikt tolk som typar attribut från schemat avvisar det gamla värdet rakt av, och det är exakt den felklass som förvandlas till en reparationsdialog; artikeln om OPC- och markup-reglerna bakom Excel-reparationsdialogen täcker hur de dialogerna utlöses
Varför klipptes pivottabeller under rad 65535?
XLSX-pivottabeller placerade på eller under rad 65536 klipptes eftersom den gemensamma pivotmodellen lagrade FirstRow, LastRow, FirstHeaderRow, FirstDataRow och kolumnmotsvarigheterna som Word, och radskiftkoden klämde av dem med Min(.., High(Word)). Det är en kvarlämning från BIFF8-posten SxView, där 16 bitar räcker, men ett XLSX-ark löper till 1 048 576 rader. Sedan v2.384.37 är de egenskaperna på TXLSPivotTable Integer, avklämningarna är borta, och bara BIFF8-skrivaren smalnar av värdena. TXLSXWorksheet.AddPivotTable och AddPivotTableCopy returnerar nu nil för ett ankare utanför 1..1048576 gånger 1..16384, eller för en kopia vars utsträckning skulle springa utanför rutnätet
var
Pivot: TXLSPivotTable;
Check: TXLSXWorkbook;
begin
// Rad 70001 brukade slå runt in i 16-bitsintervallet; nu överlever den sparning och inläsning
Pivot := Sheet.AddPivotTable('Data!$A$1:$D$800', 70001, 1, 'LateTotals');
if Pivot = nil then
Exit; // ankare utanför arket eller olösligt källområ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;
Den klassiska XLS-motorn fick motsvarande fix i v2.384.38. Dess modell lagrade förr de råa 0-baserade SxView- och DConRef-värdena och släppte AddPivotTable-ankaren rakt igenom, medan dokumentationen, demos och XLSX-motorn alla använde 1-baserade celler som Cells[Row, Col]. Båda motorerna håller nu 1-baserade positioner i modellen, BIFF8-läsaren lägger till 1 och skrivaren subtraherar 1 vid postgränsen, så kod som ankade vid (0, 0) måste flytta till (1, 1), eftersom klassiska AddPivotTable nu returnerar nil för ett ankare utanför 1..65536 gånger 1..256; det nya anropet skriver samma byte som det gamla. Postlayouten i sig är oförändrad och beskrivs i BIFF8 SX-posterna bakom klassiska .xls-pivottabeller
Validera mot schemat, inte din egen läsare
Lärdomen generaliserar bortom pivots: en tillgiven läsare gömmer skrivarfel, så en rundtur genom din egen kod bevisar konsistens, inte korrekthet. Varje bugg här överlevde eftersom den toleranta sidan och den felande sidan bodde i samma bibliotek. Kontrollerna som faktiskt fångar den här felklassen är en schemavalidering av de genererade delarna, filer producerade av Excel matade genom din läsare med attribut utelämnade på sina default-värden, och fixturer som låser exakt token i stället för det tolkade resultatet. Pivots du bygger genom API:t, inklusive beräknade fält, beräknade items och procent-av-total-layouterna som visas i att bygga och uppdatera XLSX-pivottabeller med beräknade fält, får den korrigerade XML:en utan kodändring, medan pivots lästa från Excel-filer fortsätter spela upp sina ursprungliga delar tills du ändrar dem
Alla dessa fixar följer med den nuvarande HotXLS Delphi-kalkylbladskomponenten, som läser och skriver XLS, XLSX och pivottabeller från Delphi och C++Builder utan Excel eller COM-automatisering på maskinen