مقاله فنی

تولید گزارش‌های اکسل بر اساس قالب در Delphi با HotXLS

راه مطمئن برای تولید یک گزارش Excel سبک‌دار از Delphi این است که از workbookای شروع کنید که یک طراح از پیش ساخته است. کسی در واحد مالی صورت‌حساب را در Excel می‌چیند: لوگو، سرستون‌ها، کادرهای نوار جزئیات، ردیف پررنگ جمع کل، قالب‌های ارزی. کد شما آن فایل را باز می‌کند، دادهٔ زنده را در سلول‌هایی که طراح برایش کنار گذاشته می‌ریزد و نتیجه را ذخیره می‌کند. ظاهر مال آنهاست؛ عددها مال شما. HotXLS، کتابخانه‌ای بومی برای Delphi و C++Builder که workbookهای XLS و XLSX را بدون راه‌اندازی Excel می‌خواند و می‌نویسد، سه عملیاتی را که این رویکرد لازم دارد در اختیارتان می‌گذارد: جست‌وجوی یک سلول بر پایهٔ متنش، کپی یک بازه همراه با سبک‌ها و فرمول‌هایش، و درج ردیف به‌گونه‌ای که هر چیزی زیر آن همراه داده پایین برود

تنها قاعده‌ای که یک تولیدکننده را که از ویرایش‌های قالب جان سالم به در می‌برد از تولیدکننده‌ای که با نخستین ویرایش می‌شکند جدا می‌کند این است: هرگز سلول‌ها را با شمارهٔ ردیف و ستون عینی آدرس ندهید. قالب سندی است که آدم‌های دیگر ویرایشش می‌کنند. تیم مالی یک سطر مالیات اضافه می‌کند، ارتفاع ردیف لوگو را بالا می‌برد، بلوک نشانی را جابه‌جا می‌کند، و قالب فایل هیچ کمکی به شما نمی‌کند: ذخیرهٔ BIFF یا OOXML موفق می‌شود، فارغ از اینکه ردیف 10 هنوز همان معنای فصل گذشته را بدهد یا نه. تولیدکننده‌ای که نخستین سطر جزئیات را در ردیف سفت‌وسخت 10 می‌نویسد، همان بار اولی که کسی بلوکی بالای بخش جزئیات درج کند، اقلام را روی سلول‌های نادرست مهر می‌زند و بازه‌ای را جمع می‌بندد که دیگر داده را پوشش نمی‌دهد. هیچ استثنایی برانگیخته نمی‌شود، هر ذخیره موفقیت برمی‌گرداند، و تنها نشانه، مشتری‌ای است که متوجه یک صورت‌حساب غلط می‌شود

دیاگرام خط لولهٔ قالب HotXLS در Delphi؛ لنگرگذاری توکن‌ها با FindText و باز کردن نوار جزئیات و راستی‌آزمایی جمع محاسبه‌شده و سپس ذخیره
تولید گزارش قالبی در Delphi به‌صورت چهار مرحله HotXLS اجرا می‌شود: لنگر توکن‌ها، بسط نوار جزئیات، راستی‌آزمایی مجموع محاسبه‌شده، سپس تحویل

هر مختصات را به یک توکن جانگهدار لنگر بزنید

راه‌حل این است که قالب مختصات خودش را با خود حمل کند. طراح توکن‌هایی مانند {{CUSTOMER}}، {{DATE}} و {{DETAIL_START}} را در سلول‌هایی می‌نویسد که تولیدکننده باید به آنها دست بزند، و تولیدکننده هر موقعیت را در زمان اجرا از جایی که آن توکن‌ها را می‌یابد به دست می‌آورد. ویرایش‌های چیدمان دیگر اهمیتی ندارند، چون توکن همراه سلولی که در آن نشسته جابه‌جا می‌شود. نیمهٔ دوم این قرارداد، قاعدهٔ شکست است: اگر یک توکن لازم غایب باشد، کار پیش از آنکه هیچ دادهٔ مشتری به فایل برسد متوقف می‌شود. قالبی که رانش کرده باید یک تیکت کار ناموفق تولید کند، نه یک سند تحویل‌شده

یافتن توکن‌ها: FindText و ReplaceText

