Teknik Makale

Delphi ile XLS, XLSX, ODS ve CSV için Çekme Satır İmleci

HotXLS .xls, .xlsx, .xlsm, .ods, CSV ve TSV kaynaklarını tek bir çekme satır imleci olan TXLSRowCursor aracılığıyla okur; imlecin FindFirst ve FindNext yordamları her çağrıda bir mantıksal satır ilerler ve yalnızca o satır bellekte kalır. Altı değerli bir durum makinesi, ilk-öncesi durumunu EOF, iptal edilen ve hatalı olandan ayırır; eski geri çağrım okuyucu artık aynı imlecin üzerinde bir bağdaştırıcıdır

Bu senaryo, bir içe aktarım özelliği sunmuş herkese tanıdıktır. 200 MB boyutunda bir .xlsx gelir, bir OnCell işleyicisi bağlarsınız ve "oku" isteğinden sonraki ilk gereksinim "ters çevrilen ilk yüz kayıttan sonra dur" olur. Artık kodunuzun biçimi size karşı savaşır: döngü kitaplığın içindedir, işleyicinizin bir bayrak yükseltmesi gerekir, ayrıştırıcı fark edene kadar sonraki her geri çağrım yine tetiklenir ve biriken durum — şimdiye kadar kaç eşleşme, hangi sütun eşleşti, sırada ne yapmalı — yalnızca geri çağrıma oturacak bir yer vermek için var olan bir sınıfın alanlarında yaşamak zorundadır. Bunların hiçbiri bir ayrıştırma sorunu değildir. Bu bir denetim akışı sorunudur ve çekme imlecinin ortadan kaldırdığı şey de budur

200 MB boyutunda bir gönderme geri çağrımının gerçek maliyeti nedir?

Gönderme denetimi tersine çevirir ve tersine çevirme, tam olarak filtreleyen ya da birleştiren bir çağıranın karşılayamayacağı şeydir. Geri çağrım API ile döngünün sahibi kitaplıktır; bu yüzden çağıran Break kullanamaz, iki kaynağı araya seremez, okuyucuyu sürülmeyi bekleyen bir yordama veremez ve "karar vermeden önce sonraki satıra göz at" isteğini arabellekleme olmadan ifade edemez. Maliyet aktarım hızı değildir — iyi yazılmış bir SAX geri çağrım yolu akışı sorunsuz yapar — maliyet, önemsiz olmayan her tüketicinin, yazmasına izin verilmeyen döngüyü taklit etmek için kendi küçük durum makinesini büyütmesidir. Bunu, her biri tarihsel olarak kendi tarama giriş noktasına sahip dört dosya biçimiyle çarpın; filtreleme, formül ve hata semantiği aralarında ayrışmaya başlar ve HotXLSin kapatmak için yola çıktığı kayma tam olarak budur

Çekme imleci çağıran kodunuzu nasıl değiştirir?

Döngüyü size geri verir ve onunla birlikte sıradan Pascal denetim akışını. TXLSRowCursor.Open bir dosya adı ya da bir TStream kabul eder, biçimi algılar, paylaşılan dizgeleri ve tarih stili meta verilerini bir kez yükler ve 1. sayfayı seçer. SelectSheet (1 tabanlı) ya da SelectSheetByName başka bir çalışma sayfasına yeniden hedefler ve imleci ilk-öncesi durumuna sıfırlar. FindFirst ve FindNext bundan sonra bir sonraki dolu satıra konumlanır — çözülebilir hücresi olmayan satırlar atlanır, bu yüzden RowIndex sıçrayabilir — ve geçerli satır CellCount, Cells[] ve ValueByCol[] olarak sunulur; hepsi sütun ekseninde 1 tabanlıdır. Döngüden çıkmak bir Break olur

var
  Cursor: TXLSRowCursor;
  Hits: Integer;
begin
  Cursor := TXLSRowCursor.Create;
  try
    Cursor.FirstRow := 2;        // başlık bandını atla
    Cursor.IncludeColumn(1);     // yalnızca bu iki sütunu çöz
    Cursor.IncludeColumn(7);
    if not Cursor.Open('postings-200mb.xlsx') then
      Exit;
    if not Cursor.SelectSheetByName('Ledger') then
      Exit;

    Hits := 0;
    if Cursor.FindFirst then
      repeat
        if VarToStr(Cursor.ValueByCol[7]) = 'REVERSED' then
        begin
          Inc(Hits);
          if Hits = 100 then
            Break;               // sıradan Break; iptal bayrağı yok, nöbetçi yok
        end;
      until not Cursor.FindNext;
  finally
    Cursor.Free;                 // yıkıcı geçişi sonlandırır
  end;
