Teknik Makale

HotXLS: Delphi'de Çalışma Kitabı Denetimi ve Biçim Dönüşümü

Toplu bir elektronik tablo normalleştirme işi, tek bir palto giymiş üç sorundur. Elinizde karışık biçimlerden oluşan bir arşiv var: BIFF döneminden .xls dosyaları, modern .xlsx dosyaları, bir LibreOffice denemesinden kalma birkaç .ods ve parolası eski bir çalışanla birlikte çıkıp gittiği için kimsenin açamadığı bir avuç dosya. Amaç her şeyi XLSX ve CSV biçimine çevirmek. Çoğu kişinin yazdığı sürüm, her dosyayı açıp yeni bir uzantıyla kaydeden bir döngüdür ve biri hangi dosyaların grafiklerini yitirdiğini, makrolarını düşürdüğünü ya da hiç açılmadığını sorana kadar da işini görür. Döngünün yanıtı yoktur; çünkü dönüştürme tek başına kayıt tutmaz. Bir tezgâh ise tutar: önce envanter çıkarır, sonra dönüştürür, sonra doğrular ve bunların herhangi birinin güvenilir olması için bu üç aşamanın bilgi paylaşması gerekir

Bu tezgâhı Delphi ya da C++Builder içinde kurmak, hiçbiri hattın hiçbir yerinde Excel kurulumu gerektirmeyen dört HotXLS yeteneğini birbirine bağlamak demektir. İki yerel motor var: .xls için bir BIFF8 cephesi ve .xlsx ile .ods için bir OOXML cephesi. Dosyanın tamamını ayrıştırmadan meta veri okuyan ucuz yoklama çağrıları var. Bir çalışma kitabının gerçekte ne barındırdığını söyleyen sayfa başına denetim sayaçları var. Ve her rota için belgelenmiş bir sadakat profili taşıyan bir dönüşüm matrisi var. İş, bunların her birinin nerede keskin bir kenarı olduğunu bilmekte; çünkü hepsinin var ve o kenarlar, temiz bir gece yığınını pazartesi sabahı olayına çeviren şeylerin ta kendisi

Delphi'de önce denetim yapan bir HotXLS dönüşüm tezgâhının hat şeması: xls, xlsx ve ods dosyalarından oluşan karışık bir arşivin envanteri çıkarılır, rotaya göre dönüştürülür, sonra envanter sırasında kaydedilen önceki sayılarla doğrulanır
Tezgâh üç aşamada dönüştürür ve envanter sırasında kaydedilen denetim sayaçları, doğrulamanın karşılaştıracağı önceki sayılara dönüşür

Yüklemeden önce yoklayın: sayfa adları ve şifreleme tespiti

200 MB boyutunda bir çalışma kitabını yalnızca şifreli olduğunu keşfetmek için açmak dosya başına dakikalar harcar ve büyük bir arşiv boyunca çarpıldığında günler harcar. Her iki cephe de GetSheetNames sunar; bu çağrı çalışma kitabını doldurmadan sayfa meta verisini okur. BIFF gerçeklemesi yalnızca akışın başındaki BoundSheet kayıtlarını tarar; OOXML gerçeklemesi ise zip içindeki yalnızca workbook.xml parçasını okur. Onun yanında CanReadEncrypted, şifre çözmeye kalkışmadan bir şifreleme kapsayıcısını tespit eder:

var
  Probe: TXLSXWorkbook;
  Names: TStringList;
begin
  Names := TStringList.Create;
  Probe := TXLSXWorkbook.Create;
  try
    if Probe.CanReadEncrypted(FileName) then
    begin
      Writeln(FileName + ': encrypted container - route to manual handling');
      Exit;
    end;
    if Probe.GetSheetNames(FileName, Names) <= 0 then
      Writeln(FileName + ': unreadable - quarantine')
    else
      Writeln(Format('%s: %d sheet(s), first "%s"',
        [FileName, Names.Count, Names[0]]));
  finally
    Probe.Free;
    Names.Free;
  end;
