Åpne en gammel xls, lagre den igjen, og add-in-formelen som kalte inn i et registrert analysebibliotek peker nå på en tom referanse inne i arbeidsboken selv. HotXLS sporer den stille korrupsjonen til én dårlig antakelse: at en BIFF SupBook-post enten er self eller en ekstern fil. [MS-XLS] definerer syv typer, ikke to
Hvorfor mister en lagret arbeidsbok sine add-in-lenker?
Fordi klassifiseringstesten var strukturell i stedet for typet. Den tradisjonelle snarveien leser en SupBook-post ($01AE), sjekker om den bærer self-markøren, og hvis ikke, behandler enhver streng som følger som en dokument-URL. Hver post som er ingen av de to tingene faller gjennom i en standard gren, og standard grenen er nesten alltid at dette er arbeidsboken selv. En add-in støttelenke, en same-sheet-lenke, en ubrukt plass og en avkortet post ender alle opp med å bære samme feil etikett. Ingenting kaster mens dette skjer: posten parset, formelen rekompilerte, filen lagret uten en advarsel, og defekten dukker opp tre uker senere når noen legger merke til en kolonne med nuller der en valutakonvertering pleide å være. [MS-XLS] §2.4.271 beskriver en post som kan være en selvreferanse, en same-sheet-referanse, en add-in funksjonsbeholder, en ekstern arbeidsbok med en virtuell bane og et arknavn-tabell, en DDE- eller OLE-datalenke, eller en ubrukt plassholder — og en syvende tilstand som ikke er i spesifikasjonen men finnes på ekte disker, posten som ikke parser. Fiksen er ikke en bedre heuristikk; den er å nekte å ha en heuristikk i det hele tatt
De syv typene en SupBook-post kan bære
HotXLS deklarerer støttelenke-taksonomien som en lukket opplisting i lxExternSheet.pas, og enhver nedstrøms beslutning bytter på den. Ni opplistingsverdier dekker de syv kategoriene, for DDE- og OLE-tilfellet trenger en foreløpig tilstand før den kan løses:
type
TXLSSupportingLinkKind = (
slkUnknown, // feilet å parse, eller etterfølgende byte gjenstod
slkSelf, // denne arbeidsboken
slkSameSheet, // U+0000-markør
slkAddIn, // add-in funksjonsbeholder
slkExternalWorkbook, // virtuell bane + arknavn-tabell
slkDde, // løst fra ExternName-flagg
slkOle, // løst fra ExternName-flagg
slkDdeOrOle, // én av de to, ikke ennå kjent hvilken
slkUnused); // enkelt-mellomrom plassholder
TXLSFormulaReferenceClass = (
frcInternal,
frcExternalWorkbook,
frcExternalOther,
frcUnknownOrMalformed);
TXLSXtiInfo = record
XtiIndex : Integer; // null-basert, som lagret i ExternSheet.rgXTI
ExternID : Integer; // én-basert, den interne konvensjonen
SupBookIndex: Integer;
Sheet1Index : Integer;
Sheet2Index : Integer;
LinkKind : TXLSSupportingLinkKind;
end;
Dispatchen er avsender-drevet, ikke streng-drevet. En feltverdi på $0401 markerer self-posten. Et arkantall på én paret med $3A01 markerer en add-in-beholder. Bare en verdi i rekkevidden 1 til $00FF betyr at en kodet virtuell bane følger, og bare da dekoder HotXLS en streng i det hele tatt. Alt utenfor de tre formene forblir slkUnknown, og en post hvis arknavn-tabell ikke konsumerer postkroppen nøyaktig degraderes tilbake til slkUnknown selv når hodet så plausibelt
Hvorfor dekoder same-sheet-markøren som en tom streng?
Fordi den generelle BIFF strengleseren ødelegger byten klassifiseringen avhenger av. Same-sheet støttelenken er en éntegns streng hvis eneste tegn er U+0000, og TXLSBlob.GetBiffString overleverer den som en tom WideString, uatskillelig fra en genuint tom bane — nøyaktig inndataen en selvreferanse-heuristikk svarer self til. HotXLS leser derfor det rå første kodepunktet ut av postkroppen i stedet for å stole på den dekodede verdien:
StringOffset := offset;
FDocUrl := Data.GetBiffString(offset, False, True);
FirstChar := $FFFF;
if val = 1 then
begin
StringOptions := Data.GetByte(StringOffset + 2);
if (StringOptions and $01) = 0 then
FirstChar := Data.GetByte(StringOffset + 3) // komprimert, én byte
else
FirstChar := Data.GetWord(StringOffset + 3); // bred, to byte
end;
if FirstChar = 0 then
FKind := slkSameSheet
else if (Length(FDocUrl) = 1) and (FDocUrl[1] = WideChar(#32)) then
FKind := slkUnused
else if Pos(WideChar(#3), FDocUrl) > 0 then
FKind := slkDdeOrOle
else if FDocUrl <> '' then
FKind := slkExternalWorkbook;
Merk komprimert-mot-bred grenen. Opsjonsbyten sitter ved en fast offset fra strenghodet, og det første kodepunktet er én byte eller to avhengig av bit 0, så å lese den som en byte ubetinget virker på de fleste filer og feiler på de skrevet av lokaliserte bygg — den verste mulige distribusjonen for en feil. Den ubrukte plassholderen fanges på samme måte, ved sin bokstavelige enkelt-mellomrom-nyttelast, og DDE- eller OLE-tilfellet av U+0003-separatoren innebygd i den kodede banen
Hvorfor kan ikke DDE og OLE separeres ved SupBook-tid?
Fordi SupBook-posten ikke bærer de skillende bitene. Den forteller deg at lenken er én av de to; fOle- og fOleLink-flaggene som avgjør hvilken bor i ExternName-posten ($0023) som ankommer senere i strømmen. HotXLS registrerer slkDdeOrOle ved parsetid og snevrer den inn i ParseExternalName, og hvis ingen ExternName noensinne ankommer forblir typen foreløpig for alltid — noe som er korrekt, for filen sier det genuint ikke. Hvert forbruker nedstrøms behandler den foreløpige verdien som en ekte verdi snarere enn en manglende, så ingen kaller må finne opp et tiebreak. Å gjette sannsynligvis DDE her ville kjøpe en ryddigere opplisting og en klasse av feil svar ingen kunne spore tilbake:
if FKind = slkDdeOrOle then
begin
if Data.DataLength < 2 then
Exit;
Flags := Data.GetWord(0);
if (Flags and $0010) <> 0 then
FKind := slkOle
else if (Flags and $0008) <> 0 then
FKind := slkDde;
end;
XTI-indekser er null-baserte på disk og én-baserte inne
HotXLS utfører off-by-one konverteringen nøyaktig én gang, ved punktet et symbol entrer det interne syntakstreet, og ingen andre steder. PtgNameX.ixti ([MS-XLS] §2.5.198.85) er en null-basert indeks inn i rgXTI-matrisen til ExternSheet-posten ($0017, §2.4.106), mens bibliotekets interne ExternID-konvensjon er én-basert med null reservert for ingen eksternt ark. BIFF8-lesebanen gjør FExternID := wValue + 1 når den dekoder et tNameX-symbol og skrivebanen avgir StoreExternID - 1, og lar det rå symbolsynet og on-disk semantikken urørt. Å ta feil på dette er uvanlig vanskelig å fange: eksterne definerte navn løser til nabooppføringen, og i en fil med én enkelt XTI-oppføring blir indeks 0 til indeks 1, bommer, og navnet degraderer stille. En regresjon som bare trener rekompilert formeltekst ser den aldri, for rekompilering rører aldri diskindeksen i det hele tatt — samme felle som gjør definerte navn som spenner ark og arbeidsbøker verdt å teste mot ekte byte-strømmer. Løsning er avgrenset i begge ender: TlxExternSheetSheet.TryResolveXti returnerer False for en negativ indeks eller en manglende oppføring, TXLSSupBook.TryGetKind returnerer False for en SupBook-indeks utenfor matrisen, og ClassifyXti mapper så slkSelf og slkSameSheet til frcInternal, slkExternalWorkbook til frcExternalWorkbook, og slkAddIn, slkDde, slkOle og slkDdeOrOle til frcExternalOther. Alt annet, hver utenfor-rekkevidde-bane inkludert, lander på frcUnknownOrMalformed
Klassifisere en formel før den fryses
TXLSCompiledFormula.ClassifyReferences skanner den bevarte BIFF symbolstrømmen direkte i stedet for å dekompilere formelen og søke etter hakeparenteser. Parentes-jakt i formeltekst er en tekstheuristikk iført en parsers frakk: den matcher strengliteraler, den matcher strukturerte referanser, og den bommer helt på eksterne definerte navn, siden de bærer ingen hakeparenteser i dekompilert form. Symbolskanningen ser bare på PtgNameX, PtgRef3d, PtgArea3d, PtgRefErr3d og PtgAreaErr3d, og faller tilbake til en syntakstre-gjennomgang når ingen BIFF-strøm overlever. Sammenfleting er villet pessimistisk — den faste prioriteten er frcUnknownOrMalformed, så frcExternalWorkbook, så frcExternalOther, så frcInternal — så et enkelt uleselig symbol forgifter hele formelen. For et eksternt definert navn valideres navneindeksen også: én-basert, i rekkevidde, og støttet av en beholdt ExternName-post
var
Wb : TXLSWorkbook;
Sheet: TXLSWorksheet;
i : Integer;
begin
Wb := TXLSWorkbook.Create;
try
Wb.Open('quarterly.xls');
for i := 1 to Wb.Sheets.Count do // Sheets er én-basert
begin
Sheet := Wb.Sheets[i];
// fryser BARE formler klassifisert frcExternalWorkbook;
// interne, add-in, DDE/OLE og feilformede referanser forblir formler
Sheet.ConvertFormulasToValues(True);
end;
Wb.SaveAs('quarterly-detached.xls');
finally
Wb.Free;
end;
end;
OnlyExternal-parameteren er der taksonomien betaler for seg. Å fryse en formel er irreversibelt, så operasjonen må bevise at en referanse er en ekstern arbeidsbok snarere enn bare mistenke det. Add-in-kall overlever, DDE- og OLE-lenker overlever, og alt parseren ikke kunne forstå fullt ut overlever, for det trygge utfallet av usikkerhet er å endre ingenting. Samme disiplin styrer rebinding av formler kopiert mellom arbeidsbøker, der en feilklassifisert referanse re-binder til feil bok i stedet for å feile høyt
Poster som ikke vil parse skrives tilbake urørt
HotXLS beholder den opprinnelige SupBook-nyttelasten og avgir den på nytt byte for byte når posten aldri ble redigert. En parsefeil setter slkUnknown og rydder den deriverte tilstanden, men den fangede kroppen forblir i FRawData og lagringsbanen foretrekker den fremfor enhver rekonstruksjon så lenge elementet ikke er skittent og ikke er self-posten. Alternativet — å normalisere en uparset post inn i en selvreferanse slik at skriveren har noe velformet å avgir — konverterer en post du ikke forsto til en post som er definitivt gal. Det prinsippet er samme kontrakt anvendt på VBA-prosjekter og deres eksterne referanser på tvers av en last-og-lagre-syklus, og det er forskjellen mellom et bibliotek som round-tripper ekte verden filer og et som round-tripper filene testsuiten tilfeldigvis inneholder. En arbeidsbok som har passert gjennom femten år av Excel-versjoner, en rapportgenerator og to migrasjonsverktøy vil inneholde poster ingen nålevende designet. Skriv dem tilbake slik du fant dem
Typet klassifisering av SupBook- og XTI-poster leverte i HotXLS 2.361.2 gjennom 2.361.4, sammen med avgrenset XTI-løsning og den tryggere ConvertFormulasToValues-banen beskrevet her. Vedlikeholder du Delphi- eller C++Builder-kode som leser eldre xls-filer som bærer add-in-kall, DDE- eller OLE-lenker, eller eksterne definerte navn, håndterer HotXLS Delphi spreadsheet component hele taksonomien nativt, uten Excel-installasjon og uten OLE-automatisering på maskinen som gjør arbeidet