Teknik Makale

HotXLS'te ODS yinelenen satırlar: satır yüksekliği run'ları

45 satır veri taşıyan on dört sütunluk bir elektronik tablo, ODS olarak kaydedildiğinde genelde beklediğinizden bir eleman fazla ile biter: <table:table-row table:style-name="ro1" table:number-rows-repeated="1048530"><table:table-cell table:number-columns-repeated="14"/></table:table-row>. O tek satır, 46'dan aşağısı ızgaranın dibine dek her satırı tarif eder. Gerçekten genişletin — o satırın 1.048.530 kez yazılmış hâlini — küçücük bir content.xml bir milyon satır kaydına, bir milyon yükseklik girdisine ve sütunlar varsayılan hücre stili taşıyorsa neredeyse on beş milyon stilli boş hücreye dönüşür

HotXLS bir ODS yinelenen satırını kompakt duruma nasıl çevirir: table:number-rows-repeated 1048530 ve ro1 stili taşıyan content.xml elemanı, 46'dan 1048575'e dek 12.81 pt'lik tek bir TXLSXRowHeightRun kaydına artı aynı stilli her sütun aralığı için bir StyleOverlays girdisine eşlenir; aynı elemanın bir milyon SetRowHeight girdisine ve hücre nesnesine genişletilmesi yerine
Tekrar sayımı, ro1 satır yüksekliği ve sütun varsayılan stilleri, 45. satırın altındaki her boş satırı tarif eder; böylece içe aktarıcı bir milyon koordinata dokunmadan bir run kaydı ve birkaç sütun aralığı overlay'i kurabilir

Tek bir yinelenen satırın içe aktarımı neden pahalıya gelir?

Maliyet genişlemeden gelir ve genişleme, içe aktarıcı yazmanın en bariz yoludur. ro1 stili style:row-height="0.452cm" koyar; bu 12.81 puandır. Yinelenen satır başına bir SetRowHeight(RowIndex + i, RowHeight) çağrısıyla uygulayan bir satır bitiricisi, nesne modelinin tuttuğu satır başına yükseklik tablosuna 1.048.530 girdi yazar. O tablo, her yazım önce mevcut anahtarı arayan anahtarlı bir string listesiyse içe aktarma tekrar sayısına göre kuadratiktir; bir milyonluk tekrar sayısı, kuadratiğin kuramsal olmaktan çıktığı girdinin ta kendisidir

Boş hücreler işi daha da bozar. Böyle bir dosyada her <table:table-column> çoğunlukla bir table:default-cell-style-name adlandırır ve yinelenen satırdaki her boş hücre onu miras alır. Stilli bir boş hücre, çoğu nesne modeline göre hâlâ bir hücredir. Yinelenen satır başına sütun başına bir tane somutlaştırırsanız bellek faturası yükseklik tablosunu gölgede bırakır

Kaydetme tarafında aynanın öbür yüzü vardır. Model, 1.048.575. satırın yüksekliği olduğunu söylediği anda, 1. satırdan üzerinde bir şey olan son satıra dek yürüyen bir yazıcı bir milyon <table:table-row> elemanı üretir. Ucuz içe aktarımın ardından pahalı kayıt, maliyeti yalnızca başka bir düğmeye taşır

HotXLS yinelenen satır yüksekliğini nasıl saklar?

HotXLS Delphi Excel Component, yinelenen bir ODS satır yüksekliğini run olarak saklar: FirstRow, LastRow ve tek bir Height taşıyan bir TXLSXRowHeightRun kaydı. TXLSXWorkbook.OpenODS, tekrar sayısı 1 olan bir satırı bitirdiğinde herkes gibi SetRowHeight çağırır. Daha büyük her sayım için bütün kuşağı tek run olarak kaydeder; böylece örnekteki kuyruk, 12.81 pt'te 46'dan 1.048.575'e dek olan satırlar olur. Izgaranın sonunu aşan bir tekrar sayısı reddedilmez, 1.048.576. satırda kesilir

