Tekninen artikkeli

XLSX pivot-kenttien skeemanmukaisuus Delphissä HotXLS:llä

HotXLS kirjoittaa XLSX-pivot-taulukoiden määritelmät joiden pivotField- ja cacheField-elementit valideeravat ECMA-376 Part 1 §18.10-skeemaa vasten: akseliattribuutit käyttävät ST_Axis-tokeneja axisRow, axisCol ja axisPage, arvoalueen kentät kantavat dataField="1"ia, itemilistat eivät ole koskaan tyhjiä, ja cache-kentät tallentavat numeerisen numFmtIdin. Versiosta v2.384.33 alkaen lukija kunnioittaa myös niitä skeeman oletuksia joita se sai aiemmin väärin

Tämän siivouksen taustalla olevilla bugeilla on imartelematon piirre: yksikään niistä ei koskaan kaatanut testiä. HotXLS kirjoitti pivotin, HotXLS luki sen takaisin, jokainen kenttä laskeutui oikealle akselille, ja round trip -testisarja pysyi vihreänä vuosia. Ongelma oli, että kirjoittaja ja lukija olivat hiljaisena sopineet omasta murteesta. Delphistä rakennettu pivot näytti hyvältä sen komponentin silmiin joka sen teki, kun taas tarkistus CT_PivotFieldia ja CT_CacheFieldia vasten paljasti virheellisiä luettelointitokeneja, tyhjän elementin jonka skeema kieltää ja lippuja joita Excel odotti mutta ei koskaan saanut. Jos generoit pivoteja palvelimella ja toimitat niitä ihmisille jotka avaavat ne Excelissä tai syöttävät niitä omiin jäsentimiinsä, ainoa sopimus joka merkitsee on skeema, ei se mitä oma lukijasi sattuu anteeksi antamaan

Miksi HotXLS:n round tripit eivät koskaan nähneet vääriä akselitokeneja?

HotXLS:n round tripit eivät koskaan nähneet vääriä akselitokeneja, koska lukija hyväksyi molemmat kirjoitusasut. Vanha XlsxPivotAxisAttr emittoi muodot axis="rowAxis", colAxis ja pageAxis, jotka luettiin luontevasti englanniksi mutta joita ei ole skeemassa; ST_Axis määrittelee täsmälleen neljä arvoa, axisRow, axisCol, axisPage ja axisValues. Samaan aikaan tiedoston lxPivotXml.pas PivotAxisFromToken täsmäsi sekä skeematokenin että keksityn, joten jokainen itsetesti meni läpi. Kirjoittaja emittoi nyt vain skeematokenit, ja lukija hyväksyy yhä vanhat kirjoitusasut, jotta aiempien HotXLS-versioiden tallentamat tiedostot latautuvat yhä asettelun ehjänä

<!-- ennen v2.384.33:a: virheellinen ST_Axis-arvo, tyhjä CT_Items -->
<pivotField axis="rowAxis" defaultSubtotal="1"><items count="0"></items></pivotField>

<!-- v2.384.33:stä alkaen -->
<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:n pivotField-XML ennen ja jälkeen v2.384.33:n jossa keksitty akseliarvo rowAxis ja tyhjä items-elementti rikkovat CT_PivotFieldia kunnes kirjoittaja emittoi ST_Axis-tokeneja kuten axisRow oikeilla itemimerkinnöillä, säilytettyä h-merkintää ja häntädefault-välisummaa jonka skeema hyväksyy
Salliva lukija hyväksyi molemmat kirjoitusasut, joten jokainen round trip meni läpi kun taas tiedosto rikkoi minkä tahansa tiukan skeematarkistuksen — kirjoita vain neljä ST_Axis-tokenia ja anna CT_Itemsin kantaa vähintään yksi item

Mitä CT_PivotField vaatii mitä vanha kirjoittaja ohitti?

CT_PivotField vaatii kolme asiaa jotka vanha BuildPivotTableXml jätti pois tai sai väärin. Ensiksi, arvoalueella agregatoituvan kentän on sanottava se omassa määritelmässään dataField="1"illa; kirjoittaja asettaa nyt kyseisen lipun jokaiselle kentälle johon DataFieldsin merkintä viittaa, ei vain <dataFields>-listassa. Toiseksi, CT_Items tarvitsee vähintään yhden itemin, joten itemitön kenttä ei enää saa tyhjää <items count="0">a vaan koko elementti jätetään yksinkertaisesti pois. Kolmanneksi, jokainen item säilyttää tilansa: h="1" piilotetulle itemille (TXLSPivotItem.IsHidden) ja sd="0" supistetuille yksityiskohdille (IsDetailHidden), molempia joita vanha kirjoittaja pudotti jokaisella tallennuksella

