Teknik Makale

Delphi ile BIFF SupBook ve XTI Dış Bağlantı Sınıflandırması

Eski bir xls dosyasını açıp yeniden kaydettiğinizde, kayıtlı bir analiz kitaplığını çağıran eklenti formülü artık çalışma kitabının içindeki boş bir başvuruyu gösteriyor. HotXLS bu sessiz bozulmayı tek bir hatalı varsayıma kadar izler: BIFF SupBook kaydının ya self ya da dış dosya olduğu varsayımı. [MS-XLS] iki değil yedi tür tanımlar

Kaydedilen bir çalışma kitabı eklenti bağlantılarını neden kaybeder?

Çünkü sınıflandırma sınaması türlü değil yapısaldı. Geleneksel kestirme bir SupBook kaydını ($01AE) okur, self işaretçisini taşıyıp taşımadığına bakar ve taşımıyorsa ardından gelen dizgiyi belge URL değeri sayar. Bu ikisinden hiçbiri olmayan her kayıt bir varsayılan dala düşer ve varsayılan dal neredeyse her zaman «bu, çalışma kitabının kendisi» anlamına gelir. Bir eklenti destek bağlantısı, bir aynı sayfa bağlantısı, kullanılmayan bir yuva ve kesilmiş bir kayıt, hepsi aynı yanlış etiketi taşır. Bu sırada hiçbir şey hata vermez: kayıt ayrıştırılır, formül yeniden derlenir, dosya uyarısız kaydedilir ve kusur üç hafta sonra, birisi kur çevrimi sütununun yerinde sıfırlar gördüğünde ortaya çıkar. [MS-XLS] §2.4.271 şu kaydı tanımlar: bir öz başvuru, bir aynı sayfa başvurusu, bir eklenti işlev kapsayıcısı, sanal yol ve sayfa adı tablosu taşıyan bir dış çalışma kitabı, bir DDE veya OLE veri bağlantısı ya da kullanılmayan bir yer tutucu olabilir; ayrıca belirtimde yer almayan ama gerçek disklerde bulunan yedinci bir durum daha vardır: ayrıştırılamayan kayıt. Çözüm daha iyi bir buluşsal yöntem değil; hiç buluşsal yöntem kullanmamaktır

Bir SupBook kaydının taşıyabileceği yedi tür

HotXLS, destek bağlantısı sınıflandırmasını lxExternSheet.pas içinde kapalı bir numaralandırma olarak bildirir ve sonraki her karar bunun üzerinden dallanır. Dokuz numaralandırma değeri yedi kategoriyi kapsar; çünkü DDE ve OLE durumu çözümlenmeden önce geçici bir duruma gereksinim duyar:

type
  TXLSSupportingLinkKind = (
    slkUnknown,           // ayrıştırma başarısız veya arta kalan baytlar var
    slkSelf,              // bu çalışma kitabı
    slkSameSheet,         // U+0000 işaretçisi
    slkAddIn,             // eklenti işlev kapsayıcısı
    slkExternalWorkbook,  // sanal yol + sayfa adı tablosu
    slkDde,               // ExternName bayraklarından çözümlendi
    slkOle,               // ExternName bayraklarından çözümlendi
    slkDdeOrOle,          // ikisinden biri, hangisi henüz bilinmiyor
    slkUnused);           // tek boşluklu yer tutucu

  TXLSFormulaReferenceClass = (
    frcInternal,
    frcExternalWorkbook,
    frcExternalOther,
    frcUnknownOrMalformed);

  TXLSXtiInfo = record
    XtiIndex    : Integer;   // sıfır tabanlı, ExternSheet.rgXTI içindeki haliyle
    ExternID    : Integer;   // bir tabanlı, iç kural
    SupBookIndex: Integer;
    Sheet1Index : Integer;
    Sheet2Index : Integer;
    LinkKind    : TXLSSupportingLinkKind;
  end;

Dağıtım dizgiyle değil nöbetçi değerlerle yürür. $0401 alan değeri self kaydını işaretler. $3A01 ile eşleşen bir sayfa sayısı bir eklenti kapsayıcısını işaretler. Yalnızca 1 ile $00FF aralığındaki bir değer kodlanmış bir sanal yolun izlediği anlamına gelir ve HotXLS yalnızca o durumda bir dizginin kodunu çözer. Bu üç biçimin dışında kalan her şey slkUnknown kalır; sayfa adı tablosu kayıt gövdesini tam olarak tüketmeyen bir kayıt, baş kısmı makul görünse bile slkUnknown değerine geri düşürülür

HotXLS bir BIFF SupBook kaydını yedi türden birine sınıflandırmak için kullandığı nöbetçi değerli merdiven; dizginin kodunu yalnızca kodlanmış yol aralığındaki değerler için çözer ve varsayılan dala değil bilinmeyen türe düşer
Her türe bir dizgi sınamasıyla değil bir nöbetçi değerle ulaşılır; hiçbir biçimle eşleşmeyen kayıt, bu çalışma kitabı anlamına gelen varsayılan dala düşmek yerine bilinmeyen kalır

