Teknik Makale

HotXLS'te ODS Yinelenen Satırlar ve Yükseklik Run'ları

HotXLS Delphi Component, table:number-rows-repeated ve bir satır yüksekliği taşıyan bir ODS satırını, yinelenen her satır için bir yükseklik girdisi yerine tek bir TXLSXRowHeightRun kaydı olarak — ilk satır, son satır, tek yükseklik — saklıyor ve o satırların devraldığı boş hücre stilini tek bir aralık stil katmanına indiriyor. HotXLS 2.382.2'nin, kuyruğu 1.048.530 boş satır yineleyen bir tabloyu 2.382.1'in zaman aşımına uğradığı yerde 0,02 saniyede açmasının ve aynı dosyanın ODS'e bir milyon düz satır olarak değil de tekrar sayısı korunarak kaydedilmesinin bütün nedeni bu

Söz konusu dosya sıradan. LibreOffice Calc on dört sütunlu bir sayfayı 45 satır veriyle yazıyor, sonra altındaki her şeyi tek bir öğeyle tarif ediyor: <table:table-row table:style-name="ro1" table:number-rows-repeated="1048530"><table:table-cell table:number-columns-repeated="14"/></table:table-row>. ro1 stili style:row-height="0.452cm" ayarlıyor ve her <table:table-column> bir table:default-cell-style-name taşıyor; run içindeki her boş hücre bunu devralıyor. content.xml dosyasının tamamı 103 KB. Dosyada pahalı olduğunu söyleyen hiçbir şey yok; masraf tamamen bizim tarafımızdan geliyordu

HotXLS'in tek bir ODS yinelenen satırını nasıl kompakt duruma çevirdiği: table:number-rows-repeated değeri 1048530 ve stili ro1 olan content.xml öğesi, 46 ile 1048575 satırlarını 12,81 pt ile kapsayan tek bir TXLSXRowHeightRun kaydına ve sütun başına bir StyleOverlays girdisine karşılık gelir; 2.382.1 sürümü aynı öğeyi bir milyon SetRowHeight girdisine ve hücre nesnesine genişletiyordu
Tekrar sayısı, ro1 satır yüksekliği ve sütun varsayılan stilleri 45. satırın altındaki her boş satırı tarif eder, bu yüzden içe aktarıcı bir milyon koordinata dokunmadan tek bir run kaydı ve sütun başına katmanlar kurabiliyor

Tek bir yinelenen satır neden ODS içe aktarmayı zaman aşımına uğratıyor?

Çünkü içe aktarıcı onu genişletiyordu. 2.382.1'de satır bitirici, yinelenen her satır için bir kez SetRowHeight(RowIndex + i, RowHeight) çağırıyor ve her yüksekliği satır numarasıyla anahtarlanmış bir Name=Value string listesine yazıyordu. O listeye yapılan her ekleme, içinde zaten bulunan her şey üzerinde bir IndexOfName araması çalıştırıyordu; yani bir milyon yükseklik bir milyon doğrusal tarama demekti — HXLS-005'in açıldığı kuadratik liste araması. Aynı sırada OdsCommitRow, stil devralan her sütun için, yinelenen satırların her birinde bir hücre nesnesi somutlaştırıyordu, çünkü stilli bir boş hücre de hücre sayılıyordu

Kayıt tarafının da kendi sorun sürümü vardı. LibreOffice dosyası büyük tekrardan sonra bir ro1 satırı daha içeriyor, yani stilli en yüksek satır sayfanın en dibinde oturuyordu ve OdsBuildTableXml ona kadar her satırı dolaşıp <table:table-row> öğelerini teker teker yazıyordu. Ucuza içe aktarılmış bir workbook bile pahalıya yazılırdı. İçe aktarmayı düzeltip dışa aktarmayı düzeltmemek zaman aşımını ortadan kaldırmaz, sadece yerini değiştirirdi

HotXLS'te satır yüksekliği run'ı nedir?

