Teknisk artikel

Excel betinget formatering-evaluering i Delphi med HotXLS

HotXLS er en native regnearkskomponent til Delphi og C++Builder, og siden version 2.209.0 kan den besvare spørgsmålet, Excel normalt holder for sig selv: for netop denne celle, hvilke betingede formateringsregler udløses, og hvilken fyld, skrifttype, databjælke eller ikon løser de sig til. Det svar er, hvad man behøver, i det øjeblik ens output er en HTML-rapport, en PDF eller et grid, man selv maler

Dette er et andet problem end at oprette regler. To tidligere noter dækker forfattersiden: betinget formatering og rich text-stilarter handler om at knytte regler og differentielle formater til et interval, og opdeling af forankrede betingede formater handler om, hvad der sker med et reglens interval, når rækker og kolonner indsættes eller slettes. Begge er strukturelle. Denne handler om semantik: givet en projektmappe, der allerede bærer regler, beregn fremhævningen

Hvorfor filformatet ikke fortæller, hvilke celler der lyser op

Det korte svar er, at ECMA-376 og ISO 29500-1 definerer lagring, ikke evaluering. Et conditionalFormatting-element (§18.3.1.18) bærer en sqref og en liste af cfRule-børn (§18.3.1.10), og hver regel bærer en type, en valgfri operator, en priority, et stopIfTrue-flag, ét eller to formula-børn og for de visuelle familier et sæt cfvo-tærskler. Hver eneste af dem beskriver troværdigt, hvad brugeren konfigurerede, og ingen af dem er en algoritme. For halvdelen af regeltyperne betyder det gab intet: cellIs med operator="greaterThan" betyder større end, og containsText betyder, at delstrengen er til stede. Gabet åbner sig ved de samlede familier. En top10-regel med rank="10" og percent="1" over 27 udfyldte numeriske celler fremhæver hvor mange celler? To komma syv er ikke et tal. Rund op, ned eller til nærmeste — specifikationen tier, og at vælge forkert betyder, at din PDF er uenig med den projektmappe, kunden har åben ved siden af den

Enkeltcelle-regler, og hvor TCondFormatRule.Evaluate stopper

HotXLS tog den billige halvdel først. TCondFormatRule.Evaluate i lxCondFormat.pas, tilføjet i 2.199.0, besvarer, om én regel udløses for én celle, uden at kende noget til resten af intervallet. Den håndterer de otte BIFF-sammenligningsoperatorer bag cellIs (between, notBetween, equal, notEqual, greater, less, greaterEqual, lessEqual), fritstående expression-regler evalueret ved cellen, så relative referencer rebaser korrekt, de fire tekstprædikater samt blanks- og errors-prædikaterne. Tærskler kommer fra FFormula1 og FFormula2 løst gennem TXLSCalculator.GetRangeValue ved cellepositionen, og omvendte grænser byttes om frem for afvist

var
  I: Integer;
  Rule: TCondFormatRule;
  Value: Variant;
begin
  Value := Sheet.Cells[Row, Col].Value;
  for I := 0 to CondFormat.RuleCount - 1 do
  begin
    Rule := CondFormat.Rule(I);
    // Single-cell verdict only. Aggregate and visual kinds answer False.
    if Rule.Evaluate(Calculator, SheetIndex, Row, Col, Value) then
      ApplyHighlight(Row, Col, Rule.Style);
  end;
end;

Den ærlige del af den metode er, hvad den nægter at gætte på. top10, aboveAverage, belowAverage, duplicateValues og uniqueValues returnerer False, ikke fordi de er svære, men fordi de er uafgørbare fra én celle — hver af dem behøver en statistik over hele domænet. De fire visuelle familier, dataBar, colorScale2, colorScale3 og iconSet, returnerer False af en anden grund: de producerer aldrig en boolean overhovedet, de producerer en render-payload, og en boolsk returtype er den forkerte form for dem

Hvordan undgår en arknivea-evaluator at genscanne arket?

Ved at beregne hver delt størrelse én gang, ved konstruktion, og aldrig igen. TXLSXConditionalFormatEvaluator i lxHandleX.pas er et uforanderligt snapshot for ét regneark, bygget gennem TXLSXWorksheet.CreateConditionalFormatEvaluator, og hele dens design er et forsvar mod den naive implementering, hvor hver malet celle udløser en fuld interval-scanning