end;

İki operasyonel ayrıntı bu döngüyü ucuz kılar. GetSheetNames çalışma kitabı örneğini ne sıfırlar ne doldurur; dolayısıyla tek bir yoklama nesnesi yeniden oluşturulmadan binlerce dosyayı sınıflandırabilir. Ayrıca aynı çağrının XLS cephesindeki sürümü .xlsx paketlerini de anlar; bu da dosya uzantılarına güvenilemediğinde, ki o kadar eski bir arşivde nadiren güvenilir, onu kullanışlı tek bir yoklama hâline getirir. Yüklemeden önce triyaj kendi başına ele alınmayı hak ediyor; hafif incelemenin mekaniği sayfa listeleme ve hafif çalışma kitabı incelemesi üzerine yazımızda

Delphi'de HotXLS çalışma kitabı yığınları için triyaj akış şeması: CanReadEncrypted şifreli kapsayıcıları elle işlemeye yönlendirir, GetSheetNames okunamayan dosyaları karantinaya alır ve geçen dosyalar dönüşüm rotasına karar veren denetim geçişine girer
CanReadEncrypted ve GetSheetNames ile yoklama her dosyayı yüklemeden önce sınıflandırır, böylece şifreli ve okunamayan çalışma kitapları dönüşüm döngüsüne hiç ulaşmaz

Bir çalışma kitabının gerçekte ne içerdiğini saymak

Bir dosya triyajı geçtiğinde dönüşüm rotasına denetim geçişi karar verir. XLSX cephesi, bir sadakat kararını etkileyen her özellik ailesi için bir sayaç sunar: birleştirilmiş hücreler, grafikler, resimler, koşullu biçimler, veri doğrulamaları, tablolar, köprüler ve yorumlar; ayrıca makrolar, koruma ve kaynak biçim için çalışma kitabı düzeyinde bayraklar. Bir dosyanın dönüşüm rotası neredeyse tümüyle bunlardan hangilerinin sıfırdan farklı döndüğüne bağlıdır

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  I: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open(FileName) <> 1 then Exit;
    for I := 0 to Book.Sheets.Count - 1 do
    begin
      Sheet := Book.Sheets[I];
      Writeln(Format('%s: cells=%d merges=%d charts=%d cf=%d dv=%d protected=%s',
        [Sheet.Name, Sheet.Cells.Count, Sheet.MergedCells.Count,
         Sheet.Charts.Count, Sheet.ConditionalFormats.Count,
         Sheet.DataValidations.Count, BoolToStr(Sheet.IsProtected, True)]));
    end;
    if Book.HasVbaProject then
      Writeln('  contains VBA project - macro policy applies');
    if Book.ExternalLinks.Count > 0 then
      Writeln(Format('  %d external link(s)', [Book.ExternalLinks.Count]));
  finally
    Book.Free;
  end;
end;

Cells.Count değerini bir çekinceyle okuyun. Hücre deposu seyrektir; dolayısıyla bu sayı, kullanılan aralığın dikdörtgen alanını değil örneklenmiş hücreleri sayar. A1 hücresinde bir değer, ZZ9999 hücresinde bir değer bulunan bir sayfa, aralarındaki milyona yakın hücreyi değil iki hücre bildirir. BIFF tarafındaki eşdeğer tarama UsedRange sınırlarını ForEachCell ile birlikte kullanır ve neredeyse herkesi ilk seferde tökezleten bir birim kaymayı taşır: UsedRange.FirstRow ve kardeşleri 0 tabanlıyken Cells.Item[Row, Col] 1 tabanlıdır. Her sınıra bir eklemeyi unutan bir gezinti yanlış dikdörtgeni denetler ve bunu asla söylemez