Bir run, satır 46 ile 1.048.575 arasındaki tüm satırlar 12,81 punto yüksekliğinde ifadesini 1.048.530 kez söylemeden tarif edebilen en küçük şeydir. TXLSXRowHeightRun, FirstRow, LastRow ve Height alanlarından oluşan bir kayıttır; TXLSXRowHeightRuns bunların dinamik bir dizisidir ve her TXLSXWorksheet, mevcut satır başına yükseklik listesinin yanında FRowHeightRuns içinde bir tane tutar. ODS içe aktarmada satır bitirici artık tekrar sayısına göre dallanıyor: 1 olan sayım yine SetRowHeight çağırıyor, daha büyük olan her şey tüm aralık için bir kez XlsxAssignRowHeightRun çağırıyor. Aralık XlsxMaxRow değerine, yani 1.048.576'ya kırpılıyor; böylece sayfayı aşan bir tekrar sayısı reddedilmek yerine kısaltılıyor

XlsxAssignRowHeightRun dizinin tek yazıcısıdır ve run'ları kuruluşu gereği ayrık tutar. Yeni bir aralık verildiğinde tamamen dışında kalan her mevcut run'ı kopyalar, onunla kesişen herhangi bir run'ı öncesindeki ve sonrasındaki parçaya böler, sonra Present true ise yeni aralığı ekler — Present false ise hiçbir şey eklemez, ClearRowHeightın tek satırlık bir delik açması da böyle olur. Bundan iki şey çıkar. Dizi hiçbir zaman kesişen aralık içermez, bu yüzden bir arama ilk isabette durabilir. Dizi hiçbir zaman yerinde değiştirilmez; her çağrıda yeni bir kopya kurulur, bu da söz konusu boyutlarda hiçbir maliyet getirmez ve bütün bir aliasing hata sınıfını ortadan kaldırır

var
  Workbook: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Workbook := TXLSXWorkbook.Create;
  try
    // Kuyruk satırı tek bir satır stili altında 1.048.530 kez yinelenen bir sayfa
    Workbook.OpenODS('conditional-formatting.ods');
    Sheet := Workbook.Sheets[1];
    // İki okuma da aynı run üzerinden çözümlenir; hiçbir şey genişletilmedi
    Writeln(Sheet.RowHeight[46]:0:2, ' pt');
    Writeln(Sheet.RowHeight[1048575]:0:2, ' pt');
    // Tek satırlık bir override run'ı bölmeden gölgeler
    Sheet.RowHeight[500000] := 36;
    // Run içindeki bir satırı temizlemek run'ı iki parçaya keser
    Sheet.ClearRowHeight(500001);
    Writeln(Sheet.HasRowHeight(500001)); // False
    Writeln(Sheet.RowHeight[500002]:0:2, ' pt'); // hâlâ run yüksekliği
  finally
    Workbook.Free;
  end;
end;

Arama sırası ezberlenmeye değer kısım. TXLSXWorksheet.GetRowHeight önce satır başına listeye bakar ve run'lara yalnızca satırın açık bir girdisi yoksa danışır; HasRowHeight de aynısını yapar. Yani Sheet.RowHeight[500000] := 36 run'a hiç dokunmaz: satır başına listeye bir girdi ekler ve o girdi, önce arandığı için kazanır. ClearRowHeight bunun tersidir: varsa satır başına girdiyi kaldırır ve ardından Present = False ile XlsxAssignRowHeightRun çağırır, çünkü temizlenmiş bir satır bir run onu kapsasa bile yükseklik yok olarak okunmalıdır. ClearRowHeights iki yapıyı birden boşaltır

HotXLS'te satır yüksekliği run'ı ameliyatı: OpenODS sonrasında tek bir run 46 ile 1048575 satırlarını 12,81 pt ile kapsarken satır başına bir girdi 500000. satırı 36 pt yapar ve GetRowHeight önce satır başına listeye baktığı için aramayı kazanır; 500001. satırın ClearRowHeight çağrısı ise run'ı deliğin etrafında iki ayrık parçaya böler
XlsxAssignRowHeightRun temizlenen aralığın dışındaki parçaları kopyalar ve aralığın kendisi için hiçbir şey eklemez, böylece run'lar kuruluşu gereği ayrık kalır ve 500000. satırdaki override'a dokunulmadan bir arama ilk isabette durabilir