Fire ting sker i konstruktøren. Hver distinkte multi-area sqref parses nøjagtig én gang til en TXlsxCfRangeSnapshot, så ti regler, der deler ét interval, deler én parsing og ét statistikgennemløb. Det gennemløb strømmer gennemsnit, populationsafvigelse, minimum og maksimum over de udfyldte celler i én gennemgang, og gemmer kun et ordnet numerisk array, når en Top/Bottom- eller percentilregel faktisk behøver rangordningsstatistik. Duplikat- og unikke-nøgler bygges Unicode-sikkert og batch-sorteres én gang i stedet for pr. opslag. Så skæres rækkeaksen i bånd ved hver area-grænse, så EvaluateCell binærsøger et bånd og kun besøger regler, hvis intervaller overhovedet kan nå den række

Det fjerde er det, der betyder mest ved skala. En relativ regel-formel som =A1>AVERAGE($A$1:$A$100) betyder noget forskelligt i hver celle i domænet, og den oplagte implementering kompilerer et frisk syntakstræ pr. celle. TXlsxCfRulePlan kompilerer det én gang og genevaluerer det samme træ gennem reversible koordinat-offsets, hvilket bevarer Excels ankeradfærd uden en syntakstræ-allokering pr. celle. Regler lagdeles så efter priority, og et match på en regel, hvis StopIfTrue er sat, bryder løkken, nøjagtig som Excel kortslutter

var
  Evaluator: TXLSXConditionalFormatEvaluator;
  Res: TXLSXCfCellResult;
begin
  Evaluator := Sheet.CreateConditionalFormatEvaluator;
  try
    if Evaluator.EvaluateCell(Row, Col, Res) then
    begin
      if Res.HasFillColor then
        Canvas.Brush.Color := TColor(Res.FillColor);
      if Res.HasIcon then
        // IconIndex is zero-based inside Res.IconSetType
        DrawIcon(Res.IconSetType, Res.IconIndex, Res.IconCount);
      if Res.HasDataBar then
        // DataBarAxis and DataBarEnd are normalised to 0..1
        DrawBar(Res.DataBarAxis, Res.DataBarEnd, Res.DataBarColor);
      if not Res.ShowCellValue then
        Exit;  // showValue="0" on the rule hides the number
    end;
  finally
    Evaluator.Free;
  end;
end;

Hvordan runder Excel egentlig en Top 10 procent-regel?

Den runder ned, med et minimum på ét, og den medtager uafgjorte ved grænseværdien. Det står ingen steder skrevet i ISO 29500-1 — det blev fastlåst ved at teste Excel 16 med håndbyggede projektmapper og læse tilbage, hvilke celler applikationen fremhævede. HotXLS implementerer netop det: rangantallet er Floor(Count * Min(Rank, 100) / 100), hævet til 1, når det lander på nul, klemt til det udfyldte antal, og grænseværdien sammenlignes så med >=, så hver celle lig med grænsen fremhæves, selv når det overskrider det ønskede antal. Syvogtyve værdier og en 10 procent-regel fremhæver to celler, plus eventuelle yderligere celler, der er uafgjorte med nummer to

Over-gennemsnit-regler skjulte en anden tvetydighed: aboveAverage med stdDev="1" vælger celler én standardafvigelse over gennemsnittet, men stikprøve- og populationsafvigelse er forskellige med Bessel-korrektionen, og de er synligt uenige på små intervaller, hvilket er præcis der, betinget formatering bliver brugt. Excel 16 bruger populationsafvigelsen, og HotXLS matcher det, med equalAverage-flaget, der gør den strenge sammenligning inklusiv kun, når intet afvigelsesbånd er i spil. Duplikat- og unikke-regler tændte i stedet på nøgleidentitet. Holder én celle tallet 100, og en anden holder teksten "100", behandler Excel dem som samme duplikatnøgle, så HotXLS normaliserer numerisk tekst ind i det numeriske nøglerum frem for at sammenligne rå strenge. Blanke celler er spejltilfældet: en ægte tom celle deltager i intervaltallet, men males ikke selv, så de tomme celler i en kolonne lyser ikke alle op som duplikater af hinanden