Büyük eski dosyalar üzerinde yalnızca denetim amaçlı bir geçişin maliyetini iki kaldıraç düşürür. Bir .xls dosyasını açmadan önce _DisableGraphics değerini true yapmak OfficeArt çizim katmanı ayrıştırmasını tümüyle atlar; bu da şekil yoğunluklu çalışma kitaplarında gerçek zaman kazandırır. Ancak bu kesinlikle salt okunur bir iyileştirmedir: öyle açılmış bir örnekten kayıt yapmak, hiç ayrıştırmadığı çizimleri düşürür; dolayısıyla bayrak yalnızca dosyayı asla geri yazmayacak yollara aittir. Denetim sayılar yerine hücre başına içerik gerektirdiğinde, ForEachCell geri çağırması dolu hücreleri doğrudan dolaşır ve dizinli hücre özelliklerinin her okumada ödediği erişim başına Variant yükünü aşar; bu yük milyonlarca hücre boyunca hızla birikir

Tutarsız dönüş kodlarını erkenden normalleştirin

HotXLS G/Ç çağrıları hataları istisnalarla değil tamsayı sonuçlarıyla bildirir ve gelenekler API genelinde tek biçimli değildir. Çoğu açma ve kaydetme çağrısı başarıda 1, başarısızlıkta -1 döndürür. GetSheetNames sayfa sayısını döndürür ya da liste temizlenmiş olarak -1 döndürür. XLSX tarafındaki SaveAsHTML deseni yeniden kırar: başarı için 0, aralık dışındaki bir sayfa dizini için -1 döndürür. Her yerde = 1 sınayan bir tezgâh, başarıyı başka biçimde bildiren çağrıları sessizce yanlış sınıflandırır; <> -1 sınayan bir tezgâh ise farklı bir kodla başarısız olanları yutar

API'nin tamamıyla temasa dayanan kural göründüğünden dardır: sayı döndüren çağrılarda <= 0 değerini başarısızlık sayın, gerçekten kullandığınız her kaydetme yordamı için belgelenmiş başarı değerini denetleyin ve her ikisini de küçük bir sonuç denetleme işlevinin arkasına koyun ki gelenek tam olarak tek bir yerde yaşasın. Yığın hatları, egzotik herhangi bir ayrıştırıcı hatasından çok daha sık, denetlenmemiş dönüş kodlarının yavaş birikmesi yüzünden başarısız olur ve bunu yanlış yapmanın bedeli kırk bin dosya sonra, hangi dönüşümlerin gerçekten tuttuğunu kimse hatırlamazken ortaya çıkar

Dönüşüm matrisi ve her yolun veriyi nerede kaybettiği

İki cephe dönüşüm işini aralarında paylaşır. TXLSXWorkbook, XLSX, ODS ve CSV açar; XLSX, ODS, CSV, HTML, RTF ve AES ile şifrelenmiş XLSX kaydeder. TXLSWorkbook, BIFF açıp kaydeder ve HTML, RTF ile CSV dışa aktarır. İşe yarayan yan şu ki her yol, belirsiz bir doğruluk sözü yerine belgelenmiş bir sadakat profiliyle gelir; böylece hangi rotaların hangi dosyalar için güvenli olduğuna önceden karar verebilirsiniz

CSV dışa aktarımı BOM ile UTF-8, CRLF satır sonları ve RFC 4180 tırnaklaması yazar. Yapmadığı şey formülleri değerlendirmektir: =SUM(...) tutan bir hücre düz formül metni olarak dışa aktarılır; dolayısıyla önce değerleri hesaplamazsanız bir formül sayfası bir dizge sayfasına dönüşür. HTML dışa aktarımı tek bir tablo üretir; birleştirilmiş hücrelerin yerini colspan ve rowspan alır ve temel stiller satır içine gömülür. RTF dışa aktarımının sınırı daha keskindir: birleştirilmiş hücreleri sütunlar boyunca yayamaz; bu yüzden bir birleştirmenin devam hücreleri boş çıkar. ODS içe aktarımı ise kütüphanenin kendi belgelerine göre bilerek hafiftir. Skaler değerler ve önbelleklenmiş formül sonuçları geçer; stiller, canlı ODF formül ifadeleri ve çizimler geçmez. Arşivde OASIS ODF 1.3 ile yönetilen gerçek OpenDocument dosyaları bulunduğu anda bu önem kazanır; orada görsel olarak sadık bir dönüşüme yaklaşan her şey, bu içe aktarma yolunun taşımak üzere kurulduğundan fazlasını ister ve yığın onları sessizce düzleştirmeden önce bu dosyaların varlığını size söyleyen şey denetim geçişidir

