Teknisk artikkel

HotXLS kryss-arbeidsbok-kopiering og formel-rebinding i Delphi

HotXLS' AddCopy-metode kopierer et regneark fra én Excel-arbeidsbok inn i en annen ved å dekompilere hver formel på det arket til A1-stil-tekst og rekompilere teksten inne i målarbeidsboken, i stedet for å kopiere det kompilerte formeltreet direkte, fordi diagramserie-referanser, rik-tekst-skrifttypeindekser, og ekstern-lenke-nummerering alle tildeles uavhengig inne i hver arbeidsbokfil

Feilen viser seg i nøyaktig den arbeidsboken man ville forvente: en månedsslutt-jobb som trekker ett ark ut av hver filialkontors rapport og legger det til en sammendragsfil. Åpne resultatet, og et delsum-diagram plotter en helt annen filials tall, en notis som var fet og rød i kilden, er tilbake til vanlig svart tekst, og en formel som en gang hentet en skattesats fra en tilhørende oppslagsarbeidsbok, viser nå et frosset tall ingen kan forklare. Ingenting kaster et unntak her — filen åpnes, tallene ser plausible ut, og skaden ligger der til noen legger merke til et diagram med feil tittel ved siden av det

Hvorfor kan ikke AddCopy bare kopiere det kompilerte formeltreet?

AddCopy kan ikke flytte det kompilerte formeltreet uendret, fordi en kompilert BIFF-formel ikke er fritt stående tekst — det er en sekvens av tokens, og flere av de tokenene er små heltall som bare løses korrekt inne i arbeidsboken som produserte dem. En 3D-referanse slik som Sheet2!A1:A10 bærer ikke det bokstavelige navnet Sheet2 når den først er kompilert; den bærer et felt BIFF-spesifikasjonen kaller ixti (HotXLS holder den samme verdien i sitt eget kompilerte tre under feltnavnet FExternID), en indeks inn i den arbeidsbokens private EXTERNSHEET-tabell, nummerert på hvilken som helst måte den bestemte arbeidsboken tilfeldigvis registrerte sine ark og eksterne bøker. Flytt tokenet uendret inn i en arbeidsbok hvis EXTERNSHEET-tabell ble bygget i en annen rekkefølge, og indeks 3 betyr ikke lenger Sheet2 — det betyr hvilket ark som enn tilfeldigvis opptar slot 3 der borte, og Excel har ingen måte å flagge feilen på, fordi så langt filformatet er opptatt, er formelen perfekt velformet. Dette er nøyaktig feilen TXLSWorksheets.AddCopy finnes for å unngå: kalt fra en av arbeidsbøkenes egen ark-samling i Delphi- eller C++Builder-kode, kopierer den et regneark — celleverdier, formater, formler, diagrammer, kommentarer, sammenslåinger, sideoppsett, og mer — fra en kildearbeidsbok som kan være eller ikke være den man kaller den på, og legger resultatet til målet under et navn man velger eller en disambiguert kopi av originalen

var
  Summary, Branch: IXLSWorkbook;   // interface-counted: do not Free
begin
  Summary := TXLSWorkbook.Create;
  Branch := TXLSWorkbook.Create;
  Branch.Open('branch-east.xls');

  // Appends a copy of Branch's first sheet onto Summary, renamed to
  // stay unique inside the destination workbook
  Summary.Sheets.AddCopy(Branch.Sheets[1], 'East Detail');
  Summary.SaveAs('consolidated.xls');
end;

Løsningen: dekompiler til tekst, rekompiler i målet

