Tekninen artikkeli

HotXLS:n työkirjojen välinen kopiointi ja kaavojen uudelleensidonta Delphissä

HotXLS:n AddCopy-metodi kopioi työarkin yhdestä Excel-työkirjasta toiseen dekompiloimalla jokaisen kyseisen arkin kaavan A1-tyyliseksi tekstiksi ja kääntämällä tekstin uudelleen kohdetyökirjan sisällä, sen sijaan että kopioisi käännetyn kaavapuun suoraan, koska kaaviosarjaviittaukset, rich text -fontti-indeksit ja ulkoisten linkkien numerointi kaikki osoitetaan itsenäisesti jokaisen työkirjatiedoston sisällä

Vikatila näkyy täsmälleen siinä työkirjassa, jota odottaisitkin: kuukauden lopun ajo, joka poimii yhden arkin jokaisen sivuliikkeen raportista ja liittää sen yhteenvetotiedostoon. Avaa tulos, ja välisumma­kaavio piirtää täysin toisen sivuliikkeen lukuja, huomautus, joka oli lihavoitu ja punainen lähteessä, on taas tavallista mustaa tekstiä, ja kaava, joka aiemmin haki verokannan kumppanihakutyökirjasta, näyttää nyt jäätyneen luvun, jota kukaan ei osaa selittää. Mikään ei nosta poikkeusta tässä — tiedosto avautuu, luvut näyttävät uskottavilta, ja vahinko istuu siellä, kunnes joku huomaa kaavion, jossa on väärä otsikko, sen vieressä

Miksei AddCopy voi vain kopioida käännettyä kaavapuuta?

AddCopy ei voi siirtää käännettyä kaavapuuta muuttumattomana, koska käännetty BIFF-kaava ei ole itsenäistä tekstiä — se on sekvenssi tunnisteita, ja useat noista tunnisteista ovat pieniä kokonaislukuja, jotka ratkeavat oikein vain sen työkirjan sisällä, joka ne tuotti. 3D-viittaus kuten Sheet2!A1:A10 ei kanna kirjaimellista nimeä Sheet2 enää sen jälkeen, kun se on käännetty; se kantaa kentän, jota BIFF-spesifikaatio kutsuu nimellä ixti (HotXLS pitää saman arvon omassa käännetyssä puussaan kenttänimellä FExternID), indeksin tuon työkirjan yksityiseen EXTERNSHEET-tauluun, numeroituna miten tahansa tuo tietty työkirja sattui rekisteröimään arkkinsa ja ulkoiset kirjansa. Siirrä tunniste muuttumattomana työkirjaan, jonka EXTERNSHEET-taulu rakennettiin eri järjestyksessä, ja indeksi 3 ei enää tarkoita Sheet2:ta — se tarkoittaa mitä tahansa arkkia, joka sattuu miehittämään paikan 3 siellä, eikä Excelillä ole tapaa merkitä virhettä, koska tiedostomuodon näkökulmasta kaava on täysin hyvin muodostettu. Tämä on juuri se epäonnistuminen, jonka välttämiseksi TXLSWorksheets.AddCopy on olemassa: kutsuttuna joko työkirjan omasta arkkikokoelmasta Delphi- tai C++Builder-koodissa, se kopioi työarkin — solujen arvot, muodot, kaavat, kaaviot, kommentit, yhdistelmät, sivuasetukset ja muuta — lähdetyökirjasta, joka saattaa olla tai olla olematta se, jota vasten sitä kutsut, ja liittää tuloksen kohteeseen nimellä, jonka valitset, tai alkuperäisen yksiselitteistetyllä kopiolla

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;

Korjaus: dekompiloi tekstiksi, käännä uudelleen kohteessa