Devralınan boş hücre stilleri nereye gidiyor?

Hücre nesnelerine değil, sütun başına bir aralık stil katmanına. OdsCommitRow sütun değeri başına bunun kompakt bir boş olup olmadığına karar verir: satır birden fazla yineleniyor ve hücrenin değeri, formülü, zengin metni yok. Kompakt bir boş için gerçek hücreyi yalnızca run'ın ilk satırında oluşturur, devralınan stili ona uygular, sonra aynı altı stil indeksini — font, dolgu, kenarlık, sayı biçimi, hizalama, koruma — o sütunda run'ın ikinci satırından sonuna kadar kapsayan bir StyleOverlays.Add olarak kaydeder. İlk satırdan sonraki satırlar somutlaştırma döngüsünde tamamen atlanır

Regresyon testi bu biçimi somutlaştırıyor. İkinci satırı kalın bir sütun varsayılan stili altında 1.048.575 kez yinelenen bir sayfa açıldıktan sonra Sheet.Cells.Count değerinin 10'un altında olduğu doğrulanıyor ve Sheet.Cells[700000, 1].FontIndex hâlâ kalın fonta çözümleniyor — katman, o koordinata dokunulduğu anda stili sağlıyor. Bu, XLSX tarafında biçimlendirilmiş ama boş bir sütunun bir milyon hücreye mal olmasını engelleyen mekanizmanın aynısı; satır bloğu hücre depolaması ve aralık stil katmanları notları katmanların nasıl üst üste bindiğini ve çözümlendiğini ele alıyor. Burada yeni olan, ODS içe aktarıcısının bunları bir uygulamanın bir aralığı biçimlendirmesini beklemeden, tekrar sayısından kendisinin oluşturması

SaveAsODS tekrar sayısını nasıl geri yazıyor?

Sayfanın boş kuyruğunu yalnızca gerçekten bir şeyin değiştiği yerlerde bölerek. OdsBuildTableXml artık iki sınır takip ediyor: değer, formül, hyperlink ya da elle satır sonu taşıyan son satır olan contentMaxRow ve buna ek olarak yalnızca stilli boş hücreleri, tek satırlık yükseklikleri, her run'ın LastRow değerini ve her katmanın alt kenarını da kapsayan maxRow. Yalnızca stilli boş bir hücre artık içerik sayılmıyor — onu dışlayan şey TXLSXCells.IsStyleOnlyBlank — böylece LibreOffice dosyasındaki sondaki stilli satır, içerik sınırını sayfanın dibine çekmeyi bırakıyor

contentMaxRow üstündeki satırlar eskisi gibi teker teker yazılıyor. Altındaysa yazıcı nextRow değerini şunların en küçüğü olarak hesaplıyor: sonraki run'ın FirstRow değeri, geçerli run'ın LastRow + 1 değeri, sonraki tek satırlık yükseklik girdisi, sonraki katman kenarı ve sonraki somutlaştırılmış hücre. Ardından geçerli satırdan nextRow - 1 değerine kadar her şey, table:number-rows-repeated değeri farka ayarlanmış tek bir <table:table-row> olarak yazılıyor; sütun başına bir <table:table-cell/> taşıyor ve o sütunu bir katman kapsıyorsa katmanın çözümlediği stil adı kullanılıyor. Satır stilinin kendisi TOdsAutoStylePool.RowStyleFor(AHidden, ABreakBefore, AHeightSpec) fonksiyonundan geliyor; bu fonksiyon artık yükseklik metnini — diyelim 12.81pt — gizli ve sayfa sonu bayraklarının yanında tekilleştirme anahtarına katıyor, böylece run'daki her satır tek bir style:row-height özelliği taşıyan bir ro<N> stilini paylaşıyor

