Tehnični članak

HotXLS: kopiranje med delovnimi zvezki in ponovno vezanje formul v Delphiju

HotXLS-ova metoda AddCopy kopira delovni list iz enega Excelovega delovnega zvezka v drugega tako, da vsako formulo na tem listu razstavi v besedilo sloga A1 in nato besedilo znova prevede v ciljnem delovnem zvezku, namesto da bi neposredno kopirala prevedeno drevo formule, saj so sklici serij grafov, indeksi pisav obogatenega besedila in številčenje zunanjih povezav v vsaki datoteki delovnega zvezka dodeljeni neodvisno

Napaka se pokaže natanko v delovnem zvezku, ki bi ga pričakovali: mesečno opravilo iz poročila vsake podružnice vzame en list in ga doda v zbirno datoteko. Odprite rezultat in graf vmesnih vsot prikazuje številke povsem druge podružnice, opomba, ki je bila v izvoru krepka in rdeča, se vrne v navadno črno besedilo, formula, ki je nekoč pridobivala davčno stopnjo iz spremljevalnega iskalnega delovnega zvezka, pa zdaj kaže zamrznjeno številko, ki je nihče ne zna pojasniti. Tu se ne sproži nobena izjema — datoteka se odpre, številke so videti verjetne, škoda pa ostane skrita, dokler kdo ne opazi grafa z napačnim naslovom poleg njega

Zakaj AddCopy ne more preprosto kopirati prevedenega drevesa formule?

AddCopy ne more nespremenjenega prenesti prevedenega drevesa formule, ker prevedena formula BIFF ni samostojno besedilo — je zaporedje žetonov, nekateri od teh žetonov pa so majhna cela števila, ki se pravilno razrešijo samo znotraj delovnega zvezka, ki jih je ustvaril. 3D-sklic, kot je Sheet2!A1:A10, po prevajanju ne vsebuje dobesednega imena Sheet2; vsebuje polje, ki ga specifikacija BIFF imenuje ixti (HotXLS hrani isto vrednost v lastnem prevedenem drevesu pod imenom polja FExternID), to je indeks v zasebno tabelo EXTERNSHEET tega delovnega zvezka, oštevilčeno glede na to, kako je ta delovni zvezek registriral svoje liste in zunanje knjige. Če žeton nespremenjen premaknete v delovni zvezek, katerega tabela EXTERNSHEET je bila zgrajena v drugačnem vrstnem redu, indeks 3 ne pomeni več Sheet2 — pomeni list, ki je tam zasedel mesto 3, Excel pa napake ne more označiti, ker je formula z vidika zapisa datoteke povsem pravilna. Prav tej napaki se želi izogniti TXLSWorksheets.AddCopy: v kodi Delphi ali C++Builder, poklicani iz zbirke listov katerega koli delovnega zvezka, kopira delovni list — vrednosti celic, oblikovanja, formule, grafe, pripombe, spojitve, nastavitev strani in še več — iz izvornega delovnega zvezka, ki je lahko ali pa ni tisti, na katerem jo kličete, ter rezultat doda v cilj pod imenom, ki ga izberete, ali pod razločeno kopijo izvirnika

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;

Popravek: razstavi v besedilo in znova prevedi v cilju