end;

İzdüşüm ve aralık geçişten önce ayarlanır, sonradan filtrelenmez. FirstRow, LastRow, IncludeColumn, ClearColumnProjection, IncludeFormulaText, DetectDates ve DetectTextTypes seçeneklerinin hepsi arka uçların içinde onurlandırılır; bu yüzden seçilmemiş bir sütun hiçbir zaman değerini, formül dizgesini ya da zengin metin yükünü en başta ayırmaz — regresyon paketi bunu 16 KiB boyutundaki formüllerle ve sütunları izdüşümlenmediğinde hiçbir zaman maddeselleştirilmeyen önbellekli dizgelerle kanıtlar. Bu seçenekler bir geçiş etkinken bilinçli olarak dondurulur ve EOF noktasında, SelectSheet üzerinde ya da Close sonrasında yeniden yazılabilir hale gelir; böylece tek bir tarama asla iki çözümleme sözleşmesini karıştıramaz. Satırlar yerine yalnızca sayfa envanterine gereksiniminiz varsa, yalnızca meta veri ve seçici sayfa yükleme daha ucuz giriş noktasıdır

Biçim başına bir arka uç, her birine bir tarama döngüsü

HotXLS içinde her biçimin tam olarak bir ileri tarayıcısı vardır ve hem çekme imleci hem de geri çağrım okuyucu aynı tarayıcıyı sürer. TXLSXForwardRowBackend, ECMA-376 Part 1 §18.3 sayfa bölümleri için tek çalışma sayfası SAX durum makinesidir; XML okuyucuyu, paylaşılan formül tablosunu ve zengin metin ayrıştırıcısını tutar ve çağrı başına tam olarak bir fiziksel <row> sınırına ilerler. TXLSBiffForwardParser, [MS-XLS] kayıt akışı için genel verilerin, sayfa seçiminin ve satır ilerletmesinin sahibidir; onu duraklatılabilir yapmak tüm tasarımın en keskin kısıtını doğurdu, çünkü önbellekli dizge formülü, hemen ardından bir String kaydının geldiği bir Formula kaydıdır; bu yüzden satır başı askı noktası asla ikisinin arasına düşmemelidir. TXLSForwardTextBackend BOM farkında bir okuyucu, etkin ayraç ve bir mantıksal kayıt tutar — CSV ilk kayıttan virgül, noktalı virgül, sekme ya da dikey çubuğu tırnak içi karakterleri yok sayarak sezer ve çok satırlı tırnaklı alanlar #10 ile birleştirilir, böylece satır numarası fiziksel yeni satırlar yerine mantıksal kayıtları izler. TXLSForwardOdsBackend OpenDocument §9 tabloları için tek bir fiziksel satır kalıbı tutar, table:number-rows-repeated değerini bir genişletme yerine kalan sayı olarak ele alır ve kapsanan hücreleri değer yaymadan geçer. Akışlı doğrudan okuyucu aynı paylaşılan dizge ve tarih stili yükleyicisini paylaşır

HotXLS çekme satır imlecinin biçim başına bir ileri tarayıcıya dağıtımı: XLSX için bir SAX arka ucu, BIFF için bir kayıt ayrıştırıcısı, ayraç sezen bir metin arka ucu ve bir ODS satır kalıbı; geri çağrım okuyucu üstte bir bağdaştırıcı olarak yapılandırılır
Her biçimin tam olarak bir ileri tarayıcısı vardır ve hem çekme imleci hem de geri çağrım okuyucu aynı tarayıcıyı sürer; böylece filtreleme ve hata semantiği ayrışamaz

Neden tek bir Eof bayrağı yerine altı durum?

Çünkü tek bir boole değeri dört farklı durumu birbirinden ayırt edilemez kılar ve çağıranlar hepsi hakkında yanlış tahmin yürütür. TXLSRowCursorState bunları açıkça adlandırır

  • xrcsClosed — açık kaynak yok
  • xrcsBeforeFirst — açıldı ya da yeniden hedeflendi, henüz satır okunmadı
  • xrcsActive — geçerli bir satır üzerinde duruyor
  • xrcsEof — sayfa sonuna kadar tüketildi
  • xrcsCancelled — çağıran geçişi bilinçli olarak durdurdu
  • xrcsFaulted — geçiş başarısız oldu ve özgün özel durum yükseltildi

