Teknisk artikel

Sortera pivotelement efter värde: autoSortScope i Delphi

HotXLS sorterar PivotTable-element efter värde i Delphi via två egenskaper på TXLSPivotField: SortType väljer stigande eller fallande, och SortDataField namnger det nollbaserade datafält vars summor driver ordningen. När HotXLS öppnar en Excel-författad XLSX återvinner den datafältet från autoSortScope-elementet, där Excel lagrar Values-fältet som field="4294967294", den oesignerade stavningen av -2

Att sortera efter värde är förfrågan varje säljrapport förr eller senare får: "visa regionerna från störst till minst". I Excel-gränssnittet är det två klick. I filformatet är det ett attribut, ett nästlat pivotområde, ett reserverat fältindex och en vald datafältsordning, och en läsare som missar vilken som helst av de bitarna öppnar filen ändå utan klagan. Den sorterar bara efter fel sak

Var lagrar Excel ett sortera-efter-värde-mått?

Excel lagrar sorteringsmåttet bara inne i autoSortScope, inte på fältet själv. Attributet sortType på <pivotField> (ett ECMA-376 Part 1 ST_FieldSortType-värde: manual, ascending eller descending) säger åt vilket håll som ska sorteras. Vilket mått som ska sorteras efter bor i ett barn <autoSortScope>, som wrappar en <pivotArea> vars dataaxelreferens väljer ut ett datafält

<pivotField axis="axisRow" showAll="0" sortType="descending">
  <items count="5"> ... </items>
  <autoSortScope>
    <pivotArea dataOnly="0" outlineLevel="all" fieldPosition="0">
      <references count="1">
        <reference field="4294967294" count="1" selected="0">
          <x v="0"/>
        </reference>
      </references>
    </pivotArea>
  </autoSortScope>
</pivotField>

Elementet reference pekar normalt på ett pivotfält efter index. Fältindex -2 är reserverat i PivotTable-delen av ECMA-376 för Values-pseudofältet, det Excel visar som "Values" när två eller flera mått sitter på en axel. Attributet är typat som ett oesignerat 32-bitars heltal, så -2 går över tråden som 4294967294. <x v="0"/> under det väljer datafält 0, första posten i <dataFields>. Det finns också ett rankBy-attribut på <pivotField>, men i filerna vi jämförte lät Excel det ligga och förlitade sig på scopet ensamt

Excel lagrar en PivotTable-värdsortering i autoSortScope pivotArea-referensen, där fält 4294967294 är den oesignerade 32-bitarsstavningen av -2 för Values-pseudofältet och x-barnet väljer datafältet HotXLS sorterar efter
Den nästlade referensen gör den abstrakta sorteringsriktningen till ett konkret mått: x v=0 pekar på datafält 0, den ordningstal HotXLS exponerar som SortDataField på TXLSPivotField

Varför kom sorteringsmåttet tillbaka som -1?

HotXLS-läsaren tappade ursprungligen sorteringsmåttet eftersom 4294967294 inte får plats i ett signerat 32-bitars heltal. Attributhjälparen är en tunn wrapper över StrToIntDef, och ett värde utanför intervallet returnerar tyst standardvärdet, så dataaxeltestet matchade aldrig, SortDataField stannade på -1, och fältet behöll bara sortType="descending" utan något mått fäst

Felet var tyst på värsta sättet. Ett fallande fält utan mått sorterar ändå, bara efter elementetikett i omvänd ordning, så en rapport Excel ordnat efter summa kom tillbaka från HotXLS som elementordningen bakvänd. Inget kastade ett undantag och talen i varje cell var korrekta. Bara radordningen var fel, och bara för filer Excel hade författat

Fixen i lxPivotXml.pas jämför den råa attribut-tokenen som en sträng före varje heltalskonvertering. Har rankBy redan satt ett mått vinner den; annars löser en referens vars field-token är exakt '4294967294' SortDataField från sitt valda x-värde. Utvärderingssidan behövde ingen ändring: komparatorn färdigställde redan per-medlem-summor på rad- och kolumnaxlarna, hanterade fallande ordning och bröt oavgjorda värden. Datan nådde den bara aldrig