İçe aktarıcıda, sonucu kaynak dosyayla karşılaştıranları şaşırtan bir kural vardır. ODS yazan tablo uygulamaları, kimsenin boyutlandırmadığı satırlar dahil her satır stiline bir style:row-height vermeye ve bunları style:use-optimal-row-height="true" ile işaretlemeye eğilirlidir. Hiç hücre saklamayan bir satır için otomatik yükseklik zaten varsayılan yüksekliktir; HotXLS bu yüzden ona bir yükseklik kaydetmez. Bir şey saklayan satırlar, optimal işaretli olsalar bile yüksekliklerini korur; çünkü içeriğine sığmak için büyümüş bir metin kaydırmalı satır, çalışma kitabı XLSX olarak yeniden açıldığında küçülüp kalırdı. Ödünleşim doğrudur: gerçek bir yüksekliği düşürmek görünür bir düzen değişimi üretir, gereksiz birini tutmak tek bir run'a mal olur

Yükseklikleri geri okumak sıradan RowHeight özelliğinden geçer. Önce satır başına listeye bakar, satırın açık bir girdisi yoksa run'lara düşer; HasRowHeight aynı sırayı izler. Run'lar hiç örtüşmediği için geri düşüş, satırı içeren ilk run'da durur

var
  Workbook: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Spans: TXLSXAxisSpans;
  I: Integer;
begin
  Workbook := TXLSXWorkbook.Create;
  try
    // Son satır elemanı 1.048.530 kez yinelenen bir sayfa
    Workbook.OpenODS('tail-repeat.ods');
    Sheet := Workbook.Sheets[1];  // Sheets 1 tabanlıdır
    // Saklı satır üstverisi: önce satır başına girdiler, sonra run'lar
    Spans := Sheet.GetStoredAxisSpans(True);
    for I := 0 to High(Spans) do
      Writeln(Spans[I].FirstIndex, '..', Spans[I].LastIndex);
    // İki okuma da aynı run'dan gelir
    Writeln(Sheet.RowHeight[46]:0:2, ' pt');
    Writeln(Sheet.RowHeight[1048575]:0:2, ' pt');
    // Satır başına girdi, run'dan önce arandığı için o kazanır
    Sheet.RowHeight[500000] := 36;
    // Run içinde bir satırı silmek ona tek satırlık bir delik açar
    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;

Örnekteki iki düzenleme bilerek farklı davranır. Sheet.RowHeight[500000] := 36 run'a dokunmaz ve satır başına listeye bir girdi ekler; o girdi önce bakıldığı için run'ı gölgeler. ClearRowHeight böyle çalışamaz; silinmiş bir satır, üzerine run uzansa bile yükseklik yok bildirmek zorundadır. O yüzden önce satır başına girdileri siler, sonra run listesini yeniden kurar: silinen satırın dışındaki her run olduğu gibi kalır, onu içeren run öncesi ve sonrası diye ikiye bölünür ve 500.001. satır için hiçbir şey saklanmaz. GetStoredAxisSpans(True) artık 46..500000 ile 500002..1048575'i iki ayrı run olarak, 500.000'in tek satırlık girdisinin yanında listeler. ClearRowHeights iki yapıyı tek çağrıda boşaltır

HotXLS'te satır yüksekliği run cerrahisi: OpenODS'ten sonra tek run 46'dan 1048575'e dek 12.81 pt'yi kapsar; satır başına bir girdi 500000'i 36 pt'ye koyar ve RowHeight önce satır başına listeye baktığı için aramayı kazanır; 500001'in ClearRowHeight'ı run'ı deliğin çevresinde ayrık iki parçaya böler
ClearRowHeight, run'ın silinen satırın dışındaki parçalarını tutar ve satırın kendisi için hiçbir şey saklamaz; böylece run'lar hiç örtüşmez, arama ilk buluşta durabilir ve 500000'deki geçersiz kılmaya dokunulmaz

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

