مقاله فنی

رفت و برگشت بدون اتلاف XLSX در دلفی: Theme، extLst، calcChain

کتابخانه بومی اکسل برای دلفی و C++Builder یعنی HotXLS، برای رفت و برگشت بدون اتلاف XLSX ساخته شده است: یک کتاب کار را باز کنید، یک سلول را تغییر دهید، ذخیره نمایید، و تم سفارشی مشتری، بلوک‌های افزونه خارجی extLst و زنجیره محاسباتی همگی سالم باقی می‌مانند. سه مکانیسم این کار را انجام می‌دهند — کش کردن کلمه به کلمه فایل xl/theme/theme1.xml، سریال‌سازی مجدد مبتنی بر رویداد از بلوک‌های ناشناخته <ext> و یک فایل جدید و معتبر xl/calcChain.xml در هر ذخیره‌سازی کتاب کار فرمول‌دار

سناریویی که انگیزه هر سه مورد است، به شکل افسرده‌کننده‌ای رایج است. یک سرویس صورتحساب، قالبی را بارگذاری می‌کند که مشتری در اکسل طراحی کرده است — تم رنگی سازمانی، نمودارهای درون‌سلولی (sparklines) در یک ستون شاخص کلیدی عملکرد (KPI)، قانون قالب‌بندی شرطی اضافه‌شده توسط نسخه جدیدتر اکسل — کل مبلغ فاکتور را در سلول B3 می‌نویسد و ذخیره می‌کند. مشتری خروجی را باز می‌کند و رنگ‌های برند به رنگ آبی پیش‌فرض آفیس برگشته، نمودارهای درون‌سلولی ناپدید شده‌اند و اکسل پیشنهاد "تعمیر" فایل را می‌دهد. هیچ چیز در کد به آن ویژگی‌ها دست نزده است. کتابخانه صرفاً با ذخیره کردن این کار را کرده است

چرا فایل‌های اکسل پس از ویرایش توسط کتابخانه قالب‌بندی خود را از دست می‌دهند؟

فایل‌های اکسل پس از ویرایش توسط کتابخانه قالب‌بندی خود را از دست می‌دهند زیرا بیشتر کتابخانه‌ها فایل را ویرایش نمی‌کنند — بلکه آن را بازسازی می‌نمایند. یک بسته .xlsx یک فایل ZIP از بخش‌های XML است: xl/workbook.xml، یک فایل xl/worksheets/sheetN.xml برای هر برگه، xl/styles.xml، xl/theme/theme1.xml، xl/calcChain.xml و موارد دیگر. یک کتابخانه معمولی این بخش‌ها را هنگام باز کردن به یک مدل شیء تجزیه می‌کند و در زمان ذخیره، هر بخش را از روی آن مدل بازتولید می‌نماید. هر ویژگی که مدل آن را نشان ندهد — تمی که هرگز تجزیه نکرده، یک بلوک افزونه از اکسل جدیدتر — جایی برای زندگی در حافظه ندارد، بنابراین بخش بازتولید شده بی سر و صدا آن را حذف می‌کند

استاندارد ECMA-376 نیمی از این مشکل را پیش‌بینی کرده بود. SpreadsheetML بخش extLst (استاندارد ECMA-376 بخش 1، "ناحیه ذخیره‌سازی داده‌های ویژگی‌های آینده"، §18.2.10 برای عنصر در سطح کتاب کار) را به عنوان یک نقطه افزونه مشخص تعریف می‌کند: تولیدکنندگان جدیدتر ویژگی‌ها را در آنجا پارک می‌کنند، که هر کدام در یک عنصر <ext> حاوی ویژگی uri که ویژگی را شناسایی می‌کند پیچیده شده‌اند، و از مصرف‌کنندگان قدیمی‌تر انتظار می‌رود آنچه را که درک نمی‌کنند حفظ نمایند. نمودارهای درون‌سلولی، اسلایسرها و انواع جدیدتر قالب‌بندی‌های شرطی همگی از این طریق حرکت می‌کنند. کتابخانه‌ای که بلوک‌های ناشناخته <ext> را دور می‌اندازد، بنابراین نه تنها باعث از دست رفتن داده می‌شود — بلکه پیمان سازگاری رو به جلو را که فرمت بر اساس آن طراحی شده نقض می‌کند. سوالی که باید از هر کتابخانه صفحه‌گسترده‌ای که در حال ارزیابی آن هستید بپرسید صریح است: اگر یک سلول را تغییر دهم، چه چیز دیگری تغییر می‌کند