En HotXLS pivotläsare tappade Excels värdsortering eftersom 4294967294 spill över ett signerat 32-bitars StrToIntDef och lämnade SortDataField på -1; fixen jämför råa tokensträngen före varje heltalskonvertering
Den gamla vägen degraderade en fallande värdsortering till en omvänd etikettsortering utan undantag; den nya vägen i lxPivotXml.pas löser måttet närhelst fälttokenen är exakt 4294967294

Den allmänna lärdomen gäller vilken OOXML-läsare skriven i Delphi som helst. ECMA-376 typar flera indexattribut som xsd:unsignedInt och använder sedan toppen av intervallet som sentinel, så en signerad läsare med standardreserv gör en reserverad varden till "attribut saknas". Jämför tokenen, eller parsa in i Int64 eller Cardinal, varhelst ett fältindex kan betyda Values

Att sortera pivotelement efter ett mått från Delphi-kod

För att sortera ett HotXLS-pivotfält efter värde, sätt SortType till xlpfstAscending eller xlpfstDescending och sätt SortDataField till det nollbaserade indexet av måttet i tabellens DataFields-lista. Med SortType = xlpfstManual, som är standard, ignoreras SortDataField och elementen behåller sin cache- eller programmatiska ordning

uses
  SysUtils, lxHandleX, lxPivot;

var
  Book: TXLSXWorkbook;
  Data, Report: TXLSXWorksheet;
  Pivot: TXLSPivotTable;
  Region: TXLSPivotField;
begin
  Book := TXLSXWorkbook.Create;
  try
    Data := Book.Sheets.Add('Data');
    Data.Cells[1, 1].Value := 'Region';
    Data.Cells[1, 2].Value := 'Product';
    Data.Cells[1, 3].Value := 'Units';
    // ... fyll rader 2..41 ...
    Report := Book.Sheets.Add('Report');

    Pivot := Report.AddPivotTable('Data!A1:C41', 3, 1, 'UnitsByRegion');
    Region := Pivot.AddRowField('Region');
    Pivot.AddDataFieldByName('Units', xlpaSum);   // datafält 0

    Region.SortType := xlpfstDescending;  // största regionen först
    Region.SortDataField := 0;            // sortera efter Sum of Units
    Region.AutoSortScope := '0';          // sänd också ut Excels egen autoSortScope

    if Book.MaterializeNativePivotTable(Pivot) <> 1 then
      raise Exception.Create('Pivot layout was rejected');
    Book.SaveAs('units-by-region.xlsx');
  finally
    Book.Free;
  end;
end;

Två detaljer förtjänar en notering. HotXLS-skrivaren sänder ut SortDataField som rankBy, och när AutoSortScope inte är tomt skriver den också ett autoSortScope-block med 4294967294-dataaxelreferensen, med egenskapssträngen i x v-platsen. Vid inläsning tar AutoSortScope emot samma x v-token, så för värdsorteringar håller egenskapen datafältsordningstalet som text. Att sätta den till '0' ger alltså en fil som bär måttet på båda platserna, vilket är den närmaste matchningen av vad Excel själv skriver. MaterializeNativePivotTable mellanlagrar sedan det sorterade rutnätet och dess layoutmetadata tillsammans och returnerar 1 vid framgång eller -1 med en diagnostik

Elementrader är inte det enda som sorteras. Kolumnfält går genom samma komparator, med per-kolumn-summor i stället för per-rad-summor, så ett kolumnfält med SortType = xlpfstDescending och en giltig SortDataField ordnar sina kolumner efter värde på exakt samma sätt

Att granska sorteringen en Excel-arbetsbok redan bär