Aynı sayfa işaretçisi neden boş dizgi olarak çözümlenir?

Çünkü genel amaçlı BIFF dizgi okuyucu, sınıflandırmanın dayandığı baytı yok eder. Aynı sayfa destek bağlantısı, tek karakteri U+0000 olan tek karakterli bir dizgidir ve TXLSBlob.GetBiffString bunu boş bir WideString olarak geri verir; gerçekten boş bir yoldan ayırt edilemez — ki bu tam olarak bir öz başvuru buluşsal yönteminin «self» yanıtı verdiği girdidir. HotXLS bu nedenle çözümlenmiş değere güvenmek yerine ilk ham kod noktasını kayıt gövdesinden okur:

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)     // sıkıştırılmış, tek bayt
  else
    FirstChar := Data.GetWord(StringOffset + 3);    // geniş, iki bayt
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;

Sıkıştırılmış ile geniş arasındaki dala dikkat edin. Seçenek baytı dizgi başlığından sabit bir uzaklıkta durur ve ilk kod noktası, 0 numaralı bite bağlı olarak bir ya da iki bayttır; onu koşulsuz bayt olarak okumak çoğu dosyada çalışır ve yerelleştirilmiş derlemelerin yazdığı dosyalarda başarısız olur — bir hata için olası en kötü dağılım. Kullanılmayan yer tutucu da aynı biçimde, kendi tek boşluklu yüküyle yakalanır; DDE veya OLE durumu ise kodlanmış yolun içine gömülü U+0003 ayırıcısıyla yakalanır

HotXLS neden bir BIFF SupBook kayıt gövdesinden çözümlenmiş dizgi yerine ham ilk kod noktasını okur; çünkü genel amaçlı dizgi okuyucu aynı sayfa U+0000 işaretçisini boş bir değere dönüştürür
Aynı sayfa işaretçisi, karakteri U+0000 olan tek karakterli bir dizgidir; genel dizgi okuyucu onu boş bir değere indirger ve yalnızca seçenek baytı uzaklığındaki ham kod noktası onu korur

DDE ile OLE neden SupBook aşamasında ayrılamaz?

Çünkü SupBook kaydı ayırt edici bitleri taşımaz. Size bağlantının ikisinden biri olduğunu söyler; hangisi olduğuna karar veren fOle ve fOleLink bayrakları, akışta daha sonra gelen ExternName kaydında ($0023) yaşar. HotXLS ayrıştırma anında slkDdeOrOle değerini kaydeder ve ParseExternalName içinde daraltır; hiç ExternName gelmezse tür sonsuza dek geçici kalır — ki bu doğrudur, çünkü dosya gerçekten söylemez. Sonraki her tüketici bu geçici değeri eksik bir değer olarak değil gerçek bir değer olarak ele alır; böylece hiçbir çağıran taraf bir beraberlik kırıcı uydurmak zorunda kalmaz. Burada «muhtemelen DDE» tahmini yapmak daha derli toplu bir numaralandırma ve kimsenin izini süremeyeceği bir yanlış yanıtlar sınıfı satın alırdı:

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 dizinleri diskte sıfır tabanlı, içeride bir tabanlıdır

HotXLS bu birer kayma dönüşümünü tam olarak bir kez, bir belirteç iç sözdizimi ağacına girdiği noktada uygular ve başka hiçbir yerde uygulamaz. PtgNameX.ixti ([MS-XLS] §2.5.198.85), ExternSheet kaydının ($0017, §2.4.106) rgXTI dizisine sıfır tabanlı bir dizindir; kitaplığın iç ExternID kuralı ise bir tabanlıdır ve sıfır «dış sayfa yok» için ayrılmıştır. BIFF8 okuma yolu bir tNameX belirtecini çözerken FExternID := wValue + 1 yapar ve yazma yolu StoreExternID - 1 yayar; ham belirteç görünümü ile disk üstü anlambilim olduğu gibi kalır. Bunu yanlış yapmak alışılmadık ölçüde zor yakalanır: dış tanımlı adlar komşu girdiye çözümlenir ve tek XTI girdili bir dosyada dizin 0, dizin 1 olur, ıskalar ve ad sessizce bozulur. Yalnızca yeniden derlenen formül metnini çalıştıran bir regresyon bunu asla görmez; çünkü yeniden derleme disk dizinine hiç dokunmaz — sayfalar ve çalışma kitapları arasında uzanan tanımlı adların gerçek bayt akışlarına karşı sınanmasını değerli kılan tuzak da budur. Çözümleme iki uçtan da sınırlıdır: TlxExternSheetSheet.TryResolveXti negatif dizin veya eksik girdi için False döndürür, TXLSSupBook.TryGetKind dizi dışındaki bir SupBook dizini için False döndürür ve ardından ClassifyXti, slkSelf ile slkSameSheet değerlerini frcInternal, slkExternalWorkbook değerini frcExternalWorkbook, slkAddIn, slkDde, slkOle ve slkDdeOrOle değerlerini frcExternalOther ile eşler. Aralık dışı tüm yollar dahil geriye kalan her şey frcUnknownOrMalformed üzerine düşer