چگونه HotXLS یک تم سفارشی را بایت به بایت حفظ می‌کند؟

HotXLS تم یک کتاب کار را با کش کردن بایت‌های اصلی xl/theme/theme1.xml در زمان باز کردن و نوشتن کلمه به کلمه آن‌ها در زمان ذخیره حفظ می‌کند. بخش تم (استاندارد ECMA-376 بخش 1، §14.2.7) در واقع DrawingML است، نه SpreadsheetML — طرح‌های رنگی، طرح‌های فونت، طرح‌های قالب‌بندی — و یک موتور صفحه‌گسترده هیچ دلیلی برای مدل‌سازی عمیق آن ندارد. نسخه‌های قبلی HotXLS یک تم ثابت آفیس را در هر ذخیره‌سازی بازتولید می‌کردند که این دقیقاً همان شکست "رنگ‌های برند برگشته" در بالا است؛ از نسخه v2.89.46 تم بسته باز شده به صورت خام ذخیره شده و بدون تغییر مجدداً منتشر می‌شود، و تم داخلی آفیس فقط برای کتاب‌های کاری که از ابتدا ساخته می‌شوند تولید می‌گردد. بایت‌های خام قوی‌ترین تضمین ممکن برای دقت هستند: بدون تجزیه، بدون سریال‌سازی مجدد، بدون احتمال انحراف

کپی کلمه به کلمه عمداً بر دسترسی برنامه‌نویسی‌شده به تم پیروز می‌شود. کلاس TXLSXWorkbook ویژگی‌های ThemeMajorFont و ThemeMinorFont را ارائه می‌دهد تا بتوانید قلم‌های تیتر و بدنه را برای کتاب‌های کاری جدید انتخاب کنید، اما وقتی یک تم کلمه به کلمه در زمان باز کردن ضبط شده باشد، آن تنظیم‌کننده‌ها هیچ تاثیری روی فایل ذخیره‌شده ندارند — رفت و برگشت اولویت دارد. اگر واقعاً نیاز به تغییر تم یک کتاب کار موجود دارید، این نشانه‌ای است برای ویرایش قالب در خود اکسل به جای استفاده از یک API داده‌محور. موارد روزمره اصلاً به هیچ API نیاز دارند:

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('branded-invoice.xlsx');
    Book.Sheets[0].Cells[3, 2].Value := 42750.00;  // one edit
    Book.SaveAs('branded-invoice-out.xlsx');
    // فایل theme1.xml در خروجی از نظر بایتی کاملاً با ورودی یکسان است
  finally
    Book.Free;
  end;
end;

چه اتفاقی برای بلوک‌های ناشناخته extLst در زمان ذخیره می‌افتد؟