HotXLS løser indekseringsproblemet ved aldri å la selve det kompilerte treet krysse arbeidsbokgrensen. For hver formelcelle på en kryss-arbeidsbok-kopi dekompilerer AddCopy kildeformelen til den samme A1-stil-teksten en bruker ville sett i Excels formellinje, og overleverer deretter den teksten til målarbeidsboken, som parser den tilbake til et tre ved bruk av sine egne tabeller fra bunnen av — en ark-kvalifisert referanse slik som Data!D2:D100 er bare en streng på det tidspunktet, og en streng betyr det samme i enhver arbeidsbok, så hvis målet allerede har et ark ved navn Data, løses referansen korrekt uten noen indeks-oversettelse i det hele tatt, fordi det aldri var noen rå indeks underveis å oversette. HotXLS betaler bare for denne rundturen når den må: å kopiere et ark inne i den samme arbeidsboken tar en billigere vei der det kompilerte treet ganske enkelt dupliseres i minnet, ettersom hver indeks inne i det allerede er gyldig der det blir, og tekst-omveien kjører bare når AddCopy oppdager at kilden og målet er genuint forskjellige arbeidsbok-instanser. Det er verdt å være presis om hva denne omskrivingen ikke er, også. Den har ingenting å gjøre med rad- og kolonneforskyvningen som kjører når man setter inn eller sletter rader inne i ett enkelt ark, noe en følgeartikkel dekker i detalj — den motoren skriver om A1-tekst på plass for å spore celler som flyttet seg noen rader opp eller ned innenfor én arbeidsbok, mens denne kjører når en formel forlater arbeidsboken som kompilerte den i sin helhet, der flyttede rader ikke er problemet og arbeidsbok-privat nummerering er det

// Conceptually, this is what AddCopy does for each formula cell: turn
// the compiled tree back into text using the source workbook's own
// tables, then let the destination workbook parse that text back into
// a tree using its own tables, from scratch
FormulaText := SourceBook.GetUnCompiledFormula(SourceFormula, Row, Col, SourceSheetID);
DestFormula := DestBook.GetCompiledFormula(FormulaText, DestSheetID);

Hva hvis målet ikke har det arket, eller det navnet, ennå?

AddCopys rekompilering lykkes bare når målarbeidsboken allerede har alt formelteksten refererer til, og de to gapene som viser seg i praksis, er et likt-navngitt ark som ennå ikke er kopiert over i denne batchen, og et arbeidsbok-omfattet definert navn som aldri har eksistert i målet i det hele tatt. HotXLS kaster ikke et unntak når rekompileringen feiler halvveis gjennom en ark-kopi — cellens Value-tildeling lagrer i stedet formelteksten stille som en ren streng, en bevisst, inspiserbar feilmodus snarere enn en stille en, ettersom en formelcelle som uventet viser bokstavelig tekst som =SUM(Q1!B2:B12) i stedet for et beregnet tall, er tegnet på at noe oppstrøms i kopieringen ikke løste seg. Før den gir opp, prøver AddCopy én reparasjon: den går gjennom den mislykkede formelens syntakstre og samler hver definert-navn-ID formelen berører, og for hvert arbeidsbok-omfattet navn som finnes i kilden, men ikke ennå i målet, kopierer den navnet over og rekompilerer den samme teksten en andre gang. Ark-omfattede navn ligger utenfor det denne reparasjonen kan fikse, ettersom et navn synlig bare for formler på ett ark av kildearbeidsboken, ikke har noe tilsvarende sted å migrere inn i, og et mål som allerede eier et navn med samme stavemåte, forblir urørt i stedet for overskrevet, ut fra antakelsen om at et navn kalleren bevisst forhåndsopprettet, er det de vil skal respekteres. Inne i én enkelt arbeidsbok går et kryss-ark-formels navneoppslag fra arkomfang opp til arbeidsbok-omfang automatisk, noe som er mekanismen HotXLS' artikkel om definerte navn og kryss-ark-formler dekker; å krysse en faktisk arbeidsbokgrense fjerner det sikkerhetsnettet fullstendig, og et navn må bevisst bæres over, ellers degraderer formelen som er avhengig av det, til tekst

Diagramserie-referanser trenger den samme løsningen, men en annen kodevei

