HotXLS kiekvieną BIFF8 šrifto nuorodą numeruoja taip, kaip apibrėžia [MS-XLS] §2.5.129 FontIndex: reikšmės 0 iki 3 yra nuo nulio, reikšmės virš 4 — nuo vieneto, o 4 niekada nepasirodo, tad penktasis FONT įrašas yra ifnt 5, o didžiausia teisėta ifnt reikšmė lygi FONT įrašų skaičiui. Nuo HotXLS 2.384.4 XF rašytojas, XF skaitytojas, rich text string run ir tarp darbaknygių keliaujantis run migracija visi laikosi tos taisyklės, o 2.384.5 ir 2.384.6 ją išplėčia į komentarų ir teksto laukų run, įskaitant kopijas ir eilučių įterpimus
Taisyklė atrodo kaip spaudos klaida, kol į jos neužkliūjate. Kas nors atveria darbaknygę su aštuoniais FONT įrašais, randa XF, rodantį į 8 šriftą, ir padaro išvadą, jog rašytojas išdavė indeksą už ribų. Būtent toks samprotavimas išvyko į HotXLS 2.384.1 kaip „pataisymas“ ir teisingą implementaciją pavertė tokia, kurioje kiekvienas savas šriftas Excel atvertame faile nukrisdavo vieta ankščiau. Įdomi čia ne pati nuklydimas per vienetą, o tai, kiek vietų BIFF8 bibliotekoje nešioja tą pačią konvenciją, ir kaip šrifto pririšimas gali išgyventi vieną įrašymą ir sulūžti per antrąjį. Jei jau kovojėte su ilgio ir koduotės keistenybėmis, aprašytomis kaip dekoduojamas BIFF8 XLUnicodeString cch ir fHigh, tai tos pačios šeimos bugas: failas geras, aritmetika — ne
Ką iš tikrųjų sako [MS-XLS] FontIndex taisyklė?
[MS-XLS] §2.5.129 sako, jog FontIndex žemiau 4 yra įrašo pozicija nuo nulio, FontIndex virš 4 — pozicija nuo vieneto, o reikšmės 4 NAUDOTI DRAUDŽIAMA. Tą patį FontIndex tipą naudoja XF įrašai, SST formatavimo run ir TXO formatavimo run, todėl viena per klaidą perskaityta taisyklė sugadina visus tris. Įrodymą nesunku atkurti su Excel sukurtus failus: SOLVSAMP.XLS, platinamas su Office, turi 19 FONT įrašų ir didžiausią XF ifnt 19, 43 įrašų darbaknygė siekia 43, o Excel 16 išsaugotas failas su 30 FONT įrašų savo Courier New langelius nukreipia į ifnt 22, dvidešimt antrą įrašą. Nė viename niekada nerandama 4. Jei diagnostiniam įrankiui atvaizdavimą tenka analizuoti pačiam, konversija — dvi trumpos funkcijos
// [MS-XLS] 2.5.129 FontIndex: 0..3 nuo nulio, > 4 nuo vieneto, 4 neteisėtas
function FontIndexToRecordNo(Ifnt: Word): Integer; // FONT įrašas nuo 1
begin
if Ifnt < 4 then
Result := Ifnt + 1
else if Ifnt > 4 then
Result := Ifnt
else
Result := -1; // 4 negali pasirodyti
end;
function RecordNoToFontIndex(RecordNo: Integer): Word;
begin
if RecordNo <= 4 then
Result := RecordNo - 1
else
Result := RecordNo;
end;
HotXLS viduje ta pati taisyklė gyvena dvejose veidrodiškose vietose. TXLSFontList.GetSaveIndex ima šrifto poziciją nuo 1 iš nuorodų sąrašo ir sumažina vienetu tik pozicijas 1 iki 4, todėl pozicija 5 užrašoma kaip ifnt 5. TXLSReader.ParseXF įkeliant daro atvirkščiai: bet koks ifnt nuo 5 ir daugiau sumažinamas vienetu iki fontų sąrašo vietos nuo nulio, o žemesni lieka vietoje. SST rich run permapas ir CountRichRunFontRefs taiko tą pačią ifnt >= 5 konversiją — tai ir yra esmė: viena konvencija, visi vartotojai
// TXLSFontList.GetSaveIndex (rašytojo pusė)
Result := inherited GetSaveIndex(Index); // pozicija nuo 1
if (Result > 0) and (Result < 5) then
Dec(Result); // 1..4 tampa 0..3, 5+ nesikeičia
// TXLSReader.ParseXF (skaitytojo pusė)
fnti := Data.GetWord(0);
if fnti >= 5 then
Dec(fnti); // ifnt 5 yra fontų sąrašo 4 vieta
Kodėl „nuo nulio“ pataisymas perkėlė kiekvieną savą šriftą per vienetą?
Nuo nulio persirašymas HotXLS 2.384.1 perkėlė kiekvieną savą šriftą, nes indeksą nuo vieneto skaitė kaip indeksą nuo nulio ir paskui pakeitė keturis kvietimo taškus, kad jie atitiktų tą perskaitymą: GetSaveIndex, ParseXF, SST run permapą ir tarp darbaknygių vykstančią run migraciją Sheets.AddCopy viduje. HotXLS paties round trip vis dar atrodė geras, nes rašytojas ir skaitytojas sutarė tarpusavyje. Excel nesutarė. 2.384.1 parašytas failas pirmąjį savą šriftą padėdavo į ifnt 4, kurį Excel laiko numatytuoju šriftu, o kiekvienas vėlesnis savas šriftas nukrisdavo įrašu ankščiau; Excel failo atvėrimas klydavo kita kryptimi, pririšdamas kiekvieną šriftą įrašu vėliau
Požymis, kuris turėjo sustabdyti tą pakeitimą, glūdėjo tame pačiame kode. CountRichRunFontRefs, diagramų FONTX ir FBI permapas bei stilių variklio fontų sąrašas niekada nebuvo liečiami ir toliau naudojo praleidimą-4, tad biblioteka pati sau prieštaravo tą akimirką, kai 2.384.1 pasirodė, ir tik atsitiktinumas, jog rich text šriftus dažniausiai dar rodydavo koks nors XF, slėpė prieštaravimą. Kai viena konvencija atsiranda septyniose vietose, o keičiate keturias, įtarkite savo pakeitimą anksčiau nei kitas tris. Versija 2.384.4 atstatė specifikacijos numeravimą visose keturiose vietose, o senas regresijos testas, teigęs ifnt < FontCount ir taip įkaltinęs tos perskaitymo klaidą, buvo pakeistas testais, kurie kiekvieną parašytą ifnt per specifikacijos formulę sugražina iki FONT įrašo vardo. Viena sąžininga riba lieka: 2.384.1 iki 2.384.3 išsaugoti failai su penkiais ar daugiau šriftų neša pasislinkusius indeksus, kurių skaitytojas negali atskirti nuo teisėtų duomenų, tad vienintelis vaistas — pergeneruoti juos
Kodėl komentarų šriftų run lūžta tik per antrąjį įrašymą?
Komentarų ir teksto laukų run lūždavo per antrąjį įrašymą, nes HotXLS pirmuosius N-1 FONT įrašus laikydavo besąlygiškai, o išmesdavo tik paskutinįjį, kai jokio XF į jį nerodo, kol TXO formatavimo run ([MS-XLS] §2.4.329) būdavo parašomi atgal baitas į baitą be jokio renumeriavimo. Excel sukurti .xls failai visada baigiasi nereferuojamu galiniu šriftu (9pt DengXian sistemoje su kinų lokalė), tad per pirmąjį įrašymą šriftas, naudotas tik komentarų run, niekada nebūdavo paskutinis, ir niekas akiviazdiai nesikeisdavo. Tas pirmasis įrašymas, tačiau, išmesdavo galinį šriftą ir pakeldavo tik komentarui skirtą šriftą į paskutinę vietą. Antrasis įrašymas tada jo atsisakydavo kaip nereferuojamo, run ifnt rodydavo pro pabaigą, ir Excel grišdavo prie numatytojo šrifto; jei darbaknygė tuo tarpu būdavo gavusi naują šriftą, run tyliai pririšdavosi prie to naujojo, kas testuose stilingą teksto lauko run paverdavo Arial. Komentais sunkūs failai, tokie kaip aprašyti kaip sukurti komentarų ir hipersaitų peržiūros darbo eigą, yra būtent ten, kur tai kandžiojasi, nes jie atveriami, komentuojami ir išsaugomi vėl ir vėl
HotXLS 2.384.5 TXO run elgiasi kaip su SST run. CountRichRunFontRefs dabar pereina kiekvieną TMSOShapeTextBox kiekviename darbalapyje, paverčia kiekvieno run praleidžiantį-4 ifnt į vietą ir suskaičiuoja jį kaip nuorodą, tad tik run naudojamas šriftas išgyvena įrašymo filtrą. Gauta vietų ir įrašymo indeksų lentelė patenka į kiekvieno piešinio FontRunRemap, o TMSOShapeTextBox.Store perrašo run indeksus žalių run baitų privačioje kopijoje, palikdamas galinį TxOLastRun ramybėje, nes jis neneša šrifto. Taikomosios kodui kontraktas paprastas: TXLSComment.TextRuns.FontIndex ir TXLSTextBox.TextRuns.FontIndex naudoja failo numeravimą, 4 praleidžiant, tiksliai taip, kaip perskaityta; run indeksai yra nuo 1, o CharIndex — simbolio poslinkis, kur run prasideda. Po įrašymo saugomas skaičius gali skirtis nuo jūsų nustatytojo, bet vis tiek rodo į tą patį šriftą
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]; // failo numeravimas, 4 praleistas
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;
Kopijos, eilučių įterpimai ir tarp darbaknygių vykstanti run migracija
Nuo HotXLS 2.384.6 kiekvienas klasikinio variklio kopijavimo kelias išlaiko komentarų formatavimo run, nes Range.Copy, CopyRange, Sheets.AddCopy ir langelių poslinkiai už Range.Insert bei Range.Delete visi eina per TXLSRange.CopyCell, o CopyCell anksčiau kopijuodavo tik komentaro tekstą ir autorių. Poslinkis yra kopija plus išvalymas, tad vienos eilutės įterpimas virš dviejų run pastabos palikdavo ją su nuliu run ir vienu šriftu. Pataisymas kopijuoja kiekvieną run ir perkelia jo šriftą per TXLSWorkbook.MigrateRunFontIndex, kuri praleidžiantį-4 indeksą paverčia vieta, pagal reikšmę perkelia šriftą į paskirties fontų lentelę ir sugražina į failo numeravimą; SST rich text migracija Sheets.AddCopy viduje dabar kviečia tą pačią funkciją vietoj savos aritmetikos kopijos. Kartu atėjo du kraštiniai atvejai: į vietą atliekamas įklijavimas, kur šaltinis ir paskirtis yra tas pats komentaras, neturi išvalyti savo run prieš jas perskaitydamas, o Sheets.AddCopy dabar daro antrą pravažiavimą komentarams, pririštiems prie langelių be saugomo langelio įrašo, kurių anksčiau visai praleisdavo. Tarp darbaknygių kopijavimo fontų lentelės pusė seka tą pačią pagal reikšmę logiką kaip formulės pusė, aprašyta tarp darbaknygių kopijavime ir formulės perpirišime. XLSX variklyje kopijavimo keliai run jau klonuodavo pagal reikšmę; spraga glūdėjo pačioje komentarų dalyje, kur skaitytojas ignoravo rFont, strike, u ir vertAlign, o rašytojas niekada neišduodavo u ar vertAlign, tad run dabar išgyvena įrašymą ir atvėrimą simetriškai
Kaip turėtumėte testuoti šriftų indeksus BIFF8 failuose?
Testuokite šriftų indeksus išsaugodami ir atverdami iš naujo, idealiu atveju per daugiau nei vieną kartą, ir siedami kiekvieną ifnt su FONT įrašu, vietoj to, kad teigtumėte skaitinį diapazoną. Kiekvienas šios istorijos bugas praėjo atminties testą: 2.384.1 regresija gyveno susitaikiusioje rašytojo ir skaitytojo poroje, TXO nuklydimui reikėjo dviejų įrašymų su fontų lentelės pokyčiu tarp jų, o prarasti komentarų run XLSX atsiskleisdavo tik po atvėrimo. Naudinga banda atveria Excel sukurtą pavyzdį, du kartus išsaugo jį per HotXLS, tarp įrašymų prideda arba pašalina šriftą, o paskui tikrina run pozicijas ir, baitų lygiu, šriftų vardus už kiekvieno ifnt. Nepalyginkite FontIndex reikšmių prieš ir po įrašymo, nes renumeriavimas yra teisėtas
procedure CheckNoteSurvivesShiftAndSave(const SrcFile, OutFile: string);
var
Book: IXLSWorkbook;
Note: TXLSComment;
RunCount: Integer;
SecondRunAt: Word;
begin
Book := TXLSWorkbook.Create;
Assert(Book.Open(SrcFile) = 1); // sukurtas Excel, C2 turi du run
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 persikelia į C3
Assert(Book.SaveAs(OutFile) = 1);
Book := TXLSWorkbook.Create; // atveriame iš naujo, atmintimi nepasitikime
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;
Jei iš Delphi ar C++Builder skaitote ir rašote klasikinį XLS ir nenorėtumėte sekti, kuris iš daugybės bibliotekos šriftų vartotojų vis dar sutampa su [MS-XLS] §2.5.129, čia aprašytas numeravimas be 4, run renumeriavimas įrašant ir run migracija pagal reikšmę yra įdiegti HotXLS Delphi skaičiuoklės komponente, kuris XLS ir XLSX skaito bei rašo be Excel ir be OLE automation