SaveAsODS'in run destekli bir sayfa için yazdığı şey: contentMaxRow değerlerin bittiği 45. satırda dururken maxRow yükseklik run'ı ve override'ları boyunca uzanır, sınırın üstündeki satırlar teker teker yazılır ve kuyruk, satır stili RowStyleFor'dan gelen ve hücre stilleri katmanlar üzerinden çözümlenen yinelenen table-row öğeleri olarak yazılır
Her yinelenen öğe tek bir tekdüze parçayı kapsar ve sonraki run kenarında, yükseklik girdisinde, katman kenarında ya da somutlaştırılmış hücrede durur; böylece doğrulamasız bir sayfa bir avuç öğeyle kaydedilirken doğrulamalar ya da XLSX dışa aktarımı satır başına ödeme yapar
var
  Workbook, Reopened: TXLSXWorkbook;
  Saved: TMemoryStream;
begin
  Workbook := TXLSXWorkbook.Create;
  Reopened := TXLSXWorkbook.Create;
  Saved := TMemoryStream.Create;
  try
    Workbook.OpenODS('conditional-formatting.ods');
    Workbook.Sheets[1].RowHeight[500000] := 36;
    Workbook.Sheets[1].ClearRowHeight(500001);
    // Boş kuyruk bir milyon değil, bir avuç yinelenen satır olarak yazılıyor
    Workbook.SaveAsODS(Saved);
    Writeln('ODS size: ', Saved.Size, ' bytes');
    Saved.Position := 0;
    Reopened.Open(Saved);
    // Override, delik ve run round-trip'ten sağ çıkıyor
    Writeln(Reopened.Sheets[1].RowHeight[500000]:0:2);   // 36.00
    Writeln(Reopened.Sheets[1].HasRowHeight(500001));    // False
    Writeln(Reopened.Sheets[1].RowHeight[500002]:0:2);   // run yüksekliği
  finally
    Saved.Free;
    Reopened.Free;
    Workbook.Free;
  end;
end;

Bunu sabitleyen test, yükseklik run'ı 1.048.575 satırı kapsayan ve ortasına bir override ile bir delik açılmış bir sayfa için kaydedilen akışın 64 KB'ın altında olduğunu doğruluyor. Bu sayının yanına iki dürüst sınır yakışır. Birincisi, herhangi bir veri doğrulaması içeren bir worksheet contentMaxRow değerini maxRow yapar, yani doğrulamalar o sayfada kuyruk sıkıştırmayı devre dışı bırakır ve sayfa yine satır satır yazılır. İkincisi, XLSX'te tekrar özniteliği yoktur — bir SpreadsheetML <row> tek bir satırı tarif eder — bu yüzden run destekli bir sayfayı .xlsx olarak dışa aktarmak, run'ın kapsadığı satırları numaralandırıp her birine bir ht özniteliği yazar. Model bellekte kompakt kalır; dosyanın nasıl görüneceğine dosya biçimi karar verir

Satır numaralarını değiştiren her düzenleme run'lara ne borçlu?

Bakım. Satır üst verisinin yeni bir temsili, ancak satır numaralarını değiştiren her işlem onu yanında durduğu satır başına listelerle birlikte taşıyorsa doğrudur ve bu commit o işlemlerin her birine dokunuyor. InsertRows ve DeleteRows, XlsxShiftRowHeightRuns üzerinden geçer; bu fonksiyon diziyi, her run'ın düzenleme noktasından önce kalan parçasını koruyarak, silme penceresinin içine düşeni atarak ve kalanı delta kadar kaydırarak yeniden kurar — yani bir eklemenin ortasına denk gelen bir run aralarında boşluk olan iki run'a dönüşür, bir silmenin ortasına denk gelen run ise küçülür. TileRangeAxisMetadata run'ları tüm döşenen aralık boyunca temizler, sonra her kaynak run'ı kopya başına bir kez kendi offset'inde yeniden kaydeder. TXLSXWorksheet.CopyFrom ve TXLSXSheets.AddCopy dizinin kendisini atamak yerine Copy() değerini alır; testin bir kopyada tüm yükseklikleri temizleyip orijinal sayfayı 1.048.576. satırda hâlâ sağlam bulmasının nedeni de budur