Miras kalan boş stiller stil overlay'lerine dönüşür: arkalarında hücre nesnesi olmayan, stil taşıyan dikdörtgenler. Bir satırı okurken içe aktarıcı, yinelenen hücreleri aynı stile çözülen bitişik sütun aralıkları hâlinde gezer. Stili tam olarak Default görünen bir aralık hiçbir şey saklamaz. Gerçek biçimlendirme taşıyan bir aralık — söz gelimi ortalanmış ya da koyu bir sütun varsayılanı — tüm sütunlarını kapsayan tek satır değeri olur

O satır değeri işlendiğinde ve satır yineleniyorsa, HotXLS aralığın sol üstünde tek bir hücre somutlaştırır, miras kalan stili ona uygular ve altı stil indeksini (font, dolgu, kenarlık, sayı biçimi, hizalama, koruma) bütün dikdörtgen için Sheet.StyleOverlays.Add ile kaydeder: aralıktaki her sütun, ilk yinelenen satırdan sonuncusuna dek. Kalan 1.048.529 satır için başka hiçbir şey kurulmaz. Sheet.Cells.Count, gerçekten veri tutan hücre sayısına yakın kalır

Dikdörtgenin içindeki bir hücre, kodunuz ona dokunduğu anda overlay'i üzerine alır. Kuyruğu koyu bir sütun varsayılanıyla biçimlendirilmiş bir sayfayı açtıktan sonra Sheet.Cells[700000, 1].FontIndex, bir satır önce o koordinatta hiç hücre olmamış olsa da koyu fonta çözülür. Biçimlendirilmiş ama boş sütunlu XLSX dosyaları aynı düzeneği kullanır; satır bloğu hücre saklama ve aralık stil overlay'leri yazısı, birden çok overlay'in nasıl katmanlaşıp çözüldüğünü anlatır. ODS içe aktarıcısı yalnızca overlay'lerin kaynağında ayrışır: onları tekrar sayılarından kendisi türetir

SaveAsODS tekrar sayısını nasıl geri yazar?

TXLSXWorkbook.SaveAsODS, birbirinin tıpatıp aynı satır kuşaklarını tek bir table:number-rows-repeated taşıyan <table:table-row> olarak yazar ve kuşağı yalnızca satırlarla ilgili bir şey değiştiği yerde bitirir. Tablo, üzerinde bir şey taşıyan en düşük satıra kadar iner: bir değer, yalnızca stil taşıyan boş bir hücre, tek satırlık bir yükseklik, bir yükseklik run'ının sonu, bir overlay'in alt kenarı, gizli ya da anahat girdisi olan bir satır ya da bir doğrulama ve koşullu biçim alanı

Yazıcı 1. satırdan yola çıkıp sonraki sınırı önceden bakarak arar. Adaylar: somutlaşmış bir sonraki hücre, bir yükseklik run'ının başlangıcı ya da sonu, herhangi bir satır başına girdi (yükseklik, gizli bayrağı, anahat düzeyi, daraltılmış durumu, elle sayfa sonu), overlay kenarları, birleştirilmiş aralıklar, köprüler, yorumlar, görseller ve her doğrulama ile koşullu biçim dikdörtgeninin kenarları. O sisteme dek her şey tek elemandır. Her elemanın içinde aynı mantık sütunlar boyunca işler; böylece boş bir kuşak, overlay'in çözümlediği stil adıyla table:number-columns-repeated taşıyan tek bir <table:table-cell> olur

Satır stilleri paylaşılır. Satır yüksekliği metin olarak biçimlendirilir, örnek için 12.81pt; o metin, gizli bayrağı ve sayfa sonu bayrağıyla birlikte otomatik satır stilinin anahtarını oluşturur. Aynı üç değeri taşıyan her satır, tek bir style:row-height özelliği tutan aynı ro<N> stilini işaret eder. Yüksekliği olmayan, görünür ve sayfa sonu olmayan bir satır hiç satır stili almaz