HotXLS težavo z indeksi rešuje tako, da prevedenemu drevesu nikoli ne dovoli prestopiti meje med delovnima zvezkoma. Pri vsaki celici s formulo v kopiji med delovnima zvezkoma AddCopy izvorno formulo razstavi v isto besedilo sloga A1, kot bi ga uporabnik videl v Excelovi vrstici za formule, nato pa besedilo preda ciljnemu delovnemu zvezku, ki ga od začetka razčleni nazaj v drevo z uporabo svojih tabel — sklic, kvalificiran z listom, kot je Data!D2:D100, je v tistem trenutku samo niz in niz pomeni isto v katerem koli delovnem zvezku, zato se sklic pravilno razreši brez prevajanja indeksov, če cilj že ima list z imenom Data, saj v postopku nikoli ni bilo surovega indeksa, ki bi ga bilo treba prevesti. HotXLS ta povratni korak izvede samo, kadar je potreben: kopiranje lista znotraj istega delovnega zvezka gre po cenejši poti, pri kateri se prevedeno drevo preprosto podvoji v pomnilniku, saj je vsak indeks v njem že veljaven tam, kjer ostane, besedilni obvoz pa se izvede šele, ko AddCopy zazna, da sta izvorni in ciljni delovni zvezek res različna primerka. Natančno je treba povedati tudi, kaj ta ponovni zapis ni. Nima nič skupnega s premikanjem vrstic in stolpcev, ki se izvede pri vstavljanju ali brisanju vrstic znotraj enega lista, kar podrobno obravnava spremljevalni članek — ta mehanizem sproti prepisuje besedilo A1, da sledi celicam, ki so se premaknile za nekaj vrstic navzgor ali navzdol znotraj enega delovnega zvezka, ta mehanizem pa se sproži, ko formula povsem zapusti delovni zvezek, ki jo je prevedel, kjer problem niso premaknjene vrstice, temveč zasebno številčenje delovnega zvezka

// 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);

Kaj, če cilj še nima tega lista ali tega imena?

AddCopyjevo ponovno prevajanje uspe samo, če ciljni delovni zvezek že vsebuje vse, na kar se besedilo formule sklicuje, v praksi pa se pokažeta dve vrzeli: list z enakim imenom, ki v tem paketu še ni bil kopiran, in ime, določeno na ravni delovnega zvezka, ki v cilju še nikoli ni obstajalo. HotXLS ne sproži izjeme, ko ponovno prevajanje sredi kopiranja lista ne uspe — dodelitev celice Value namesto tega tiho shrani besedilo formule kot navaden niz, kar je namerno in preverljivo stanje napake, saj je celica s formulo, ki nepričakovano prikazuje dobesedno besedilo, kot je =SUM(Q1!B2:B12), namesto izračunane številke znak, da se nekaj predhodno v kopiji ni razrešilo. Preden odneha, AddCopy poskusi eno popravilo: prehodi sintaktično drevo neuspele formule, zbere vsak ID določenega imena, ki se ga formula dotika, in za vsako ime na ravni delovnega zvezka, ki obstaja v izvoru, ne pa še v cilju, kopira ime ter isto besedilo drugič prevede. Imena na ravni lista so zunaj dosega tega popravila, saj ime, vidno samo formulam na enem listu izvornega delovnega zvezka, nima ustreznega mesta, kamor bi ga bilo mogoče prenesti, ciljni delovni zvezek, ki že ima ime z enakim črkovanjem, pa ostane nespremenjen, ker je domneva, da želi klicatelj uporabiti ime, ki ga je namerno vnaprej ustvaril. Znotraj enega delovnega zvezka iskanje imena pri formuli med listi samodejno preide iz obsega lista v obseg delovnega zvezka, kar obravnava članek HotXLS o določenih imenih in formulah med listi; prehod čez dejansko mejo delovnih zvezkov to varnostno mrežo povsem odstrani in ime je treba namerno prenesti, sicer se formula, ki je od njega odvisna, spremeni v besedilo

Sklici serij grafov potrebujejo isti popravek, vendar drugo kodno pot