Üretimde önemli olan son ayrımdır. Eksik bir çalışma sayfası bölümü ya da başarısız bir geçiş başlangıcı EReadError hatasını korur ve imleci xrcsFaulted durumuna taşır; asla bir çağıranın "bu sayfa boştu" diye okuyacağı düz bir False değerine indirgenmez. Cancel, bilinçli olarak Close işlevinden daha dardır: geçerli çalışma sayfası arka ucunu ve onun açma alt akışını kapatır ve geçerli satırı geçersiz kılar, ancak ZIP arşivini ya da kaynak akışı bırakmaz ve onu iki kez çağırmak etkisiz bir işlemdir. İptalden sonra açıkça SelectSheet çağırarak devam edersiniz — imleç sizin adınıza sessizce bir geçişi yeniden başlatmaz. Akış sahipliği aynı savunmacı kuralı izler: xsoBorrowed varsayılandır ve kapanışta akış konumunu geri yükler; xsoOwned ise sahipliği ancak Open zaten başardıktan sonra devreder, böylece başarısız bir açılış çağıranın hâlâ tuttuğu bir akışı asla serbest bırakmaz

HotXLS satır imlecinin altı durumu ve aralarındaki geçişler; Cancel işlevinin etkin bir geçişi iptal edilene taşıması, başarısız bir geçiş başlangıcının onu hatalıya taşıması ve ikisinin de sayfa sonundan nasıl ayrı kaldığı
Altı adlandırılmış durum, boş bir sayfayı, bilinçli bir durdurmayı ve başarısız bir geçişi ayırt edilebilir tutar; tek bir Eof boole değeri bunu yapamaz
var
  Cursor: TXLSRowCursor;
  Src: TFileStream;
begin
  Src := TFileStream.Create('quarter.ods', fmOpenRead or fmShareDenyWrite);
  try
    Cursor := TXLSRowCursor.Create;
    try
      // xsoBorrowed: imleç Src akışını asla serbest bırakmaz ve Close, Open
      // çağrıldığında akışın sahip olduğu konumu geri yükler
      if not Cursor.Open(Src, xffAuto, xsoBorrowed) then
        Exit;

      if Cursor.FindFirst then
        repeat
          if UserPressedStop then
          begin
            Cursor.Cancel;   // çalışma sayfası arka ucunu ve onun açma
            Break;           // alt akışını kapatır; idempotent
          end;
        until not Cursor.FindNext;

      case Cursor.State of
        xrcsEof:       Log('sheet consumed to the end');
        xrcsCancelled: Log('stopped by the operator');
        xrcsFaulted:   Log('pass failed; the EReadError was already raised');
      end;
    finally
      Cursor.Free;
    end;
  finally
    Src.Free;                // hâlâ bizim, hâlâ geçerli, konum geri yüklendi
  end;
end;

Geçerli satırı kopyalamadan ödünç almak

IXLSRowCursorView bir satırı hücre dizisini çoğaltmadan başka bir yordama verir. Görünüm, imleç işaretçisini tutan paylaşılan bir koruyucu ile bir UInt64 nesil sayacı depolar; ilerletmek, sayfa seçmek, iptal etmek, kapatmak ve imleci yıkmak o nesli hep artırır ve yıkım ayrıca koruyucu sahibini temizler. Böylece bayat bir görünüm boşaltılmış belleği okuyamaz: Valid, istediğiniz zaman çağırabileceğiniz özel durum üretmeyen bir yoklamadır; diğer her üye ise önce doğrular ve EXLSRowCursorViewInvalidated hatasını yükseltir. Bu sözleşmenin ne olduğu konusunda dürüst olun — bu yaşam süresi hızlı başarısızlığıdır, bir iş parçacığı güvenliği garantisi değildir ve birinci iş parçacığı imleci ilerletirken ikinci bir iş parçacığından satır okumaya izin vermez

var
  View: IXLSRowCursorView;
  Cell: TXLSRowCursorCell;
  I: Integer;