SaveXLSWorkbookAsXLSX bir veri köprüsüdür, düzen köprüsü değil

BIFF cephesi doğrudan OOXML yazamaz; bu yüzden .xls biçiminden .xlsx biçimine geçiş, lxXlsxExport biriminde bulunan SaveXLSWorkbookAsXLSX işlevi üzerinden yürür. O köprünün sadakatini açıkça söylemek gerekir; çünkü ad, olduğundan fazlasını çağrıştırır. Değerleri, formülleri, sayı biçimlerini, dolgu renklerini, temel yazı tipi niteliklerini, sütun genişliklerini ve kılavuz çizgileri gibi görünüm ayarlarını kopyalar. Kenarlıkları, birleştirilmiş aralıkları, yorumları, grafikleri ya da koşullu biçimleri kopyalamaz. Aşağı akış sistemlerinin sonucu ayrıştıracağı ve biçimlendirmeye kimsenin bakmayacağı veri düzeyinde normalleştirme için bu tam olarak yeterlidir ve kimsenin ihtiyaç duyduğu hiçbir şey kaybolmaz. Bir insan tarafından okunacak biçimlendirilmiş bir yönetim raporu içinse yeterli değildir ve denetim sayaçları tam olarak burada yerini hak eder: denetimin grafik ve koşullu biçim taşıdığını işaretlediği bir dosya, ikisini de tek kelime etmeden düşürecek bir köprüden değil elle işlenen bir kuyruğa gitmelidir

Delphi'de HotXLS SaveXLSWorkbookAsXLSX için köprü sadakati şeması: değerler, formüller, sayı biçimleri, dolgu renkleri, temel yazı tipi nitelikleri, sütun genişlikleri ve görünüm ayarları BIFF xls biçiminden XLSX biçimine geçerken kenarlıklar, birleştirilmiş aralıklar, yorumlar, grafikler ve koşullu biçimler düşürülür
SaveXLSWorkbookAsXLSX bir ayrıştırıcının ihtiyaç duyduğu veriyi BIFF ile OOXML arasındaki köprüden taşır ve grafikleri ile birleştirmeleri düşecek dosyaları işaretleyen şey denetim sayaçlarıdır
var
  Legacy: IXLSWorkbook;        // arayüz başvurusu: Free çağırmayın
  Modern: TXLSXWorkbook;
begin
  if SameText(ExtractFileExt(FileName), '.xls') then
  begin
    Legacy := TXLSWorkbook.Create;
    if Legacy.Open(FileName) <= 0 then Exit;
    if SaveXLSWorkbookAsXLSX(Legacy,
         ChangeFileExt(FileName, '.xlsx')) <= 0 then
      Writeln('bridge failed: ' + FileName);
  end
  else
  begin
    Modern := TXLSXWorkbook.Create;
    try
      Modern.StreamingWrite := True;     // sayfa XML'ini zip içine akıt
      if Modern.Open(FileName) = 1 then
        Modern.SaveAsCSV(ChangeFileExt(FileName, '.csv'), 0, ',');
    finally
      Modern.Free;
    end;
  end;
end;