HotXLS bir BIFF PtgNameX belirtecinin sıfır tabanlı XTI dizinini tek bir noktada bir tabanlı iç ExternID değerine dönüştürür; iki uçta sınırlı çözümleme ve onu tüketen sınıflandırma eşlemesiyle
Sıfır tabanlı disk dizini ile bir tabanlı iç ExternID arasındaki birer kayma, belirteç sözdizimi ağacına girerken bir kez uygulanır ve çözümlenemeyen her dizin bozuk sınıfına düşer

Bir formülü dondurmadan önce sınıflandırmak

TXLSCompiledFormula.ClassifyReferences, formülü tersine derleyip köşeli ayraç aramak yerine korunan BIFF belirteç akışını doğrudan tarar. Formül metninde ayraç avcılığı, ayrıştırıcı kılığına girmiş bir metin buluşsal yöntemidir: dizgi sabitleriyle eşleşir, yapılandırılmış başvurularla eşleşir ve tersine derlenmiş biçimde hiç ayraç taşımayan dış tanımlı adları tamamen ıskalar. Belirteç taraması yalnızca PtgNameX, PtgRef3d, PtgArea3d, PtgRefErr3d ve PtgAreaErr3d belirteçlerine bakar ve hayatta kalan BIFF akışı yoksa bir sözdizimi ağacı gezintisine geri döner. Birleştirme kasıtlı olarak kötümserdir — sabit öncelik sırası frcUnknownOrMalformed, sonra frcExternalWorkbook, sonra frcExternalOther, sonra frcInternal biçimindedir; böylece okunamayan tek bir belirteç bütün formülü zehirler. Bir dış tanımlı ad için ad dizini de doğrulanır: bir tabanlı, aralık içinde ve korunan bir ExternName kaydıyla desteklenmiş

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 bir tabanlıdır
    begin
      Sheet := Wb.Sheets[i];
      // YALNIZCA frcExternalWorkbook olarak sınıflandırılan formülleri dondurur;
      // iç, eklenti, DDE/OLE ve bozuk başvurular formül olarak kalır
      Sheet.ConvertFormulasToValues(True);
    end;
    Wb.SaveAs('quarterly-detached.xls');
  finally
    Wb.Free;
  end;
end;

Sınıflandırmanın bedelini karşıladığı yer OnlyExternal parametresidir. Bir formülü dondurmak geri alınamaz; bu yüzden işlem, bir başvurunun dış çalışma kitabı olduğundan kuşkulanmak yerine onu kanıtlamak zorundadır. Eklenti çağrıları hayatta kalır, DDE ve OLE bağlantıları hayatta kalır ve ayrıştırıcının tam anlayamadığı her şey hayatta kalır; çünkü belirsizliğin güvenli sonucu hiçbir şeyi değiştirmemektir. Aynı disiplin, yanlış sınıflandırılmış bir başvurunun yüksek sesle hata vermek yerine yanlış kitaba yeniden bağlandığı çalışma kitapları arasında kopyalanan formüllerin yeniden bağlanması konusunda da geçerlidir

Ayrıştırılamayan kayıtlar olduğu gibi geri yazılır

HotXLS, özgün SupBook yükünü saklar ve kayıt hiç düzenlenmediyse onu bayt bayt aynen geri yayar. Bir ayrıştırma hatası slkUnknown değerini ayarlar ve türetilmiş durumu temizler, ancak yakalanan gövde FRawData içinde kalır; öğe kirli olmadığı ve self kaydı olmadığı sürece yazma yolu onu her türlü yeniden kuruluma yeğler. Alternatif — ayrıştırılamayan kaydı, yazıcının yayabileceği düzgün biçimli bir şeye sahip olması için bir öz başvuruya normalleştirmek — anlamadığınız bir kaydı kesinlikle yanlış bir kayda dönüştürür. Bu ilke, bir yükle ve kaydet döngüsü boyunca VBA projelerine ve dış başvurularına uygulanan sözleşmeyle aynıdır; gerçek dünya dosyalarını kayıpsız aktaran bir kitaplık ile yalnızca sınama takımının rastlantısal olarak içerdiği dosyaları aktaran bir kitaplık arasındaki fark da budur. On beş yıllık Excel sürümü, bir rapor üreteci ve iki geçiş aracı elinden geçmiş bir çalışma kitabı, şu an hayatta olan kimsenin tasarlamadığı kayıtlar içerecektir. Onları bulduğunuz gibi geri yazın

SupBook ve XTI kayıtlarının türlü sınıflandırması, sınırlı XTI çözümlemesi ve burada anlatılan daha güvenli ConvertFormulasToValues yoluyla birlikte HotXLS 2.361.2 ile 2.361.4 arasında yayımlandı. Eklenti çağrıları, DDE veya OLE bağlantıları ya da dış tanımlı adlar taşıyan eski xls dosyalarını okuyan Delphi veya C++Builder kodu bakımı yapıyorsanız, HotXLS Delphi elektronik tablo bileşeni tüm sınıflandırmayı yerel olarak işler; işi yapan makinede Excel kurulumu ve OLE otomasyonu olmadan