Teknik Makale

Dosyanın Kod Sayfası Yokken DBF, SYLK ve DIF Yazmak

Batılı bir PC'de .dbf olarak yazılan Çince şehir adları, başarılı raporlayan bir dışa aktarımdan soru işaretleri olarak geri gelebilir; çünkü dBASE, SYLK ve DIF dosyalarının bir kodlamayı kaydedecek hiçbir yeri yoktur. Güvenli bir yazıcı, ana ANSI kod sayfasını kullanır ve ilk bayttan önce her adı ve değeri o sayfaya karşı denetler; HotXLS, o sayfanın tutamayacağı her şeyi reddeder

Ana kod sayfası, bu dosyaların sahip olduğu tek kodlamadır

Üç format da PC'nin ilk yıllarından gelir: dBASE Ashton-Tate'ten, SYLK Microsoft'un Multiplan döneminden, DIF Software Arts'tan, yani VisiCalc'in şirketinden. Her biri metni tek bayt olarak saklar ve okuyucunun yazanın kod sayfasını paylaştığını varsayar — ofisteki her makine aynı DOS kurulumunu çalıştırdığında makul bir bahisti. Bugün Windows'ta o sayfa, GetACP'nin döndürdüğü şeydir: Batı Avrupa kurulumunda 1252, Basit Çince'de 936, Kiril'de 1251. dBASE IV ve FoxPro sonradan tablo başlığının 29. baytına bir dil sürücüsü işareti koydu; ama HotXLS'in yazdığı dBASE III düzeni o baytı sıfırda bırakır ve SYLK, DIF ile boşluklarla hizalanmış .prn metninin bunun için hiçbir başlık alanı yoktur. Yani kod sayfası, dosyanın dışında anlaşılır. HotXLS yazarken ve okurken ana ANSI kod sayfasını kullanır ve yazmadan önce her karakterin o sayfada bir baytı olup olmadığını denetler

FormatSaveAs / Open uzantısıDosyada bildirilen kodlamaAna sayfanın tutamadığı metin
dBASE III tablosu.dbf; gerektiğinde bir .dbt memo dosyası eklenirYok (29. bayt sıfır kalır)Yazmadan önce EXlsDbf fırlatılır ve az önce oluşturulan .dbf / .dbt silinir
SYLK.slk, .sylkYokSaveAs -1 döndürür ve dosya oluşturmaz
DIF.difYokSaveAs -1 döndürür ve dosya oluşturmaz
Biçimlendirilmiş metin.prnYokSaveAs -1 döndürür ve dosya oluşturmaz

Bir dBASE alan adı için on bayt, kod sayfasında sayılarak

Bir dBASE tablosundaki her boyut sınırı bir bayt sayımıdır. Çok baytlı kod sayfalarının canını burada yakar. Bir alan tanımlayıcısı adı on bir baytta tutar — ona on bayt, sonlandırıcı NUL'una bir bayt — ve tür harfini hemen ardından 11. ofsete, genişliği 16'ya, ondalık sayısını 17'ye yazar. 936 kod sayfasında bir Çince karakter iki bayttır; on karakterlik bir Çince ad yirmi eder. O baytları ölçmeden içeri kopyalarsanız doğrudan NUL'un, tür harfinin ve genişliğin üzerinden geçer; ad on karakterlik denetimi geçmiş olsa bile sonra o tanımlayıcıyı hiçbir okuyucu geri kazanamaz. dBASE alan adlarını bir CJK iş istasyonunda bile ASCII tutun. O adlara güvenenler var: shapefile öznitelik tablolarını okuyan xBase araçları, ODBC sürücüleri ve GIS yazılımı; yalnızca 936 altında dekode olan bir ad, başka bir sayfa çalıştıran her makinede başka baytlara dönüşür