هر دو خانوادهٔ کلاس HotXLS جست‌وجو در سطح کاربرگ را در اختیار می‌گذارند. FindText ردیف و ستون نخستین سلولی را برمی‌گرداند که متنش مطابقت دارد، به‌همراه یک overload که حساسیت به بزرگی و کوچکی حروف را اضافه می‌کند. ReplaceText همهٔ رخدادها را جایگزین می‌کند و تعداد تغییرها را برمی‌گرداند. این دو، دو گونه توکنی را که معمولاً دارید پوشش می‌دهند. یک لنگر تکی مانند نام مشتری را یک‌بار پیدا می‌کنید و کنارش می‌نویسید؛ توکنی که باید دقیقاً یک‌بار بیاید، مانند تاریخ گزارش، را جایگزین می‌کنید و شمارش را بررسی می‌کنید. در سمت XLSX، پرکردنی که خودش را این‌گونه لنگر می‌اندازد چنین شکلی دارد:

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  R, C: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open('invoice-template.xlsx') <> 1 then
      raise Exception.Create('Cannot open invoice template');
    Sheet := Book.Sheets[0];               // TXLSXSheets.Items مبنا-صفر است

    if not Sheet.FindText('{{CUSTOMER}}', R, C) then
      raise Exception.Create('Template drift: {{CUSTOMER}} anchor missing');
    Sheet.Cells[R, C].Value := 'ACME Corp';

    if Sheet.ReplaceText('{{DATE}}',
         FormatDateTime('yyyy-mm-dd', Date)) = 0 then
      raise Exception.Create('Template drift: {{DATE}} token missing');
    // گسترش جزئیات و ذخیره در ادامه می‌آید
  finally
    Book.Free;
  end;
end;

دو جزئیات اهمیت دارد. نخست، FindText و ReplaceText با مقدار متنی یک سلول تطبیق می‌کنند؛ توکنی که درون رشتهٔ یک فرمول جاسازی شده باشد برایشان نامرئی است، پس توکن‌های جانگهدار جایشان در سلول‌های ساده است، هرگز درون فرمول‌ها. دوم، شمار جایگزینی، آشکارساز رانش شماست. قالبی که باید دقیقاً یک توکن {{DATE}} داشته باشد اما صفر جایگزینی گزارش می‌کند ویرایش شده است، و برانگیختن یک استثنا در همان لحظه دقیقاً همان چیزی است که رانش خاموش چیدمان را به یک شکست دیدنی تبدیل می‌کند

همسان‌سازی ردیف جزئیات بدون از دست دادن سبک‌ها یا فرمول‌ها

بخش جزئیات یک صورت‌حساب همراه داده رشد می‌کند. نوشتن مستقیم مقدارها در ردیف‌های خالی زیر سطر نمونه، هر چیزی را که طراح آماده کرده دور می‌ریزد: کادرها، قالب‌های عددی، فرمول‌های هر ردیف. الگویی که همهٔ آن را نگه می‌دارد این است که یک ردیف نمونهٔ کاملاً قالب‌بندی‌شده در قالب باقی بماند و برای هر قلم همسان‌سازی شود. CopyRange سبک‌ها و فرمول‌ها را در یک فراخوانی تکثیر می‌کند و پس از آن تولیدکننده فقط سلول‌های مقدار را بازنویسی می‌کند

دیاگرام لنگرهای توکن در قالب Delphi در HotXLS؛ جای‌نگهدار غایب، کار را پیش از نوشتن هر داده‌ای شکست می‌دهد
توکن‌های قالبی مختصات خودشان را حمل می‌کنند، و توکن غایب کار را پیش از نوشتن هر داده‌ای متوقف می‌کند
const
  DetailRow = 10;            // ردیف نمونهٔ قالب‌بندی‌شده در قالب
var
  I: Integer;
begin
  // نخست پیش از بلوک جمع کل جا باز کن تا بازهٔ SUM
  // زیر نوار جزئیات همراه با داده کش بیاید
  if Length(Items) > 1 then
    Sheet.InsertRows(DetailRow + 1, Length(Items) - 1);

  for I := 0 to High(Items) do
  begin
    if I > 0 then              // سبک‌ها و فرمول‌ها را از ردیف نمونه همسان کن
      Sheet.CopyRange(DetailRow, 1, DetailRow, 5, DetailRow + I, 1);
    Sheet.Cells[DetailRow + I, 1].Value := Items[I].Name;
    Sheet.Cells[DetailRow + I, 2].Value := Items[I].Qty;
    Sheet.Cells[DetailRow + I, 3].Value := Items[I].UnitPrice;
    Sheet.Cells[DetailRow + I, 4].Formula :=
      Format('B%d*C%d', [DetailRow + I, DetailRow + I]);  // بدون پیشوند '='
  end;
end;