HotXLS ratkaisee indeksointiongelman koskaan päästämättä itse käännettyä puuta ylittämään työkirjan rajaa. Jokaiselle kaavasolulle työkirjojen välisessä kopioinnissa AddCopy dekompiloi lähdekaavan samaksi A1-tyyliseksi tekstiksi, jonka käyttäjä näkisi Excelin kaavapalkissa, ja antaa sitten tuon tekstin kohdetyökirjalle, joka jäsentää sen takaisin puuksi käyttäen omia taulukoitaan alusta — arkkikohtaisesti määritelty viittaus kuten Data!D2:D100 on siinä vaiheessa vain merkkijono, ja merkkijono tarkoittaa samaa asiaa missä tahansa työkirjassa, joten jos kohteella on jo arkki nimeltä Data, viittaus ratkeaa oikein ilman minkäänlaista indeksinkäännöstä, koska liikkeellä ei koskaan ollut raakaa indeksiä käännettäväksi. HotXLS maksaa tästä edestakaisesta matkasta vain, kun sen on pakko: saman työkirjan sisällä olevan arkin kopiointi käyttää halvempaa polkua, jossa käännetty puu yksinkertaisesti kopioidaan muistissa, koska jokainen sen sisällä oleva indeksi on jo pätevä siellä, missä se pysyy, ja tekstikiertotie ajetaan vain, kun AddCopy havaitsee, että lähde ja kohde ovat aidosti eri työkirjainstansseja. Kannattaa olla täsmällinen myös siitä, mitä tämä uudelleenkirjoitus ei ole. Sillä ei ole mitään tekemistä rivi- ja sarakesiirtymän kanssa, joka ajetaan, kun lisäät tai poistat rivejä yhden arkin sisällä, mikä käsitellään yksityiskohtaisesti rinnakkaisartikkelissa — tuo moottori kirjoittaa A1-tekstin paikallaan uudelleen seuratakseen soluja, jotka siirtyivät muutaman rivin ylös- tai alaspäin yhden työkirjan sisällä, kun taas tämä ajetaan, kun kaava jättää sen kääntäneen työkirjan kokonaan, missä siirtyneet rivit eivät ole ongelma vaan työkirjakohtainen numerointi on

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

Entä jos kohteella ei vielä ole tuota arkkia tai nimeä?

AddCopy:n uudelleenkäännös onnistuu vain, kun kohdetyökirjalla on jo kaikki, mihin kaavateksti viittaa, ja kaksi aukkoa, jotka näkyvät käytännössä, ovat samanniminen arkki, jota ei ole vielä kopioitu tässä erässä, ja työkirjan laajuinen määritelty nimi, jota ei ole koskaan ollut olemassa kohteessa lainkaan. HotXLS ei nosta poikkeusta, kun uudelleenkäännös epäonnistuu kesken arkin kopioinnin — solun Value-asetus tallentaa hiljaa kaavatekstin tavallisena merkkijonona sen sijaan, tarkoituksellisen, tarkastettavan epäonnistumistilan pikemminkin kuin hiljaisen, koska kaavasolu, joka odottamatta näyttää kirjaimellista tekstiä kuten =SUM(Q1!B2:B12) lasketun luvun sijaan, on merkki siitä, että jotain aiemmin kopioinnissa ei ratkennut. Ennen luovuttamista AddCopy yrittää yhtä korjausta: se käy läpi epäonnistuneen kaavan syntaksipuun kerätäkseen jokaisen määritellyn nimen ID:n, jota kaava koskettaa, ja jokaiselle työkirjan laajuiselle nimelle, joka on olemassa lähteessä mutta ei vielä kohteessa, se kopioi nimen yli ja kääntää saman tekstin uudelleen toisen kerran. Arkin laajuiset nimet ovat sen ulkopuolella, mitä tämä korjaus voi korjata, koska nimellä, joka on näkyvä vain lähdetyökirjan yhden arkin kaavoille, ei ole vastaavaa paikkaa, johon se voisi siirtyä, ja kohde, jolla jo on nimi samalla kirjoitustavalla, jätetään koskematta ylikirjoittamisen sijaan, olettaen, että nimi, jonka kutsuja tarkoituksella ennalta loi, on se, jota he haluavat kunnioitettavan. Yhden työkirjan sisällä arkkien välisen kaavan nimihaku kulkee arkin laajuudesta työkirjan laajuuteen automaattisesti, mikä on mekanismi, jonka käsittelee HotXLS:n määriteltyjen nimien ja arkkien välisten kaavojen artikkeli; todellisen työkirjarajan ylittäminen poistaa tuon turvaverkon kokonaan, ja nimi täytyy tarkoituksella kantaa yli, tai muuten kaava, joka riippuu siitä, heikkenee tekstiksi

Kaaviosarjaviittaukset tarvitsevat saman korjauksen, mutta eri koodipolun

