مقاله فنی

تکرار یک Worksheet در XLSX در Delphi با HotXLS

شما یک sheet را دقیقاً درست ساخته‌اید. نوار header merge شده، عرض ستون‌ها با داده‌ها جور است، دو ردیف بالایی freeze شده‌اند، print area و marginها برای یک خروجی A4 تمیز تنظیم شده‌اند، و tab رنگی است تا finance بتواند آن را پیدا کند. حالا report به دوازده نسخه از این sheet نیاز دارد، یکی برای هر region، که همه از همان layout شروع شوند. بازسازی این sheet در کد، دوازده بار، همان چیزی است که drift ظریف را وارد می‌کند: region 7 یک ستون را یک point باریک‌تر می‌سازد، region 11 freeze را از دست می‌دهد، و کسی متوجه نمی‌شود تا وقتی PDF روی میز یک manager بیفتد. چیزی که واقعاً می‌خواهید نسخهٔ برنامه‌نویسی‌شدهٔ right-click اکسل، Move or Copy، Create a copy است: sheet نهایی را بردارید و duplicateهای مستقل بسازید

موتور XLSX در HotXLS، یک کتابخانهٔ بومی Delphi و C++Builder که فایل‌های Excel را بدون خودکارسازی خودِ Excel می‌خواند و می‌نویسد، از قبل می‌توانست sheetها را جابه‌جا کند، sheetها را حذف کند، و محدوده‌های سلول را بین sheetها copy کند. چیزی که تا v2.91.0 نمی‌توانست انجام دهد clone کردن یک worksheet کامل در یک call بود. آن release دو نقطهٔ ورود اضافه می‌کند: TXLSXWorksheet.CopyFromTXLSXWorksheet.CopyFromTXLSXSheets.Duplicate، که state سطح sheet را از یک worksheet به دیگری copy می‌کند، و CopyFromTXLSXSheets.Duplicate

، که یک sheet تازه اضافه می‌کند و CopyFrom را برایتان اجرا می‌کند. بخش جالب این نیست که چیزها را copy می‌کند؛ بلکه خط آگاهانه‌ای است که میان چیزهایی که deep-copy می‌شوند و چیزهایی که نمی‌شوند کشیده شده، و اینکه چرا آن خط همان‌جا قرار گرفته است

یک call برای clone کردن یک sheet نهاییDuplicateعملیات سطح‌بالا Items[] است. index یک‌مبنای sheet source را به آن بدهید و یک worksheet کاملاً تازه برمی‌گرداند که layout و data اصلی را mirror می‌کند. convention اندیس با 1 در سمت XLSX هم‌خوان است، پس sheet یک index 0 دارد، نه nil؛ اگر indexی خارج از بازه بدهید،

var
  Book: TXLSXWorkbook;
  Template, Copy: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Template := Book.Sheets.Add('Template');
    Template.Cells[1, 1].Value := 'Quarterly Statement';
    Template.Range['A1:C1'].Merge;
    Template.ColWidth[1] := 18;
    Template.FreezePanes(2, 1);          // freeze top row + first column
    Template.TabColorIsAuto := False;
    Template.TabColor := $FF1F4E79;

    // Clone with an explicit name...
    Copy := Book.Sheets.Duplicate(1, 'Region-North');
    // ...or let it pick the Excel-style default name.
    Copy := Book.Sheets.Duplicate(1);    // -> "Template (2)"

    Book.SaveAs('regions.xlsx');
  finally
    Book.Free;
  end;
end;

دو چیز در آن snippet ارزش آهسته‌تر شدن دارند. اول، FreezePanes آرگومان‌هایش را row-first می‌گیرد، FreezePanes(ARow, ACol)، پس با indexing Cells[Row, Col] هم‌راستا می‌شود؛ duplicate دقیقاً همان split freeze را به ارث می‌برد. دوم، متد را Duplicate نام‌گذاری کرده‌اند نه Copy واضح‌تر، و این مسئلهٔ سلیقهٔ سبک نیست. Copy یک routine استاندارد در unit System است که دائماً برای stringها و dynamic arrayها استفاده می‌شود. متدی با نام Copy روی یک class داخل بدنهٔ متدها آن را shadow می‌کند و دقیقاً همان نوع ambiguity در resolution را می‌سازد که شش ماه بعد گریبانتان را می‌گیرد. Duplicate کل مشکل را دور می‌زند و در محل فراخوانی درست می‌خواند

نام پیش‌فرض از قانون خودِ Excel پیروی می‌کند