För Excel-författade filer fyller HotXLS SortType, SortDataField och AutoSortScope medan pivotdefinitionen läses in, så du kan granska eller ändra en rapports sortering utan att bygga om tabellen. Snutten nedan listar varje fält som sorterar efter ett mått och namnger måttet efter dess DisplayName

var
  Book: TXLSXWorkbook;
  Pivot: TXLSPivotTable;
  Field: TXLSPivotField;
  I: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('sales-report.xlsx');
    Pivot := Book.Sheets.SheetByName('Report').PivotTables[0];
    for I := 0 to Pivot.FieldCount - 1 do
    begin
      Field := Pivot.Fields[I];
      if (Field.SortType <> xlpfstManual) and (Field.SortDataField >= 0) then
        Writeln(Field.Name, ' sorted by ',
          Pivot.DataFields[Field.SortDataField].DisplayName);
    end;

    // vänd Region-sorteringen till stigande och bygg om befintligt rutnät
    Field := Pivot.FindFieldByName('Region');
    if Field <> nil then
      Field.SortType := xlpfstAscending;
    if Book.MaterializeNativePivotTable(Pivot, True) <> 1 then
      raise Exception.Create('Could not re-materialize the pivot');
    Book.SaveAs('sales-report-asc.xlsx');
  finally
    Book.Free;
  end;
end;

Det andra argumentet hos MaterializeNativePivotTable säger åt HotXLS att anta pivotens befintliga utdataområde i stället för att behandla det som en kollision. Samma mönster förekommer i guiden till Show Values As-rang och löpande summor i HotXLS, där sorteringsordning spelar roll eftersom löpande summor ackumuleras i visningsordning

Hur bryter HotXLS oavgjorda värden när två element har samma värde?

HotXLS bryter oavgjorda värden efter elementetikett, deterministiskt, vilket är en känd skillnad från Excel. När två medlemmar har identiska summor faller komparatorn tillbaka på att jämföra deras bildtexter, och den fallande flaggan vänder den jämförelsen också. Element vars mätvärde inte är ett giltigt tal sorteras efter de giltiga i stigande ordning, och den fallande flaggan vänder hela jämförelsen, den grupperingen inkluderad

Excel lovar ingen ordning vid oavgjorda värden alls. I en testfixtur där fyra medlemmar, A, B, C och D, alla hade samma summa gav en stigande sortering i Excel B, C, A, D. Ingenting i den sparade metadatan förutsäger den permutationen; det är en biprodukt av en instabil sortering, och den kan skilja sig mellan filer. HotXLS returnerar A, B, C, D för samma indata. Vår verifiering för det fallet kontrollerar medlemsuppsättningen och per-medlem-värdena i stället för sekvensen, och den fallande fixturen med 15/15 oavgjort matchade Excel cell för cell. Beror din nedströmskod på den exakta positionen av lika rader, sortera på ett andra mått eller gör värdena åtskilda

HotXLS bryter Excel pivot värdsorterings-oavgjort i Delphi deterministiskt efter elementbildtext, och returnerar A, B, C, D för en fyramedlemsfixtur där en instabil Excel-sortering gav B, C, A, D
Lika summor faller vidare till en bildtextjämförelse som den fallande flaggan vänder också, så bara ordningen av lika rader kan skilja mellan de två motorerna

Kollapsade medlemmar och det innersta fältet

Kollapsade medlemmar materialiseras i HotXLS som en aggregerad rad per kollapsat element. När Excel döljer ett elements detaljer skriver den sd="0" på det <item>; HotXLS exponerar det som TXLSPivotItem.IsDetailHidden. Det kollapsade elementet renderas med sin vanliga etikett, ersätter hela sitt delträd, och kollapsen gäller elementet under varje förälder, medan omslutande block behåller sina egna delsummor

var
  Category: TXLSPivotField;
