مقاله فنی

خواندن فایل‌های Excel 2.0 تا 4.0 در دلفی با HotXLS

HotXLS کارپوشه‌هایی را که Excel 2.0، 3.0 و 4.0 نوشته‌اند، مستقیماً از دلفی و C++Builder باز می‌کند. این فایل‌ها مقدم بر ظرف سند مرکب OLE هستند که هر .xls بعدی از آن استفاده می‌کند، بنابراین جریان‌های رکورد خام BIFF هستند، بدون هیچ پوشش ذخیره‌سازی‌ای، و یک خواننده ساخته‌شده برای BIFF8 حتی یک ساختار قابل‌تشخیص درون آن‌ها نمی‌یابد. بازکردن یکی از آن‌ها همان فراخوانی Open را استفاده می‌کند که هر کارپوشه دیگری استفاده می‌کند؛ خواننده قالب را تشخیص می‌دهد و مسیر را تغییر می‌دهد

این فایل‌ها همچنان سر و کله‌شان پیدا می‌شود، و همین تنها دلیلی است که این موضوع اهمیت دارد. بایگانی‌های مهندسی، نگهداری سوابق دولتی، داده‌های آزمایشگاهی از ابزارهایی که نرم‌افزار کنترل‌شان در سال ۱۹۹۳ نوشته شده، و سیستم‌های حسابداری دیرپا، همگی کارپوشه‌های BIFF2 و BIFF4 را برجای گذاشته‌اند. اکسل مدرن چندین مورد از آن‌ها را کاملاً از بازکردن سر باز می‌زند، چون مبدل‌های قدیمی را به دلایل امنیتی حذف کرده، که یک مجموعه‌داده را برجای می‌گذارد که هیچ‌کس نمی‌تواند با ابزاری که کسی دارد آن را بخواند

چه چیزی یک کارپوشه پیش از OLE را متفاوت می‌کند؟

هر .xls از Excel 5.0 به بعد یک فایل مرکب OLE2 است، یک سیستم فایل کوچک درون یک فایل، که کارپوشه در یک جریان به نام Workbook یا Book زندگی می‌کند. تجزیه یکی از آن‌ها با تجزیه آن ظرف شروع می‌شود، همان‌طور که در قالب باینری فایل مرکب در پاسکال شرح داده شده

BIFF2 تا BIFF4 هیچ ظرفی ندارند. فایل بی‌درنگ با یک رکورد BOF شروع می‌شود، و شماره رکورد آن BOF نسل را رمزگذاری می‌کند: $0009 برای BIFF2، $0209 برای BIFF3 و $0409 برای BIFF4. HotXLS طول بدنه BOF، که بین چهار تا شش بایت است، و نوع زیرجریان، $0010 برای یک کاربرگ، $0020 برای یک نمودار و $0040 برای یک کاربرگ ماکرو را پیش از متعهدشدن به مسیر خام، اعتبارسنجی می‌کند. همین اعتبارسنجی است که از تفسیر یک فایل خراب یا نادرست‌شناسایی‌شده به‌عنوان یک کارپوشه بسیار قدیمی جلوگیری می‌کند

سه نسل، سه چیدمان رکورد

رکوردهای سلولی جایی هستند که نسل‌ها آشکارترین واگرایی را دارند. BIFF2 یک بلوک پیوسته از شماره‌های رکورد پایین را اشغال می‌کند، $0001 تا $0005 برای سلول‌های خالی، عدد صحیح، عدد، برچسب و بولین-یا-خطا، و هر بدنه یک فیلد ویژگی سه‌بایتی حمل می‌کند که نسخه‌های بعدی در آن یک اندیس قالب گسترش‌یافته می‌گذارند. BIFF3 و BIFF4 آن را کنار می‌گذارند و شماره‌ها و چیدمان‌های رکورد BIFF5 را دوباره استفاده می‌کنند، $0201، $0203، $0204 و $0205، با یک اندیس XF دوبایتی