HotXLS-kaaviosarja, joka piirtää soluälueen, törmää täsmälleen samaan numerointiongelmaan kuin tavallinen solukaava, koska kaavion datavälin viittaus on myös käännetty kaava-tunnistevirta — BIFF-spesifikaatio kutsuu tietuetta, joka kantaa sen, BRAI:ksi ([MS-XLS] osio 2.4.51) — mutta AddCopy ei voi korjata sitä käyttämällä uudelleen tavallista kaavionlatauspolkua, koska tuo polku on juuri se, mikä luo bugin. Kun kaaviotietue jäsennetään levyltä tavallisessa tiedoston avausprosessissa, sen kaavapuu rakennetaan kääntämällä raa'at tavut minkä tahansa laskinistanssin kautta, joka sattuu tekemään jäsentämisen; syötä lähdekaavion raa'at BRAI-tavut sen sijaan kohdetyökirjan oman tavallisen tietuelataajan läpi, ja noihin tavuihin upotettu ixti ratkeaa kohteen EXTERNSHEET-taulua vasten, joten sarja osoittaa hiljaa mihin tahansa arkkiin, joka miehittää tuon paikan siellä — sama virheluokka kuin solun käännetyn puun kopioiminen muuttumattomana, vain vaikeampi huomata, koska kukaan ei lue kaaviosarjakaavoja samalla tavalla kuin he lukevat solukaavoja. HotXLS välttää ansan omistetulla klooniuspolulla sen sijaan: TXLSCustomChart.AssignFrom kopioi jokaisen kaaviotietueen omat ei-kaavaotsikkotavut sanatarkasti, ja rakentaa sitten liitetyn alueen uudelleen saman dekompiloi-ja-käännä-uudelleen-primitiivin kautta, jota käytetään tavallisille soluille, joten uusi puu rakennetaan kohteen EXTERNSHEET-taulua vasten alusta sen sijaan, että se tulkittaisiin uudelleen sitä vasten jälkikäteen

Sama numerointiongelma, yksi fontti-indeksi kerrallaan

Ei jokainen työkirjapaikallinen numero kaaviossa tai rich text -solussa ole kaava, ja fontti-indeksi on sama ongelmaluokka pienoiskoossa. Rich text -jaksot, yhdessä kahden muun kaaviotietuetyypin kanssa, jotka kantavat otsikon tai akselin fontin, tallentavat fonttiviittauksen raakana kokonaislukuindeksinä omistavan työkirjan omaan fonttitauluun, eikä tuo indeksi tarkoita mitään eri työkirjan taulussa — se voisi yhtä hyvin osoittaa täysin eri kirjasintyyppiin, kokoon tai väriin siellä. HotXLS ratkaisee tämän arvon perusteella eikä numeron perusteella: se hakee todelliset fonttiattribuutit tuosta indeksistä lähdetaulusta, löytää tai luo täsmäävän merkinnän kohteen fonttitauluun, ja kirjoittaa tallennetun indeksin uudelleen osoittamaan tuota uutta paikkaa. Yksi muotoerikoisuus tekee hausta itsestään hankalan — tiedostossa numeroitu indeksi ohittaa paikan 4, numerointiaukon, jonka [MS-XLS] osio 2.5.339 dokumentoi, joten koodin on siirrettävä indeksiä alaspäin yhdellä ennen fonttien vertailua ja takaisin ylöspäin yhdellä ennen tuloksen kirjoittamista

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

Mitä tapahtuu kaavalle, joka jo osoittaa työkirjan ulkopuolelle?

Kaava, joka ulottuu kolmanteen työkirjaan ennen kuin koskaan kutsut AddCopy-metodia, on se yksi tapaus, jota tekstin edestakainen matka ei voi kantaa, koska HotXLS:n oma kaavasta-tekstiksi-dekompiloija tarkoituksella ei synteoi [Book]Sheet!-hakasulketekstiä ulkoiselle viittaukselle, eikä toisessa päässä oleva kääntäjä hyväksy tuota syntaksia syötteenä myöskään — joten tämä yksi tapaus kulkee toisen mekanismin läpi, joka ei koskaan kosketa tekstiä lainkaan. Kun yllä kuvattu nimenmigraatiokorjaus yhä jättää solun merkkijonoksi, ja lähdetyökirjalla on todellinen tiedostonimi, AddCopy vaihtaa strategiaa: se syväkopioi itse käännetyn kaavapuun tekstin sijaan, ja antaa kopion sitten omistetulle uudelleensidontakierrokselle, RebindExternRefsInTree, joka käy sen läpi solmu kerrallaan. Jokaiselle löytämälleen aluviittaukselle tuo kierros ratkaisee lähteen EXTERNSHEET-merkinnän takaisin arkkinimipariksi, ja rekisteröi tai käyttää uudelleen vastaavaa merkintää kohteen omissa ulkoisten viittausten tauluissa, luoden aivan uuden ulkoisen työkirjalinkin, jos kohde ei ole koskaan aiemmin viitannut tuohon lähdetiedostoon

