LibreOffice Calc'in kaydettiği bir çalışma kitabını açın ve hidden="false" olarak yazılmış bir sütun, dosyada hiçbir yanlışlık yokken gizli olarak geri gelebilir. O özelliğin arkasındaki tür olan xsd:boolean uyarınca true, false, 1 ve 0'ın hepsi meşrudur; çevresindeki boşluklarla da olsun olmasın. Delphi için HotXLS, XLSX şemasının bir boolean bildirdiği her yerde o yazımların hepsini okur
Excel'in hiç yazmadığı xsd:boolean yarısı
W3C XML Schema Part 2 §3.2.2, boolean türünü dört meşru literalle tanımlar — true, false, 1 ve 0 — ve whiteSpace facet'ini collapse olarak sabitler; yani customHeight=" 1 ", customHeight="1" kadar geçerlidir. Aynı bölüm, true ile false'u kanonik biçimler olarak adlandırır. SpreadsheetML bunu daraltan hiçbir şey yapmaz: sml.xsd, customHeight, hidden, collapsed, showGridLines, wrapText, quotePrefix ve düzinelerce komşusunu düz xsd:boolean olarak bildirir; çoğu açık bir false varsayılanı taşır, showGridLines gibi birkaçı da true'a varsayılanlaşır. Excel tesadüfen yalnızca kanonik olmayan rakamları yazar. LibreOffice ile diğer bazı üreticiler kelimeleri yazar; bu yüzden Calc'tan gelen bir paket <sheetFormatPr defaultRowHeight="12.8" customHeight="true"/> taşır — Excel'in customHeight="1" yazdığı yerde
Bu asimetri, tek rakamlı karşılaştırmaların OOXML okuyucularında yıllarca sağ çıkmasının nedenidir. Test derlemesini Excel çıktısından kurarsanız, göreceğiniz her boolean 0 ya da 1'dir; karşılaştırma her testi geçer. Sonra bir Calc dosyası bir kullanıcıya ulaşır. Okuyucu testlerini süren herhangi bir XLSX derlemesine en azından birkaç LibreOffice ile kaydedilmiş paket koyun — boolean yazımı, iki üreticinin farklılaştığı birkaç yerden biridir; çalışma kitaplarını ODS üzerinden Excel ile LibreOffice arasında taşırken formüllerde ve kurallarda beliren aynı açıklıktır. Parser tasarımı için veri türünün bir ayrıntısı daha önemlidir: literaller büyük/küçük harfe duyarlıdır, dolayısıyla TRUE hiçbir şekilde geçerli bir xsd:boolean değildir — kendi okuyucunuzun ne kadar esnek olacağına karar verirken karşınıza çıkacak nokta budur
Tek karakterlik bir karşılaştırmanın yanlış gördüğü
Bir özniteliği tek bir rakamla karşılaştıran okuyucu, bir yönde ya da öbüründe yanılır; yön, karşılaştırmanın sessizce kodladığı varsayılana bağlıdır. Tablo, elle yazılmış OOXML kodunda görülen biçimleri, her birinin kelime yazımlarına yaptığı şeyi ve zararın görüldüğü birkaç özniteliği listeler. showDropDown'u dışarıda bırakır — onun sorunu yazımı değil adıdır: şema varsayılanı false iken, liste doğrulamasındaki showDropDown="1" hücre içi oku gizler; bu yüzden TXLSXDataValidation.ShowDropDown, kullanıcıya bakan anlamı açığa vurur (True oku gösterir) ve ters çevrilmiş OOXML bayrağını saklar
| Saf test | Örtük varsayılan | Yanlış okur | Nerede belli olur |
|---|---|---|---|
Value = '1' | false | true false olur | sheetFormatPr customHeight; <v> ögesi, bir t="b" hücresinde |
Value <> '0' | var olduğu anda true | false true olur | sütun hidden, definedName hidden, alignment wrapText |
Value = '0' | true | false yok sayılır, varsayılan açık kalır | sheetView showGridLines, sheetProtection izin anahtarları |
Copy(Raw, P + 5, 1) = '1' | false | true, t'ye kırpılır | ChartML veri tablosu kenarlıkları, seri smooth |
En görünür zararı orta satır verir; çünkü hidden="false", bir <col> elemanı üzerinde, görünen bir sütunun genişliğini kaydeden bir üreticinin yazacağı son derece olağan bir şeydir ve sıfır olmayan her metni kurulmuş sayan bir okuyucu, yüklerken o sütunu gizler. HotXLS bu özniteliklerin her birini, çevresindeki boşlukları kırpıp rakamları da kelimeleri de kabul eden ve öteki her şey için özniteliğin kendi varsayılanını döndüren tek bir xsd:boolean dönüşümünden geçirir. Son dal, söylenmeye değer bir tasarım kararını yansıtır: yes gibi bir değer bozuk bir dosyadır — ama tek bir bozuk görünürlük bayrağı yüzünden bir çalışma kitabını açmayı reddetmek, üreticinin hatasını kullanıcıya ödetir; bu yüzden okuyucu, sabit bir öznitelik bazlı yedekle yoluna devam eder
LibreOffice tarzı bir çalışma sayfasını HotXLS ile okumak
Sayfa düzeyindeki bayraklar, TXLSXWorksheet üzerinde sıradan özellikler olarak ortaya çıkar: CustomHeight, ThickTop ile ThickBottom, <sheetFormatPr>'den gelir; ColHidden[], RowHidden[], RowThickTop[] ve RowThickBottom[], 1 tabanlı sütun ve satır numaraları alır. Bir boolean hücre, TXLSXCell.Value içinde bir Boolean varyantı olarak gelir; <v> elemanı hangi yazımı kullanmış olduysa olsun. Aşağıdaki rutin, ilk sayfa için bunların hepsini yazdırır; arkasındaki liste, Calc tarzı işaretleme için beklenen çıktıyı verir
uses
SysUtils, Variants, lxHandleX;
procedure DumpSheetBooleans(const FileName: string);
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Flag: Variant;
Col, Row: Integer;
begin
Book := TXLSXWorkbook.Create;
try
if Book.Open(FileName) <> 1 then
raise Exception.CreateFmt('Cannot open %s', [FileName]);
if Book.Sheets.Count = 0 then
raise Exception.Create('Workbook has no sheets');
Sheet := Book.Sheets[1]; // Sheets indeksleyicisi 1 tabanlıdır
Writeln('Default row height is custom: ', Sheet.CustomHeight);
Writeln('Rows default to thick top / bottom: ',
Sheet.ThickTop, ' / ', Sheet.ThickBottom);
for Col := 1 to 8 do
if Sheet.ColHidden[Col] then
Writeln('Column ', Col, ' is hidden');
for Row := 1 to 20 do
if Sheet.RowHidden[Row] or Sheet.RowThickTop[Row] or
Sheet.RowThickBottom[Row] then
Writeln('Row ', Row, ': hidden=', Sheet.RowHidden[Row],
' thickTop=', Sheet.RowThickTop[Row],
' thickBot=', Sheet.RowThickBottom[Row]);
Flag := Sheet.Cells[2, 1].Value;
if VarType(Flag) = varBoolean then
Writeln('A2 holds the boolean ', Boolean(Flag));
finally
Book.Free;
end;
end;
<sheetFormatPr defaultRowHeight="12.8" customHeight="true"/>:CustomHeightTRUE yazdırır<col min="3" max="3" width="20" hidden="false"/>: 3. sütun listelenmez<row r="5" thickBot="true">: 5. satırthickBot=TRUEraporlar<c r="A2" t="b"><v>true</v></c>: A2 TRUE tutar
Satır ipucu, şemanın kendisinin huysuzlandığı yerdir. Tek fikir, iki yazım: CT_SheetFormatPr özniteliklerini thickTop ile thickBottom olarak adlandırırken CT_Row, thickTop ile thickBot'u kullanır; bir satırda thickBottom arayan okuyucu hiçbir şey bulamaz. HotXLS her biçimi kendi elemanına geri yazar ve ipuçları, paketin geri kalanıyla birlikte bir kaydı atlatır — tıpkı bir kayıpsız XLSX gidiş-dönüşünde dokunulmamış bölümlerin atlatması gibi
Bir grafik, val'i olmayan bir elemanı nasıl okumalı?
val özniteliği olmayan bir ChartML anahtarı açıktır. DrawingML Charts, o anahtarları CT_Boolean ile, dml-chart.xsd içinde bildirir; türün val'i, isteğe bağlı bir xsd:boolean'dir ve true'a varsayılanlaşır, dolayısıyla <c:plotVisOnly/> ile <c:showHorzBorder/>, val="1" yazmakla aynı anlama gelir. O varsayılan, çoğu SpreadsheetML özniteliğinin tersidir ve grafik XML'ini metin olarak okumanın alışılmış kısayolunu kırar: elemanı adıyla bul, sonra ileriye doğru bir sonraki val=" ara. Eleman val'i atladığında o arama, başlangıç etiketinin sonunu aşar ve parçada sonra gelen hangi eleman varsa onun değerini ödünç alır; belki iki kardeş aşağıda bir val="0". Hiçbir şey eşleşmediğinde PosEx 0 döndürür ve P + 5 üzerinde dilimleyen kod, parçanın 5. karakterinden okumaya koyulur; o karakter l'dir, <?xml'in l'si. İki başarısızlık da hiçbir şey fırlatmaz; grafik özelliği yalnızca dosyayla ters düşer
Tırnaklar, sorunun öbür yarısıdır. XML 1.0, bir öznitelik değerinin çift ya da tek tırnakla sınırlandırılmasına izin verir (§2.3'teki AttValue üretimi); dolayısıyla <c:majorTickMark val='out'/>, çift tırnaklı biçim kadar meşrudur ve val=" araması onu tamamen atlar. HotXLS, özgün ChartML'i saklar; böylece modellemediği işaretlemeyi kaybetmeden Excel grafiklerini düzenler ve o metinden okunan anahtarlar yalnızca elemanın kendi başlangıç etiketi içinde aranır: iki tırnak biçimi de kabul edilir, yok bir val ise CT_Boolean varsayılanı okunur. Açık grafik özellikleri bu okumayı yansıtır
procedure DumpChartSwitches(Sheet: TXLSXWorksheet);
const
TickNames: array[TXLSXChartTickMark] of string =
('none', 'in', 'out', 'cross');
var
I: Integer;
Chart: TXLSXChart;
begin
for I := 0 to Sheet.Charts.Count - 1 do // Charts 0 tabanlıdır
begin
Chart := Sheet.Charts[I];
Writeln(Chart.Name, ': plotVisOnly=', Chart.PlotVisibleOnly,
' dTable showHorzBorder=', Chart.ShowDataTableHorzBorder);
if Chart.ValueAxis <> nil then
Writeln(' value axis majorTickMark=',
TickNames[Chart.ValueAxis.MajorTickMark]);
if Chart.SeriesCount > 0 then
Writeln(' first series smooth=', Chart.Series[0].Smooth);
end;
end;
<c:plotVisOnly/> taşıyan ve sonra val="0"'lı bir eleman gelen, <c:showHorzBorder/><c:showVertBorder val="0"/>'li bir veri tablosu ile <c:majorTickMark val='out'/>'li bir değer ekseni içeren grafik parçası için beklenen: PlotVisibleOnly ile ShowDataTableHorzBorder ikisi de TRUE'dur ve onay işareti out yazdırır
Kendi OOXML kodunuz için sınırlı bir xsd:boolean okuyucu
Paketi kendiniz sonradan işliyorsanız — uygulamanızın eklediği özel bir parça ya da bir derleme adımındaki hızlı grafik XML taraması gibi — aynı iki kural geçerlidir: tam sözcüksel alanla dönüştürün ve bir öznitelik aramasının, ait olduğu elemanın başlangıç etiketinden çıkmasına asla izin vermeyin. Aşağıdaki fonksiyonlar ikisini de yapar. ChartSwitch, TagStart'in elemanın <'sini göstermesini bekler ve CT_Boolean varsayılanını, val eksikken uygular
uses
SysUtils, StrUtils;
function TryParseXsdBoolean(const Literal: string; out Value: Boolean): Boolean;
var
S: string;
begin
S := Trim(Literal); // whiteSpace=collapse: dolgu meşrudur
Result := True;
if (S = '1') or (S = 'true') then
Value := True
else if (S = '0') or (S = 'false') then
Value := False
else
Result := False; // TRUE, yes, on: xsd:boolean değildir
end;
function ReadXsdBoolean(const Literal: string; Default: Boolean): Boolean;
begin
if not TryParseXsdBoolean(Literal, Result) then
Result := Default;
end;
function AttrInStartTag(const Xml: string; TagStart: Integer;
const AttrName: string; out Value: string): Boolean;
var
TagEnd, P, Q: Integer;
Quote: Char;
begin
Result := False;
Value := '';
TagEnd := PosEx('>', Xml, TagStart);
if TagEnd = 0 then
Exit;
// ad bir token başlatmalı, öteki bir öznitelik adını bitirmemeli
P := PosEx(AttrName + '=', Xml, TagStart);
while (P > 0) and (P < TagEnd) and
not CharInSet(Xml[P - 1], [' ', #9, #10, #13]) do
P := PosEx(AttrName + '=', Xml, P + 1);
if (P = 0) or (P >= TagEnd) then
Exit;
Q := P + Length(AttrName) + 1;
Quote := Xml[Q];
if (Quote <> '"') and (Quote <> '''') then
Exit;
P := PosEx(Quote, Xml, Q + 1);
if (P = 0) or (P > TagEnd) then
Exit;
Value := Copy(Xml, Q + 1, P - Q - 1);
Result := True;
end;
function ChartSwitch(const Xml: string; TagStart: Integer): Boolean;
var
V: string;
begin
if not AttrInStartTag(Xml, TagStart, 'val', V) then
Exit(True); // CT_Boolean: yok bir val true demektir
Result := ReadXsdBoolean(V, True);
end;
TryParseXsdBoolean içindeki katı dal, veri türüne harfine uyarak taşınır; işte okuyucunuz ile HotXLS'in biraz ayrıldığı yer burasıdır: HotXLS dönüşümü kelimeleri büyük/küçük harfe bakmadan karşılaştırır, dolayısıyla TRUE true okunur. Biz, bir bayrağı kapatmaktan çok şemanın yasakladığı bir yazımı kabul etmeyi seçeriz — hiçbir üretici false demek için TRUE yazmaz; buna karşılık bir validator katı dalda kalmalı ve değeri raporlamalıdır. Metin tarayıcı ayrıca ChartML anahtarları için güvenli, XML için genel olarak yanlış iki kısayol kullanır. Bir >, öznitelik değeri içinde meşrudur; en erken olanında durmak, değerlerinde on barındıran bir etiketi kesebilir. XML ayrıca = çevresinde beyaz boşluğa izin verir ve AttrName + '=' araması onu görmez. ChartML anahtar değerleri içinde > olmayan kısa tokenlerdir ve eşittir çevresindeki boşlukları atlamak iki satırlık bir ektir; ama bir avuç bilinen anahtarın ötesinde her şey için, öznitelikleri tek tek eleman bazında veren bir XML okuyucusu her iki sorunu da kaldırır — HotXLS'in çalışma sayfası parçaları için kullandığı da odur
Bir Calc çalışma kitabı, Excel ikiziyle farklı gizli sütunlar ya da satır yükseklikleriyle yüklenirse ikisini de açın ve bayrakların nasıl yazıldığına bakın; hidden="false" ile hidden="0" karşılaştırması, açığı genellikle bir dakikada açıklar. İki yazımı da — dolgulu ya da dolgusuz — tek bir çalışma sayfası, hücre ve grafik özellikleri kümesine eşlemek, HotXLS Delphi spreadsheet component'in her yüklemede yaptığı iştir