همین جزئیات آخر یک شکست خاص و به‌آسانی نادرست‌تشخیص‌داده‌شده ایجاد می‌کند. یک رکورد LABEL در BIFF3 یا BIFF4 از نظر ساختاری با همتای BIFF5 آن یکسان است، سطر و ستون که با اندیس قالب و سپس تعداد نویسه دنبال می‌شوند. یک خواننده بنویسید که چیدمان BIFF2 را فرض می‌کند و آن دو بایت کمتر از حد لازم می‌خواند، سپس از انتهای رکورد بیرون می‌رود و هر چیزی پس از آن را نادرست تفسیر می‌کند. علامت آن یک استثنا نیست؛ یک کارپوشه است که با آشغال قابل‌قبول درون آن خوانده می‌شود

رکوردهای فرمول یک شماره‌گذاری موازی در هر سه دارند، $0006، $0206 و $0406. وقتی یک فرمول نتیجه‌ای رشته‌ای تولید می‌کند، آن رشته در یک رکورد بعدی جداگانه می‌رسد، $0007 یا $0207، و شکل BIFF2 آن از یک پیشوند طول تک‌بایتی استفاده می‌کند، نه پیشوند دوبایتی‌ای که بعداً استفاده می‌شود

چرا فرمول‌ها به‌صورت مقدار بازمی‌گردند، نه متن؟

HotXLS نتیجه ذخیره‌شده یک فرمول را در این فایل‌ها می‌خواند و تلاش نمی‌کند عبارت فرمول را بازسازی کند. این یک مرز عمدی است، نه یک شکاف در انتظار پرشدن

عبارت تجزیه‌شده در BIFF2 تا BIFF4 از یک رمزگذاری نشانه (token) استفاده می‌کند که با BIFF5 و نسخه‌های بعدی به شیوه‌هایی فراتر از ظاهری متفاوت است: طول‌های نشانه به‌شکل متفاوتی پیشوندگذاری شده‌اند، نشانه‌های ارجاع اندازه‌های متفاوتی دارند، و جدول‌های اندیس تابع بین نسل‌ها دوباره‌شماره‌گذاری شده‌اند. عبور این بایت‌ها از یک مترجم عبارت BIFF8 یک فرمول نادرست تولید نمی‌کند، یک فرمول تصادفی تولید می‌کند. خواندن مقدار ذخیره‌شده، عدد یا رشته‌ای را که اکسل آخرین‌بار محاسبه کرده به شما می‌دهد، که همان چیزی است که یک مهاجرت بایگانی واقعاً به آن نیاز دارد

مقدار ذخیره‌شده در یک افست وابسته‌به‌نسل درون رکورد قرار دارد: بایت ۷ برای BIFF2 و بایت ۶ برای BIFF3 و BIFF4. مقادیر ویژه، رشته‌ها، بولین‌ها، خطاها و خالی‌ها، در یک واژه نشانگر $FFFF با یک تفکیک‌کننده رمزگذاری شده‌اند، همان قراردادی که نسل‌های بعدی BIFF نگه داشتند

بازکردن یکی

کد فراخوانی‌کننده عادی است، و همین نکته است. تشخیص درون Open رخ می‌دهد:

uses
  lxHandle;

var
  Book: TXLSWorkbook;
  Sheet: TXLSWorksheet;
  R, C: Integer;
  V: Variant;
begin
  Book := TXLSWorkbook.Create;
  try
    if Book.Open('archive\1993-inventory.xls') <> 1 then
    begin
      Writeln('unreadable - quarantine for manual review');
      Exit;
    end;
    Sheet := Book.Sheets[1];          // Sheets[] یک‌پایه است
    for R := Sheet.UsedRange.FirstRow + 1 to Sheet.UsedRange.LastRow + 1 do
      for C := Sheet.UsedRange.FirstCol + 1 to Sheet.UsedRange.LastCol + 1 do
      begin
        V := Sheet.Cells[R, C].Value;
        if not VarIsEmpty(V) then
          Writeln(Format('R%dC%d = %s', [R, C, VarToStr(V)]));
      end;
  finally
    Book.Free;
  end;