Yukarıdaki döngü OOXML tarafındaki verim kaldıracını da gösteriyor. StreamingWrite değerini true yapmak, çalışma sayfası XML'ini bellekte tek bir dev dizge olarak hazırlamak yerine doğrudan çıktı paketine akıtır; dosyalar yüz binlerce satıra ulaştığında rahat bir çalıştırma ile bellek yetmezliği çökmesi arasındaki fark budur. Bu kipin boyutlandırma ve bellek davranışı sunucu yığın işleri için akışlı yazma üzerine yazımızda ayrıca ele alınıyor. Her çekirdeği kullanmak isteyen bir yığın için bir özellik daha önemlidir: hiçbir cephe iş parçacığı güvenli değildir ama hiçbiri genel durum da paylaşmaz; dolayısıyla paralel dönüşüm için desteklenen desen, aralarında kilitleme olmaksızın işçi iş parçacığı başına bir çalışma kitabı örneğidir

Parolalı dosyalar ve onlarla ne yapmalı

Arşivin kilitli dosyaları biçime göre temiz biçimde ayrılır ve bu ayrım nereye gideceklerini belirler. Eski .xls şifrelemesi, ister RC4, ister CryptoAPI üzerinden RC4, ister eski XOR gizlemesi olsun, okunabilir: parolayı Open çağrısına verin, dosya diğerleri gibi dönüşür. Şifreli .xlsx paketleri ise başka bir hikâye. HotXLS onları CanReadEncrypted ile tespit eder ama şifrelerini çözemez; dolayısıyla tek dürüst hamle, bir insanın her birini Excel'de açıp yeniden kaydettiği bir kuyruğa yönlendirmek ve dosyayı ondan sonra hatta geri katmaktır. Bu asimetriyi baştan tasarıma katmak gerekir; çünkü şifreli XLSX dosyaları, birinin gerçekten önemsediği kayıtlar olma olasılığı en yüksek olanlardır

Döngüyü doğrulamayla kapatmak

Üçüncü aşama atlanan aşamadır ve onu atlamak toplu bir dönüşümü bir yükümlülüğe çevirir. HotXLS'te hiçbir kaydetme yolu formülleri değerlendirmez. Excel bir dosyayı açtığında yeniden hesaplar; dolayısıyla XLSX biçiminden XLSX biçimine bir dönüşüm doğru kalır ama bir CSV hedefi, hat önce hücrelerde Calculate çalıştırıp sonuçları geri yazmadıkça formül metnini olduğu gibi alır. Bunu önceden bilmek, sayılarla dolu bir CSV ile aşağı akış bir içe aktarma onlara takılana dek kimsenin fark etmediği =SUM(...) dizgeleriyle dolu bir CSV arasındaki farktır

Doğrulamanın kendisi, dışarıda bırakmayı mazur gösteremeyecek kadar ucuzdur. Dönüştürülen her dosyayı aynı kütüphaneyle yeniden açın, denetim sayaçlarını yeniden çalıştırın ve envanter geçişinin zaten kaydettiği dönüşüm öncesi sayılarla karşılaştırın. Düşmüş bir sayfa sayısı, kaynakta üç varken sıfıra inen bir grafik sayısı, uçurumdan yuvarlanan bir hücre sayısı: her biri, tek bir açma maliyetiyle yakalanmış sessiz bir kayıptır. Bunun üstüne Excel ya da LibreOffice içinde gözle bir örneklem denetimi yapın; bu bileşim, dönüşüm hasarının ezici çoğunluğunu daha yola çıkmadan yakalar. Envanter aşamasının doğrulama aşamasını beslemesinin bütün nedeni budur. Önceki sayılar olmadan sonraki sayılar hiçbir şey kanıtlamaz

Önce denetim yapan bir tezgâh, riskli bir toplu dönüşümü, temiz geçemeyen dosyalar için karantina şeridi olan ölçülebilir bir sürece çevirir. Burada gösterilen bütün yoklama, sayma ve dönüştürme çağrıları HotXLS Delphi Component parçasıdır ve hepsini Excel otomasyonu olmadan süreç içinde yerel olarak çalıştırır