Farveskalaer og ikonsæt: interpolation og grænseregler

De visuelle familier løses til render-klare tal frem for booleans, og deres kantadfærd blev fastlåst på samme måde. For en farveskala med eksplicitte numeriske tærskler klemmer HotXLS positionsbrøken til det lukkede interval nul til én, og interpolerer så pr. kanal med afkortning frem for afrunding — en værdi under minimum får minimumsfarven frem for en ekstrapoleret, en skala med tre trin vælger sit par ved at sammenligne mod midtpunktstærsklen, og en degenereret skala, hvis to ender bærer samme tærskel, kollapser til topfarven frem for at dividere med nul. Ikonsæt behøvede den modsatte slags omhu, fordi hver cfvo efter den første bærer sin egen sammenligningsstrenghed: HotXLS læser ThresholdEqualsInclude pr. tærskel og anvender >= eller > derefter, gående opad, så den højeste opfyldte tærskel vinder ikonindekset. Et omvendt sæt vender det løste indeks frem for tærsklerne, per-ikon-overrides kan trække et symbol fra en anden familie, og enhver ugyldig tærskel afbryder reglen frem for at producere et plausibelt udseende forkert ikon

Fodring af et grid, en HTML-eksport og en PDF fra ét resultat

Fordi EvaluateCell returnerer et fuldt løst TXLSXCfCellResult — differentiel fyld- og skrifttypefarve med tema-tint allerede anvendt, fed, kursiv, understregning, talformat-id, retningsbestemte positive og negative bjælke-udstrækninger, akseposition, ikonfamilie og -indeks — læser hver forbruger samme record, og ingen af dem behøver forstå regel-interna. HotXLS bruger den ene sti til HTML-eksport, PDF-eksport og den interaktive viewer, hvilket er den eneste praktiske måde at holde tre renderere fra at glide fra hinanden. Version 2.210.0 koblede den ind i TXLSWorkbookViewer, som cacher én forberedt evaluator pr. aktivt regneark og genbruger den på tværs af scrolling, markering og gentegning, og frigiver den, når projektmappen eller regnearket skifter — at genopbygge snapshot'et ved hver Paint ville besejre hele det konstruktionstidsbaserede design. Den cache er også grunden til, at TXLSWorkbookViewer.RefreshConditionalFormats findes: snapshot'et er uforanderligt, så hvis man ændrer den tilknyttede projektmappe på plads, er de samlede statistikker og løste tærskler forældede, indtil man kalder den

// Editing behind a live viewer: the cached snapshot must be invalidated.
Sheet := Viewer.XlsxWorkbook.Sheets[1];
Sheet.Cells[5, 2].Value := 4200;         // changes mean, min, max, ranking
Viewer.RefreshConditionalFormats;        // drop evaluator, repaint

Hvad evaluatoren ikke gør for dig

Tre grænser fortjener at blive sagt ligeud. Den klassiske enkeltcelle-TCondFormatRule.Evaluate og den arkniveau-TXLSXConditionalFormatEvaluator er forskellige flader med forskellige egenskaber, og enkeltcelle-udgaven afviser bevidst de samlede og visuelle familier frem for at tilnærme dem — behøver man Top/Bottom eller en farveskala, byg evaluatoren. Relative datoperioder afhænger af maskinens ur ved evalueringstidspunktet, så en timePeriod-regel gengiver forskelligt i en PDF genereret i dag og én genereret næste uge, hvilket er korrekt adfærd og stadig en supportsag, der venter på at ske, hvis dit arkiv forventes at være byte-stabilt. Den tredje er grammatisk snarere end teknisk: formel-grammatikken for betinget format forbyder strukturerede tabel-referencer, så en regel kan ikke adressere en tabelkolonne ved navn, sådan som en arkformel kan, og det er en begrænsning i formatet, ikke i implementeringen

Bygger man rapportoutput, en eksportpipeline eller et brugerdefineret grid, der skal stemme overens med Excel celle for celle, driver samme løste resultat også det brugerdefinerede VCL-regnearksgrid, beskrevet andetsteds på denne blog. Fuld API-dokumentation, regelmodellen og trial-downloads til HotXLS Delphi regnearkskomponent findes på produktsiden