وقتی overload تک‌آرگومان را صدا می‌زنید، یا یک رشتهٔ نام خالی می‌دهید، sheet تازه بعد از source با پسوند (2) نام‌گذاری می‌شود، و این پسوند بالا می‌رود تا نام یکتا شود. sheet Template را یک بار duplicate کنید و Template (2) می‌گیرید؛ دوباره duplicate کنید و Template (3) می‌گیرید، چون Template (2) از قبل گرفته شده است. این دقیقاً نام‌هایی را بازتاب می‌دهد که Excel از دستور Create a copy خود می‌سازد، پس workbookی که کد شما تولید می‌کند همان‌طور به نظر می‌رسد که کاربر از یک نسخه‌برداری دستی انتظار دارد. بررسی یکتایی روی sheet collection زنده اجرا می‌شود، و این یعنی از نام‌هایی که دستی ساخته‌اید هم عبور می‌کند، نه فقط نام‌هایی که از duplicateهای قبلی آمده‌اند

اگر دارید برای هر region یا هر month یک sheet می‌سازید، بهتر است به‌جای آن از overload با نام صریح استفاده کنید. یک scheme قابل‌پیش‌بینی Region-North، Region-South از رشته‌ای از پسوندهای (2)، (3) تمیزتر است، و نام‌های تعریف‌شده و فرمول‌های بین-sheetی شما را خوانا نگه می‌دارد

CopyFrom چه چیزهایی را deep-copy می‌کند

در پشت صحنه، Duplicate sheet را اضافه می‌کند و بعد CopyFrom(ASource) را صدا می‌زند، که وقتی می‌خواهید روی sheetی که از قبل ساخته‌اید clone بزنید هم می‌توانید مستقیم صدا بزنید. CopyFrom از همان ابتدا جلوی دو حالت degenerate را می‌گیرد: کپی از nil، یا کپی کردن یک sheet روی خودش، هر دو فوراً برمی‌گردند و هیچ کاری نمی‌کنند. هر چیزی بعد از آن خودِ copy است، و عمداً گسترده است

دادهٔ سلول‌ها اول می‌آیند. CopyFrom از source می‌خواهد UsedRange آن را بدهد، همان bounding box فشردهٔ سلول‌های پرشده و ناحیه‌های merge شده، و از machinery موجود CopyRangeTo استفاده می‌کند تا هر value، formula، و index سبک هر سلول را در target از A1 منتقل کند. روی سلول‌ها، تمام لایهٔ state سطح sheet را که یک template را کامل نشان می‌دهد دوباره اجرا می‌کند:

  • نواحی merged، که با coordinate بازسازی می‌شوند تا banner همان rectangle را بپوشاند
  • عرض ستون و ارتفاع ردیف، به‌علاوهٔ فهرست‌های hidden، collapsed، و outline-level، عیناً کپی می‌شوند تا rowها و columnهای غیرپیش‌فرض دقیقاً هم‌راستا بمانند
  • Freeze panes و state نما: سطح zoom، نمایش gridline و zero-value، جهت right-to-left، و نوع نما
  • state protection همراه با گزینه‌های permission برای هر action، تا یک template قفل‌شده همان‌طور قفل بماند
  • تمام بلوک page-setup: marginها، orientation، paper size، scaling و fit-to-page، print area، print titles، header و footer، و flagهای print-gridlines و print-headings
  • بازهٔ AutoFilter، tab color، و visibility sheet

نتیجه یک sheet است که دقیقاً مثل source خود print، filter، و present می‌شود. و چون cellها، mergeها، و فهرست‌های dimension به‌طور فیزیکی روی sheet تازه بازسازی می‌شوند نه به‌صورت alias، duplicate کاملاً مستقل است. 999999SheetCopy demo این را صریحاً assert می‌کند

چیزهایی که shallow می‌مانند، و چرا

حالا بخش صادقانه. chartها، imageهای embedded، tableهای XLSX، data validationها، و ruleهای conditional-formatting نه کپی نمی‌شوند. این یک boundary مستند و عمدی است، نه یک oversight، و ارزش دارد منطقش را بفهمید تا بتوانید از قبل برایش برنامه‌ریزی کنید، نه اینکه از آن غافلگیر شوید

هر کدام از این collectionها هویت و referenceهایی حمل می‌کنند که با یک copy سادهٔ field زنده نمی‌مانند. یک chart به یک data range source اشاره می‌کند و یک drawing relationship در بستهٔ OOXML دارد؛ clone کردن object بدون remap کردن relationship و referenceهای series، chartی تولید می‌کند که روی data اشتباه render می‌شود، یا بسته‌ای که Excel آن را به تعمیر نیازمند علامت می‌زند. یک table نامی دارد که باید در workbook یکتا باشد، یک header row که به ستون‌های مشخصی وصل است، و relationship تولیدشدهٔ خودکار خودش را دارد. conditional formatها و data validationها به rangeهای coordinate می‌چسبند و در case validation می‌توانند از طریق formula به rangeهای دیگر ارجاع دهند. deep-copy کردن درست هر کدام از این‌ها یعنی بازنویسی referenceها و ساختن هویت‌های تازه، که کار واقعی با failure modeهای واقعی است. انجام دادن آن به‌صورت نیمه‌کاره، با copy کردن object اما نه referenceهایش، از copy نکردن هم بدتر است: فایلی می‌دهد که با repair prompt باز می‌شود و محتوا را بی‌صدا می‌اندازد. پس engine چیزهایی را که می‌تواند تمیز copy کند copy می‌کند و collectionهای referenceدار را برای caller می‌گذارد، کسی که می‌داند target باید به چه چیزی اشاره کند