begin
  // Pivoten har Category (yttre) och Product (inre) på rader
  Category := Pivot.FindFieldByName('Category');
  if (Category <> nil) and (Category.ItemCount > 0) then
    Category.Items[0].IsDetailHidden := True;   // kollapsa första kategorin
  if Book.MaterializeNativePivotTable(Pivot, True) <> 1 then
    raise Exception.Create('Collapse was rejected');
end;

Kollapsning samspelar med sortering på ett förutsägbart sätt: en kollapsad rad är fortfarande en medlem av sitt fält, så den tar sin position från sin summa som vilket annat element som helst. Det innersta fältet är annorlunda. Det har ingen inre nivå, så det har inget att kollapsa och, sedan en tidigare fix, renderar HotXLS aldrig en delsummorad för det heller, även när dess defaultSubtotal-flagga är satt. Excel beter sig likadant på enkel- och flernivåaxlar, och före den ändringen kunde HotXLS sända ut en fantom-Total-rad under det innersta fältet. För fältnivåattributen inblandade här täcker HotXLS-genomgången av CT_PivotField-schema giltighet vad schemat kräver på <pivotField> och dess items

Fallgropar vid kontroll mot Excel-automation

När du korskontrollerar en HotXLS-sortering mot Excel via COM-automation, anropa AutoSort med måttets lokaliserade bildtext, stavat exakt som Excel visar det. På en kinesiskspråkig Excel är standardbildtexten för ett summerat Units-fält '求和项:Units'; att skicka 'Sum of Units' på den maskinen kastar inget fel och ändrar ingenting. Samma skript som fungerar på en engelskspråkig Excel blir tyst en no-op annorstädes, så läs bildtexten från datafältet i stället för att hårdkoda den

  • PivotField.SortType kunde inte läsas tillbaka via COM i Excel-bygget vi testade, så verifiera sorteringen från sparad XML eller de renderade cellerna i stället
  • I det bygget undertryckte ett manuellt-grupp-föräldrafält på samma axel utanför det sorterade fältet medlemsortering efter värde; sorterad rendering under manuella gruppföräldrar är inte verifierad
  • Skrivningar till PivotField.Outline och PivotField.LayoutForm avvisades i det bygget också, så sätt en outline-layout för en fixtur via PivotTable.RowAxisLayout i stället

Inget av de quirken ändrar filformatet, men de avgör om ett regressionstest bevisar något. Ett test som anropar AutoSort med fel bildtext passerar mot en pivot som aldrig sorterades

FAQ

Vad betyder fält 4294967294 i en PivotTable-XML-fil?

Fält 4294967294 är den oesignerade 32-bitarskodningen av fältindex -2, som ECMA-376 reserverar för Values-pseudofältet. Inuti autoSortScope markerar det en dataaxelreferens, och dess x-barn väljer vilket datafält elementen sorteras efter

Sorterar HotXLS pivotelement efter värde utan att Excel är installerat?

Ja. HotXLS utvärderar sorteringen själv när den materialiserar pivotrutnätet, med per-medlem-summor av det valda datafältet, och skriver sortType, rankBy och valfritt autoSortScope så att Excel behåller samma ordning efter en uppdatering. Bara ordningen vid oavgjorda värden kan skilja sig från Excel

Varför är mina pivotelement sorterade alfabetiskt i stället för efter värde?

Oftast för att fältet har en sorteringsriktning men inget mått. Kontrollera att SortType inte är xlpfstManual och att SortDataField ligger mellan 0 och DataFieldCount - 1; med -1 ordnar HotXLS elementen efter etikett i den begärda riktningen

Värdsortering, kollapsade medlemmar och resten av den nativa pivotpipelinen levereras i HotXLS, Delphi-kalkylarkskomponenten för XLS och XLSX, som läser, redigerar och skriver Excel-arbetsböcker utan att Excel är installerat. Bygger du pivoten från grunden först täcker HotXLS-handledningen om att bygga och uppdatera pivottabeller cachar, fält och uppdatering innan du kommer till att ordna dem