Tek pivot tabluluk bir ODS çalışma kitabı bir kütüphaneden geçer, tek bir kaynak hücre düzenlenir, kaydedilen dosya tüm hücre değerleri yerinde ama pivotsuz açılır: content.xml girişte bir <table:data-pilot-table> taşıyordur, çıkışta sıfır. Akla gelen ilk onarım, pivot XML'ini string olarak kopyalamaktır. Bu eleman sayısını geri getirir; ardından namespace duyarlı bir parser, kaydedilen parçayı bağlanmamış prefix hatasıyla tümüyle reddeder. İki başarısızlığın da tek kökü var. Bir XML parçası, dayandığı namespace bildirimleri onunla birlikte yol alıyorsa ancak güvenle yol alabilir
Bir ODS pivot tanımı content.xml'de nerede durur?
OpenDocument pivot tablosu — biçimin deyişiyle data pilot — content.xml'de sayfaların ardından saklanan bir tanımdır; bu bölümü kendi nesne modelinden yeniden kuran bir yazıcı, modelde ona yer varsa saklar. ODF 1.3 Part 3 §9.6 kapsayıcıyı tarif eder: <table:data-pilot-tables>, pivot başına bir <table:data-pilot-table> tutar; her birinin table:source-cell-range'i, bir dizi table:data-pilot-field çocuğu, table:target-range-address'i ve table:buttons listesi vardır. Pivotun gösterdiği sonuç hücreleri çalışma sayfasının içinde sıradan hücreler olarak oturur. Dolayısıyla yalnızca hücreleri, stilleri, doğrulamaları, adlandırılmış ifadeleri ve veritabanı aralıklarını yeniden üreten bir yazıcı, ekranda aynı sayıları gösteren kusursuzca geçerli bir elektronik tablo üretir — arkalarında pivot olmadan
HotXLS Delphi Excel Component, OpenODS sırasında, tek bir hücreyi parse etmeden önce content.xml'in tüm <table:data-pilot-tables> alt ağacını okur ve sonra bu koleksiyonu pivot modeline çevirmeye çalışır. Modelin temsil ettiği düz biçimdeki bir tanım — yerel kaynak aralığı, bir satır alanı, toplama ölçüleri ve kaynak veriyle uyuşan saklı sonuçlar — çalışma sayfasının PivotTables listesinde bir TXLSPivotTable olur; XLSX pivotundan aldığınız nesnenin aynısı, Delphi'den hesaplanan alanlarla genişletip yenileyebilirsiniz. O biçimin dışında kalan her şey, yakalanan XML olarak koleksiyonda kalır ve SaveAsODS onu geri yazar. Çevirmenin bilmediği bir namespace'teki bir attribute bile yeter: LibreOffice'in kaydettiği pivotlar loext: ve calcext: attribute'ları taşıyabilir ve bir loext: attribute, tanımı yakalanan yola düşürür
Geri düşme yolu, çalışma kitabı başına hep ya hep değildir. Üç pivotluk bir koleksiyonda tek pivot çevrilebilir biçimin dışında kalırsa, üçü de yakalanan XML olarak kalır. Bölmek, content.xml'de tek kapsayıcı için iki doğruluk kaynağı bırakırdı: yarısı nesnelerden yeniden kurulmuş, yarısı metinden oynatılmış; iki yarım arasındaki her uyuşmazlık kullanıcının dosyasına düşerdi. Koleksiyonu bütün tutmak daha güvenli varsayılandır; HotXLS'in XLSX'teki bilinmeyen extLst bloklarına uyguladığı politika da aynıdır: modelin taşıyamadığı şey yazıldığı gibi kalır
Kesilip çıkarılan bir pivot tanımı kaydedilen dosyayı neden bozar?
String aramasıyla content.xml'den kesilen bir pivot tanımı, prefixlerine anlam veren namespace bildirimlerini kaybeder; Namespaces in XML, bildirimi olmayan prefix'i iyi biçimlilik hatası sayar. String yaklaşımı kısadır: bölümü bir WideString'e çöz, <table:data-pilot-tables'i Pos ile bul, sonra karşılık gelen kapanış etiketini bul ve aradaki her şeyi kopyala. Çıkan metin eksiksiz görünür. Bir sayfa alanında loext:ignore-selected-page="true", her düzeyde calcext:repeat-item-labels="false" taşır; ama bu prefixleri bağlayan xmlns:loext ve xmlns:calcext bildirimleri belge kökünde, <office:document-content>'te, binlerce karakter geridedir
Namespaces in XML 1.0 §6.1, bildirimi taşıyan başlangıç etiketinden eşleşen kapanış etiketine dek kapsama sokar ve içindeki her prefixli ad ona karşı çözülür. Bir alt ağacı belgeden keserseniz, kapsamdan da kesmiş olursunuz. Parça yeni bir belgeye yapıştırıldığında her prefix, ancak yeni kök tesadüfen onu bildiriyorsa çözülür. HotXLS kendi <office:document-content>'ini, yazıcısının kullandığı namespace'lerle yazar: aralarında office, table, text, style, fo, draw, calcext, tableooo ve of var. Dilinmiş kopyada table: çözülür, calcext: çözülür, loext: çözülmez. Namespace duyarlı bir parser'ın tüm parçayı reddetmesi için tek bir bağlanmamış prefix yeter; pivot nazikçe bozulmaz, çevresindeki sayfa verisi de okunamaz hale gelir
String aramasının, okuyucu tabanlı bir yakalamanın hiç karşılaşmadığı başka kör noktaları da vardır. Pos, etiket metnini bir yorum içinde, bir CDATA bölümünde ya da onu tırnaklayan bir attribute değeri içinde de eşler. Aynı adlı iç içe elemanları birbirinden ayıramaz. Ve sabit table: prefix'ini eşler; oysa ODF, üreticinin table namespace'ini istediği prefix'e bağlamasına izin verir
HotXLS namespace kapsamını fragmana nasıl taşır?
HotXLS pivot alt ağacını, her namespace bildirimini göründüğü derinlikle birlikte izleyen bir streaming XML okuyucusuyla yakalar ve hedef elemana varıldığı anda hâlâ geçerli olan bildirimleri fragmanın açılış etiketine kopyalar. Yığın, xmlns ya da xmlns:prefix bildiren her açılış etiketinde büyür, bağlantıyı bildiren eleman kapandığında küçülür. Boş elemanlar aynı işlemi gerektirir: <x xmlns:p="..."/> ayrı bir kapanış etiketi olayı üretmez; dolayısıyla pop boş açılış etiketinde de tetiklenmek zorundadır, yoksa bir bağlantı kardeşlerine sızar
Hedefte yakalama, yığını en içteki bağlantıdan dışa doğru yürütür ve her prefix'i bir kez yazar. İçten dışa yürümek, gölgelemeyi gerçekleyen şeydir. <office:spreadsheet>, kökün çoktan bildirdiği bir prefix'i yeniden bağlıyorsa, pivot için geçerli olan yakın değerdir; tam olarak §6.1'in istediği gibi, ve çoktan yazılmış prefixler kümesi dış değerin peşinden gelmesini önler. Pivot elemanının kendisinde bildirdiği prefix atlanır; çünkü xmlns:table'ı tek etikette iki kez yazmak başka bir iyi biçimlilik hatasıdır
Hedef namespace'e göre eşlenir; t'yi ODF table URI'sine bağlayan bir üreticinin yazdığı <t:data-pilot-tables> de tanınır, yakalanan etiketler ise dosyadaki hecelemeyi korur. Boşluk metin düğümleri yazıldığı gibi geri döner. Attribute tırnaklaması ile <x/> / <x></x> seçimi normalleştirilir; çünkü parça bayt bayt kopyalanmak yerine okuyucu olaylarından yeniden serileştirilir. content.xml, pivot alt ağacı hâlâ açıkken biterse — kesik bir dosyada olduğu gibi — açma işlemi bir istisnaıyla başarısız olur; sonraki kayıtta geri yazılacak yarım bir tanım biriktirilmez
Taşınan küme bilerek cömerttir. Yakalama, alt ağacın kullanıp kullanmadığına bakmadan kapsamdaki her bağlantıyı kopyalar; dolayısıyla fragman kökü, kaydedilen belge kökü de onları bildirdiği hâlde xmlns:table ile xmlns:calcext'i yineler. Bir prefix'i iç içe kapsamda yeniden bildirmek Namespaces in XML'e göre yasaldır ve kökünde düzinelerce namespace bildiren bir üreticinin dosyası birkaç kilobayt fazla taşır. Listeyi budurmak, alt ağacı eleman adları, attribute adları ve QName taşıyan attribute değerlerinde her prefix için taratmak demektir; oradaki tek bir ıskalama bağlanmamış-prefix başarısızlığını olduğu gibi geri getirir. Birkaç gereksiz bildirim, o riske kıyasla çok ucuzdur
Pivot taşıyan bir çalışma kitabını açmak, düzenlemek ve kaydetmek
Çağıran tarafta açıp kapatılacak bir şey yoktur: pivot tanımı, sıradan bir OpenODS ile SaveAsODS'e ortak olur. Çalışma sayfaları 1'den numaralanır ve her iki metot da başarıda 1 döndürür:
uses
System.SysUtils, lxHandleX;
procedure EditPivotSource(const InFile, OutFile: string);
var
Book: TXLSXWorkbook;
begin
Book := TXLSXWorkbook.Create;
try
if Book.OpenODS(InFile) <> 1 then
raise Exception.CreateFmt('Cannot open %s', [InFile]);
// E2, Sheet1.A2:E30 pivot kaynak aralığının içinde yer alır
Book.Sheets[1].Cells[2, 5].Value := 1250.0;
if Book.SaveAsODS(OutFile) <> 1 then
raise Exception.CreateFmt('Cannot save %s', [OutFile]);
finally
Book.Free;
end;
end;
Kaydedilen content.xml'de pivot koleksiyonu, <office:spreadsheet> içinde, yazıcının ürettiği <table:named-expressions>'in hemen ardından ve <table:database-ranges>'in önünde oturur. Hem bir AutoFilter (HotXLS bunu veritabanı aralığı olarak yazar) hem pivot taşıyan bir çalışma kitabı bu iki elemanı yan yana koyar; boru hattınız katı bir ODF şema doğrulayıcı çalıştırıyorsa önce oraya bakın
Örnekteki düzenleme bir kaynak değeri değiştirir ve pivotun sonuç hücrelerine dokunmaz. Yakalanan bir tanım yazıldığı gibi oynatılır: HotXLS onu yeniden hesaplamaz; dosyayı açan tablo uygulaması pivotu bir sonraki yenilemede hesaplar. Aynı şey adresleri için de geçerlidir. Sheet1'i yeniden adlandırın ya da kaynak bloğu taşıyın; kaydedilen tanım yine Sheet1.A2:E30'u gösterir ve onu tüketen uygulama yenilemede bunu kırık aralık olarak bildirir. Pivotları bu yolla taşınan her çalışma kitabında sayfa adlarını ve kaynak bloğu sabit tutun
Verilen bir pivot hangi yolu izledi?
En hızlı kontrol, çalışma sayfasının PivotTables listesidir: çevrilmiş bir tanım orada TXLSPivotTable olarak görünür, yakalanan görünmez. Dönüşüm raporu sorunun ikinci yarısını, pivotun farklı bir çıktı biçiminde başına ne geleceğini yanıtlar. GetConversionReport çalışma kitabını kaydetmeden ya da yeniden hesaplamadan inceler ve döndürülen raporun sahibi çağırandır:
uses
System.SysUtils, lxHandleX, lxDiagnostics;
function DescribeOdsPivots(Book: TXLSXWorkbook): string;
var
I, Modeled: Integer;
Report: TXLSConversionReport;
begin
Modeled := 0;
for I := 1 to Book.Sheets.Count do // Sheets 1 tabanlıdır
Inc(Modeled, Book.Sheets[I].PivotTables.Count);
Report := Book.GetConversionReport(xlsConversionOpenXmlWorkbook);
try
for I := 0 to Report.Count - 1 do
if (Report[I].Feature = 'OpenDocumentPivotTables') and
(Report[I].Fidelity = xlsConversionDropped) then
Exit('Captured ODF pivot XML: keep this workbook on .ods');
finally
Report.Free;
end;
if Modeled > 0 then
Result := Format('%d pivot table(s) in the pivot model', [Modeled])
else
Result := 'No pivot definitions';
end;
Çıktı yolu önemlidir, çünkü yakalanan XML ODF söz varlığıdır ve onu yalnızca ODS yazıcısı yazar. .xlsx olarak kaydedilince yakalanan koleksiyonun SpreadsheetML karşılığı yoktur; rapor onu OpenDocumentPivotTables altında xlsConversionDropped ile listeler. İkili .xlsb yazıcısı, hâlâ onu taşıyan bir çalışma kitabını reddeder. Çevrilmiş pivot sıradan bir model nesnesidir ve hedef biçimin kurallarına uyar. ODS içe ve dışa aktarma yolları arasındaki farklar burada da her yerde olduğu gibi geçerlidir; pivotlu dosyalar için güvenli rota .ods girişte, .ods çıkışta
Yaptırımsız-ya-da-tamam geri düşmesinden bir kural daha çıkar. Bir çalışma kitabı ya yakalanan koleksiyonu oynatır ya da pivotlarını modelden yazar; tek bir content.xml'de ikisini asla harmanlamaz. Zaten yakalanan pivot XML'i tutan bir çalışma kitabına AddPivotTable ile pivot eklemek, ODS kaydının üretilemiş XML'i oynatılan XML'in yanına dikmek yerine bir hatayla durmasını sağlar
Kaydedilen parçayı ikinci bir parser'la kontrol etmek
Eleman sayımı namespace hakkında hiçbir şey kanıtlamaz. Kaydedilen content.xml'de data-pilot-table açılış etiketleri arayan bir düzenli ifade metin görür; ikinci bölümdeki dilinmiş kopya bu testi geçer. Dosyayı yazan kütüphaneyle hiç akrabalığı olmayan namespace duyarlı bir parser çok daha katı bir yargıçtır ve Delphi zaten bir taneyle gelir. Aşağıdaki fonksiyon, kendi test paketinize eklemeniz için bir kontroldür; TZipFile ile TXMLDocument Delphi RTL'sindendir, HotXLS'den değil:
uses
System.SysUtils, System.Classes, System.Zip,
Xml.xmldom, Xml.XMLIntf, Xml.XMLDoc;
// Kendi test yardımcınız: .ods paketindeki content.xml
// namespace duyarlı bir DOM parser'a yüklenirse True
function OdsContentParses(const OdsFile: string; out Error: string): Boolean;
var
Zip: TZipFile;
Bytes: TBytes;
Stream: TBytesStream;
Doc: IXMLDocument;
begin
Error := '';
Zip := TZipFile.Create;
try
Zip.Open(OdsFile, zmRead);
Zip.Read('content.xml', Bytes);
finally
Zip.Free;
end;
Stream := TBytesStream.Create(Bytes);
try
Doc := TXMLDocument.Create(nil);
try
Doc.LoadFromStream(Stream); // bağlanmamış prefix burada başarısız olur
Result := True;
except
on E: EDOMParseError do
begin
Error := E.Message;
Result := False;
end;
end;
finally
Stream.Free;
end;
end;
Kodunuzun kaydettiği her .ods'te çalıştırın; pivotlu dosyalar için bir adım daha atın ve giriş ile çıktının pivot alt ağacını, parse ettikten sonra kıyaslayın: etiket, sıralı attribute'lar, metin ve çocuklar, özyinelemeli, adlar namespace URI artı yerel ad olarak açılmış hâlde. Bu kıyaslama başka hecelenmiş bir prefix'i geçirtir, bağlanmamışı yakalar; zararsızla kırık arasındaki çizgi tam da budur. Kullanıcılarınızın gerçekten koştuğu her üreticinin dosyasını besleyin. Table namespace'ini başka bir prefix'e bağlayan, bir prefix'i pivot elemanının kendisinde bildiren ya da <office:spreadsheet>'te yeniden bağlayan bir belge, atlama ve gölgeleme kurallarını tek bir LibreOffice dosyasının yapamayacağı şekilde zorlar
ODS okuyucusu ve yazıcısı, hem XLSX hem ODS için pivot modeli ve yukarıda kullanılan dönüşüm raporu, HotXLS Delphi Excel Component'in parçasıdır; ürün sayfası Delphi ve C++Builder için tam XLSX, XLS ve ODS okuma-yazma desteğini anlatır