Teknik Makale

Excel 1 Yazar, LibreOffice true Yazar: XLSX Boole Değerleri

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

Delphi'de HotXLS xsd:boolean öznitelik ayrıştırma: Excel customHeight="1" yazar, LibreOffice Calc customHeight="true"; ikisi de true, false, 1 ve 0'ın beyaz boşluk çökmesiyle dört literalli sözcüksel alanının içinde, tek bir HotXLS dönüşümüyle okunuyor
Excel yalnızca rakamları üretir, LibreOffice Calc kelimeleri üretir; ama iki yazım da, bir okuyucunun eksiksiz kabul etmek zorunda olduğu tek bir dört literalli veri türünün içindedir

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ılanYanlış okurNerede belli olur
Value = '1'falsetrue false olursheetFormatPr customHeight; <v> ögesi, bir t="b" hücresinde
Value <> '0'var olduğu anda truefalse true olursütun hidden, definedName hidden, alignment wrapText
Value = '0'truefalse yok sayılır, varsayılan açık kalırsheetView showGridLines, sheetProtection izin anahtarları
Copy(Raw, P + 5, 1) = '1'falsetrue, t'ye kırpılırChartML 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

Delphi'de HotXLS XLSX boolean öznitelik okuyucusu ile saf testlerin karşılaştırması: Value = '1', Value <> '0', Value = '0' ve Copy(Raw, P + 5, 1); hangi kelime yazımını customHeight, sütun hidden, showGridLines ve ChartML smooth için yanlış okuduğunu gösteriyor
Her tek karakterlik test, 1 ve 0'da geçer ve tam olarak bir kelime yazımında düşer; bu yüzden yalnızca Excel içeren bir test derlemesi onu asla yakalamaz

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"/>: CustomHeight TRUE yazdırır
  • <col min="3" max="3" width="20" hidden="false"/>: 3. sütun listelenmez
  • <row r="5" thickBot="true">: 5. satır thickBot=TRUE raporlar
  • <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

Delphi'de HotXLS ChartML CT_Boolean anahtar okuma: ileriye val=" araması, sonraki bir val="0" ödünç alır, val='out''i atlar ve PosEx 0 döndürünce <?xml dilimler; HotXLS ise yalnızca başlangıç etiketinden PlotVisibleOnly ile ShowDataTableHorzBorder okur
val aramasını elemanın kendi başlangıç etiketiyle sınırlamak, üç sessiz grafik yanlış okumasını birden düzeltir: ödünç alınmış bir değer, atlanmış tek tırnaklı bir değer ve XML bildiriminden başlayan bir dilim
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