Tässä työkirjapaikallinen numerointiongelma on kirjaimellisimmillaan, koska ulkoinen viittaustunniste niputtaa kolme erillistä koordinaattia yhteen kenttään, ja jokainen niistä on yksityinen sille työkirjalle, joka sen kirjoitti: mikä ulkoinen työkirja, paikka kohteen omassa ulkoisten kirjojen listassa, osoitettuna missä tahansa järjestyksessä, jossa tuo työkirja sattui ne rekisteröimään; mikä arkki tuon ulkoisen työkirjan omassa arkkilistassa, tallennettuna 1-pohjaisena indeksinä, joka on rajattu nimenomaan tuohon ulkoiseen kirjaan, täysin eri numerointialue kuin kohteen oma sisäinen arkki-ID:t; ja itse soluälue, tavalliset rivi- ja sarakekoordinaatit, jotka eivät tarvitse käännöstä, koska ne eivät koskaan olleet työkirjasuhteellisia alun perinkään. Saa jompikumpi kahdesta ensimmäisestä väärin, ja Excel silti avaa tiedoston, näyttää silti kaavan, ja arvioi sen väärien ulkoisten solujen perusteella ilman valitusta. Yksi solmutyyppi voittaa jopa tämän puutason uudelleensidonnan: viittaus määriteltyyn nimeen, indeksi oman työkirjansa yksityiseen nimitauluun täsmälleen samalla tavalla kuin arkki-indeksi on yksityinen omalle EXTERNSHEET-taululleen, ilman vastaavaa puutason korjausta saatavilla — heti kun uudelleensidontakierros kohtaa nimiviittauksen missä tahansa puussa, se hylkää koko kaavan sen sijaan, että kirjoittaisi ulos osittain oikean. Vaikka uudelleensidonta onnistuisikin, kohdesolu ei näytä juuri uudelleenlaskettua lukua; se näyttää arvon, joka lähdesolulla jo oli kopiointihetkellä, säilytettynä välimuistipaikassa samalla tavalla kuin Excel itse välimuistittaa minkä tahansa ulkoisen viittauksen viimeksi tunnetun arvon, kunnes nimenomaisesti päivität linkit, mikä on oikea oletus, koska uudelleenlaskenta elävän linkin yli toiseen tiedostoon on juuri sellainen toiminto, jonka haluat laukaista kerran, tarkoituksella, ei jokaisella avauksella

Mitä tämä suunnittelu maksaa sinulle

AddCopy:n dekompiloi-ja-käännä-uudelleen-koneisto ei ole ilmaista, ja kustannus kannattaa suunnitella etukäteen ennen kuin skriptaat suuren konsolidointityön eikä sen jälkeen. Arkin kopiointi saman työkirjan sisällä käyttää halpaa polkua, suoraa muistinvaraista käännetyn puun kaksinkertaistamista, koska jokainen sen sisällä oleva indeksi on jo pätevä työkirjassa, johon se jää; työkirjojen välinen kopio maksaa aidon jäsennyksen jokaiselle kaavasolulle sen sijaan, dekompiloi tekstiksi ja käännä sitten tuo teksti uudelleen tyhjästä, ja vaikka ero ei ole mittaamisen arvoinen arkilla, jossa on muutama tusina kaavoja, lähdetyökirjan, jossa on kymmeniätuhansia kaavasoluja, kopioituna yhtenä arkkina kymmenien joukossa eräajossa, pitäisi odottaa uudelleenkäännöksen hallitsevan ajoaikaa eikä sitä ympäröivän tiedosto-I/O:n. Kopiointijärjestys on merkityksellinen toisesta syystä nopeuden lisäksi: kaava, joka viittaa arkkiin, jota AddCopy ei ole vielä tavoittanut tässä erässä, epäonnistuu uudelleenkäännöksessään samasta syystä kuin kaava, joka viittaa aidosti olemattomaan arkkiin, joten työ, joka kopioi arkin B ennen arkin A kaavaa, josta se riippuu, näkee tuon kaavan heikkenevän täsmälleen kuten yllä kuvattiin, merkkijonotekstinä tai ulkoisen linkin varakeinona, joka osoittaa suoraan takaisin lähdetiedostoon, josta se juuri tuli. Ja koska jokainen lähdetyökirja konsolidointierässä on yleensä laadittu itsenäisesti, kannattaa nimenomaisesti testata sitä yhtä vikatilaa, josta mikään yksittäinen lähdetiedosto ei koskaan olisi voinut varoittaa sinua — viisi sivuliiketyökirjaa, joista jokainen laskee yhteen vertaisliikkeen luvut, voivat yhdistyä aidoksi kehäviittaukseksi yhteenvetotyökirjan sisällä ilman että yksikään yksittäinen lähdetiedosto koskaan sisältää sellaista, kierre, joka on olemassa vasta, kun jokainen arkki on päätynyt samaan paikkaan ja uudelleenlaskenta ajetaan yhdistetyn joukon yli

Työkirjojen välinen työarkkien kopiointi toimitetaan vakiokäytöksenä AddCopy-metodissa HotXLS Delphi Excel -komponentissa Delphille ja C++Builderille; tuotesivulla on koko työarkki- ja työkirja-API:n viite, mukaan lukien tässä kuvatut kaavio-, rich text- ja ulkoisten viittausten käytökset