Serija grafa HotXLS, ki izriše obseg celic, zadene natanko isto težavo s številčenjem kot navadna formula v celici, saj je sklic na podatkovni obseg grafa prav tako preveden tok žetonov formule — specifikacija BIFF zapis, ki ga nosi, imenuje BRAI ([MS-XLS] razdelek 2.4.51) — vendar AddCopy tega ne more popraviti z vnovično uporabo običajne poti za nalaganje grafov, saj prav ta pot ustvari napako. Ko se zapis grafa pri običajnem odpiranju datoteke razčleni z diska, se njegovo drevo formule zgradi s prevajanjem surovih bajtov skozi tisti primerek kalkulatorja, ki razčlenjuje podatke; če surove bajte BRAI izvornega grafa namesto tega pošljete skozi običajni nalagalnik zapisov ciljnega delovnega zvezka, se ixti, vgrajen v te bajte, razreši glede na ciljno tabelo EXTERNSHEET, zato serija tiho kaže na list, ki tam zaseda to mesto — isti razred napake kot pri nespremenjenem kopiranju prevedenega drevesa celice, le težje opazen, ker nihče ne bere formul serij grafov tako kot formul celic. HotXLS pasti uide z namensko potjo kloniranja: TXLSCustomChart.AssignFrom dobesedno kopira lastne nebajtne glave vsakega zapisa grafa, nato pa pripeti obseg znova zgradi z istim primitivom razstavi-in-znova-prevedi, ki se uporablja za navadne celice, zato je novo drevo zgrajeno glede na ciljno tabelo EXTERNSHEET od začetka, ne pa naknadno raztolmačeno glede nanjo

Ista težava s številčenjem, po en indeks pisave naenkrat

Ni vsako število, lokalno za delovni zvezek, znotraj grafa ali celice z obogatenim besedilom formula, indeks pisave pa je v malem isti razred težave. Izseki obogatenega besedila in še dve vrsti zapisov grafov, ki nosita pisavo napisa ali osi, hranijo sklic na pisavo kot surov celoštevilski indeks v lastni tabeli pisav delovnega zvezka, ta indeks pa v tabeli drugega delovnega zvezka ne pomeni nič — prav tako bi lahko kazal na povsem drugačno pisavo, velikost ali barvo. HotXLS to rešuje po vrednosti in ne po številu: poišče dejanske lastnosti pisave pri tem indeksu v izvorni tabeli, v ciljni tabeli pisav poišče ali ustvari ujemajoč se vnos in shranjeni indeks prepiše tako, da kaže na novo mesto. Ena posebnost zapisa naredi samo iskanje zapleteno — indeks v datoteki preskoči mesto 4, vrzel v številčenju, dokumentirana v razdelku 2.5.339 specifikacije [MS-XLS], zato mora koda pred primerjavo pisav indeks zmanjšati za ena in ga pred zapisom rezultata znova povečati za ena

// 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);

Kaj se zgodi s formulo, ki že kaže zunaj delovnega zvezka?

Formula, ki seže v tretji delovni zvezek, še preden pokličete AddCopy, je primer, ki ga besedilni povratni korak ne more prenesti, ker HotXLS-ov lastni razstavljalnik formule v besedilo namerno ne ustvari oglatih sklicev [Book]Sheet! za zunanji sklic, prevajalnik na drugi strani pa te sintakse prav tako ne sprejme kot vhod — zato ta primer uporablja drugi mehanizem, ki se besedila sploh ne dotakne. Ko popravilo prenosa imen, opisano zgoraj, celico še vedno pusti kot niz in ima izvorni delovni zvezek pravo ime datoteke, AddCopy zamenja strategijo: globoko kopira prevedeno drevo formule namesto besedila in nato kopijo preda namenski poti za ponovno vezanje, RebindExternRefsInTree, ki jo prehodi vozlišče za vozliščem. Za vsak najdeni sklic na obseg ta pot izvorni vnos EXTERNSHEET razreši nazaj v par imen listov ter v lastne tabele zunanjih sklicev cilja registrira ali ponovno uporabi ustrezen vnos in ustvari povsem novo povezavo do zunanjega delovnega zvezka, če se cilj na to izvorno datoteko še nikoli ni skliceval