var
  Sheet: TXLSXWorksheet;
begin
  Sheet := Workbook.Sheets[1];
  Sheet.RowHeight[500000] := 36;
  Sheet.ClearRowHeight(500001);
  // 500000'e iki satır ekle: override 500002'ye, delik 500003'e kayar
  Sheet.InsertRows(500000, 2);
  Writeln(Sheet.RowHeight[500002]:0:2);   // 36.00
  Writeln(Sheet.HasRowHeight(500003));    // False
  // Yeniden sil: her şey geri kayar
  Sheet.DeleteRows(500000, 2);
  Writeln(Sheet.RowHeight[500000]:0:2);   // 36.00
  // 2..4 satırlarını sayfada iki kez döşe; run yükseklikleri her kopyayı izler
  Sheet.TileRangeAxisMetadata(2, 1, 3, 1, 2, 1);
  Writeln(Sheet.RowHeight[7]:0:2);        // run yüksekliği
end;

Okuma tarafındaki sınırların da aynı yükümlülüğü var. GetUsedRange alt kenarını her run'ın FirstRow ve LastRow değerine kadar yükseltiyor ve BuildRowMajorCellOrder üst veriyi kapsayan maksimum satırını her run boyunca uzatıyor, böylece XLSX yazıcısı yalnızca yükseklik taşıyan satırları da ziyaret ediyor. HotXLS nesne modelinin üzerine kendi satır anahtarlı yapınızı eklerseniz kontrol listesi şu: ekleme, silme, döşeme, kopyalama, kullanılan aralık ve her serileştirici. Birini kaçırırsanız hata sessiz olur — yükseklikler ekleme sayısı kadar kayar ve hiçbir şey istisna fırlatmaz

Neler satır başına kalıyor ve sayılar şimdi nasıl görünüyor

Gizli bayrakları, outline düzeyleri ve daraltılmış durum hâlâ genişliyor. Satır bitirici, yinelenen her satır için bir kez SetRowHidden ve SetRowOutlineLevel çağırıyor; yani bir milyon satırlık kuyruğu gizleyen ya da bunu bir table:table-row-group içine yerleştiren bir sayfa, bu özniteliklerin her biri için satır başına bir girdi ödüyor. 2.382.2 değişikliği HXLS-005'in gerçekten ölçtüğü iki şeyle sınırlı — yükseklikler ve devralınan boş stiller — ve aynı run tekniği, bir dosya talep ettiği gün diğerlerine de uygulanabilir. ODS reader ayrıca style:use-optimal-row-height üzerinde işlem yapmıyor; optimal diyen ve bir yükseklik veren bir satır stili, o yükseklikle içe aktarılıyor

Corpus karşısında conditional-formatting.ods artık aç, doğrula, kaydet, yeniden aç ve yeniden doğrula döngüsünü Win32'de 0,178 saniyede, Win64'te 0,158 saniyede tamamlıyor; açma aşamasının kendisi 0,020 saniye ve daha önce tükettiği 60 saniyelik bütçenin içinde kalıyor. Biçimin geçtiği workbook düzeyi arayüzler ODS dosyalarını açma ve kaydetme yürüyüşünde, büyük dosyalar için daha geniş kol seti büyük workbook performansı notunda anlatılıyor; ODF satır öğesinin kendisi, tekrar ve stil öznitelikleriyle birlikte ODF 1.3 Part 3 §9.1.4'te tanımlı

HotXLS, XLS, XLSX ve ODS dosyalarını Excel ya da LibreOffice kurulu olmadan yerel Delphi ve C++Builder kodundan okur ve yazar; bir milyon satırlık tekrarın harici bir sürece devredilecek değil, kütüphanenin iyi modellemesi gereken bir şey olmasının nedeni de budur — HotXLS Delphi spreadsheet component sayfası desteklenen biçimleri ve RAD Studio sürümlerini listeliyor