end;

به حساب اندیس در آن حلقه توجه کنید. کران‌های UsedRange صفر-پایه هستند درحالی‌که هم مجموعه کاربرگ و هم دسترسی سلولی یک‌پایه هستند، ناسازگاری‌ای که مقدم بر API فعلی است و برای سازگاری حفظ شده. فراموش‌کردن این تعدیل، مستطیل نادرستی را ممیزی می‌کند و هیچ چیز غیرعادی‌ای را در حین انجام آن گزارش نمی‌دهد. پیش‌بررسی‌های ارزانی که اصلاً از بارگذاری یک فایل اجتناب می‌کنند در بازرسی سبک کارپوشه پوشش داده شده

چه چیزی نمی‌گیرید، و باید در موردش چه کاری کرد؟

قالب‌بندی تفسیر نمی‌شود. HotXLS رکوردهای XF و FONT این نسل‌ها را تجزیه نمی‌کند، بنابراین فونت‌ها، رنگ‌ها، حاشیه‌ها و قالب‌های عددی در دسترس نیستند، و سلول‌هایی که اکسل زمانی به‌صورت تاریخ نمایش می‌داد، به‌صورت اعداد ترتیبی خام خود بازمی‌گردند

همین مورد آخر به رسیدگی در کد خودتان نیاز دارد نه در خواننده، و دلیل آن صادقانه است: قالب‌های عددی در BIFF2 تا BIFF4 به‌اندازه کافی قابل‌اتکا نیستند تا یک تصمیم خودکار تاریخ را هدایت کنند. یک ستون از اعداد پنج‌رقمی ممکن است تاریخ باشد، یا ممکن است شماره قطعه باشد. با استفاده از سیستم تاریخ کارپوشه، که قواعد آن در اعداد ترتیبی تاریخ، سیستم ۱۹۰۴ و قالب‌های عددی شرح داده شده، به‌عمد تبدیل کنید:

// به ازای هر ستون تصمیم بگیرید، هرگز به ازای هر مقدار: یک عدد پنج‌رقمی
// می‌تواند یک تاریخ باشد یا یک شماره قطعه، و قالب قدیمی به شما نمی‌گوید
if ColumnHoldsDates(C) then
begin
  // دو سیستم تاریخ ۱۴۶۲ روز فاصله دارند، بنابراین همان عدد ترتیبی
  // دو تاریخ با فاصله چهار سال را نشان می‌دهد. سیستم را از کارپوشه
  // بخوانید نه اینکه یکی را فرض کنید
  if Book.Date1904 then
    Writeln(DateToStr(SerialToDate1904(V)))
  else
    Writeln(DateToStr(SerialToDate1900(V)));
end
else
  Writeln(VarToStr(V));

دو نکته ساختاری تصویر را کامل می‌کنند. رکوردهای حفاظت رمز عبور و صفحه‌کد درون همان جریان کاربرگ واحد ظاهر می‌شوند نه در یک جریان سطح‌کارپوشه، زیرا جریان سطح‌کارپوشه‌ای برای گذاشتن آن‌ها در آن وجود ندارد، بنابراین باید در بافت کاربرگ شناسایی شوند. و یک فایل BIFF2 تا BIFF4 دقیقاً یک زیرجریان کاربرگ دارد؛ کارپوشه‌های چندکاربرگی تا زمانی که قالب ظرف خود را به دست آورد وجود نداشتند

بنابراین مسیر عملی مهاجرت دومرحله‌ای است: فایل قدیمی را برای مقادیر آن بخوانید، سپس یک کارپوشه مدرن بنویسید که آن مقادیر را با قالب‌بندی‌ای که خودتان اعمال می‌کنید حمل کند. خواندن قدیمی، نوشتن مدرن و هر چیزی بین این دو، در یک کتابخانه واحد برای دلفی و C++Builder اجرا می‌شود، که در صفحه مؤلفه صفحه‌گسترده HotXLS برای دلفی شرح داده شده