Hienovarainen osa ovat häntävälisumma-itemit. Kun kentällä on itemeitä, Excel listaa yhden ylimääräisen itemin jokaista välisummafunktiota kohden data-itemien jälkeen, tyypitettynä ST_ItemTypeilla: <item t="default"/> automaattiselle välisumalle, sitten sum, countA, avg, max, min, product, count, stdDev, stdDevP, var ja varP eksplisiittisille. HotXLS johtaa kyseiset merkinnät TXLSPivotField.Subtotalsista tallennushetkellä ja laskee ne items countin sisään. AddPivotTablein luomat kentät alkavat tyhjällä Subtotals-joukolla, mikä kirjoittaa defaultSubtotal="0"in eikä häntäitemiä, joten pyydä välisummat eksplisiittisesti kun raportti tarvitsee niitä. Huomaa nimennän ansa: xlpsCount mapautuu arvoon countA (kaikki merkinnät) ja xlpsCountNums arvoon count (vain numerot)

HotXLS:n pivot-itemilistan anatomia jossa data-itemimerkintöjä seuraavat häntävälisumma-itemit jotka johdetaan TXLSPivotField.Subtotalsista kuten t=default ja t=avg ja lasketaan items countiin, xlpsCountin ja countAn sekä xlpsCountNumsin ja countin nimennän ansa selitettynä
AddPivotTableista tulevat kentät alkavat tyhjällä Subtotals-joukolla, mikä kirjoittaa defaultSubtotal=0:n eikä häntäitemiä — pyydä haluamasi funktiot ja kirjoittaja johtaa yhden itemin jokaista funktiota kohden countiin
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-pohjainen, kuten XLS-koneessa
    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 jos kenttää ei ole
    if Region <> nil then
      Region.Subtotals := [xlpsDefault, xlpsAverage];  // -> t="default", t="avg"
    Pivot.AddColumnField('Quarter');
    Pivot.AddDataFieldByName('Revenue', xlpaSum);      // asettaa Revenuelle dataField="1":n

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

Miten HotXLS lukee välisumma-itemit ja skeeman oletukset nyt?

HotXLS:n lukija ohittaa nyt jokaisen itemin jonka t-attribuutti on läsnä ja ei ole data, koska välisumma-, kokonaissumma- ja tyhjämerkinnät eivät kanna cache-indeksiä. Ennen v2.384.34:ää kyseiset merkinnät ladattiin tavallisina itemeina CacheItemIndex arvolla -1, joten Excelillä tehty pivot palasi aavejäsenien kanssa jotka eivät osoittaneet mihinkään, ja kaiken Itemsia kävelevän koodin piti suodattaa ne käsin. Koska kirjoittaja rakentaa häntämerkinnät uudelleen Subtotalsista, lukijan tehtävä on kääntää ne kyseiseksi joukoksi, ei pitää niitä datana

Toinen lukijakorjaus koskee attribuutteja jotka ovat poissa. Skeemassa defaultSubtotal arvolla CT_PivotField ja containsString arvolla CT_SharedItems kumpikin oletuvat arvoon true, ja Excel jättää ne pois kun ne kantavat kyseistä oletusta. HotXLS luki puuttuvan attribuutin arvona false, mikä tarkoitti että jokainen Excelin tallentama pivot menetti oletusvälisummansa hiljaisena ladattaessa, ja pelkkää tekstiä kantava cache-kenttä luokiteltiin miksatuksi eikä merkkijonoksi. Tämä on akselibugin peilikuva: kirjoittaja joka kirjoittaa aina jokaisen attribuutin auki ei koskaan harjoita oletuspolkua, joten vain toisen tuottajan tiedostot paljastavat sen

Miksi numFmtId="General" oli virheellinen cache-kentillä?

Arvo numFmtId="General" oli virheellinen koska ST_NumFmtId on etumerkitön kokonaisluku, ei muodon nimi. Vanha cache-kirjoittaja kovakoodasi kyseisen merkkijonon jokaiselle cacheFieldille, lainaten nimen jonka käyttäjät näkevät Muotoile solut -dialogissa. HotXLS kirjoittaa nyt cache-kentän NumberFormatin numerona, joka on 0 (sisäänrakennettu General-muoto) ellei jokin asettanut sitä. Tiukka jäsentin joka tyypittää attribuutit skeemasta hylkää vanhan arvon suoraan, ja se on täsmälleen se virheluokka joka muuttuu korjausdialogiksi; artikkeli Excelin korjauskehotteen takana olevista OPC- ja merkkaussäännöistä kattaa miten kyseiset dialogit laukeavat

Miksi rivin 65535 alapuolella olleet pivot-taulukot leikkautuivat?