Tu je težava z lokalnim številčenjem delovnega zvezka najbolj dobesedna, saj zunanji sklicni žeton združi tri ločene koordinate v eno polje, vsaka od njih pa je zasebna za delovni zvezek, ki ga je zapisal: kateri zunanji delovni zvezek, mesto na lastnem seznamu zunanjih knjig cilja, dodeljeno v vrstnem redu, v katerem jih je ta delovni zvezek registriral; kateri list znotraj lastnega seznama listov zunanjega delovnega zvezka, shranjen kot indeks, ki se začne z 1 in velja samo za zunanjo knjigo, kar je povsem druga domena številčenja kot notranji ID-ji listov cilja; in sam obseg celic, navadne koordinate vrstic in stolpcev, ki jih ni treba prevajati, ker niso bile nikoli relativne na delovni zvezek. Če je kateri koli od prvih dveh napačen, Excel še vedno odpre datoteko, še vedno prikaže formulo in jo brez pripombe ovrednoti glede na napačne zunanje celice. Ena vrsta vozlišča premaga celo to ponovno vezanje na ravni drevesa: sklic na določeno ime, indeks v zasebno tabelo imen lastnega delovnega zvezka na enak način, kot je indeks lista zaseben za lasten EXTERNSHEET, pri čemer popravek na ravni drevesa ni na voljo — ko sprehod za ponovno vezanje kjer koli v drevesu naleti na sklic na ime, opusti celotno formulo, namesto da bi zapisal delno pravilno različico. Tudi ko je ponovno vezanje uspešno, ciljna celica ne prikaže sveže preračunane številke; prikaže vrednost, ki jo je imela izvorna celica ob kopiranju, shranjeno v predpomnjenem mestu, tako kot Excel sam predpomni zadnjo znano vrednost katerega koli zunanjega sklica, dokler povezav izrecno ne osvežite, kar je pravilna privzeta nastavitev, saj je preračun prek žive povezave v drugo datoteko prav tista operacija, ki jo želite sprožiti enkrat, namerno, ne ob vsakem odpiranju

Kakšen je strošek te zasnove

Mehanizem AddCopy za razstavljanje in ponovno prevajanje ni brezplačen, zato je stroške vredno načrtovati, preden napišete veliko opravilo za združevanje, ne šele potem. Kopiranje lista znotraj istega delovnega zvezka gre po poceni poti, z neposrednim podvajanjem prevedenega drevesa v pomnilniku, ker je vsak indeks v njem že veljaven v delovnem zvezku, kjer ostane; kopija med delovnima zvezkoma pa plača pravo razčlenjevanje za vsako celico s formulo, najprej razstavljanje v besedilo in nato ponovno prevajanje tega besedila od začetka, in če razlike ni vredno meriti na listu z nekaj deset formulami, mora izvorni delovni zvezek z več deset tisoč celicami s formulami, kopiranimi kot en list med desetinami drugih v paketnem opravilu, pričakovati, da bo ponovno prevajanje prevladalo nad časom izvajanja, ne pa vhodno-izhodne operacije okoli njega. Vrstni red kopiranja je pomemben še iz drugega razloga poleg hitrosti: formula, ki se sklicuje na list, do katerega AddCopy v tem paketu še ni prišel, pri ponovnem prevajanju odpove iz istega razloga kot formula, ki se sklicuje na res neobstoječ list, zato bo opravilo, ki kopira list B pred formulo na listu A, od katerega je ta formula odvisna, videlo, da se formula poslabša natanko tako, kot je opisano zgoraj, v besedilo niza ali nadomestno zunanjo povezavo, ki kaže naravnost nazaj v izvorno datoteko, iz katere je pravkar prišla. Ker je vsak izvorni delovni zvezek v paketu združevanja navadno ustvarjen neodvisno, se splača izrecno preveriti še način napake, na katerega vas nobena posamezna izvorna datoteka ne bi mogla opozoriti — pet delovnih zvezkov podružnic, ki vsak sešteva številke druge podružnice, se lahko združi v pravo krožno sklicevanje znotraj zbirnega delovnega zvezka, ne da bi ga katera koli posamezna izvorna datoteka vsebovala, saj cikel nastane šele, ko vsi listi pristanejo na istem mestu in se preračun izvede nad združenim nizom

Kopiranje delovnih listov med delovnima zvezkoma je standardno vedenje metode AddCopy v Excelovi komponenti HotXLS za Delphi za Delphi in C++Builder; stran izdelka vsebuje celoten API za delovne liste in delovne zvezke, vključno z opisanimi zmožnostmi grafov, obogatenega besedila in zunanjih sklicev