Delphi'de HotXLS dBASE III DBF alan tanımlayıcı düzeni: NUL'lu on baytlık ad yuvası, 11. ofsette tür harfi, 16'da genişlik, 17'de ondalık; AddField('CITY') sığarken 936 kod sayfasında on karakterlik bir Çince ad yirmi baytı aşıyor
32 baytlık bir dBASE alan tanımlayıcısının içinde, on karakterlik denetimi geçen bir Çince alan adı, 936 kod sayfasında NUL'a, tür harfine ve genişliğe taşar
BaytlardBASE III içeriği
0Sürüm: 0x03; tabloya bir .dbt memo dosyası eşlik ediyorsa 0x83
1–3Son güncelleme: 1900'den beri yıl, ay, gün
4–7Kayıt sayısı, 32-bit little-endian
8–11Başlık uzunluğu ve kayıt uzunluğu, 16-bit sözcükler
32 ve sonrasıAlan başına bir 32 baytlık tanımlayıcı, ardından 0x0D; kayıtlar izler, her biri bir silme bayrağının arkasında

Genişlikler de bayttır. Bir karakter alanı (C) 254 baytta tavan yapar; N değerleri, bildirilen ondalıklarla sağa yaslı metindir; D değerleri YYYYMMDD biçiminde sekiz karakterdir ve alanına sığmayacak kadar geniş bir değer, yıldız dizisi olur — dBASE'in kendisinin kullandığı gelenek bu. Daha uzun metin, memo (M) alanına gider; bu alan, blok numarasını .dbt eki içine tutar: 512 baytlık bir başlık, ardından 512 baytlık bloklar; her memo 0x1A 0x1A ile son bulur. TXLSXWorkbook.SaveAs bir .dbf adıyla çağrıldığında, etkin sayfanın kullanılan ilk satırını alan adlarına çevirir, sütun başına C, N, L ya da D türünü çıkarır, karışık sütunlarda C'ye düşer ve 254 karakteri aşan metni M'ye taşır. Genişlikleri bayt cinsinden kontrol etmeniz gerektiğinde tabloyu kendiniz kurun: TXlsDbfTable ile her C alanını, en uzun değerinin kodlanmış uzunluğuna göre boyutlandırın

uses
  Windows, SysUtils, Variants, lxDbf;

procedure WriteCustomerTable(const FileName: string);
var
  Table: TXlsDbfTable;
  Row: Integer;