به انتساب فرمول با دقت نگاه کنید. ویژگی Formula در XLSX عبارت را بدون علامت مساوی آغازین می‌گیرد، در حالی که نمای XLS انتظار دارد '=B10*C10' از طریق Value نسبت داده شود. قاطی‌کردن این دو قرارداد رایج‌ترین اشتباه انتقال میان خانواده‌های کلاس است، و بی‌سروصدا شکست می‌خورد: سلول فقط یک رشتهٔ عینی نگه می‌دارد که Excel آن را متن نشان می‌دهد. اگر قالب نوار جزئیات را با ردیف‌های عنوان ادغام‌شده تزئین می‌کند، به یاد داشته باشید که تنها سلول بالا-چپ یک ناحیهٔ ادغام‌شده مقدار حمل می‌کند. قاعده‌های چیدمان در مقالهٔ همراه دربارهٔ سلول‌های ادغام‌شده در قالب‌های گزارش چیدمان‌محور توضیح می‌دهد چرا ناحیه‌های ادغام باید کاملاً بیرون از نوار داده بمانند

InsertRows چه چیزی را جابه‌جا می‌کند و چه چیزی را جا می‌گذارد

درج ردیف پیش از بلوک جمع کل همان چیزی است که بازهٔ SUM را همراه رشد بخش جزئیات کش می‌آورد. در سمت XLSX، InsertRows فهرست بلندی از ساختارهای وابسته را همراه سلول‌ها پایین می‌برد: بازه‌های ادغام‌شده، ارتفاع ردیف‌ها، پیوندها، یادداشت‌ها، قاب‌های منجمد، بازه‌های فیلتر خودکار، قالب‌بندی شرطی، اعتبارسنجی داده، جدول‌ها، نام‌های تعریف‌شده و لنگرهای تصویر و نمودار. در آن فهرست یک مرز هست که ارزش سپردن به حافظه را دارد. بازنویسی فرمول‌ها فقط به ارجاع‌های درون همان برگه می‌رسد. فرمولی روی یک برگهٔ خلاصه که به ناحیهٔ جابه‌جاشده اشاره می‌کند مختصات قدیمی‌اش را نگه می‌دارد و بی‌سروصدا سلول‌های نادرست را می‌خواند، و به همین دلیل جمع‌های کشیده‌شده میان برگه‌ها را امن‌تر است با نام‌های سطح workbook بیان کنید. مقالهٔ همراه دربارهٔ نام‌های تعریف‌شده و فرمول‌های میان‌برگه‌ای آن الگو را گام‌به‌گام باز می‌کند

قالب قدیمی XLS مرز را جای سخت‌تری می‌کشد. HotXLS جدول‌های محوری، جدول‌های پرس‌وجو و اتصال‌های دادهٔ بیرونی را در فایل‌های BIFF به‌شکل بلوک‌های بایت خام نگه می‌دارد. اینها باز و ذخیره را بدون تغییر پشت سر می‌گذارند، اما مدل‌سازی نشده‌اند، پس درج ردیف هرگز به آنها دست نمی‌زند. قالبی که یک جدول محوری را زیر یک بلوک جزئیات در حال گسترش پارک می‌کند بدون هیچ هشداری ذخیره می‌شود، در حالی که مستطیل منبع محوری از روی داده می‌لغزد. راه بیرون‌رفت ساختاری است نه دفاعی: محتوای محوری و پرس‌وجو را روی برگه‌هایی نگه دارید که تولیدکننده هرگز در آنها درج نمی‌کند، آن‌گاه کهنگی اصلاً نمی‌تواند رخ دهد

دیاگرام آنچه HotXLS با InsertRows در XLSX جابه‌جا می‌کند و مرزهای فرمول بین‌برگه‌ای و جدول محوری BIFF که مولدهای Delphi باید رعایت کنند
InsertRows روی XLSX ساختارهای وابسته را پایین می‌برد، در حالی که فرمول‌های بین‌کاربرگی و بلوک‌های خام BIFF مرزها را علامت می‌زنند

پیش از تحویل بازمحاسبه کنید، یا بدانید چرا از آن گذشتید

HotXLS در حین SaveAs فرمول‌ها را ارزیابی نمی‌کند. وقتی کسی فایل را باز می‌کند، Excel همه چیز را بازمحاسبه می‌کند (نمای XLS اگر لازم باشد آن را هدایت کنید CalculationMode و RecalcOnSave را در اختیار می‌گذارد)، پس گزارشی که مقصدش صندوق ورودی یک انسان است چیز بیشتری از شما نمی‌خواهد. تصویر همان لحظه‌ای تغییر می‌کند که workbook خوراک برنامهٔ دیگری می‌شود. خروجی CSV فرمول‌ها را به‌صورت متن عینی می‌نویسد و هرگز محاسبه‌شان نمی‌کند، و هر تجزیه‌گر پایین‌دستی که به مقدارهای کش‌شده اعتماد کند عددهای کهنه یا خالی می‌خواند. برای آن مسیرها، محاسبه را روی سرور با Calculate انجام دهید، که یک عبارت دلخواه را در برابر workbook بارگذاری‌شده ارزیابی می‌کند و نتیجه را برمی‌گرداند:

