HotXLS' AddCopy-metode kopierer et regneark fra én Excel-projektmappe til en anden ved at dekompilere hver formel på det ark til A1-stil-tekst og rekompilere teksten inde i destinationsprojektmappen, frem for at kopiere det kompilerede formeltræ direkte, fordi diagramserie-referencer, rig-tekst-skrifttype-indekser og eksternt-link-nummerering alle er tildelt uafhængigt inde i hver projektmappefil
Fejlen viser sig i præcis den projektmappe, man ville forvente: et månedsafslutnings-job, der trækker ét ark ud af hver filials rapport og tilføjer det til en oversigtsfil. Åbn resultatet, og et subtotal-diagram plotter en helt anden filials tal, en note der var fed og rød i kilden, er tilbage til almindelig sort tekst, og en formel der engang trak en skattesats fra en ledsagende opslags-projektmappe, viser nu et frosset tal, ingen kan forklare. Intet kaster en undtagelse her — filen åbner, tallene ser plausible ud, og skaden sidder der, indtil nogen bemærker et diagram med den forkerte titel ved siden af det
Hvorfor kan AddCopy ikke bare kopiere det kompilerede formeltræ?
AddCopy kan ikke flytte det kompilerede formeltræ uændret, fordi en kompileret BIFF-formel ikke er fritstående tekst — det er en sekvens af tokens, og flere af de tokens er små heltal, der kun løses korrekt inde i den projektmappe, der producerede dem. En 3D-reference såsom Sheet2!A1:A10 bærer ikke det bogstavelige navn Sheet2, når den først er kompileret; den bærer et felt, BIFF-specifikationen kalder ixti (HotXLS beholder den samme værdi i sit eget kompilerede træ under feltnavnet FExternID), et indeks ind i den projektmappes private EXTERNSHEET-tabel, nummereret uanset hvordan netop den projektmappe tilfældigvis registrerede sine ark. Flyt tokenet uændret ind i en projektmappe, hvis EXTERNSHEET-tabel blev bygget i en anden rækkefølge, og indeks 3 betyder ikke længere Sheet2 — det betyder, hvilket ark der tilfældigvis optager plads 3 derovre, og Excel har ingen måde at flage fejlen, fordi formlen, hvad angår filformatet, er perfekt velformet. Det er præcis den fejl, TXLSWorksheets.AddCopy findes for at undgå: kaldt fra enten projektmappes egen ark-samling i Delphi- eller C++Builder-kode kopierer den et regneark — celleværdier, formater, formler, diagrammer, kommentarer, sammenlægninger, sideopsætning og mere — fra en kilde-projektmappe, der måske eller måske ikke er den, man kalder den på, og tilføjer resultatet til destinationen under et navn, man vælger, eller en disambigueret kopi af det oprindelige
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 destinationen
HotXLS løser indekseringsproblemet ved aldrig at lade selve det kompilerede træ krydse projektmappe-grænsen. For hver formelcelle på en kopiering på tværs af projektmapper dekompilerer AddCopy kildeformlen til den samme A1-stil-tekst, en bruger ville se i Excels formellinje, og overdrager derefter den tekst til destinationsprojektmappen, som parser den tilbage til et træ ved brug af sine egne tabeller fra bunden — en ark-kvalificeret reference såsom Data!D2:D100 er bare en streng på det tidspunkt, og en streng betyder det samme i enhver projektmappe, så hvis destinationen allerede har et ark ved navn Data, løses referencen korrekt uden nogen indeksoversættelse overhovedet, fordi der aldrig var et rå indeks i flugt at oversætte. HotXLS betaler kun for den tur-retur, når den er nødt til: at kopiere et ark inden for den samme projektmappe tager en billigere vej, hvor det kompilerede træ simpelthen duplikeres i hukommelsen, da hvert indeks inden i det allerede er gyldigt der, hvor det bliver, og tekstomvejen kører kun, når AddCopy opdager, at kilden og destinationen genuint er forskellige projektmappe-instanser. Det er også værd at være præcis om, hvad denne omskrivning ikke er. Den har intet at gøre med den række- og kolonneforskydning, der kører, når man indsætter eller sletter rækker inde i et enkelt ark, hvilket en følgeartikel dækker i detaljer — den motor omskriver A1-tekst på plads for at spore celler, der flyttede sig nogle få rækker op eller ned inden for én projektmappe, mens denne kører, når en formel forlader den projektmappe, der kompilerede den, helt igennem, hvor flyttede rækker ikke er problemet, og projektmappe-privat nummerering er
// 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);
Hvad hvis destinationen ikke har det ark, eller det navn, endnu?
AddCopy's rekompilering lykkes kun, når destinationsprojektmappen allerede har alt, formelteksten refererer til, og de to huller der viser sig i praksis er et samme-navngivet ark, der ikke er kopieret over endnu i denne batch, og et projektmappe-afgrænset defineret navn, der aldrig har eksisteret i destinationen overhovedet. HotXLS kaster ikke en undtagelse, når rekompilering fejler halvvejs gennem en ark-kopi — cellens Value-tildeling gemmer i stilhed formelteksten som en almindelig streng i stedet, en bevidst, inspicerbar fejltilstand frem for en tavs, da en formelcelle der uventet viser bogstavelig tekst som =SUM(Q1!B2:B12) i stedet for et beregnet tal, er tegnet på, at noget opstrøms i kopieringen ikke blev løst. Før den giver op, forsøger AddCopy én reparation: den gennemgår den mislykkede formels syntakstræ og indsamler hvert defineret-navn-ID formlen rører, og for hvert projektmappe-afgrænset navn, der findes i kilden, men ikke endnu i destinationen, kopierer den navnet over og rekompilerer den samme tekst en anden gang. Ark-afgrænsede navne sidder uden for, hvad denne reparation kan fikse, da et navn kun synligt for formler på ét ark af kildeprojektmappen ikke har nogen tilsvarende plads at migrere ind i, og en destination der allerede ejer et navn med samme stavning, efterlades urørt frem for overskrevet, ud fra antagelsen om, at et navn kalderen bevidst forudoprettede, er det, de vil have respekteret. Inde i en enkelt projektmappe gennemgår et opslag på tværs af ark-formlers navn fra ark-omfang op til projektmappe-omfang automatisk, hvilket er mekanismen HotXLS' artikel om definerede navne og formler på tværs af ark dækker; at krydse en faktisk projektmappe-grænse fjerner det sikkerhedsnet helt, og et navn skal bevidst bæres over, ellers degraderer formlen der afhænger af det, til tekst
Diagramserie-referencer har brug for den samme fix, men en anden kodevej
En HotXLS-diagramserie, der plotter et celleinterval, rammer præcis det samme nummereringsproblem som en almindelig cellformel, fordi en diagram-datainterval-reference også er en kompileret formel-token-strøm — BIFF-specifikationen kalder posten, der bærer den, BRAI ([MS-XLS] afsnit 2.4.51) — men AddCopy kan ikke fikse det ved at genbruge den normale diagram-indlæsningsvej, fordi den vej er præcis det, der skaber bugen. Når en diagrampost parses fra disk i den almindelige gang af at åbne en fil, bygges dens formeltræ ved at oversætte de rå bytes gennem hvilken som helst calculator-instans, der udfører parsingen; fodr en kilde-diagrams rå BRAI-bytes gennem destinationsprojektmappens egen almindelige post-loader i stedet, og ixti'en indlejret i de bytes bliver løst mod destinationens EXTERNSHEET-tabel, så serien peger i stilhed på hvilket som helst ark, der optager den plads derovre — samme klasse fejl som at kopiere en celles kompilerede træ uændret, bare sværere at bemærke, fordi ingen læser diagramserie-formler på den måde, de læser cellformler. HotXLS undgår fælden med en dedikeret klone-vej i stedet: TXLSCustomChart.AssignFrom kopierer hver diagrampost's egne ikke-formel-header-bytes ordret, og genopbygger derefter det tilknyttede interval gennem den samme dekompiler-og-rekompiler-primitiv brugt til almindelige celler, så det nye træ konstrueres mod destinationens EXTERNSHEET-tabel fra bunden frem for genfortolket mod den efterfølgende
Det samme nummereringsproblem, ét skrifttype-indeks ad gangen
Ikke hvert projektmappe-lokalt tal inde i et diagram eller en rig-tekst-celle er en formel, og et skrifttype-indeks er den samme klasse problem i miniature. Rig-tekst-forløb, sammen med to flere diagrampost-typer, der bærer en billedtekst eller akse-skrifttype, gemmer en skrifttype-reference som et rå heltal-indeks ind i den ejende projektmappes egen skrifttype-tabel, og det indeks betyder ingenting i en anden projektmappes tabel — det kunne lige så let pege på en helt anden skrifttype, størrelse eller farve derovre. HotXLS løser dette efter værdi frem for efter tal: den slår de faktiske skrifttype-attributter op ved det indeks i kilde-tabellen, finder eller opretter en matchende post i destinationens skrifttype-tabel, og omskriver det gemte indeks til at pege på den nye plads. Én format-særhed gør selve opslaget besværligt — det fil-nummererede indeks springer plads 4 over, et nummereringshul [MS-XLS] afsnit 2.5.339 dokumenterer, så koden skal skifte indekset ned med én, før den sammenligner skrifttyper, og op med én igen, 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);
Hvad sker der med en formel, der allerede peger uden for projektmappen?
En formel, der rækker ind i en tredje projektmappe, før man nogensinde kalder AddCopy, er det ene tilfælde tekst-tur-returen ikke kan bære, fordi HotXLS' egen formel-til-tekst-dekompiler bevidst ikke syntetiserer [Book]Sheet!-parentestekst til en ekstern reference, og kompileren i den anden ende accepterer heller ikke den syntaks som input — så dette ene tilfælde kører gennem en anden mekanisme, der aldrig rører tekst overhovedet. Når navne-migrations-reparationen beskrevet ovenfor stadig efterlader en celle som en streng, og kildeprojektmappen har et rigtigt filnavn, skifter AddCopy strategi: den dybde-kopierer selve det kompilerede formeltræ frem for dets tekst, og overdrager derefter kopien til en dedikeret genbindings-gennemgang, RebindExternRefsInTree, der gennemgår den node for node. For hver interval-reference den finder, løser den gennemgang kildens EXTERNSHEET-post tilbage til et par ark-navne, og registrerer, eller genbruger, en tilsvarende post i destinationens egne eksterne-reference-tabeller, og opretter et helt nyt eksternt-projektmappe-link, hvis destinationen aldrig har refereret den kildefil før
Dette er, hvor projektmappe-lokal-nummerering-problemet er mest bogstaveligt, fordi en ekstern-reference-token bundter tre separate koordinater ind i ét felt, og hver af dem er privat til den projektmappe, der skrev den: hvilken ekstern projektmappe, en plads i destinationens egen liste over eksterne bøger tildelt i hvilken rækkefølge den projektmappe tilfældigvis registrerede dem; hvilket ark inde i den eksterne projektmappes egen ark-liste, gemt som et 1-baseret indeks afgrænset til netop den eksterne bog, et helt andet nummereringsdomæne end destinationens egne interne ark-ID'er; og selve celle-intervallet, almindelige række- og kolonnekoordinater der ikke har brug for oversættelse, fordi de aldrig var projektmappe-relative i første omgang. Får man en af de første to forkert, åbner Excel stadig filen, viser stadig en formel og evaluerer den mod de forkerte eksterne celler uden protest. Én slags node besejrer selv denne træ-niveau-genbinding: en reference til et defineret navn, et indeks ind i sin egen projektmappes private navne-tabel præcis som et ark-indeks er privat til sin egen EXTERNSHEET, uden nogen tilsvarende træ-niveau-reparation tilgængelig — i det øjeblik genbindings-gennemgangen møder en navne-reference noget sted i træet, opgiver den hele formlen frem for at skrive en delvist korrekt en ud. Selv når genbindingen lykkes, viser destinationscellen ikke et frisk genberegnet tal; den viser den værdi, kildecellen allerede holdt på kopieringstidspunktet, holdt i en cachet plads på samme måde Excel selv cacher den sidst-kendte værdi af enhver ekstern reference, indtil man eksplicit opdaterer links, hvilket er den rigtige standard, da genberegning på tværs af et levende link ind i en anden fil er præcis den slags operation, man vil udløse én gang, bevidst, frem for ved hver åbning
Hvad dette design koster dig
AddCopy's dekompiler-og-rekompiler-maskineri er ikke gratis, og omkostningen er værd at planlægge omkring, før man scripter et stort konsolideringsjob frem for bagefter. At kopiere et ark inden for den samme projektmappe tager den billige vej, en ren duplikering i hukommelsen af det kompilerede træ, fordi hvert indeks inden i det allerede er gyldigt i den projektmappe, det bliver i; en kopiering på tværs af projektmapper betaler i stedet for en ægte parsing på hver formelcelle, dekompiler til tekst og kompiler så den tekst igen fra intet, og mens forskellen ikke er værd at måle på et ark med et par dusin formler, bør en kildeprojektmappe med titusinder af formelceller, kopieret som ét ark blandt dusinvis i et batch-job, forvente, at rekompileringen dominerer kørselstiden frem for fil-I/O'en omkring den. Kopieringsrækkefølge betyder noget af en anden grund end hastighed: en formel, der refererer et ark AddCopy ikke har nået endnu i denne batch, fejler sin rekompilering af samme grund som en formel, der refererer et genuint ikke-eksisterende ark, gør, så et job der kopierer ark B før ark A-formlen, der afhænger af det, vil se den formel degradere præcis som beskrevet ovenfor, streng-tekst eller en ekstern-link-fallback der peger lige tilbage på kildefilen, den lige kom fra. Og fordi hver kilde-projektmappe i en konsolideringsbatch sædvanligvis er forfattet uafhængigt, er det værd eksplicit at teste for den ene fejltilstand, ingen enkelt kildefil nogensinde kunne have advaret om — fem filial-projektmapper, der hver totaliserer en peer-filials tal, kan kombinere til en genuin cirkulær reference inde i oversigtsprojektmappen uden at nogen individuel kildefil nogensinde indeholdt én, en cyklus der kun findes, når hvert ark er landet samme sted, og genberegning kører over det kombinerede sæt
Kopiering af regneark på tværs af projektmapper leveres som standardopførsel af AddCopy i HotXLS Delphi Excel-komponenten til Delphi og C++Builder; produktsiden bærer den fulde regneark- og projektmappe-API-reference, inklusive den diagram-, rig-tekst- og eksterne-reference-opførsel beskrevet her