HotXLS dekodira XLUnicodeString BIFF8 tako, da najprej prebere cch in zastavico fHigh, nato pa izbere bralnik, ki ustreza kodiranju: TXLSBlob.GetWideString s številom bajtov cch * 2, ko je fHigh 1, in TXLSBlob.GetString, ko je fHigh 0. Zamenjajte ju in zapis vrne prazen niz ali niz polovične dolžine, nikoli pa izjeme
Prav zaradi tega je ta razred napak drag. Grafikon se odpre, serije se pravilno izrišejo, osi so prave, en napis trendne črte pa je preprosto prazen. V dnevniku ni ničesar, v obravnavalniku izjem ni ničesar, pogovornega okna o pokvarjeni datoteki ni. Datoteka je bila ves čas v redu; bralnik je zahteval napačno število bajtov in dobil natanko to, kar je zahteval
Zakaj se niz BIFF8 vrne prazen?
Niz BIFF8 se vrne prazen, ker je varovalo dolžine tovor zavrnilo, še preden je prišlo do branja, ali ker se je bralnik ustavil pri prvem NUL-u, ki ga je našel. Obe poti sta po konstrukciji tihi. V HotXLS je varovalo običajno izrecno preverjanje DataLength v obravnavalniku zapisa in izračunati ga je treba za vsako kodiranje: 16-bitni tovor za telo SXViewLink potrebuje 8 + cch * 2 bajtov, 8-bitni tovor pa samo 8 + cch. Uporabite aritmetiko širokih znakov na 8-bitnem zapisu in vsako kratko ime pade skozi vrata. Vedenje NUL je druga past, saj TXLSBlob.GetString in TXLSBlob.GetWideString oba preiščeta dekodirani rezultat za zaključkom in ga tam odrežeta, pri čemer vrneta prazen niz, ko zaključek pristane na položaju ena. Preberite 16-bitno telo s polovico števila bajtov in ohranite prvih cch div 2 znakov; preberite 8-bitno telo skozi široki bralnik in pari bajtov tvorijo poljubne kodne točke. Glasno je samo predolgo branje: TXLSBlob.EnsureReadable sproži Blob read exceeds data size, ko zahteva sega čez blob. Prekratko branje nima takega alarma
GetWideString šteje bajte, ne znakov
TXLSBlob.GetWideString(Index, Count) sprejme Count v bajtih. Znotraj izvede SetString nad PWideChar z Count div SizeOf(WideChar), zato posredovanje števila znakov niz tiho razpolovi. Postavitve zapisov BIFF8 medtem dolžino niza izražajo v znakih. Vsako 16-bitno mesto klica mora zato samo nositi pretvorbo * 2, vsako 8-bitno mesto klica pa je ne sme nositi. To je ista meja kodiranja, ki se pokaže, ko besedilo znova zapisujete namesto berete, zato je vredno članek prebrati skupaj z izvozom preglednic, varnim za Unicode, v Delphiju, če vaš cevovod premika nize v obe smeri
// 16-bitni XLUnicodeStringNoCch: cch znakov, cch * 2 bajtov
Name := Data.GetWideString(Start, cch * 2); // pravilno
Name := Data.GetWideString(Start, cch); // polovica besedila, brez napake
// 8-bitni XLUnicodeStringNoCch: cch znakov, cch bajtov
Name := WideString(Data.GetString(Start, cch)); // pravilno
Name := Data.GetWideStringWithZero(Start, cch); // še vedno široki bralnik
Konvencija velja povsod, kjer se po toku bajtov hodi ročno. Ko HotXLS dolg zapis String ($0207, [MS-XLS] 2.4.268) iz njegovih zapisov Continue ($003C) sestavi nazaj, široka veja izračuna segCh iz dolžine segmenta in nato pokliče GetWideString(3, segCh * 2), ker se telo zapisa začne na odmiku 3, števec pa je še vedno v bajtih. Bralnik obogatenega besedila naredi isto na odmiku 1 v prvem segmentu Continue. [MS-XLS] 2.5.293 zagotavlja, da meja pri fHighByte 1 pade na mejo dvobajtnega znaka, zato vodenje delnih znakov ni potrebno, vendar je bajtna aritmetika še vedno vaša odgovornost
Kaj dejansko počne GetWideStringWithZero?
TXLSBlob.GetWideStringWithZero je širokoznakovni bralnik, ki ohrani vdelane NUL-e. Pripona WithZero označuje ohranjanje NUL-a, ne širine znaka: znotraj izvede isti SetString nad PWideChar z Count div SizeOf(WideChar) kot GetWideString, samo brez iskanja zaključka. Enobajtni dvojnik je TXLSBlob.GetStringWithZero, ki vrne AnsiString. Iz imena ni razvidno, kateri je kateri, in ta dvoumnost je tej kodni zbirki povzročila resnične napake. Napačno branje je vredno poimenovati, ker je videti tako verjetno: GetWideString potrebuje cch * 2, zato mora GetWideStringWithZero sprejeti cch neposredno. Sprejme cch brez pritožbe, vrne WideString in prevajalnik je zadovoljen. Vrne pa tudi polovico znakov, sestavljenih iz napačnih parov bajtov. Pravilna 8-bitna pot je TXLSBlob.GetString z navadnim številom bajtov cch, pri prirejanju pa se rezultat pretvori v WideString. HotXLS 2.376.0 je prav to napačno uporabo popravil v dveh dekodirnikih grafikonov
SXViewLink in varovalo dolžine po kodiranju
SXViewLink ($0858, [MS-XLS] 2.4.316) je najčistejši praktični primer, ker obe asimetriji stlači v eno osem-bajtno glavo. Postavitev je rt(2), unused(2), reserved(2), cch(1), fHigh(1), nato telo XLUnicodeStringNoCch: fHigh = 1 pomeni cch * 2 bajtov UTF-16, fHigh = 0 pomeni cch bajtov enobajtnih znakov, cch pa je omejen na 255, ker je polje dolžine en sam bajt. HotXLS zapis zapiše v globalni del grafikona pred Units, poleg PivotChartBits ($0859, [MS-XLS] 2.4.196), ko se list grafikona poveže s pogledom vrtilne tabele; pogled na ravni zapisov je opisan v članku pisanje zapisov vrtilne tabele BIFF8 v Delphiju
// SXViewLink ([MS-XLS] 2.4.316): rt(2) unused(2) reserved(2) cch(1)
// nato XLUnicodeStringNoCch - fHigh(1), ki mu sledijo znaki
PivCch := Item.FData.GetByte(6);
if (PivCch > 0) and (Item.FData.GetByte(7) <> 0) and
(Item.FData.DataLength >= LongWord(8 + PivCch * 2)) then
begin
Result.PivotSourceName := Item.FData.GetWideString(8, PivCch * 2);
Result.IsPivotChart := True;
end
else if (PivCch > 0) and (Item.FData.GetByte(7) = 0) and
(Item.FData.DataLength >= LongWord(8 + PivCch)) then
begin
Result.PivotSourceName := WideString(Item.FData.GetString(8, PivCch));
Result.IsPivotChart := True;
end;
Dve veji nista kozmetični. Prejšnja različica je obe kodiranji varovala z izrazom širokih znakov 8 + cch * 2, zato ime pogleda z 8-bitnimi znaki, ki ga je zapisal Excel, ni šlo skozi preverjanje, dekodirnik pa je vrnil prazen PivotSourceName in pustil IsPivotChart na false. Povezava vrtilne tabele je izginila iz modela brez ene same diagnostike. Ista napaka je sedela v dekodirniku Trendline ($2050, [MS-XLS] 2.4.328), kjer polju imena sledi 28 bajtov številskega tovora, 2-bajtni cch je na odmiku 28, fHigh na odmiku 30 in znaki na odmiku 31; napisi trendnih črt, ki jih je Excel zapisal z 8-bitnimi znaki, so se dekodirali kot prazni nizi. Oboje je bilo popravljeno v isti izdaji. In 8-bitni primer ni zgodovinska zanimivost, omejena na datoteke Excela 2.0 do 4.0: trenutni Excel še vedno zapisuje 8-bitne tovore BIFF8, kadar vsak znak ustreza enemu bajtu
Kako v Delphiju varno dekodirati nov zapis BIFF8?
Kjer je polje res standardni XLUnicodeString, uporabite TXLSBlob.GetBiffString namesto ročnega pisanja vej. Prebere polje dolžine, prebere bajt možnosti, pošlje v ustrezni bralnik in kazalec pomakne čez telo. Dva logična parametra sta del, ki ga je treba pozorno prebrati: is8bit opisuje širino polja dolžine, ne širine znakov, iswide pa pove, ali je bajt možnosti fHigh sploh prisoten. Različice BIFF pod $0600 nimajo nobenega od njiju
var
Offset: LongWord;
begin
Offset := 6; // v SXViewLink se bajt cch začne tukaj
// is8bit = polje dolžine je široko en bajt
// iswide = bajt možnosti fHigh sledi polju dolžine
Name := Data.GetBiffString(Offset, True, True);
// Offset zdaj kaže na prvi bajt za telesom niza
Ročne veje so še vedno smiselne, ko mora obravnavalnik preživeti okrnjen ali sovražen vhod, saj se GetBiffString zanaša na to, da EnsureReadable sproži napako, namesto na preverjanje meja, ki bi ga nadzorovali vi. Zato dekodirnik grafikonov HotXLS preverja DataLength in vrne nič, namesto da bi vrgel izjemo: napačen delovni zvezek tretjega proizvajalca naj vas stane enega napisa, ne celotnega dokumenta. Kompromis je nameren in prav zato mora biti varovalo, specifično za kodiranje, pravilno, saj je varovalo tisto, ki slabše branje spremeni v tišino
Še zadnji procesni del, pridobljen na težji način v isti izdaji. Preverjajte shranjen in znova odprt delovni zvezek, ne modela v pomnilniku, ki ste ga pravkar zgradili. Paket 2.376.0 je odkril tudi oddajnik SXEx ([MS-XLS] 2.4.282), ki je prijavil 24-bajtno telo in zapisal samo 22 bajtov, s čimer je napačno poravnal vsak zapis za pogledom vrtilne tabele, vključno z EOF delovnega lista in morebitnim podtokom lista grafikona za njim. Obstoječi testi vrtilnih tabel tega niso nikoli ujeli, ker so vsi preverjali pomnilnik. Dekodiranje nizov ima isto lastnost: povratni prehod skozi datoteko je edini test, ki dejansko izvaja štetje bajtov
Če delate z notranjostjo klasičnega XLS v Delphiju ali C++Builderju in raje ne bi vzdrževali lastnega bralnika zapisov BIFF8, so zgornja pravila kodiranja že implementirana in regresijsko preizkušena v komponenti HotXLS Delphi spreadsheet, ki bere in zapisuje XLS in XLSX brez Excela ali avtomatizacije OLE