Riville 65536 tai sitä alemmas sijoitetut XLSX-pivot-taulukot leikkautuivat, koska jaettu pivot-malli tallensi FirstRowin, LastRowin, FirstHeaderRowin, FirstDataRowin ja sarakevastineet tyypin Word arvoina, ja rivisiirtokoodi lukitsi ne funktiolla Min(.., High(Word)). Se on BIFF8:n SxView-tietueen jäänne, jossa 16 bittiä riittää, mutta XLSX-taulukko jatkuu 1 048 576 riviin. Versiosta v2.384.37 alkaen kyseiset propertyt TXLSPivotTableissa ovat tyyppiä Integer, lukitukset on poistettu, ja vain BIFF8-kirjoittaja kaventaa arvot. TXLSXWorksheet.AddPivotTable ja AddPivotTableCopy palauttavat nyt nilin ankkurille alueen 1..1048576 kertaa 1..16384 ulkopuolella tai kopiolle jonka laajuus juoksisi ruudukon yli

HotXLS:n pivot-ankkuri rivillä 70001 16-bittistä kattoa vasten jossa FirstRow ja LastRow tallennettiin Word-arvoina ja lukittiin funktiolla Min arvoa High(Word) vasten rajalla 65535, leikkauttaen pivotit riville 65536 tai sitä alemmas asti kunnes v2.384.37 siirsi mallin Integer-kenttiin ja nil-palautukseen ruudukon ulkopuolella
Word-kentät olivat BIFF8 SxView -jäänne muodossa jonka taulukot juoksevat 1048576 riviin — rivin 65536 takainen ankkuri kiertyi aiemmin 16-bittiselle alueelle ja menetti pivotinsa tallennuksessa
var
  Pivot: TXLSPivotTable;
  Check: TXLSXWorkbook;
begin
  // Rivi 70001 kiertyi aiemmin 16-bittiselle alueelle; nyt se selviää tallennuksesta ja latauksesta
  Pivot := Sheet.AddPivotTable('Data!$A$1:$D$800', 70001, 1, 'LateTotals');
  if Pivot = nil then
    Exit;  // ankkuri taulukon ulkopuolella tai selvittämätön lähdealue
  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;

Klassinen XLS-kone sai vastaavan korjauksen versiossa v2.384.38. Sen malli tallensi raakat 0-pohjaiset SxView- ja DConRef-arvot ja päästi AddPivotTable-ankkurit suoraan läpi, kun taas dokumentaatio, demot ja XLSX-kone käyttivät kaikkia 1-pohjaisia soluja kuten Cells[Row, Col]. Molemmat koneet pitävät nyt 1-pohjaisia positioita mallissa, BIFF8-lukija lisää 1:n ja kirjoittaja vähentää 1:n tietuerajalla, joten kohta (0, 0) ankkuroinut koodi on siirryttävä kohtaan (1, 1), koska klassinen AddPivotTable palauttaa nyt nilin ankkurille alueen 1..65536 kertaa 1..256 ulkopuolella; uusi kutsu kirjoittaa samat tavut kuin vanha. Tietueasettelu sinänsä on muuttumaton ja on kuvattu artikkelissa klassisten .xls-pivot-taulukoiden BIFF8 SX -tietueista

Validoi skeemaa vasten, ei omaa lukijaasi

Oppi yleistyy pivotien ulkopuolelle: salliva lukija kätkii kirjoittajan rikkeet, joten round trip oman koodin läpi todistaa yhtenäisyyttä, ei oikeellisuutta. Jokainen tämän artikkelin bugi selvisi hengissä koska salliva puoli ja viallinen puoli asuivat samassa kirjastossa. Tarkistukset jotka oikeasti saavat kiinni tämän virheluokan ovat generoitujen osien skeemavalidointi, Excelin tuottamien tiedostojen syöttäminen lukijasi läpi attribuuteilla jotka on jätetty pois oletusarvoissaan, ja fixtuurit jotka lukitsevat tarkan tokenin jäsennetyn tuloksen sijaan. API:n kautta rakentamasi pivotit, mukaan lukien lasketut kentät, lasketut itemit ja prosenttia-yhteensä-asettelut jotka artikkeli XLSX-pivot-taulukoiden rakentamisesta ja päivittämisestä lasketuilla kentillä näyttää, saavat korjatun XML:n ilman koodimuutoksia, kun taas Excel-tiedostoista ladatut pivotit toistavat alkuperäisiä osiaan kunnes muokkaat niitä

Kaikki nämä korjaukset kulkevat mukana nykyisessä HotXLS Delphi Excel -komponentissa, joka lukee ja kirjoittaa XLS-, XLSX- ja pivot-taulukoita Delphistä ja C++Builderista ilman Exceliä tai COM automationia koneella