SaveAsODS'un run destekli bir sayfa için yazdığı şey: 1-45 arası satırlar değerleri taşır ve satır başına bir eleman yazılır; 1048575'e dek inen kuyruk, run kenarlarında, satır başına geçersiz kılmalar ve overlay kenarlarında bölünen yinelenen table-row elemanları olarak üretilir, aynı yükseklikteki satırlar tek otomatik satır stilini paylaşır ve hücre stilleri overlay'ler üzerinden çözülür
Her yinelenen eleman tek tip bir kuşağı kapsar ve sıradaki run kenarında, yükseklik girdisinde, overlay kenarında, kural alanında ya da somutlaşmış hücrede durur; boş bir kuyruk bir avuç eleman olarak kaydedilir, XLSX çıktısı ise kapsanan satır başına bir satır elemanı yazar
var
  Workbook, Reopened: TXLSXWorkbook;
  Saved: TMemoryStream;
begin
  Workbook := TXLSXWorkbook.Create;
  Reopened := TXLSXWorkbook.Create;
  Saved := TMemoryStream.Create;
  try
    Workbook.OpenODS('tail-repeat.ods');
    Workbook.Sheets[1].RowHeight[500000] := 36;
    Workbook.Sheets[1].ClearRowHeight(500001);
    // Kuyruk, birkaç yinelenen satır elemanı olarak yazılır
    Workbook.SaveAsODS(Saved);
    Writeln('ODS stream: ', Saved.Size, ' bytes');
    Saved.Position := 0;
    Reopened.OpenODS(Saved);
    // Beklenen: geçersiz kılma, delik ve iki run parçası gidiş-dönüşü atlatır
    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;

Yukarıdaki sayfa için kuyruk dört parçaya bölünür: 46-499.999 arası run yüksekliğiyle, 500.000 36 pt ile, 500.001 satır stilsiz, 500.002-1.048.575 arası yeniden run yüksekliğiyle. Varsayılan stili overlay bırakmış her sütun, overlay'in bittiği yere bir bölme daha ekleyebilir. Kuyruğun üzerindeki 45 veri satırı, her biri hücre taşıdığı için satır başına bir eleman yazılır

XLSX başka hikâyedir. SpreadsheetML'de tekrar attribute'ü yoktur ve bir <row> elemanı tam olarak tek satır tarif eder; dolayısıyla run destekli bir sayfayı .xlsx olarak kaydetmek, run'ın kapsadığı satırları sayar ve her birine ht ile customHeight="1" yazar. Model bellekte kompakt kalır; dosyanın ne kadar büyüyeceğine hedef biçim karar verir. ODS olarak gelip yalnızca ODS olarak çıkacak bir çalışma kitabı o satır başına maliyeti asla ödemez

Satırları yeniden numaralayan düzenlemeler

Satır yüksekliklerinin ikinci bir temsili, satır taşıyan her işlem onu da taşıdığında doğrudur. TXLSXWorksheet.InsertRows ile DeleteRows, run'ları satır başına listelerle birlikte kaydırır: düzenleme noktasından önceki run parçası yerinde kalır, silinen pencere içindeki satırlar düşer, kalan eklenen ya da silinen sayı kadar taşınır. Bu yüzden bir eklemeye çapraz düşen run, aralarında boyutsuz satır boşluğu olan iki run'a, bir silmeye çapraz düşen run ise kısaya dönüşür