var
  Total: Variant;
  LastDetail: Integer;
begin
  LastDetail := DetailRow + Length(Items) - 1;
  Total := Book.Calculate(Format('SUM(Invoice!D%d:D%d)',
    [DetailRow, LastDetail]));
  if (not VarIsNumeric(Total)) or
     (Abs(Total - ExpectedTotal) > 0.005) then
    raise Exception.Create('Invoice total does not match the order record');

  if Book.SaveAs('invoice-2026-0611.xlsx') <> 1 then
    raise Exception.Create('Save failed: check output path and permissions');
end;

مقایسهٔ جمع محاسبه‌شده با رکورد سفارش پیش از ذخیره بیمه‌ای ارزان با بازدهی خوب است. یک صورت‌حساب غلط را به یک کار ناموفق تبدیل می‌کند. یک اپراتور می‌تواند کار ناموفق را در چند ثانیه دوباره اجرا کند؛ صورت‌حساب غلطی که پیشاپیش در صندوق پستی مشتری نشسته برای یک مدیر حساب هزینهٔ یک عذرخواهی و یک اصلاحیه دارد

دو خانوادهٔ کلاس، یک الگوریتم

همین منطق میان قالب‌ها منتقل می‌شود، اما نه همین کد. TXLSWorkbook برای .xls قدیمی بر پایهٔ interface و شمارش‌ارجاع است، با اندیس‌گذاری برگهٔ مبنا-یک، و هرگز آن را دستی آزاد نمی‌کنید. TXLSXWorkbook برای .xlsx یک شیء ساده است که باید در یک try..finally آزادش کنید، با اندیس‌گذاری برگهٔ مبنا-صفر و همان قرارداد فرمولی که بالا نشان داده شد. FindText، ReplaceText، CopyRange و InsertRows همگی در هر دو سو وجود دارند، پس شکل «لنگر، همسان‌سازی، بازمحاسبه» تمیز منتقل می‌شود. توصیهٔ عملی این است که برای هر خط لوله به یک قالب پایبند بمانید، یا دو چرخهٔ عمر شیء را پشت یک آداپتور نازک از آنِ خودتان پنهان کنید به‌جای پخش‌کردن این تفاوت در سراسر تولیدکننده

اندازه برای گزارشی که این الگو تولید می‌کند به‌ندرت اهمیت دارد. همسان‌سازی یک ردیف سبک‌دار چند هزار بار برای سخت‌افزار امروزی هیچ است. مسیر ذخیره تنها وقتی گلوگاه می‌شود که نوار جزئیات به صدها هزار ردیف برسد، و در آن نقطه تنظیم StreamingWrite باعث می‌شود XML کاربرگ به‌جای بافر شدن مستقیم به بستهٔ خروجی برود؛ مقالهٔ نوشتن جریانی برای کارهای دسته‌ای سرور توضیح می‌دهد چه زمانی این معامله می‌ارزد. نمودارها همان‌گونه رفتار می‌کنند که بقیهٔ چیدمان: در سمت XLSX هم لنگر نمودار و هم ارجاع‌های سری‌هایش وقتی InsertRows بالایشان اجرا می‌شود جابه‌جا می‌شوند، پس نموداری زیر ردیف جمع کل به دادهٔ درست بسته می‌ماند، حال آنکه در سمت XLS نمودارها روی برگه‌های نمودار خودشان می‌نشینند و مانند جدول‌های محوری هرگز جابه‌جا نمی‌شوند. این یک استدلال دیگر برای دور نگه داشتن برگه‌های نمایشی از برگه‌ای است که تولیدکننده گسترشش می‌دهد

این رویکرد «لنگر، همسان‌سازی، بازمحاسبه» می‌گذارد طراح مالک ظاهر workbook باشد و کد شما مالک محتوایش، و همین معمولاً همان چیزی است که خروجی Excel تولیدشده را ارزشمند نگه می‌دارد. فراخوانی‌های جست‌وجو، کپی و درج که اینجا نشان داده شد، همراه با موتور فرمولی که برای بررسی جمع پیش از تحویل به کار رفت، با HotXLS Delphi Component برای Delphi و C++Builder عرضه می‌شوند