HotXLS هر بلوک <ext> در سطح برگه را که به طور بومی مدل‌سازی نکرده است ضبط می‌کند و آن را در extLst برگه ذخیره‌شده بازپخش می‌نماید، بنابراین ویژگی‌های نوشته شده توسط نسخه‌های جدیدتر اکسل در رفت و برگشت سالم می‌مانند. از نسخه v2.131.0، بخش‌های ضبط‌شده از طریق ویژگی فقط‌خواندنی RawWorksheetExts، که یک TStringList در هر برگه XLSX است، قابل مشاهده هستند، که این امر تایید ضمانت را در کد آزمایشی به جای یک باور ساده ممکن می‌سازد:

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  i: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('from-newer-excel.xlsx');
    Sheet := Book.Sheets[0];
    WriteLn(Format('%d foreign ext block(s) captured',
      [Sheet.RawWorksheetExts.Count]));
    for i := 0 to Sheet.RawWorksheetExts.Count - 1 do
      WriteLn(Copy(Sheet.RawWorksheetExts[i], 1, 100)); // بررسی اجمالی هر uri
  finally
    Book.Free;
  end;
end;

جزئیات پیاده‌سازی که ارزش دانستن دارد این است که ضبط فرآیند، یک سریال‌سازی مجدد در سطح رویداد است و نه یک کپی بایت خام. خواننده XML جریانی HotXLS هیچ آفست منبعی را نشان نمی‌دهد، بنابراین زیردرخت ناشناخته از رویدادهای Element, Text و EndElement در حین عبور جریان بازسازی می‌شود. این رویکرد یک تله کلاسیک را پنهان می‌کند: یک عنصر خودبسته مانند <a/> فقط یک رویداد Element با پرچم خالی را فراخوانی می‌کند و هرگز EndElement را فراخوانی نمی‌نماید، بنابراین هر شمارنده عمقی که صرفاً با EndElement کاهش می‌یابد، هرگز بسته شدن زیردرخت را نخواهد دید. با مدیریت آن، بخش بازسازی‌شده از نظر معنایی معادل نسخه اصلی است — نقل‌قول صفت‌ها و فرم‌های خودبسته نرمال‌سازی می‌شوند، بنابراین از نظر بایتی یکسان نیست، اما اکسل معنا را می‌خواند و نه بایت‌ها را. دو ویژگی خروجی خود اکسل بازپخش را ایمن می‌سازند: اکسل ویژگی‌های لازم xmlns را در عنصر <ext> یا داخل آن اعلام می‌کند، بنابراین هر بخش ضبط‌شده از نظر فضای نام خودکفا است، و همین خودکفایی دلیلی است که چرا شبیه‌سازی یک برگه در داخل یا در بین کتاب‌های کار می‌تواند بلوک‌های خارجی را با یک انتساب ساده لیست رشته به همراه داشته باشد

نوشتن calcChain.xml تا اکسل به فرمول‌های شما اعتماد کند

کامپوننت HotXLS فایل xl/calcChain.xml (بخش زنجیره محاسباتی، استاندارد ECMA-376 بخش 1، §12.3.1) را هر زمان که کتاب کار ذخیره‌شده شامل فرمول باشد می‌نویسد، و بین دو ترتیب یکی را انتخاب می‌کند. اگر نمودار وابستگی فرمول از قبل ساخته شده و جاری باشد — یعنی پس از آخرین ویرایش خود متد Recalculate را فراخوانی کرده باشید — زنجیره با ترتیب کامل توپولوژیکی (وابستگی‌ها قبل از وابسته‌ها) منتشر می‌شود، و اعضای مرجع حلقوی در انتها ضمیمه می‌گردند. در غیر این صورت، سلول‌ها به ترتیب سند فهرست می‌شوند. هر دو روش درست هستند: یادداشت‌های پیاده‌سازی مایکروسافت برای این فرمت، [MS-XLSX]، با زنجیره محاسباتی به عنوان یک راهنما رفتار می‌کنند که اکسل در حین بارگذاری آن را تایید و مرتب‌سازی مجدد می‌کند، بنابراین هر فهرست کاملی قانونی است، و HotXLS عمداً از اجبار به ساخت نمودار در داخل متد SaveAs خودداری می‌کند — ساخت یال‌ها با تعداد سلول‌ها رابطه درجه دوم دارد که هزینه پنهان غیرقابل قبولی برای ذخیره‌سازی یک میلیون سلول است

