HotXLS šteje vsak sklic na pisavo BIFF8 tako, kot ga definira [MS-XLS] §2.5.129 FontIndex: vrednosti 0 do 3 so ničelno osnovane, vrednosti nad 4 so osnovane z ena, 4 pa se nikoli ne pojavi, tako da je peti zapis FONT ifnt 5, največji veljaven ifnt pa enak številu zapisov FONT. Od HotXLS 2.384.4 pravilu sledijo pisatelj XF, bralec XF, runi nizov rich text in selitev runov med delovnimi zvezki, različici 2.384.5 in 2.384.6 pa pravilo razširita na rune komentarjev in besedilnih okvirjev, vključno skozi kopije in vstavljanje vrstic
Pravilo zgleda kot natipkanka, dokler se z njim ne srečate. Nekdo odpre delovni zvezek z osmimi zapisi FONT, najde XF, ki kaže na pisavo 8, in sklepa, da je pisatelj izdelal indeks izven obsega. Ta ista logika je odšla v HotXLS 2.384.1 kot »popravek« in iz pravilne izvedbe naredila takšno, v kateri je vsaka pisava po meri v datoteki, odprti v Excelu, pristala eno režo prezgodaj. Zanimivo ni samo odmik za ena, temveč to, koliko mest v knjižnici BIFF8 nosi isto konvencijo, in kako lahko vezava pisave preživi eno shranjevanje, pokvari pa se ob drugem. Če ste se že stežka prebili skozi zakrknjenosti dolžine in kodiranja, opisane v razčlenjevanju cch in fHigh pri BIFF8 XLUnicodeString, je to isti rod napak: datoteka je v redu, aritmetika ni
Kaj pravilo FontIndex iz [MS-XLS] dejansko pravi?
[MS-XLS] §2.5.129 pravi, da je FontIndex pod 4 ničelno osnovan položaj zapisa, FontIndex nad 4 enojno osnovan položaj zapisa, vrednosti 4 pa se NE SME uporabiti. Isti tip FontIndex uporabljajo zapisi XF, formatirni runi SST in formatirni runi TXO, zato eno napačno prebrano pravilo pokvari vse tri. Dokaz se zlahka ponovi z datotekami, napisanimi v Excelu: SOLVSAMP.XLS, poslan z Officeom, ima 19 zapisov FONT in najvišji XF ifnt 19, delovni zvezek s 43 zapisi se ustavi na 43, datoteka, shranjena z Excel 16 s 30 zapisi FONT, pa kaže svoje celice Courier New na ifnt 22, dvaindvajseti zapis. Noben nikoli ne vsebuje 4. Če morate preslikavo analizirati sami v diagnostičnem orodju, je pretvorba dve kratki funkciji
// [MS-XLS] 2.5.129 FontIndex: 0..3 ničelno, > 4 enojno, 4 neveljavno
function FontIndexToRecordNo(Ifnt: Word): Integer; // zapis FONT z osnovo 1
begin
if Ifnt < 4 then
Result := Ifnt + 1
else if Ifnt > 4 then
Result := Ifnt
else
Result := -1; // 4 ne sme nastopiti
end;
function RecordNoToFontIndex(RecordNo: Integer): Word;
begin
if RecordNo <= 4 then
Result := RecordNo - 1
else
Result := RecordNo;
end;
Znotraj HotXLS isto pravilo živi na dveh zrcalnih mestih. TXLSFontList.GetSaveIndex vzame položaj pisave v navedenem seznamu z osnovo 1 in odšteva le položaje 1 do 4, tako da se položaj 5 zapiše kot ifnt 5. TXLSReader.ParseXF ob nalaganju nareda obratno: vsak ifnt 5 ali več se odšteje na ničelno osnovano režo na seznamu pisav, karkoli pod tem pa ostane na mestu. Preslikava rich runov SST in CountRichRunFontRefs uporabljata isto pretvorbo ifnt >= 5, in to je bistvo: ena konvencija, vsak potrošnik
// TXLSFontList.GetSaveIndex (stran pisatelja)
Result := inherited GetSaveIndex(Index); // navedeni položaj z osnovo 1
if (Result > 0) and (Result < 5) then
Dec(Result); // 1..4 postane 0..3, 5+ nespremenjeno
// TXLSReader.ParseXF (stran bralca)
fnti := Data.GetWord(0);
if fnti >= 5 then
Dec(fnti); // ifnt 5 je reža 4 na seznamu pisav
Zakaj je ničelno osnovan »popravek« premaknil vsako pisavo po meri za ena?
Ničelno osnovano prenovljanje v HotXLS 2.384.1 je premaknilo vsako pisavo po meri, ker je bralo enojno osnovan indeks kot ničelno osnovanega in nato spremenilo štiri klicne točke, da se ujemajo s tem napačnim branjem: GetSaveIndex, ParseXF, preslikavo runov SST in selitev runov med delovnimi zvezki v Sheets.AddCopy. Round-tripi HotXLS so še vedno zgledali v redu, ker sta se pisatelj in bralec strinjala med seboj. Excel ni strinjal. Datoteka, napisana z 2.384.1, je prvo pisavo po meri postavila na ifnt 4, kar Excel obravnava kot privzeto pisavo, vsako kasnejšo pisavo po meri pa en zapis prezgodaj; odpiranje datoteke iz Excela je šlo v obratno smer in vsako pisavo vezalo en zapis prepozno
Znak, ki naj bi spremembo ustavil, je sedel v isti kodni bazi. CountRichRunFontRefs, preslikava FONTX in FBI grafov ter seznam pisav stilskega motorja niso bili nikoli dotaknjeni in še vedno uporabljajo preskok 4, zato si je knjižnica protislovila tistega trenutka, ko je 2.384.1 pristala, skrito pa je bilo protislovje le po naključju, da so pisave rich text običajno hkrati sklicevale tudi s kakšnega XF. Ko se ena konvencija pojavi na sedmih mestih in vi spreminjate štiri, sumite svojo spremembo, preden sumite ostale tri. Različica 2.384.4 je v vseh štirih mestih obnovila številčenje po specifikaciji, stari regresijski preizkus, ki je trdil ifnt < FontCount in s tem kodiral napačno branje, pa so zamenjali preizkusi, ki vsak zapisani ifnt preslikajo nazaj na ime zapisa FONT po formuli specifikacije. Ostaja ena poštena omejitev: datoteke, shranjene z 2.384.1 do 2.384.3 s petimi ali več pisavami, nosijo premaknjene indekse, ki jih bralec ne more ločiti od veljavnih podatkov, zato je edino zdravilo, da jih znova ustvarite
Zakaj se runi pisav komentarjev pokvarijo šele pri drugem shranjevanju?
Runi komentarjev in besedilnih okvirjev so se pokvarili pri drugem shranjevanju, ker je HotXLS prvih N-1 zapisov FONT obdržal brezpogojno, izpustil pa le zadnjega, kadar se nanj ni skliceval noben XF, formatirni runi TXO ([MS-XLS] §2.4.329) pa so se zapisali nazaj bajt za bajtom brez ponovnega številčenja. Datoteke .xls, napisane v Excelu, se vedno končajo z neklicano zaključno pisavo (9 pt DengXian na sistemu s kitajskim localem), zato pisava, uporabljena le s strani runa komentarja, pri prvem shranjevanju nikoli ni bila zadnja in nič se ni vidno premaknilo. To prvo shranjevanje pa je izpustilo zaključno pisavo in pripeljalo pisavo, ki jo uporablja samo komentar, na zadnji položaj. Drugo shranjevanje jo je nato zavrnilo kot neklicano, runov ifnt je kazal preko konca, Excel pa se je vrnil na privzeto pisavo; če je delovni zvezek vmes dobil novo pisavo, se je run tiho vezal raje na njo, kar je v preizkusu iz stiliziranega runa besedilnega okvirja naredilo Arial. Datoteke, bogate s komentarji, kot so tiste iz gradnje delovnega toka pregledovanja komentarjev in hiperpovezav, so točno tam, kjer to ugrizne, ker jih vedno znova odpirajo, označujejo in shranjujejo
HotXLS 2.384.5 obravnava rune TXO kot rune SST. CountRichRunFontRefs zdaj obide vsak TMSOShapeTextBox na vsakem delovnem listu, pretvori runov preskok-4 ifnt v režo in ga prešteje kot sklic, tako da pisava, ki jo uporablja samo run, preživi filter shranjevanja. Nastala tabela rež v shranjevalne indekse gre v FontRunRemap vsake risbe, TMSOShapeTextBox.Store pa prepiše indekse runov na zasebni kopiji surovih bajtov runov, zaključni TxOLastRun pa pusti pri miru, ker ne nosi pisave. Za aplikacijsko kodo je pogodba preprosta: TXLSComment.TextRuns.FontIndex in TXLSTextBox.TextRuns.FontIndex uporabljata številčenje datoteke, 4 izpuščeno, točno tako, kot je prebrano; indeksi runov so z osnovo 1, CharIndex pa je odmik znaka, kjer se run začne. Po shranjevanju se shranjena številka lahko razlikuje od tiste, ki ste jo nastavili, kaže pa še vedno na isto pisavo
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]; // številčenje datoteke, 4 izpuščeno
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;
Kopije, vstavljanje vrstic in selitev runov med delovnimi zvezki
Od HotXLS 2.384.6 vsaka kopirna pot klasičnega pogona obdrži formatirne rune komentarjev, ker vse poti Range.Copy, CopyRange, Sheets.AddCopy in premiki celic za Range.Insert ter Range.Delete gredo skozi TXLSRange.CopyCell, CopyCell pa je kopiral le besedilo komentarja in avtorja. Premik je kopija plus čiščenje, zato je vstavljanje ene same vrstice nad opombo z dvema runoma pustilo nič runov in eno pisavo. Popravek vsak run prekopira in njegovo pisavo prestavi prek TXLSWorkbook.MigrateRunFontIndex, ki preskok-4 indeks pretvori v režo, pisavo po vrednosti preseli v ciljno tabelo pisav in pretvori nazaj v številčenje datoteke; selitev rich text SST v Sheets.AddCopy zdaj pokliče isto funkcijo, namesto da bi nosila svojo kopijo aritmetike. Zraven sta prišla dva robna primera: lepljenje na mestu, kjer sta vir in cilj isti komentar, ne sme počistiti njegovih runov, preden jih prebere, Sheets.AddCopy pa zdaj naredi drugi prehod za komentarje, pripete celicam brez shranjenega zapisa celice, ki jih je prej popolnoma preskočila. Stran tabele pisav pri kopiranju med delovnimi zvezki sledi isti logiki po vrednosti kot stran formul, ki jo pokriva kopiranje med delovnimi zvezki in ponovna vezava formul. Na pogonu XLSX so kopirne poti rune že klonirale po vrednosti; vrzel je bila v samem delu za komentarje, kjer je bralec ignoriral rFont, strike, u in vertAlign, pisatelj pa nikoli ni izdal u ali vertAlign, tako da runi zdaj preživijo shranjevanje in ponovno odpiranje simetrično
Kako naj preizkusite indekse pisav v datotekah BIFF8?
Indekse pisav preizkušajte s shranjevanjem in ponovnim odpiranjem, najbolje čez več kot eno generacijo, in s preslikavo vsakega ifnt nazaj na zapis FONT, namesto da bi trdili številčni obseg. Vsaka napaka v tej zgodbi je prešla preizkus v pomnilniku: regresija 2.384.1 je živela v usklajenem paru pisatelj in bralec, odmik TXO je potreboval dve shranjevanji s spremembo tabele pisav vmes, izgubljeni runi komentarjev na XLSX pa so se pokazali šele po ponovnem odpiranju. Uporaben testni harness odpre vzorec, napisan v Excelu, dvakrat ga shrani skozi HotXLS, med shranjevanji doda ali odstrani pisavo, nato pa preveri položaje runov in na ravni bajtov imena pisav za vsakim ifnt. Ne primerjajte vrednosti FontIndex pred in po shranjevanju, saj je ponovno številčenje zakonito
procedure CheckNoteSurvivesShiftAndSave(const SrcFile, OutFile: string);
var
Book: IXLSWorkbook;
Note: TXLSComment;
RunCount: Integer;
SecondRunAt: Word;
begin
Book := TXLSWorkbook.Create;
Assert(Book.Open(SrcFile) = 1); // napisano v Excelu, C2 ima dva runa
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 gre v C3
Assert(Book.SaveAs(OutFile) = 1);
Book := TXLSWorkbook.Create; // ponovno odpri, nikoli ne zaupaj pomnilniku
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;
Če iz Delphija ali C++Builder berete in zapisujete klasični XLS in se vam ne da spremljati, kateri od številnih potrošnikov pisav v knjižnici se še strinja z [MS-XLS] §2.5.129, so številčenje s preskokom 4, ponovno številčenje runov ob shranjevanju in selitev runov po vrednosti, opisani tukaj, vgrajeni v komponento preglednic HotXLS za Delphi, ki bere in zapisuje XLS ter XLSX brez Excela ali OLE avtomatizacije