XLSX bendrinamos formulės pasekėjas neneša jokio formulės teksto. Jo <f t="shared" si="N"/> elementas rodo į pagrindinį langelį kitur lape, ir skaitytuvas turi atkurti tekstą, perkeldamas pagrindinę formulę eilutės ir stulpelio skirtumu. HotXLS Component Delphi ir C++Builder tą plėtimą atlieka atidarymo metu, todėl kiekvienas pasekėjas praneša pilną formulę
Jei kada nors įkėlėte realaus pasaulio XLSX trečiosios šalies bibliotekoje ir radote, kad tūkstančio formulių stulpelis turi tekstą lygiai viename langelyje, o tuščias eilutes likusiuose 999, jūs susitikote su šia funkcija iš neteisingos pusės. Niekas nesugadinta. Failas daro tai, ką leidžia ECMA-376, o skaitytuvas tiesiog sustojo taške, kur sustojo XML
Kodėl bendrinamos formulės langelis tuščias?
Todėl, kad formatas sąmoningai saugo formulę vieną kartą. ECMA-376 1 dalyje ir ISO/IEC 29500-1, <f> elementas (§18.3.1.40) neša t atributą, tipo ST_CellFormulaType, ir reikšmė shared reiškia, kad šis langelis dalyvauja grupėje, identifikuotoje si atributu. Lygiai vienas langelis grupėje, pagrindinis, taip pat neša ref atributą, nurodantį intervalą, kuriam grupė taikoma, ir tik tas langelis neša formulės tekstą kaip elemento turinį. Kiekvienas kitas langelis grupėje yra pasekėjas. Jis pakartoja t="shared" ir tą patį si, o jo elemento turinys yra tuščias. Excel rašo šias grupes agresyviai, nes 200 000 eilučių stulpelio užpildymas žemyn susitraukia nuo 200 000 formulių eilučių iki vienos eilutės plius 199 999 mažyčių žymeklinių elementų. Sutaupymas realus, o kaina visiškai krenta ant skaitytuvo: be plėtimo pasekėjas neturi jokios prasmės pats savaime
Perkėlimas yra vertimas, ne teksto kopija
HotXLS išsprendžia pasekėją, surasdamas pagrindinį langelį, registruotą tuo pačiu si, apskaičiuodamas eilutės ir stulpelio deltą nuo pagrindinio inkaro iki dabartinio langelio ir išversdamas kiekvieną nuorodą pagrindinėje formulėje ta delta. Santykiniai matmenys juda, absoliutūs matmenys nejuda, o mišrios nuorodos juda tik savo ne-absoliučia puse. Eilučių literalai visiškai praleidžiami, todėl formulė, kurioje atsitiktinai yra tekstas "A1", tą tekstą kiekviename pasekėjyje palieka nepakeistą
const
// xl/worksheets/sheet1.xml, trimmed to the interesting cells
SheetXml: WideString=
'<row r="1"><c r="A1"><v>1</v></c>'+
'<c r="B1"><f t="shared" si="4" ref="B1:B3">'+
'A1+$A$1+A$1+$A1+"A1"+SUM(A1:A2)</f><v>7</v></c></row>'+
'<row r="2"><c r="B2"><f t="shared" si="4"/><v>8</v></c></row>'+
'<row r="3"><c r="B3"><f t="shared" si="4"></f><v>9</v></c></row>';
var
Wb: TXLSXWorkbook;
Sh: TXLSXWorksheet;
begin
Wb:= TXLSXWorkbook.Create;
try
Wb.Open(FileName);
Sh:= Wb.Sheets[1];
// Master, verbatim
// B1 -> A1+$A$1+A$1+$A1+"A1"+SUM(A1:A2)
// Follower one row down: relative row moves, absolute row frozen,
// the mixed A$1 keeps its row, and the literal stays a literal
// B2 -> A2+$A$1+A$1+$A2+"A1"+SUM(A2:A3)
ShowMessage(Sh.Cells[2, 2].Formula);
finally
Wb.Free;
end;
end;
ref atributas yra vartai, ne dekoracija. Pasekėjas, kurio koordinatės krenta už pagrindinio langelio taikomo intervalo ribų, neplečiamas, nes failas tada teigia dalyką, kurio grupė nepalaiko. Panašiai, kai perkėlimas nustumtų nuorodą virš pirmos eilutės ar kairiau nuo A stulpelio, HotXLS tam žetonui išleidžia #REF!, o ne tyliai apkerpa, kas yra tai, ką pats Excel sukurtų tam pačiam redagavimui. Šis vertimas yra artima giminaitė, bet ne tas pats dalykas, kaip nuorodų perrašymas, vykstantis įterpiant ar ištrinant eilutes. Tas kelias turi savo taisykles, ką intervalas daro, kai redagavimas jį perkerta, ir tai aprašyta atskirai straipsnyje apie formulės nuorodos koregavimą įterpiant ir ištrinant. Bendrinamas plėtimas yra paprastesnis: tai grynas poslinkis nuo žinomo inkaro, taikomas vieną kartą, nagrinėjimo metu
Kokias nuorodų formas privalo apimti perkėlėjas?
Visas, arba plėtimas yra užmaskuota duomenų praradimo klaida. Naivus perkėlėjas, suprantantis tik A1 ir A1:B2, sugadins ar praras egzotiškesnes formas, o realios darbaknygės jų pilnos. HotXLS bendrinamos formulės vertėjas atpažįsta visą A1 šeimą, prieš nusprendžiant, ką perkelti. Išorinės darbaknygės nuorodos, tokios kaip [Book.xlsx]Sheet1!A1, ir 3D nuorodos, tokios kaip Sheet1:Sheet3!A1, išlaiko savo priešdėlį nepaliestą, kol seka langelio nuoroda persikelia. Kabutėse esantys lapų vardai išgyvena, įskaitant piktą atvejį, kai lapas pažodžiui vadinamas A1, todėl 'A1'!A1 perkelia tik dalį po šauktuko. Viso stulpelio A:A perkelia savo stulpelio matmenį ir nieko daugiau; visos eilutės 1:1 perkelia savo eilutės matmenį ir nieko daugiau; $A:$A visai nesijudina. Struktūrizuotos lentelės nuorodos, tokios kaip Table[A1], paliekamos nepaliestos, nes skliaustuose esanti dalis yra stulpelio vardas, ne koordinatė
// One master, expanded two columns to the right and zero rows down.
// Master D1: A1+A:A+$A:$A
// F1 : C1+C:C+$A:$A
//
// One master, expanded three rows down and zero columns across.
// Master A1: B1+$C$1+"A1"+A:A+1:1+'Data'!A1+LOG10(A1)+Table[A1]+'A1'!A1
// A3 : B3+$C$1+"A1"+A:A+3:3+'Data'!A3+LOG10(A3)+Table[A1]+'A1'!A3
//
// Note what did NOT move in the second line: the absolute $C$1, the
// string literal "A1", the whole column A:A under a pure row delta,
// the function name LOG10, and the structured reference Table[A1]
Funkcijų vardai čia yra tyli spąstas. Žetonų skaitytuvas, gaudantis raides, po kurių seka skaitmenys, mielai perrašytų LOG10 į LOG11 viena eilute žemiau. HotXLS reikalauja nuorodos ribos prieš kandidato žetoną ir po jo, todėl identifikatorius, tęsiantis į raidę, skaitmenį, pabraukimą, tašką ar atidarančius skliaustus, nėra langelio nuoroda. Jei dirbate su kita žymėjimo šeima, ta pati ribos problema pasireiškia kitaip, ir R1C1 žymėjimo straipsnis apima, kur šie du modeliai išsiskiria
Kodėl savaime užsidarantis f elementas praryja kitą reikšmę?
Todėl, kad savaime užsidarantis elementas nesukuria pabaigos-elemento įvykio. Tai vienintelė brangiausia klaida visoje funkcijoje, ir ji nespecifinė jokiam vienam XML nagrinėtuvui. TXMLReader viduje <f t="shared" si="4"/> sukelia lygiai vieną Element įvykį su IsEmptyElement, nustatytu True, ir niekada nesukelia atitinkamo EndElement. Nagrinėtuvas, uždarantis savo formulės-fiksavimo būseną tik EndElement metu, todėl lieka formulės viduje, o kitas tekstas, kurį jis mato, kuris yra podėlyje esantis rezultatas <v> viduje, prijungiamas prie formulės buferio. Blogiau, būsena išgyvena langelio ribą, todėl kitas langelis, turintis tikrą <f>, turi savo formulės tekstą absorbuotą ankstesnio langelio. Taisymas yra baigti formulės būseną pačiame Element įvykyje, kada tik IsEmptyElement yra True, ir ten atlikti visą pasekėjo sprendimo procesą, o ne laukiant. Tai reiškia skaityti t, si, ref, aca ir ca iš atributų, taikyti bendrinamą plėtimą, rašyti perskaičiavimo atributus ant langelio ir išvalyti bendrinamą būseną, viską šakoje, tvarkančioje tuščią elementą. Atkreipkite dėmesį, kad formatas leidžia abu rašymo būdus, <f t="shared" si="4"/> ir <f t="shared" si="4"></f>, o antrasis iš tikrųjų sukelia EndElement. Teisingas skaitytuvas turi tvarkyti porą identiškai, todėl HotXLS abu rašymo būdus apima tame pačiame regresiniame faile
Reti, nesurūšiuoti si reikšmės ir laukiančiųjų eilė
si atributas yra failo pateiktas nepasirašytas sveikasis skaičius, ne masyvo pozicija, kurią valdote. Niekas schemoje nereikalauja, kad bendrinami indeksai būtų tankūs, prasidėtų nuliu ar rodytųsi didėjančia tvarka, ir niekas nesustabdo priešiško ar tiesiog keisto failo, naudojančio si="4294967290" pirmame langelyje. Paieškos masyvo dydžio nustatymas pagal didžiausią stebėtą si todėl yra atminties išsekimo primityvas, ne optimizacija. HotXLS palaiko darbaknygės-atidarymo kelią ant surūšiuotos retos lentelės vietoj to: bendrinamos grupės registruojamos pagal savo sveikąjį raktą surūšiuotame TStringList, kas paverčia paiešką dvejetaine paieška per tiek grupių, kiek iš tikrųjų egzistuoja, be jokio ryšio su indeksų skaitine dydžiu. Tvarka yra antra problemos pusė. Pagrindinis langelis paprastai eina prieš savo pasekėjus dokumento tvarka, tačiau tai konvencija, ne taisyklė, todėl bet koks pasekėjas, negalintis išspręsti savo si nagrinėjimo momentu, eina į laukiančiųjų eilę. Kai lapas baigiasi, eilė perleidžiama prieš dabar pilną lentelę, ir vėluojantys pagrindiniai langeliai išsprendžia savo našlaičius. Langeliai, niekada neradę pagrindinio langelio, palieka tuščią formulę, kas yra sąžiningas rezultatas failui, nurodančiam grupę, kurios jis niekada neapibrėžė
Bendrinamų formulių plėtimas neįkeliant darbaknygės
Srautiniai skaitytuvai susiduria su tuo pačiu reikalavimu, esant daug griežtesniam atminties biudžetui, ir jie tai sprendžia darbalapio-lokalia lentele. TXLSDirectReader ir TXLSRowCursor abu išplečia pasekėjus į pilnas langelio formules, tuo pat metu išlaikydami savo ribotos atminties ir projekcijos elgesį, todėl vienkryptis 300 MB lapo perėjimas vis tiek paduoda jums realų formulės tekstą
var
Reader: TXLSDirectReader;
Cursor: TXLSRowCursor;
begin
// Projection: only rows 2..3, only column A. The master lives in row 1,
// outside the projection, and is still parsed so the followers resolve
Reader:= TXLSDirectReader.Create;
try
Reader.FirstRow:= 2;
Reader.LastRow:= 3;
Reader.IncludeColumn(1);
Reader.OnCell:= HandleCell; // Cell.Formula is fully expanded here
Reader.ReadFile(FileName);
finally
Reader.Free;
end;
// Forward-only row traversal, same expansion
Cursor:= TXLSRowCursor.Create;
try
Cursor.Open(FileName);
if Cursor.FindFirst then
repeat
if Cursor.CellCount > 0 then
WriteLn(Cursor.RowIndex, ': ', Cursor.Cells[0].Formula);
until not Cursor.FindNext;
finally
Cursor.Free;
end;
end;
Du apribojimai iškrenta iš to dizaino. Pirma, projekcija niekada negali praleisti pagrindinio langelio. Eilutės filtras, nustatytas FirstRow ir LastRow, arba stulpelio filtras, sukurtas IncludeColumn, gali praleisti pagrindinio langelio siuntimą jūsų atgaliniam iškvietimui, tačiau nagrinėtuvas vis tiek turi įrašyti jo si, inkaro koordinates, taikomą intervalą ir formulės tekstą, kitaip kiekvienas pasekėjas projekcijoje išsprendžiamas į nieką. Tik pasekėjo pusės darbas, perkėlimas ir reikšmės dekodavimas, saugu praleisti. Antra, lentelė yra kiekvienam darbalapiui, o jos gyvavimo trukmė turi būti aiškiai valdoma: TXLSRowCursor laiko vieną egzempliorių lapo perėjimo trukmei ir jį išvalo perkraunant, keičiant lapą, pasiekus failo pabaigą, esant išimčiai ir uždarant, todėl grupė, apibrėžta pirmame lape, niekada negali nutekėti į antrą lapą. Kadangi srautinis kelias yra karštas ciklas, jis naudoja atviro adresavimo sveikojo skaičiaus maišą, o ne surūšiuotą eilučių lentelę, kas išvengia sveikojo-į-eilutę konvertavimo kiekvienam langeliui
Kas nutinka išsaugant ir kur ribos
Kai pasekėjas jau išplėstas, jis yra paprasta formulė, o HotXLS jį įrašo atgal kaip nepriklausomą <f> elementą be t="shared" ir be si. Apvažiavimas stabilus, o podėlyje esantys <v> rezultatai išgyvena, tačiau išvestis yra didesnė nei įvestis stipriai bendrinamam lapui, o Excel sukurta grupavimas nėra atkuriamas išsaugant. Jei bendrinamų grupių baitų lygio tikslumas jums svarbesnis nei realaus formulės teksto turėjimas kiekviename langelyje, tai yra mainas, kurį priimate. XLS pusė kitokia, beje: BIFF8 SHRFMLA įrašas turi savo kodavimą ir savo rašytuvą, su bendrinamos grupės perjungikliu darbaknygėje
Du susiję dalykai aiškiai nėra bendrinamos formulės, nors dalijasi <f> elementu. Legacy CSE masyvo formulės naudoja t="array" su ref, apimančiu inkaruotą intervalą, o dinaminiai masyvai naudoja tą patį t="array" rašymo būdą, tačiau identifikuojami cm atributu, kuris grandinėja per cellMetadata į XLDAPR įrašą. Dinaminio-masyvo išliejimo langelio traktavimas kaip bendrinamas ar CSE pasekėjas yra tikra teisingumo klaida, ir atskyrimas aprašytas straipsnyje apie dinaminio masyvo ir išliejimo formules. Skaitykite tris atvejus kaip tris nagrinėtuvus, atsitiktinai dalijantis vieną žymos vardą, ir kodas lieka sąžiningas
Bendrinamos formulės plėtimas, srautiniai skaitytuvai ir nuorodų vertėjas, aprašyti čia, pristatomi kaip HotXLS Excel komponento dalis, skirta Delphi ir C++Builder; produkto puslapyje pateikiama pilna formulės ir tiesioginio skaitymo API nuoroda, įskaitant aukščiau naudotas projekcijos savybes