HotXLS purkaa BIFF8 XLUnicodeStringin lukemalla ensin cch-arvon ja fHigh-lipun ja valitsemalla sitten koodausta vastaavan lukijan: TXLSBlob.GetWideString cch * 2 tavun määrällä kun fHigh on 1, ja TXLSBlob.GetString kun fHigh on 0. Yhdistä ne toisin päin ja tietue tuottaa tyhjän tai puolen pituuden merkkijonon ilman poikkeusta
Juuri tämä tekee tämän luokan bugeista kalliita. Chart avautuu, sarjat piirtyvät oikein, akselit ovat paikoillaan ja yksi trendline-kuvateksti on vain tyhjä. Lokissa ei ole mitään, exception handlerissa ei ole mitään eikä korruptoituneen tiedoston dialogia tule. Tiedosto oli koko ajan kunnossa; lukija pyysi väärän määrän tavuja ja sai täsmälleen sen mitä pyysi
Miksi BIFF8-merkkijono tulee takaisin tyhjänä?
BIFF8-merkkijono tulee takaisin tyhjänä, koska pituusvahti hylkäsi payloadin ennen lukemisen alkamista tai lukija pysähtyi ensimmäiseen löytämäänsä NUL-merkkiin. Molemmat polut ovat rakenteensa vuoksi hiljaisia. HotXLS:ssä vahti on yleensä tietueenkäsittelijän eksplisiittinen DataLength-tarkistus ja se täytyy laskea koodauksen mukaan: 16-bittinen payload tarvitsee SXViewLink-rungolle 8 + cch * 2 tavua mutta 8-bittinen payload vain 8 + cch. Sovella leveän merkin aritmetiikkaa 8-bittiseen tietueeseen ja jokainen lyhyt nimi putoaa portilta. NUL-käyttäytyminen on toinen ansa, koska sekä TXLSBlob.GetString että TXLSBlob.GetWideString etsivät dekoodatusta tuloksesta terminaattorin ja katkaisevat siihen, palauttaen tyhjän merkkijonon kun terminaattori osuu ensimmäiseen positioon. Lue 16-bittinen runko puolella tavumäärällä ja saat ensimmäiset cch div 2 merkkiä; lue 8-bittinen runko leveän lukijan kautta ja tavuparit muodostavat mielivaltaisia koodipisteitä. Vain liian pitkä luku on äänekäs: TXLSBlob.EnsureReadable nostaa virheen Blob read exceeds data size, kun pyyntö ylittää blobin. Liian lyhyellä luvulla ei ole vastaavaa hälytystä
GetWideString laskee tavut eikä merkkejä
TXLSBlob.GetWideString(Index, Count) ottaa Count-arvon tavuina. Sisäisesti se tekee SetString-kutsun PWideChar-osoittimella ja arvolla Count div SizeOf(WideChar), joten merkkimäärän antaminen puolittaa merkkijonon hiljaa. BIFF8-tietueiden rakenteet ilmaisevat merkkijonon pituuden kuitenkin merkkeinä. Jokaisen 16-bittisen kutsukohdan täytyy siis kantaa * 2 -muunnos itse, eikä yksikään 8-bittinen kutsukohta saa kantaa sitä. Tämä on sama koodausraja joka näkyy kun kirjoitat tekstiä ulos etkä lue sitä, joten jos putkesi kuljettaa merkkijonoja molempiin suuntiin, aihetta kannattaa lukea yhdessä artikkelin Unicode-turvallinen spreadsheet-vienti Delphissä kanssa
// 16-bittinen XLUnicodeStringNoCch: cch merkkiä, cch * 2 tavua
Name := Data.GetWideString(Start, cch * 2); // oikein
Name := Data.GetWideString(Start, cch); // puolet tekstistä, ei virhettä
// 8-bittinen XLUnicodeStringNoCch: cch merkkiä, cch tavua
Name := WideString(Data.GetString(Start, cch)); // oikein
Name := Data.GetWideStringWithZero(Start, cch); // edelleen leveä lukija
Sääntö pätee kaikkialla missä tavustriimiä kävellään käsin. Kun HotXLS kokoaa pitkän String-tietueen ($0207, [MS-XLS] 2.4.268) jälleen Continue-tietueistaan ($003C), leveä haara laskee segCh-arvon segmentin pituudesta ja kutsuu sitten GetWideString(3, segCh * 2), koska tietueen runko alkaa offsetista 3 ja lukumäärä on edelleen tavuja. Rich-text-lukija tekee saman asian offsetista 1 ensimmäisessä Continue-segmentissä. [MS-XLS] 2.5.293 takaa että katkos osuu kaksitavuisen merkin rajalle kun fHighByte on 1, joten osittaisen merkin kirjanpitoa ei tarvita, mutta tavuaritmetiikka on edelleen sinun saatava oikein
Mitä GetWideStringWithZero oikeasti tekee?
TXLSBlob.GetWideStringWithZero on leveiden merkkien lukija joka säilyttää upotetut NUL-merkit. WithZero-pääte merkitsee NULien säilytystä eikä merkkileveyttä: sisäisesti se ajaa saman SetString-kutsun PWideChar-osoittimella ja arvolla Count div SizeOf(WideChar) kuin GetWideString, mutta ilman terminaattorin etsintää. Yhden tavun vastine on TXLSBlob.GetStringWithZero, joka palauttaa AnsiString-arvon. Nimestä ei selviä kumpi on kumpi, ja tämä epäselvyys on maksanut koodikannalle oikeita bugeja. Täsmällinen väärinlukeminen kannattaa nimetä, koska se näyttää niin uskottavalta: GetWideString tarvitsee cch * 2, joten GetWideStringWithZero on varmasti se joka ottaa cch:n suoraan. Se ottaa cch-arvon ilman valitusta, palauttaa WideString-arvon ja kääntäjä on tyytyväinen. Se palauttaa myös puolet merkeistä, jotka on koottu vääristä tavupareista. Oikea 8-bittinen polku on TXLSBlob.GetString tavallisella cch-tavumäärällä ja castattuna WideString-arvoksi sijoituksen yhteydessä. HotXLS 2.376.0 korjasi täsmälleen tämän väärinkäytön kahdessa chart-dekooderissa
SXViewLink ja koodauskohtainen pituusportti
SXViewLink ($0858, [MS-XLS] 2.4.316) on siistein toimiva esimerkki, koska se pakkaa molemmat epäsymmetriat yhteen kahdeksan tavun otsakkeeseen. Rakenne on rt(2), unused(2), reserved(2), cch(1), fHigh(1), jota seuraa XLUnicodeStringNoCch-runko: fHigh = 1 tarkoittaa cch * 2 tavua UTF-16-tekstiä, fHigh = 0 tarkoittaa cch tavua yksitavuista tekstiä ja cch on rajattu arvoon 255 koska pituuskenttä on yksi tavu. HotXLS kirjoittaa tietueen chart globals -osioon ennen Units-tietuetta ja PivotChartBits-tietueen ($0859, [MS-XLS] 2.4.196) viereen, kun chart sheet linkittyy PivotTable-näkymään; tämän koneiston tietuetason näkymää käsitellään artikkelissa BIFF8 PivotTable -tietueiden kirjoittaminen Delphissä
// SXViewLink ([MS-XLS] 2.4.316): rt(2) unused(2) reserved(2) cch(1)
// sen jälkeen XLUnicodeStringNoCch - fHigh(1) seuraa pituuskenttää
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;
Haarat eivät ole kosmeettisia. Aiempi versio suojasi molempia koodauksia leveän merkin lausekkeella 8 + cch * 2, joten Excelin kirjoittama 8-bittinen näkymänimi epäonnistui portilla ja dekooderi palautti tyhjän PivotSourceName-arvon jättäen IsPivotChart-arvoksi false. Pivot-linkki katosi mallista ilman ainuttakaan diagnostiikkaa. Sama virhe oli Trendline-dekooderissa ($2050, [MS-XLS] 2.4.328), jossa nimi seuraa 28 tavun numeerista payloadia, 2-tavuinen cch on offsetissa 28, fHigh offsetissa 30 ja merkit offsetissa 31; Excelin 8-bittisenä kirjoittamat trendline-tekstit purkautuivat tyhjiksi merkkijonoiksi. Molemmat korjattiin samassa julkaisussa. 8-bittinen tapaus ei myöskään ole vanha kuriositeetti joka rajoittuisi Excel 2.0–4.0 -tiedostoihin: nykyinen Excel kirjoittaa edelleen 8-bittisiä BIFF8-payload-rakenteita aina kun jokainen merkki mahtuu yhteen tavuun
Miten uusi BIFF8-tietue dekoodataan turvallisesti Delphissä?
Kun kenttä on todella tavallinen XLUnicodeString, käytä TXLSBlob.GetBiffString-metodia oman haaran käsin kirjoittamisen sijaan. Se lukee pituuskentän, lukee option-tavun, ohjaa oikealle lukijalle ja siirtää kursorin rungon jälkeiseen kohtaan. Kaksi Boolean-parametria ovat kohta joka täytyy lukea tarkasti: is8bit kuvaa pituuskentän leveyttä eikä merkkien leveyttä, ja iswide kertoo onko fHigh-option-tavu ylipäätään olemassa. Alle $0600 olevilla BIFF-versioilla ei ole kumpaakaan
var
Offset: LongWord;
begin
Offset := 6; // SXViewLinkissa cch-tavu alkaa tästä
// is8bit = pituuskenttä on yhden tavun levyinen
// iswide = pituuskentän jälkeen seuraa fHigh-option-tavu
Name := Data.GetBiffString(Offset, True, True);
// Offset osoittaa nyt merkkijonorungon jälkeiseen ensimmäiseen tavuun
Käsin kirjoitetut haarat ovat edelleen perusteltuja, kun käsittelijän täytyy kestää katkennutta tai vihamielistä syötettä, koska GetBiffString nojaa EnsureReadable-metodin poikkeukseen eikä sinun hallitsemaasi rajatarkistukseen. Siksi HotXLS:n chart-dekooderi tarkistaa DataLength-arvon ja palauttaa tyhjän sen sijaan että nostaisi virheen: virheellisen kolmannen osapuolen työkirjan pitäisi maksaa yksi kuvateksti eikä koko dokumenttia. Vaihtokauppa on tarkoituksellinen, ja juuri siksi koodauskohtaisen portin on oltava oikein, koska portti on se joka muuttaa huonon luvun hiljaisuudeksi
Vielä yksi prosessiasia, joka opittiin saman julkaisun aikana kantapään kautta. Tee assertion tallennetulle ja uudelleen avatulle työkirjalle, älä juuri rakentamallesi muistimallille. 2.376.0-batch löysi myös SXEx-emitterin ([MS-XLS] 2.4.282), joka ilmoitti rungon pituudeksi 24 tavua mutta kirjoitti vain 22, siirtäen jokaisen PivotTable-näkymän jälkeisen tietueen väärään kohtaan, mukaan lukien worksheet EOF:n ja seuraavan chart sheet -alistreamin. Nykyiset pivot-testit eivät löytäneet tätä, koska kaikki assertionit tehtiin muistia vasten. Merkkijonojen dekoodaus toimii samalla tavalla: tiedoston läpi tehty kierros on ainoa testi joka todella käyttää tavumääriä
Jos työskentelet klassisen XLS:n sisäosien kanssa Delphillä tai C++Builderilla etkä halua ylläpitää omaa BIFF8-tietuelukijaa, yllä olevat koodaussäännöt on jo toteutettu ja regressiotestattu HotXLS Delphi spreadsheet component -komponentissa, joka lukee ja kirjoittaa XLS- ja XLSX-tiedostoja ilman Exceliä tai OLE-automatisointia