در عمل یعنی workflow برای یک template غنی این است: sheet را duplicate کنید تا cellها، layout، و print setup را بگیرید، سپس chart، table، validationها، یا conditional formatها را روی کپی با همان API‌ای که بار اول برای ساختنشان استفاده کردید دوباره بسازید. چون آن‌ها را روی rangeهای خودِ duplicate بازسازی می‌کنید، referenceها ذاتاً درست درمی‌آیند. برای یک chart که A1:C10 را می‌خواند، یک chart جدید روی copy اضافه کنید که به A1:C10 کپی اشاره کند؛ برای یک AutoFilter که live می‌خواهید، توجه کنید که filter هم منتقل می‌شود، پس فقط criteria ستون‌ها را دوباره اعمال می‌کنید. ruleهای conditional-formatting و data-validation را هم از طریق همان callهایی که در مقالهٔ مربوط به cells ادغام‌شده و layout template گزارش توصیف شده‌اند دوباره اضافه می‌کنید، که table merge و range modelی را که copy به ارث می‌برد walk می‌کند.کجا duplication در pipeline گزارش جا می‌گیرد

Worksheet duplication همراه طبیعی generation مبتنی بر placeholder است. رویکرد token-anchored در

راهنمای generation گزارش مبتنی بر template در Delphi مسئلهٔ نوشتن data در layoutی را حل می‌کند که دیگران ویرایش می‌کنند؛ duplication مسئلهٔ نیاز داشتن به همان layout بارها در یک workbook را حل می‌کند. این دو را با هم ترکیب کنید و الگو تمیز است: یک sheet pristine با tokenها، mergeها، و print setup نگه دارید، بعد برای هر region یا period Template را صدا بزنید، tokenهای clone را با آن slice از data پر کنید، و جلو بروید. template pristine هرگز mutate نمی‌شود، پس منبعی قابل‌اعتماد برای clone بعدی می‌ماند، و هر sheet خروجی از یک layout byte-for-byte یکسان شروع می‌کند.Duplicateیک note توالی یک کلاس از ابهام را حذف می‌کند. sheet را

، قبل از اینکه data را داخلش بریزید duplicate کنید، نه بعد از آن. یک template باید ساختار و format را نگه دارد، نه اعداد فصل قبل را، و clone کردن یک sheet سبک‌شدهٔ خالی یعنی هر duplicate تمیز شروع می‌کند. اگر sheetی را duplicate کنید که از قبل data دارد، آن data هم همراهش می‌آید، چون used range را faithfully کپی می‌کند؛ گاهی دقیقاً همین را می‌خواهید، اما برای یک report fan-out معمولاً نه.CopyFrom UsedRange را faithfully کپی می‌کند؛ این گاهی دقیقاً همان چیزی است که می‌خواهید، اما برای یک report fan-out معمولاً نه

یک عادت verification سریع

چون شکاف deep-copy در برابر shallow-copy تا وقتی دنبالش نگردید دیده نمی‌شود، به‌جای اعتماد کردن به اینکه همه چیز منتقل شده، یک check پنج‌خطی را در job بگذارید. بعد از duplicate کردن، سیگنال‌های ساختاری‌ای را که clone قرار است به ارث ببرد بخوانید و assert کنید با source match می‌کنند

Copy := Book.Sheets.Duplicate(1, 'Region-North');
WriteLn(Format('merged=%d  colA=%.1f  freezeRow=%d  tabAuto=%d',
  [Copy.MergedCells.Count, Copy.ColWidth[1],
   Copy.FreezeRow, Integer(Copy.TabColorIsAuto)]));
// Prove independence: mutate the copy, confirm the source is untouched.
Copy.Cells[2, 2].Value := 999;
// Template.Cells[2, 2].Value is still whatever it was.

merge count، یک column width، freeze row، و flag tab-colour به شما می‌گویند آن لایه‌ای که است واقعاً copy شده است. جداگانه، در هر sheetی که chart، table، validation، یا conditional format داشته، آن‌ها را به‌عنوان یک فهرست برای بازسازی روی copy در نظر بگیرید: نبودشان عمدی است، و fix چند call است نه یک bug report. آن مدل ذهنی، deep در جایی که امن است و shallow در جایی که referenceها می‌شکنند، تمام داستان استفادهٔ خوب از این قابلیت است.Worksheet duplication و copy state sheet که اینجا توصیف شد در v2.91.0 از

HotXLS Delphi spreadsheet componentCopyFrom بومی ship می‌شوند، در کنار یک runnable sample که چرخهٔ clone-and-mutate را end to end exercise می‌کند.SheetCopy