Book.Open('model.xlsx');
Book.Sheets[0].Cells[10, 4].Formula := '=SUM(D2:D9)';
// اکنون ذخیره شد، calcChain.xml سلول‌های فرمول را به ترتیب سند فهرست می‌کند.
// پس از Recalculate نمودار وابستگی وجود دارد، بنابراین همین ذخیره‌سازی
// به جای آن یک ترتیب توپولوژیکی کامل را منتشر می‌کند:
Book.Recalculate;
Book.SaveAs('model-out.xlsx');

مرزهای پایان رفت و برگشت بدون اتلاف

در اینجا صداقت بیشتر از یک چک‌باکس بازاریابی اهمیت دارد، بنابراین مرزها شایسته توجه یکسان هستند. HotXLS کل بسته را بایت به بایت کپی نمی‌کند: XML برگه، سبک‌ها، رشته‌های مشترک و بخش‌های کتاب کار از روی مدلِ تجزیه‌شده بازتولید می‌شوند، بنابراین خروجی از نظر معنایی وفادار است اما از نظر باینری یکسان نیست — هدرهای محلی ZIP به تنهایی مهرهای زمانی جدید DOS را حمل می‌کنند. بخش‌های <ext> ضبط‌شده همان‌طور که در بالا توضیح داده شد نرمال‌سازی شده برمی‌گردند. در صورت وجود یک تم کلمه به کلمه، بازنویسی قلم‌های تم با برنامه نادیده گرفته می‌شود. و شبکه حفاظتی دارای چشمه‌های تعریف‌شده‌ای است: ویژگی‌هایی که HotXLS به طور بومی مدل‌سازی می‌کند (برای مثال نمودارهای درون‌سلولی تجزیه و بازنویسی می‌شوند تا اینکه کورکورانه کپی شوند) به علاوه محتوای extLst خارجی به علاوه بخش‌های کش‌شده کلمه به کلمه. بخشی که نه مدل‌سازی شده و نه در داخل یک نقطه افزونه قرار دارد — برای مثال بخش سفارشی یک افزونه خاص — خارج از سه مکانیسم توضیح داده شده در این مقاله قرار می‌گیرد، بنابراین قالب‌های واقعی خود را به جای فرض کردن، آزمایش کنید

کارهای حفاظتی مجاور تصویر را کامل می‌کنند. پروژه‌های VBA و مراجع کتاب کار خارجی با استفاده از همان فلسفه "حفظ آنچه مدل‌سازی نمی‌کنید" که در مقاله مکمل درباره حفظ VBA و پیوند خارجی پوشش داده شده است، از ذخیره‌سازی عبور می‌کنند و ویژگی‌های سند در docProps دارای API خواندن و نوشتن مخصوص به خود هستند تا اینکه به طور خاموش حذف شوند. وقتی هر کتابخانه صفحه‌گسترده‌ای را ارزیابی می‌کنید، آزمایش یک سلول را اجرا نمایید: یک کتاب کار تولیدی پر از ویژگی را باز کنید، یک مقدار را تغییر دهید، ذخیره کنید و بخش‌های غیرفشرده را با نسخه اصلی مقایسه (diff) نمایید. اینکه چه چیزهایی فراتر از برگه‌ای که لمس کردید تغییر کرده است، اطلاعات بیشتری درباره کتابخانه نسبت به هر جدول ویژگی به شما می‌دهد

مکانیسم‌های رفت و برگشت توضیح داده شده در اینجا — حفظ کلمه به کلمه تم از نسخه v2.89.46، ضبط extLst خارجی و انتشار calcChain.xml از نسخه v2.131.0 — در نسخه فعلی کامپوننت اکسل دلفی HotXLS ارائه می‌شوند که صفحه محصول آن مجموعه ویژگی‌های کامل خواندن و نوشتن XLSX را برای دلفی و C++Builder مستند می‌کند