HotXLS numeroi jokaisen BIFF8-fonttivittauksen siten kuin [MS-XLS] §2.5.129 FontIndex määrittelee: arvot 0–3 lasketaan nollasta, arvot yli 4:n ykkösestä, ja 4 ei koskaan esiinny, joten viides FONT-tietue on ifnt 5 ja suurin kelvollinen ifnt on yhtä suuri kuin FONT-tietueiden määrä. HotXLS 2.384.4:stä alkaen XF-kirjoittaja, XF-lukija, rich text -merkkijonojen runit ja työkirjojen välinen run-migraatio noudattavat kaikkia tätä sääntöä, ja 2.384.5 ja 2.384.6 laajentavat sen kommentti- ja tekstilaatikkoruneihin, myös kopioiden ja rivien lisäysten läpi
Sääntö näyttää kirjoitusvirheeltä kunnes törmäät siihen. Joku avaa työkirjan jolla on kahdeksan FONT-tietuetta, löytää XF:n joka osoittaa fonttiin 8, ja päätteli kirjoittajan tuottaneen alueen ulkopuolisen indeksin. Juuri tuo päättely lähti matkaan HotXLS 2.384.1:ssä korjauksena, ja se muutti oikean toteutuksen sellaiseksi, että jokainen mukautettu fontti Excelillä avatussa tiedostossa laskeutui yhden paikan liian aikaisin. Kiinnostava osa ei ole off-by-one sinänsä, vaan se kuinka monessa paikassa BIFF8-kirjastossa sama konventio asuu, ja kuinka fonttisidennäisyys voi selvitä yhdestä tallennuksesta ja hajota toisella. Jos olet jo tapellut pituuden ja enkoodauksen erikoisuuksien kanssa artikkelissa BIFF8 XLUnicodeStringin cch:n ja fHigh:n dekoodauksesta käsiteltyjen asioiden parissa, tämä on samaa bugisukua: tiedosto on kunnossa, aritmetiikka ei ole
Mitä [MS-XLS]:n FontIndex-sääntö oikeasti sanoo?
[MS-XLS] §2.5.129 sanoo, että alle 4:n FontIndex on nollapohjainen tietuepaikka, yli 4:n FontIndex yhdestä laskettava tietuepaikka, ja arvoa 4 EI SAA käyttää. Samaa FontIndex-tyyppiä käyttävät XF-tietueet, SST-muotoilurunit ja TXO-muotoilurunit, joten yksi väärin luettu sääntö turmelee ne kaikki kolme. Todisteet ovat helppo toistaa Excelillä tehdyillä tiedostoilla: Officen mukana toimitettava SOLVSAMP.XLS sisältää 19 FONT-tietuetta ja suurin XF ifnt on 19, 43 tietueen työkirja pysähtyy lukemaan 43, ja Excel 16:lla tallennettu tiedosto jolla on 30 FONT-tietuetta osoittaa Courier New -solunsa ifnt 22:een, 22. tietueeseen. Mikään niistä ei koskaan sisällä nelosta. Jos sinun täytyy itse analysoida indeksien kuvausta diagnostiikkatyökalussa, konversio on kaksi lyhyttä funktiota
// [MS-XLS] 2.5.129 FontIndex: 0..3 nollapohjainen, > 4 ykkösestä, 4 virheellinen
function FontIndexToRecordNo(Ifnt: Word): Integer; // FONT-tietue ykkösestä
begin
if Ifnt < 4 then
Result := Ifnt + 1
else if Ifnt > 4 then
Result := Ifnt
else
Result := -1; // 4 ei saa esiintyä
end;
function RecordNoToFontIndex(RecordNo: Integer): Word;
begin
if RecordNo <= 4 then
Result := RecordNo - 1
else
Result := RecordNo;
end;
HotXLS:n sisällä sama sääntö asuu kahdessa peilikuvapaikassa. TXLSFontList.GetSaveIndex ottaa fontin yhdestä lasketun paikan viitatussa luettelossa ja vähentää yhden vain paikoista 1–4, joten paikka 5 kirjoitetaan muotoon ifnt 5. TXLSReader.ParseXF tekee käänteisen operaation luettaessa: mikä tahansa ifnt vähintään 5 vähennetään nollapohjaiseksi fonttiluettelon paikaksi, ja kaikki sitä alemmat jäävät ennalleen. SST:n rich-run-uudelleenkartoitus ja CountRichRunFontRefs soveltavat samaa ifnt >= 5 -konversiota, ja se on koko pointti: yksi konventio, jokainen kuluttaja
// TXLSFontList.GetSaveIndex (kirjoittajan puoli)
Result := inherited GetSaveIndex(Index); // viitattu paikka ykkösestä
if (Result > 0) and (Result < 5) then
Dec(Result); // 1..4 muuttuu 0..3:ksi, 5+ ennallaan
// TXLSReader.ParseXF (lukijan puoli)
fnti := Data.GetWord(0);
if fnti >= 5 then
Dec(fnti); // ifnt 5 on fonttiluettelon paikka 4
Miksi nollapohjainen "korjaus" siirsi jokaista mukautettua fonttia yhdellä?
Nollapohjainen uudelleenkirjoitus HotXLS 2.384.1:ssä siirsi jokaista mukautettua fonttia, koska se luki yhdestä lasketun indeksin nollapohjaisena ja muutti sitten neljä kutsupaikkaa vastaamaan tuota väärinlukemaa: GetSaveIndex, ParseXF, SST-runkien uudelleenkartoitus ja työkirjojen välinen run-migraatio metodissa Sheets.AddCopy. HotXLS:n omat round tripit näyttivät yhä hyviltä, koska kirjoittaja ja lukija olivat keskenään samaa mieltä. Excel ei ollut samaa mieltä. 2.384.1:n kirjoittama tiedosto pani ensimmäisen mukautetun fontin arvoon ifnt 4, jota Excel pitää oletusfonttina, ja jokainen myöhempi mukautettu fontti yhden tietueen liian aikaisin; Excel-tiedoston avaaminen meni toisin päin ja kytki jokaisen fontin yhden tietueen liian myöhään
Väärinlukeman joka olisi pysäyttänyt muutoksen olisi löytynyt samasta koodikannasta. CountRichRunFontRefs, kaavioiden FONTX- ja FBI-uudelleenkartoitus sekä tyylikoneen fonttiluettelo jäivät koskemattomiksi ja käyttivät yhä nelosen ohitusta, joten kirjasto ajatteli itsensä vastaan heti kun 2.384.1 laskeutui, ja vain se sattuma että rich text -fontteihin viittasi yleensä jokin XF piti ristiriidan piilossa. Kun yksi konventio esiintyy seitsemässä paikassa ja muutat neljää, epäile muutostasi ennen kuin epäilet kolmea muuta. Versio 2.384.4 palautti spekin numeroinnin kaikissa neljässä paikassa, ja vanha regressiotesti, joka assertoi ifnt < FontCount ja siten koodasi väärinlukeman sisäänsä, korvattiin testeillä jotka kuvaavat jokaisen kirjoitetun ifnt:n takaisin FONT-tietueen nimeen spekin kaavalla. Yksi rehellinen rajoitus jää: 2.384.1–2.384.3-versioilla viidellä tai useammalla fontilla tallennetuissa tiedostoissa on siirtyneitä indeksejä, joita lukija ei erota kelvollisesta datasta, joten ainoa parannus on luoda ne uudelleen
Miksi kommenttien fonttirunit hajoavat vasta toisella tallennuksella?
Kommentti- ja tekstilaatikkorunit hajoivat toisella tallennuksella, koska HotXLS piti ensimmäiset N-1 FONT-tietuetta ehdottomasti ja pudotti vain viimeisen kun yksikään XF ei viitannut siihen, kun taas TXO-muotoilurunit ([MS-XLS] §2.4.329) kirjoitettiin takaisin tavu tavulta uudelleennumeroimatta. Excelillä tehdyt .xls-tiedostot päättyvät aina viittamattomaan häntäfonttiin (9 pt:n DengXian kiinalaisen localen järjestelmässä), joten ensimmäisellä tallennuksella vain kommenttiruin käyttämä fontti ei koskaan ollut viimeinen, eikä mikään liikkunut näkyvästi. Se ensimmäinen tallennus pudotti kuitenkin häntäfontin ja ylensi vain kommenttia palvelevan fontin viimeiselle paikalle. Toinen tallennus sitten hylkäsi sen viittamattomana, runin ifnt osoitti lopun ohi, ja Excel palautui oletusfonttiin; jos työkirja oli sillä välin saanut uuden fontin, runi kytkeytyi hiljaisena siihen sen sijaan, mikä testissä muutti tyylitetyn tekstilaatikkorunin Arialiksi. Kommenttipainotteiset tiedostot kuten artikkelissa kommenttien ja hyperlinkkien katselmointiprosessin rakentamisesta kuvatut ovat täsmälleen se paikka jossa tämä puraisee, koska niitä avataan, annotoidaan ja tallennetaan toistuvasti
HotXLS 2.384.5 kohtelee TXO-runeja kuten SST-runeja. CountRichRunFontRefs kävelee nyt jokaisen TMSOShapeTextBoxin jokaisella laskentataulukolla, konvertoi jokaisen runin nelosen ohittavan ifntin paikaksi ja laskee sen viittaukseksi, joten vain runien käyttämä fontti selviää tallennussuodattimesta. Syntyvä paikasta tallennusindeksiin -taulukko menee jokaisen piirroksen FontRunRemapiin, ja TMSOShapeTextBox.Store kirjoittaa run-indeksit uudelleen raakarunitavujen yksityiselle kopiolle jättäen häntä-TxOLastRunin rauhaan koska se ei kanna fonttia. Sovelluskoodille sopimus on yksinkertainen: TXLSComment.TextRuns.FontIndex ja TXLSTextBox.TextRuns.FontIndex käyttävät tiedostonumerointia, nelonen ohitettuna, täsmälleen kuten luettiin; run-indeksit ovat yhdestä laskettuja ja CharIndex on merkkisiirtymä josta runi alkaa. Tallennuksen jälkeen tallennettu numero voi poiketa asettamastasi, mutta se osoittaa yhä samaan fonttiin
var
Book: IXLSWorkbook;
Note: TXLSComment;
I: Integer;
Ifnt: Word;
begin
Book := TXLSWorkbook.Create;
if Book.Open('review-notes.xls') <> 1 then
raise Exception.Create('Cannot open review-notes.xls');
Note := Book.Sheets[1].Range['C2', 'C2'].Comment;
if Note <> nil then
for I := 1 to Note.TextRuns.Count do
begin
Ifnt := Note.TextRuns.FontIndex[I]; // tiedostonumerointi, 4 ohitettu
if Ifnt = 4 then
raise Exception.CreateFmt('Run %d uses invalid ifnt 4', [I]);
Writeln(Format('run %d at char %d: ifnt %d = FONT record #%d',
[I, Note.TextRuns.CharIndex[I], Ifnt, FontIndexToRecordNo(Ifnt)]));
end;
end;
Kopiot, rivin lisäykset ja työkirjojen välinen run-migraatio
HotXLS 2.384.6:sta alkaen jokainen klassisen koneen kopiointipolku säilyttää kommenttien muotoilurunit, koska Range.Copy, CopyRange, Sheets.AddCopy ja solusiirrot joita Range.Insert ja Range.Delete tekevät kulkevat kaikkien TXLSRange.CopyCellin kautta, ja CopyCell kopioi ennen vain kommenttitekstin ja tekijän. Siirto on kopio plus tyhjennys, joten yhden rivin lisääminen kaksirunisen muistiinpanon yläpuolelle jätti sille nolla runia ja yhden fontin. Korjaus kopioi jokaisen runin ja siirtää sen fontin TXLSWorkbook.MigrateRunFontIndexin kautta, joka konvertoi nelosen ohittavan indeksin paikaksi, siirtää fontin arvona kohdefonttitaulukkoon ja konvertoi takaisin tiedostonumerointiin; SST:n rich text -migraatio metodissa Sheets.AddCopy kutsuu nyt samaa funktiota sen sijaan että kantaisi omaa kopiotaan aritmetiikasta. Kaksi reunatapausta tuli mukana: paikalleen liittäminen jossa lähde ja kohde ovat sama kommentti ei saa tyhjentää sen runeja ennen kuin ne on luettu, ja Sheets.AddCopy tekee nyt toisen kierroksen kommentteille jotka on kiinnitetty soluihin ilman tallennettua solutietuetta, jotka se aiemmin ohitti kokonaan. Fonttitaulukon puoli työkirjojen välisessä kopioinnissa noudattaa samaa arvona siirtämisen logiikkaa kuin kaavojen puoli jota käsittelee artikkeli työkirjojen välinen kopiointi ja kaavojen uudelleenkytkentä. XLSX-koneella kopiointipolut kloonasivat runit jo arvona; aukko oli itse kommenttiosassa, jossa lukija jätti huomiotta rFontin, striken, u:n ja vertAlignin ja kirjoittaja ei koskaan kirjoittanut uta tai vertAlignia, joten runit selviävät nyt tallennuksesta ja uudelleenavauksesta symmetrisesti
Miten fontti-indeksejä kannattaa testata BIFF8-tiedostoissa?
Testaa fontti-indeksejä tallentamalla ja avaamalla uudelleen, mieluiten yli yhden generoin, ja kuvaamalla jokainen ifnt takaisin FONT-tietueeseen numeerisen välin assertoimisen sijaan. Jokainen tämän tarinan bugi pääsi muistissa tehdyn testin läpi: 2.384.1:n regressio asui toisiaan vastaavassa kirjoittaja–lukijaparissa, TXO:n ajautuminen vaati kaksi tallennusta ja fonttitaulukon muutoksen väliin, ja XLSX:n kadonneet kommenttirunit näkyivät vasta uudelleenavauksen jälkeen. Käypä testiharness avaa Excelillä tehdyn näytteen, tallentaa sen kahdesti HotXLS:n läpi, lisää tai poistaa fontin tallennusten välissä ja tarkistaa sitten runien paikat sekä tavutasolla jokaisen ifnt:n takana olevat fonttinimet. Älä vertaa FontIndex-arvoja tallennuksen jälkeen ja ennen sitä, koska uudelleennumerointi on laillista
procedure CheckNoteSurvivesShiftAndSave(const SrcFile, OutFile: string);
var
Book: IXLSWorkbook;
Note: TXLSComment;
RunCount: Integer;
SecondRunAt: Word;
begin
Book := TXLSWorkbook.Create;
Assert(Book.Open(SrcFile) = 1); // Excelin tekemä, C2:ssa kaksi runia
Note := Book.Sheets[1].Range['C2', 'C2'].Comment;
RunCount := Note.TextRuns.Count;
SecondRunAt := Note.TextRuns.CharIndex[2];
Book.Sheets[1].Range['C2', 'C2'].Copy(Book.Sheets[1].Range['E5', 'E5']);
Book.Sheets[1].Range['C1', 'C1'].Insert(xlShiftDown); // C2 siirtyy C3:een
Assert(Book.SaveAs(OutFile) = 1);
Book := TXLSWorkbook.Create; // avaa uudelleen, älä luota muistiin
Assert(Book.Open(OutFile) = 1);
Note := Book.Sheets[1].Range['C3', 'C3'].Comment;
Assert((Note <> nil) and (Note.TextRuns.Count = RunCount));
Assert(Note.TextRuns.CharIndex[2] = SecondRunAt);
Note := Book.Sheets[1].Range['E5', 'E5'].Comment;
Assert((Note <> nil) and (Note.TextRuns.Count = RunCount));
end;
Jos luet ja kirjoitat klassista XLS:ää Delphillä tai C++Builderilla etkä halua seurata minkä kirjaston monista fonttikuluttajista yhä noudattaa [MS-XLS] §2.5.129:ää, tässä kuvattu nelosen ohittava numerointi, runien uudelleennumerointi tallennuksessa ja runien siirto arvona ovat sisäänrakennettuina HotXLS Delphi Excel -komponentissa, joka lukee ja kirjoittaa XLS- ja XLSX-muotoja ilman Exceliä tai OLE automationia