begin
  if Cursor.FindFirst then
    repeat
      View := Cursor.CurrentRowView;      // ödünç alır; hiçbir hücre dizisi kopyalanmaz
      for I := 0 to View.CellCount - 1 do
      begin
        Cell := View.Cells[I];
        if Cell.HasFormula and not Cell.FormulaTextAvailable then
          UseCachedResult(Cell.Value)     // BIFF ileri okumaları belirteçleri değil
        else if Cell.Kind = xdkEmpty then //   önbelleğe alınmış sonucu tutar
          UseStyleOnly(Cell.StyleIndex)   // Blank / MulBlank gerçek hücrelerdir
        else
          UseValue(Cell.Col, Cell.Value);
      end;
    until not Cursor.FindNext;

  // Arabirim döngüden uzun yaşar ama ardındaki satır yaşamaz
  if not View.Valid then    // Valid asla hata vermez; Cells[] şimdi verirdi
    View := nil;            // EXLSRowCursorViewInvalidated
end;

PeakRowBufferedBytes ve kanıtlamasına izin verilen şey

PeakRowBufferedBytes, belleğin satır sayısını değil satır genişliğini izlediğini göstermek için vardır. Geçerli çıktı satırının hücre kayıtlarını, Variant değerlerini, formül dizgelerini ve zengin metin yüklerini biriktirir ve biçime özgü çalışma kümesini katlar — CSV mantıksal kaydı, ODS fiziksel satır kalıbı, BIFF kayıt tepe değeri ya da çözümlenmekte olan XLSX ham hücresi. Bunu, kaç çalışma sayfası geçişinin gerçekten başladığını sayan SheetPassesStarted ile birlikte okuyun. İki uyarı bunu dürüst tutar: rakam kesin yığın muhasebesi değil bir tahmindir ve en son Open işleminden beri monotondur; bu yüzden canlı bir ölçü aleti yerine bir hata ayıklama ve regresyon aracıdır. Çok büyük kitaplarda zamanın ve baytların nereye gittiğine ilişkin geniş tablo için Delphi ile büyük çalışma kitabı performansına bakın

Tüm sayfayı yükleyip her satırı yerleşik tutan yöntemle, yalnızca geçerli satırı ve bir biçim çalışma kümesini tutan çekme imlecini karşılaştıran bir HotXLS görünümü; PeakRowBufferedBytes işte bunu biriktirip bildirir
PeakRowBufferedBytes geçerli çıktı satırını ve biçime özgü çalışma kümesini biriktirir; böylece bellek sayfanın kaç satırı olduğunu değil bir satırın ne kadar geniş olduğunu izler

Gönderme okuyucu bir bağdaştırıcıya dönüştü ve imlecin yapmayacağı şeyler

TXLSForwardReader artık ayrı XLSX, BIFF ve metin tarama giriş noktaları taşımaz. Bir imleç yapılandırır, onu yürütür ve geçerli satırı OnSheet ve OnCell olaylarına çevirir; iki cephenin filtreleme, formül durumu ya da hata işleme konusunda artık ayrışamamasının nedeni budur. Yükseltmeden önce bilinmeye değer iki sonuç var: geri çağrım SheetIndex değeri TXLSForwardReader üzerinde artık tutarlı biçimde 1 tabanlıdır (TXLSDirectReader mevcut 0 tabanlı olay sözleşmesini korur) ve OnSheet olayı SelectSheet işlevinden önce tetiklenir; bu yüzden SkipSheet ayarlamak, çalışma sayfası bölümünün hiç açılmaması ya da hiç sıkıştırılmaması anlamına gelir. Sınırlar aynı ölçüde açıktır: bir geçiş etkinken çalışma kitabı değiştirilmemelidir, iptal açık bir yeniden başlatma gerektirir ve BIFF ileri yolu formül belirteçlerinin derlemesini asla açmaz; bu yüzden klasik formül hücreleri HasFormula değerini True, FormulaTextAvailable değerini False bildirir ve boş bir formül dizgesi icat etmek yerine önbelleğe alınmış sonucu size verir. Satır imleci ve bağdaştırıcısı, Delphi Win32 ve Win64 üzerinde 1.298 denetimi ve buna ek olarak C++Builder 37.0 Win64 statik paketini geçti

Bir çekme imlecini şu anda sahip olduğunuz yükleyiciyle tartıyorsanız, sorulacak soru hangisinin daha hızlı ayrıştırdığı değil, hangisinin gerçekten gereksinim duyduğunuz çıkış koşulunu yazmanıza izin verdiğidir. Tüm bileşen ayrıntıları, desteklenen IDE sürümleri ve lisanslama HotXLS Delphi elektronik tablo bileşeni sayfasında yer alır