procedure ShiftAroundTheRun(Sheet: TXLSXWorksheet);
begin
  Sheet.RowHeight[500000] := 36;
  Sheet.ClearRowHeight(500001);
  // 500000'e iki yeni satır: geçersiz kılma 500002'ye, delik 500003'e gider
  Sheet.InsertRows(500000, 2);
  Writeln(Sheet.RowHeight[500002]:0:2);  // 36.00
  Writeln(Sheet.HasRowHeight(500003));   // FALSE
  Writeln(Sheet.HasRowHeight(500000));   // FALSE: eklenen satırların yüksekliği yok
  // Tekrar silin: geçersiz kılma ve delik eski satır numaralarına döner
  Sheet.DeleteRows(500000, 2);
  Writeln(Sheet.RowHeight[500000]:0:2);  // 36.00
  Writeln(Sheet.HasRowHeight(500001));   // FALSE
  Writeln(Sheet.UsedRange.Row2);         // kaydırılan run'ın sonunu içerir
end;

Gidiş-dönüş kuyruğu tam olarak bıraktığı yerde bırakmaz. Ekleme, kuyruk run'ını 500.002..1.048.575'ten 500.004..1.048.577'ye iter; ızgara 1.048.576. satırda bittiği için o satırın ötesi kırpılır. Silme ardından kalanı iki yukarı taşır ve run 1.048.574'te biter: 1.048.575'in saklı yüksekliği kalmaz. Izgaranın son satırları sizin için önemliyse, bir ekle-sil çiftinden sonra yüksekliklerini yeniden atayın. Bir run'ın ortasına eklenen satırlar da onun yüksekliğini almaz: boyutsuz satır boşluğu açar, run yüksekliğini miras almaz — yukarıda HasRowHeight(500000)'un FALSE basması bu yüzden

Öteki satır taşıyan işlemler aynı kalıbı izler. TileRangeAxisMetadata, tüm döşenen aralıkta run'ları temizler ve sonra her kaynak run'ı kopya başına kendi ofsetinde bir kez kaydeder. TXLSXWorksheet.CopyFrom ile TXLSXSheets.AddCopy, yeni sayfaya run listesinin kendi kopyasını verir; böylece bir klon üzerindeki ClearRowHeights orijinalin yüksekliklerini yerinde bırakır. Okuma tarafında UsedRange her run'ın ilk ve son satırına dek uzar, XLSX yazıcısının satır turu run'lar boyunca uzar; böylece yalnızca yükseklik taşıyan satırlar da yazılır

Gizli satırlar ve anahat düzeyleri satır başına tutulur

Run'lara ve overlay'lere çöken iki şey satır yükseklikleriyle miras kalan boş stillerdir. Gizli bayrakları, anahat düzeyleri ve daraltılmış durumlar satır başına kaydedilir. Gizli bir yinelenen satır ya da <table:table-row-group> içinde oturan bir satır, o attribute'lerin her biri için yinelenen satır başına bir girdi üretir; kayıtta da her girdi kendi başına bir sınırdır, dolayısıyla gizli kuşak satır başına bir <table:table-row> yazılır

Elinizdeki şablonlarda, sayfanın gizli ve gruplanmış bölümünü verinin gerektirdiği kadar dar tutun. Raporun altındaki yirmi satırı gizlemek bedavadır. 46. satırdan ızgaranın dibine dek her şeyi gizlemek, yüklemede bir milyon girdi ve kayıtta bir milyon satır elemanı demektir; görünen tek fark, artık kimsenin raporun ötesine kaydıramamasıdır

Hangi ODS özelliğinin nesne modelinin hangi parçasına oturduğu dahil, biçimin çalışma kitabı düzeyindeki yanı ODS dosyalarını açma ve kaydetme gezintisindedir; büyük dosyalar için öteki kaldıraçlar büyük çalışma kitabı performansı altında toplanır. Satır elemanının kendisi, tekrar ve stil attribute'leriyle, ODF 1.3 Part 3'ün tablo bölümünde tanımlanır

Bir milyon satırlık tekrar, yerel bir okuyucunun iyi modellemesi gereken bir biçim ayrıntısıdır; arkada onu yutacak bir ofis paketi yoktur. HotXLS Delphi spreadsheet component sayfası, okuyup yazdığı XLS, XLSX ve ODS özelliklerini desteklenen Delphi ve C++Builder sürümleriyle listeler