En HotXLS-diagramserie som plotter et celleområde, treffer nøyaktig det samme nummereringsproblemet som en vanlig celleformel, fordi et diagrams dataområde-referanse også er en kompilert formel-token-strøm — BIFF-spesifikasjonen kaller recorden som bærer den, BRAI ([MS-XLS] avsnitt 2.4.51) — men AddCopy kan ikke fikse det ved å gjenbruke den vanlige diagram-innlastingsveien, fordi den veien er nøyaktig det som skaper bugen. Når en diagram-record parses fra disk i den vanlige gangen av å åpne en fil, bygges formeltreet dens ved å oversette de rå bytene gjennom hvilken som helst kalkulator-instans som gjør parsingen; mat i stedet en kildediagrams rå BRAI-byte gjennom målarbeidsbokens egen vanlige record-laster, og ixti-en innebygd i de bytene løses mot målets EXTERNSHEET-tabell, slik at serien stille peker på hvilket ark som enn opptar den slotten der borte — samme klasse feil som å kopiere en celles kompilerte tre uendret, bare vanskeligere å legge merke til fordi ingen leser diagramserie-formler slik de leser celleformler. HotXLS unngår fellen med en dedikert klone-vei i stedet: TXLSCustomChart.AssignFrom kopierer hver diagram-records egne ikke-formel-header-byte ordrett, og gjenoppbygger deretter det tilknyttede området gjennom den samme dekompiler-og-rekompiler-primitiven brukt for vanlige celler, slik at det nye treet konstrueres mot målets EXTERNSHEET-tabell fra bunnen av snarere enn re-tolkes mot den i ettertid

Det samme nummereringsproblemet, én skrifttypeindeks om gangen

Ikke hvert arbeidsbok-lokalt tall inne i et diagram eller en rik-tekst-celle er en formel, og en skrifttypeindeks er den samme klassen problem i miniatyr. Rik-tekst-forløp, sammen med to flere diagram-recordtyper som bærer en overskrift- eller akse-skrifttype, lagrer en skrifttypereferanse som en rå heltallsindeks inn i den eiende arbeidsbokens egen skrifttypetabell, og den indeksen betyr ingenting i en annen arbeidsboks tabell — den kunne like gjerne peke på en helt annen skrifttype, størrelse, eller farge der borte. HotXLS løser dette etter verdi snarere enn etter tall: den slår opp de faktiske skrifttype-attributtene ved den indeksen i kildetabellen, finner eller oppretter en matchende oppføring i målets skrifttypetabell, og skriver om den lagrede indeksen til å peke på den nye slotten. Én formatsærhet gjør selve oppslaget kronglete — fil-indeksen hopper over slot 4, et nummereringsgap [MS-XLS] avsnitt 2.5.339 dokumenterer, så koden må forskyve indeksen ned med én før den sammenligner skrifttyper og opp igjen med én før den skriver resultatet

// The file-numbered font index skips slot 4 (MS-XLS section 2.5.339);
// shift into the in-memory slot, migrate the font by value if the
// destination differs, then shift back before writing the result
if Ifnt >= 5 then
  Dec(Ifnt);
if DestFonts.Key[Ifnt] <> SourceFonts.Key[Ifnt] then
  Ifnt := DestFonts.SetKey(0, SourceFonts.Key[Ifnt]);
if Ifnt >= 4 then
  Inc(Ifnt);

Hva skjer med en formel som allerede peker utenfor arbeidsboken?

En formel som strekker seg inn i en tredje arbeidsbok før man noensinne kaller AddCopy, er det ene tilfellet tekst-rundturen ikke kan bære, fordi HotXLS' egen formel-til-tekst-dekompilator bevisst ikke syntetiserer [Book]Sheet!-brakett-tekst for en ekstern referanse, og kompilatoren i den andre enden aksepterer heller ikke den syntaksen som inndata — så nettopp dette tilfellet går gjennom en andre mekanisme som aldri rører tekst i det hele tatt. Når navnemigreringsreparasjonen beskrevet ovenfor fortsatt lar en celle stå som en streng, og kildearbeidsboken har et reelt filnavn, bytter AddCopy strategi: den dyp-kopierer selve det kompilerte formeltreet snarere enn teksten dets, og overleverer deretter kopien til en dedikert rebindings-passering, RebindExternRefsInTree, som går gjennom den node for node. For hver områdereferanse den finner, løser den passeringen kildens EXTERNSHEET-oppføring tilbake til et par av arknavn, og registrerer, eller gjenbruker, en tilsvarende oppføring i målets egne eksterne-referanse-tabeller, og oppretter en helt ny ekstern-arbeidsbok-lenke hvis målet aldri har referert til den kildefilen før