begin
  Table := TXlsDbfTable.Create;
  try
    // En fazla on baytlık ASCII adlar; genişlikler ana kod sayfasında bayttır
    Table.AddField('CUSTNO', 'N', 6, 0);
    Table.AddField('CITY', 'C', 40, 0);
    Table.AddField('BALANCE', 'N', 12, 2);
    Table.AddField('OPENED', 'D', 8, 0);

    Row := Table.AddRow;
    Table.SetValue(Row, 0, 1001);
    Table.SetValue(Row, 1, WideString(#$5317#$4EAC#$5E02));  // 北京市, CP936'da 6 bayt
    Table.SetValue(Row, 2, 1234.5);
    Table.SetValue(Row, 3, EncodeDate(2026, 11, 12));

    try
      XlsWriteDbfFile(FileName, Table);
    except
      on E: EXlsDbf do
        // 1252 ana bilgisayarda beklenen: mesaj, "CITY" alanını ve 1. satırı
        // adlandırır ve diskte .dbf kalmaz
        raise Exception.CreateFmt('DBF export refused under code page %d: %s',
          [GetACP, E.Message]);
    end;
  finally
    Table.Free;
  end;
end;

Beklenen: 936 ana bilgisayarda tablo yazılır ve dört alanıyla birlikte yeniden açılır. 1252 ana bilgisayarda XlsWriteDbfFile, alanı ve birden başlayan satır numarasını adlandıran bir EXlsDbf fırlatır. XlsWriteDbfStream her alan adını ve her C ya da M değerini ana sayfaya çevirip geri çevirir, karşılaştırır ve başlık çıkmadan önce her adı on baytına karşı ölçer; reddedilen bir tablo stream'e dokunmamış bırakır. Dosya biçimi bir adım daha atar ve çoktan oluşturmuş olduğu .dbf'i, varsa .dbt'yi siler

Bir string'in ana kod sayfasını atlatıp atlatmadığını nasıl test edersiniz?

AnsiString'e çevirin, oradan WideString'e geri çevirin ve özgünüyle karşılaştırın. Sayfanın bulunmadığı bir karakter, bir değişim karakteri olarak geri gelir — genellikle ? — ve karşılaştırma, dışa aktarım veri kaybedeceği tam anda başarısız olur. Yazıcılar aynı dönüşümü kullanır; denetim ile yazıcının kod sayfası hakkında anlaşamaması söz konusu değildir. Bayt uzunluğunu da tutun: on baytlık bir ad yuvasına ya da bir alan genişliğine karşı tutacağınız sayı odur

uses
  Variants;

// V'nin her karakteri ana ANSI kod sayfasını atlatınca True;
// ByteLen, bir alan genişliğiyle karşılaştırılacak kodlanmış uzunluktur
function FitsHostCodePage(const V: Variant; out ByteLen: Integer): Boolean;
var
  W: WideString;
  A: AnsiString;
begin
  W := VarToWideStr(V);   // VarToStr, bu denetim metni görmeden önce çevirme yapabilir
  A := AnsiString(W);
  ByteLen := Length(A);
  Result := WideString(A) = W;
end;

VarToWideStr üzerindeki yorum asıl dersi taşır. Delphi 2007 ve öncesinde string her yerde AnsiString'dir; Free Pascal altında ise Variants unit'i, sizin unit'inizin hangi modda olduğuna bakmaksızın, RTL'nin kendi string türü olan AnsiString ile derlenir. O derleyicilerde VarToStr, her Çince karakterin durduğu yere çoktan bir ? koymuştur. AnsiString(VarToStr(V)) üzerine kurulu bir koruma sonra soru işaretini soru işaretiyle karşılaştırır ve her şeyi kabul eder. TStringList'in oradaki sorunu aynı biçimdedir: satırları bölmek için geniş metni Text özelliğine atarsınız ve her karakter, içeri girerken ana kod sayfasından geçer. XlsParseSlk, XlsParseDifText ve XlsParsePrnText WideString girdisi alır ve her karakterini her ana bilgisayarda korur. Lazarus ile de derliyorsanız, Free Pascal altında HotXLS unit'lerinin hangi string türüyle derlendiğine bakın

Delphi'de DBF, SYLK, DIF ve PRN dışa aktarımı için HotXLS ana ANSI kod sayfası gidiş-dönüş denetimi: AnsiString'e ve WideString'e geri dönüş, Çince metni 936 ana bilgisayarda korur ama 1252'de soru işaretleri üretir; EXlsDbf ile ya da SaveAs'in -1 döndürmesiyle reddedilir
Her değeri ana kod sayfasına çevirip geri getirmek, kaybolan karakterleri hiçbir bayt diske ulaşmadan açığa çıkarır — metin VarToStr yerine VarToWideStr ile okunmuşsa

SYLK ve DIF kayıtları, satır satır

SYLK satır başına tek kayıttır; alanlar noktalı virgülle ayrılır. HotXLS bir ID;PHotXLS başlığı, hücre başına bir C;Ysatır;Xsütun;Kdeğer kaydı ve bir E sonlandırıcı yazar. Metin, gömülü tırnaklar ikilenerek tırnaklanır; bu, değerin içindeki noktalı virgülü de korur ve okuyucu, bazı üreticilerin onun yerine kullandığı ;; kaçışını da kabul eder. Boolean yoktur. Formatın kendi yazıcıları 1 ile 0 gönderir; SaveAs da bunu yazar. Hatalar #N/A gibi metin olarak çıkar, tarihler seri sayı olarak. Baştaki o iki harf, Excel'in SYLK'yi tanıdığı şekildir — ilk hücresi ID okuyan bir CSV'yi neden SYLK dosyası sandığı da budur. Kurma ile parse, ikisi de WideString üzerinde çalışır; aşağıdaki bellek içi gidiş-dönüş, her ana bilgisayarda aynı cevabı verir

uses
  SysUtils, lxSlk;

function SylkRoundTripsInMemory: Boolean;
var
  Cells, Back: TXLSSlkCells;
  Text: WideString;
  I: Integer;
begin
  SetLength(Cells, 3);
  Cells[0].Row := 1; Cells[0].Col := 1; Cells[0].Kind := slkText;
  Cells[0].Text := WideString(#$4E0A#$6D77'; Pudong');   // 上海; Pudong
  Cells[1].Row := 1; Cells[1].Col := 2; Cells[1].Kind := slkText;
  Cells[1].Text := 'Pipe 12" steel';
  Cells[2].Row := 2; Cells[2].Col := 1; Cells[2].Kind := slkNumber;
  Cells[2].Number := 1234.5;

  Text := XlsBuildSlk(Cells);           // K"上海; Pudong", K"Pipe 12"" steel", K1234.5
  Result := XlsParseSlk(Text, Back) = Length(Cells);
  for I := 0 to High(Cells) do
    if Result then
      Result := (Back[I].Row = Cells[I].Row) and (Back[I].Col = Cells[I].Col) and
        (Back[I].Kind = Cells[I].Kind) and (Back[I].Text = Cells[I].Text) and
        ((Cells[I].Kind <> slkNumber) or (Back[I].Number = Cells[I].Number));
end;

DIF daha konuşkandır. Dört başlık grubu — TABLE, VECTORS (sütun sayısı), TUPLES (satır sayısı) ve DATA — üçer satır alır; sonra her veri ögesi iki satır alır: bir tür,sayı satırı ve tırnaklı string'i ya da bir değer göstergesini tutan bir satır. Bir satır -1,0 ile BOT ile açılır; -1,0 ile EOD veriyi kapatır. Sayı, 0,değer artı V'dir; bir mantıksal değer 0,1 artı TRUE ya da 0,0 artı FALSE'tur; bir string 1,0 artı tırnaklı metnidir. Okuma tarafında HotXLS, NA ve ERROR göstergelerini #N/A ve #VALUE!'a eşler ve NULL'u boş sayar. Tek satırında Beijing, 1234.5 ve True taşıyan Orders adlı bir sayfa şöyle dışa aktarılır:

TABLE
0,1
"Orders"
VECTORS
0,3
""
TUPLES
0,1
""
DATA
0,0
""
-1,0
BOT
1,0
"Beijing"
0,1234.5
V
0,1
TRUE
-1,0
EOD

Ondalık virgül üç formatı da kırar

Kod sayfası, bu dosyalara sızan tek makine ayarı değildir. Almanca ya da Fransızca bölgesel ayarlı bir iş istasyonunda FloatToStr, 1234.5'i 1234,5'e çevirir. DIF'te öge satırı 0,1234,5 olur ve tür,değer biçimini kaybeder. SYLK'te K1234,5 bir sayı değildir. Bir dBASE N alanında xBase okuyucuları nokta bekler. HotXLS üçünü de TFormatSettings.Invariant ile yazar ve okur: SYLK, aynı double'a geri okunan en kısa token'i alır; DIF, değişmez bir FloatToStr alır ve tam sayılara .0 ekler; dBASE sayısal alanları bildirilen ondalıklarını, sağa yaslı olarak alır. Parse da yerel ayarı yok sayar; dolayısıyla ondalık virgüllü bir makineden gelen dosya her yerde aynı okunur

Delphi'de HotXLS SYLK ve DIF dışa aktarımı: ID;PHotXLS başlıklı C;Y;X;K kayıtları ve 1 olarak Boolean; BOT ile EOD arası DIF tür,sayı öge çiftleri ve ondalık virgülün DIF, SYLK ve dBASE'i kırdığı yerde 1234.5'i koruyan TFormatSettings.Invariant
Orders satırının SYLK kayıtları ve DIF öge çiftleri olarak yazılması, ondalık virgüllü bir yerel ayarın her formatı nerede bozacağını ve HotXLS'in sayıları neden değişmez ayarlarla biçimlendirdiğini gösterir

Başka programların yazdığı tabloları okumak

Bir başkasının tablosunu açmak, kod sayfası sorusunu okuyan makineye devreder. XlsReadDbfFile ile TXLSXWorkbook.Open — bir .dbf adında — 0x03, 0x83, 0x8B, 0x30, 0x31 ve 0x32 sürüm baytlarını kabul eder; bu, dBASE III ile IV'ü ve Visual FoxPro tablolarını kapsar — FoxPro'nun I (32-bit integer), B (double), Y (10,000 ile ölçeklenmiş currency) ve T (Julian günü artı milisaniye) ikili türleri dahil. Memo metni, tablonun yanındaki bir .dbt'den ya da FoxPro'nun blok düzenindeki bir .fpt'den gelir — hiçbir .dbt mevcut değilse; uzantı karar verir, böylece hiçbir eki ötekinden sanılmaz. Karakter verisi ana sayfada dekode edilir. 866 kod sayfası altında bir DOS programının yazdığı, 29. baytı sıfır bırakılmış bir dBASE III tablosu, bu yüzden bir 1252 makinesinde yanlış harfleri gösterir. Üreticiyi biliyorsanız, içe aktarımı onun kod sayfasının sistem sayfası olduğu yerde koşturun. SYLK, DIF ve .prn dosyaları aynı kuralı izler; çünkü Open, baytlarını geniş metin parser görmesinden önce ana sayfada dekode eder

Yerel ayarlar arasında ASCII dışı metni hangi format taşımalı?

Seçenek varken bu dördün hiçbiri. Bunları, alan sistem ısrar ettiğinde kullanın — bir ERP içe aktarım ekranı, bir xBase uygulaması, bir GIS hattı — ve veri ASCII olduğunda ya da iki uç da aynı kod sayfasını paylaştığında. Gerisi, kodlamasını bildiren bir formatta yol almalıdır: XLSX, SpreadsheetML 2003 ya da HotXLS'in bayt sırası işaretli UTF-8 olarak yazdığı CSV (CSV ve TSV dışa aktarımının kendi taşıma dertleri vardır). SaveAs formatı uzantıdan seçtiği için kodunuz, ilk tablodan itibaren iki reddetme biçimini de ele almak zorundadır: .dbf için bir istisna, metin formatları için -1 sonucu

uses
  SysUtils, lxHandleX, lxDbf;

function SaveForLegacyImport(const BaseName: string): string;
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Orders');   // eklenen ilk sayfa etkin olur
    Sheet.Cells[1, 1].Value := 'CUSTNO';
    Sheet.Cells[1, 2].Value := 'CITY';
    Sheet.Cells[2, 1].Value := 1001;
    Sheet.Cells[2, 2].Value := WideString(#$5317#$4EAC);  // 北京

    Result := BaseName + '.slk';
    if Book.SaveAs(Result) <> 1 then
    begin
      // Etkin kod sayfası metni tutamıyor: hiçbir .slk oluşturulmadı
      Result := BaseName + '.xlsx';
      Book.SaveAs(Result);
    end;

    try
      Book.SaveAs(BaseName + '.dbf');
    except
      on E: EXlsDbf do
        Result := Result + ' (no .dbf: ' + E.Message + ')';
    end;
  finally
    Book.Free;
  end;
end;

936 bir ana bilgisayarda bu fonksiyon, ikisi de 北京 tutan bir .slk ile bir .dbf üretir; 1252'de bir .xlsx üretir ve .dbf'in neden reddedildiğini raporlar. HotXLS Delphi spreadsheet component içindeki RTF, HTML ve ZIP girdisi adları, kendilerine özgü kodlama kurallarını izler