Dette er der det arbeidsbok-lokale nummereringsproblemet er på sitt mest bokstavelige, fordi et eksternt-referanse-token bunter tre separate koordinater inn i ett felt, og hver eneste av dem er privat for arbeidsboken som skrev den: hvilken ekstern arbeidsbok, en slot i målets egen liste over eksterne bøker tildelt i hvilken som helst rekkefølge den arbeidsboken tilfeldigvis registrerte dem; hvilket ark inne i den eksterne arbeidsbokens egen arkliste, lagret som en 1-basert indeks omfangsbegrenset spesifikt til den eksterne boken, et helt annet nummereringsdomene enn målets egne interne ark-ID-er; og selve celleområdet, rene rad- og kolonnekoordinater som ikke trenger noen oversettelse fordi de aldri var arbeidsbok-relative i utgangspunktet. Få enten av de to første feil, og Excel åpner fortsatt filen, viser fortsatt en formel, og evaluerer den mot de gale eksterne cellene uten å klage. Én type node beseirer selv denne tre-nivå-rebindingen: en referanse til et definert navn, en indeks inn i sin egen arbeidsboks private navnetabell nøyaktig slik en arkindeks er privat for sin egen EXTERNSHEET, uten noen tilsvarende tre-nivå-reparasjon tilgjengelig — i det øyeblikket rebindings-gjennomgangen møter en navnereferanse hvor som helst i treet, forlater den hele formelen i stedet for å skrive ut en delvis korrekt en. Selv når rebindingen lykkes, viser målcellen ikke et ferskt omberegnet tall; den viser verdien kildecellen allerede holdt ved kopieringstidspunktet, holdt i en bufret slot på samme måte som Excel selv bufrer den siste-kjente verdien av enhver ekstern referanse inntil man eksplisitt oppdaterer lenker, noe som er det riktige standardvalget, ettersom å omberegne på tvers av en levende lenke inn i en annen fil er nøyaktig den typen operasjon man vil utløse én gang, bevisst, snarere enn ved hver åpning

Hva dette designet koster deg

AddCopys dekompiler-og-rekompiler-maskineri er ikke gratis, og kostnaden er verdt å planlegge rundt før man skripter en stor konsolideringsjobb, ikke etterpå. Å kopiere et ark innenfor samme arbeidsbok tar den billige veien, en rett-frem duplisering i minnet av det kompilerte treet, fordi hver indeks inne i det allerede er gyldig i arbeidsboken den blir i; en kryss-arbeidsbok-kopi betaler i stedet for en genuin parsing av hver formelcelle, dekompiler til tekst og kompiler så den teksten på nytt fra ingenting, og mens forskjellen ikke er verdt å måle på et ark med noen dusin formler, bør en kildearbeidsbok med titusenvis av formelceller, kopiert som ett ark blant dusinvis i en batch-jobb, forvente at rekompileringen dominerer kjøretiden fremfor fil-I/O-en rundt den. Kopieringsrekkefølge betyr noe av en annen grunn utover hastighet: en formel som refererer til et ark AddCopy ikke har nådd ennå i denne batchen, feiler sin rekompilering av samme grunn som en formel som refererer til et genuint ikke-eksisterende ark, gjør, så en jobb som kopierer ark B før ark A-formelen som er avhengig av det, vil se den formelen degradere nøyaktig som beskrevet ovenfor, strengtekst eller en ekstern-lenke-reserveløsning som peker rett tilbake til kildefilen den nettopp kom fra. Og fordi hver kildearbeidsbok i en konsolideringsbatch vanligvis er forfattet uavhengig, er det verdt å eksplisitt teste for den ene feilmodusen ingen enkelt kildefil noensinne kunne ha advart deg om — fem filialarbeidsbøker som hver summerer en søsterfilials tall, kan kombineres til en genuin sirkulær referanse inne i sammendragsarbeidsboken uten at noen individuell kildefil noensinne inneholdt en, en syklus som først eksisterer når hvert ark har havnet på samme sted og omberegning kjører over det kombinerte settet

Kryss-arbeidsbok-regneark-kopiering leveres som standard oppførsel av AddCopy i HotXLS Delphi Excel-komponenten for Delphi og C++Builder; produktsiden bærer den fulle regneark- og arbeidsbok-API-referansen, inkludert diagram-, rik-tekst-, og ekstern-referanse-oppførselen beskrevet her