مقاله فنی

قالب‌بندی شرطی و متن غنی HotXLS در دلفی

یک قاعده قالب‌بندی شرطی در OOXML دو چیز جداگانه است که یک نام مشترک دارند. شرط (یک مقایسه، یک فرمول، یک تطبیق متن) تعیین می‌کند کدام سلول‌ها واجد شرایط‌اند. ظاهر (یک رکورد قالب تفاضلی، به اصطلاح ECMA-376 همان dxf) تعیین می‌کند آن سلول‌ها چه شکلی داشته باشند. پنجره گفتگوی اکسل این درز را با وادارکردن شما به پرکردن هر دو در یک زمان پنهان می‌کند. HotXLS چنین نمی‌کند. یک قاعده cellIs را از دلفی بسازید و استایل را جا بیندازید؛ آن‌گاه قاعده معتبر است، بازه درست است، فرمول دقیقاً روی سلول‌های درست به true ارزیابی می‌شود، و هیچ رنگی عوض نمی‌شود، چون دستور قاعده این بوده که «درست است، هیچ‌چیز را رنگ نکن». این فاصله میان شرط و پیامد، نخستین چیزی است که باید درست انجام شود، و بیشتر قاعده‌هایی که در Manage Rules درست به نظر می‌رسند ولی چیزی را برجسته نمی‌کنند از همین‌جا ناشی می‌شوند

HotXLS قالب‌بندی شرطی را به‌صورت بومی هم در فایل‌های BIFF8 با پسوند .xls و هم در فایل‌های OOXML با پسوند .xlsx می‌نویسد، و همین کار را برای ران‌های (run) متن غنی و یک مدل استایل سلولی استخری انجام می‌دهد. این سه ویژگی بیش از آنچه سطح تخت API نشان می‌دهد سیم‌کشی مشترک دارند، و جاهایی که خروجی از نیت فاصله می‌گیرد معمولاً مفصل‌های میان همین‌هاست

یک شرط به یک پیامد نیاز دارد: استایل dxf

در کاربرگ XLSX، قاعده‌های مقایسه‌ای از AddConditionalFormat می‌آیند، که یک بازه، یک عملگر از TXLSXCfOperator، و یک فرمول یا مقدار لفظی می‌گیرد، و سپس اندیس قاعده جدید را درون مجموعه ConditionalFormats کاربرگ برمی‌گرداند. شیء قاعده در آن اندیس یک ویژگی Style افشا می‌کند، و برجسته‌سازی همان‌جا زندگی می‌کند. یک پرکننده روی آن تنظیم کنید و سلول‌های واجد شرایط آن پرکننده را می‌گیرند. دست‌نخورده رهایش کنید و همان قاعده نامرئی را ساخته‌اید که در بالا توصیف شد

دیاگرام یک قاعده cellIs در HotXLS که از دلفی در دو نیمه ساخته شده است: AddConditionalFormat یک اندیس قاعده برای شرط برمی‌گرداند، ConditionalFormats[Idx].Style.SetFillBgColor پیامد dxf را فراهم می‌کند، و قاعده‌ای که استایلش هرگز تنظیم نشده اعتبارسنجی را می‌گذراند ولی هیچ‌چیز را رنگ نمی‌کند
شرط تعیین می‌کند کدام سلول‌ها واجد شرایط‌اند و استایل dxf تعیین می‌کند چه شکلی داشته باشند، پس جاانداختن استایل یعنی ساختن قاعده نامرئی
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Idx: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('kpi.xlsx');
    Sheet := Book.Sheets[0];

    // واریانس منفی: پرکننده قرمز روشن
    Idx := Sheet.AddConditionalFormat('D2:D200', xlsxCfOpLessThan, '0');
    Sheet.ConditionalFormats[Idx].Style.SetFillBgColor($FFFFC7CE);

    // شناسه‌های سفارش تکراری به همین شیوه علامت‌گذاری می‌شوند
    Idx := Sheet.AddCondFormatDuplicateValues('A2:A200');
    Sheet.ConditionalFormats[Idx].Style.SetFillBgColor($FFFFEB9C);

    // قاعده فرمول سفارشی: برجسته‌کردن ردیف‌هایی که مقدار واقعی به ۹۰٪ هدف نمی‌رسد
    Idx := Sheet.AddCondFormatExpression('B2:B200', '$C2<$B2*0.9');
    Sheet.ConditionalFormats[Idx].Style.SetFillBgColor($FFFFC7CE);

    Book.SaveAs('kpi-flagged.xlsx');
  finally
    Book.Free;
  end;
end;

رنگ‌ها در اینجا مقادیر ARGB سی‌ودو بیتی هستند، پس $FFFFC7CE همان «قرمز روشن» اکسل است که از پنجره گفتگو می‌شناسید، با یک بایت آلفای کاملاً مات که جلوی RGB نشسته است. هر نوع قاعده‌ای که بر اساس یک شرط به‌ازای هر سلول فعال می‌شود، از همین الگوی «اول بساز، بعد استایل بده» پیروی می‌کند. تطبیق‌دهنده‌های متن (AddCondFormatContainsText، AddCondFormatBeginsWith، AddCondFormatEndsWith) اندیسی برمی‌گردانند که بعداً استایلش می‌دهید، و AddCondFormatTop10، AddCondFormatAboveAverage و آشکارسازهای سلول خالی و خطا نیز همین‌طور. الگو را یک بار یاد بگیرید و کل خانواده متن و مقایسه یکسان رفتار می‌کند

نوارهای داده، مقیاس‌های رنگ و مجموعه‌های آیکون خودشان را رنگ می‌کنند

انواع قاعده دیداری برعکس کار می‌کنند. آن‌ها ظاهرشان را درون خود تعریف قاعده حمل می‌کنند و ویژگی Style را کاملاً نادیده می‌گیرند. یک پرکننده به یک قاعده نوار داده اختصاص دهید و هیچ اتفاقی نمی‌افتد، که تا وقتی رده‌بندی جا نیفتاده مثل یک باگ خوانده می‌شود: AddCondFormatDataBar رنگ نوار را به‌صورت آرگومان مستقیم می‌گیرد، مقیاس‌های رنگ دو و سه‌نقطه‌ای رنگ‌های نقاط انتهایی‌شان را به همین شکل می‌گیرند، و AddCondFormatIconSet یکی از ۲۶ نوع مجموعه آیکون مانند icsTrafficLights3 را انتخاب می‌کند. اینجا رکورد استایل جداگانه‌ای وجود ندارد که فراموشش کنید، چون اصلاً رکورد استایل جداگانه‌ای در کار نیست

پارامترهایی که در این فراخوانی‌ها ارزش فکرکردن دارند، لنگرهای مقدار هستند که با نوع TXLSCfValueKind تایپ شده‌اند. نقطه انتهایی یک نوار یا مقیاس می‌تواند روی کمینه یا بیشینه بازه، روی یک عدد لفظی، روی یک درصد یا یک صدک، یا روی نتیجه یک فرمول بنشیند. پیش‌فرض‌ها، یعنی کمینه بازه و بیشینه بازه، روی داده‌های مرتب نمایشی خوب رفتار می‌کنند و بعد روی داده‌های واقعی با مقادیر پرت به شما خیانت می‌کنند: یک مقدار افسارگسیخته مقیاس را می‌کشد و هر نوار دیگر را به یک ته‌مانده تخت می‌کند. وقتی قرار است یک داشبورد در طول دوره‌ها خوانده شود، نقاط انتهایی را به‌جای آن به اعداد ثابت یا صدک‌ها لنگر کنید، تا نصف نوار در مارس همان کمیتی را معنا دهد که نصف نوار در آوریل. یک نوار خودمقیاس فقط با خودش قابل مقایسه است

نویسنده XLS چهار نوع قاعده را پوشش می‌دهد، نه بیشتر

سمت قدیمی BIFF8 آینه کوچک‌تری از سمت XLSX نیست؛ یک زیرمجموعه عمدی است. نمای (facade) XLS می‌تواند دقیقاً چهار شکل قاعده شرطی بسازد، نوارهای داده، مقیاس‌های دورنگ، مقیاس‌های سه‌رنگ و مجموعه‌های آیکون، که به‌صورت رکوردهای CF12 در جریان نوشته می‌شوند. هیچ API ساختی برای قاعده‌های cellIs، عبارتی یا متنی ندارد. قاعده‌هایی از آن انواع که از قبل در فایلی که باز می‌کنید وجود دارند، خوانده، نگه داشته و بدون تغییر بازنویسی می‌شوند، پس بازکردن و دوباره ذخیره‌کردن فایل .xls یک مشتری هرگز به قالب‌بندی‌ای که با خود آورده آسیب نمی‌زند. آنچه نمی‌توانید انجام دهید، تولید برجسته‌سازی آستانه‌ای از صفر در یک فایل .xls است. گزینه‌ها در آنجا این است که با پرکننده‌های معمولی سلول که در کد محاسبه می‌شوند آن را تقلید کنید، یا محصول تحویلی را یک فایل .xlsx کنید، جایی که خانواده کامل قاعده‌ها روی میز است

این محدودیتی است که باید پیش از وجود لایه داده حل شود، نه بعد از آن، چون تصمیم قالب فایل را برای هر چیز داشبوردمانندی تغییر می‌دهد. تیمی که .xls را برای سازگاری برگزیده و سپس یک گزارش KPI با آستانه‌های cellIs مشخص می‌کند، دو چیز را انتخاب کرده که با هم جور درنمی‌آیند، و زمان ارزان‌تر برای متوجه‌شدن آن هنگام تصمیم قالب است نه سه هفته پس از شروع ساخت

انباشت قاعده‌ها، اولویت و بازه‌های همپوشان

داشبوردهای واقعی به‌ندرت یک قاعده به ازای هر بازه اجرا می‌کنند. یک ستون واریانس ممکن است یک نوار داده برای بزرگی، یک قاعده cellIs برای آستانه سخت، و یک قاعده عبارتی سطح ردیف بالای هر دو برای تشدیدها داشته باشد. هر TXLSXConditionalFormat یک مقدار Priority افشا می‌کند، و اکسل قاعده‌های رقیب را به ترتیب اولویت حل می‌کند. وقتی دو قاعده می‌خواهند یک سلول را رنگ کنند، برنده با عددی که شما تنظیم می‌کنید تعیین می‌شود، نه با ترتیبی که یک بازبین اتفاقاً در پنجره Manage Rules پیمایش می‌کند

با اولویت همان‌طور رفتار کنید که یک برنامه طراحی با ترتیب z رفتار می‌کند. هرجا دو قاعده می‌توانند به سلول‌های یکسانی برسند، آن را عمداً تخصیص دهید، و بین مقادیر فاصله بگذارید تا قاعده‌ای که بعداً می‌آید بدون شماره‌گذاری دوباره بقیه جا بگیرد. جایی که قاعده‌ها نمی‌توانند برخورد کنند، مثلاً یک نوار داده محدود به ستون E و یک قاعده متنی محدود به ستون G، ترتیب ساخت کافی است و اولویت ارزش توجه ندارد. آن توجه را به‌جای آن صرف مرزهای بازه کنید، چون باگ‌های پرهزینه در اینجا تقریباً هرگز وارونگی اولویت نیستند. آن‌ها بازه‌هایی مثل B2:B200 روی گزارشی هستند که به ۳۵۰ ردیف رشد کرده، جایی که دنباله پوشش‌داده‌نشده به‌صورت سلول‌های ساده رندر می‌شود که دقیقاً شبیه داده سالم به نظر می‌رسند. هر بازه قاعده را از همان مقدار شمار ردیف نهایی مشتق کنید که سری‌های نمودار و بازه‌های اعتبارسنجی را در جاهای دیگر کارپوشه هدایت می‌کند، و دنباله دیگر نمی‌افتد

یک عادت تأیید ارزش هزینه‌اش را دارد. پس از تولید، فایل را در اکسل باز کنید، بازه قالب‌بندی‌شده را انتخاب کنید، و به ازای هر تغییر قالب یک بار Manage Rules را مرور کنید. قالب‌بندی شرطی یکی از معدود حوزه‌هایی است که تنها رندرکننده مرجع در آن، برنامه‌ای است که فایل را مصرف می‌کند، پس یک آزمون واحد روی XML ثابت می‌کند قاعده نوشته شده، نه اینکه اکسل آن را همان‌طور که منظور شما بوده رنگ می‌کند. یک دقیقه نگاه‌کردن این فاصله را می‌بندد

متن غنی: قالب‌های متعدد درون یک سلول

یک سلول متن غنی در مدل XLSX فهرستی از ران‌ها نگه می‌دارد، که هر ران یک گستره متن به‌علاوه ویژگی‌های فونت خودش است. شما فهرست را در کناری به‌صورت یک شیء TXLSXRichText می‌سازید، ران‌ها را به آن اضافه می‌کنید، و سپس کل آن را به یک سلول متصل می‌کنید. قاعده مالکیت همان بخشی است که گاز می‌گیرد. تخصیص به Cell.RichText مالکیت آن شیء را به سلول می‌سپارد، و سلول هنگام تخریب خودش آن را آزاد می‌کند. آن را خودتان هم آزاد کنید و یک آزادسازی دوباره (double-free) دارید، از آن نوعی که در همان اجرایی که سببش شده ساکت می‌ماند و خیلی بعدتر به‌صورت یک کرش در جایی بی‌ربط سر برمی‌آورد

دیاگرام ران‌های متن غنی HotXLS در دلفی: تخصیص یک شیء TXLSXRichText به Cell.RichText مالکیت را به سلول منتقل می‌کند پس یک Free دوم خیلی بعدتر heap را خراب می‌کند، و رنگ یک ران فقط پس از پاک‌شدن ColorIsAuto رعایت می‌شود
مالکیت فهرست ران‌ها هنگام تخصیص به سلول منتقل می‌شود، و تخصیص رنگ فقط وقتی می‌چسبد که ColorIsAuto پاک شده باشد
var
  Rich: TXLSXRichText;
  Run: TXLSXRichTextRun;
begin
  Rich := TXLSXRichText.Create;
  Rich.AddRunText('Status: ');
  Run := Rich.AddRunText('OVERDUE');
  Run.Bold := True;
  Run.Color := $FFC00000;
  Run.ColorIsAuto := False;
  Run := Rich.AddRunText(' (escalated to regional manager)');
  Run.Italic := True;
  Sheet.Cells[2, 7].RichText := Rich;   // مالکیت به سلول منتقل می‌شود: Free نکنید
end;

ColorIsAuto := False صریح، تزیین اختیاری نیست. یک ران پرچم رنگ خودکار حمل می‌کند، و تخصیص رنگ فقط وقتی رعایت می‌شود که آن پرچم پاک شده باشد. Color را تنظیم کنید و ColorIsAuto را فراموش کنید و ران پررنگ ولی سرسختانه سیاه بیرون می‌آید، بدون هیچ خطایی که به علت اشاره کند. ران‌ها همچنین از خط‌خوردگی، گونه‌های زیرخط، و ترازبندی عمودی برای بالانویس و زیرنویس پشتیبانی می‌کنند، و PlainText کل فهرست را وقتی نیاز دارید محتوای متنی را صادر یا مقایسه کنید، دوباره به یک رشته واحد تخت می‌کند

متن غنی سطح سلول فقط مخصوص XLSX است. نمای XLS هیچ API عمومی برای نوشتن آن ندارد، هرچند ران‌ها در آنجا روی یادداشت‌ها و جعبه‌های متن از طریق TextRuns در دسترس‌اند، و رشته‌های غنی خوانده‌شده از یک فایل .xls موجود یک رفت‌وبرگشت را دست‌نخورده پشت سر می‌گذارند. کشش همان کشش قالب‌بندی شرطی است: هر چیزی که قالب‌ها را درون یک سلول ترکیب می‌کند به نویسنده XLSX تعلق دارد

استخر استایل و خطای یک‌واحدی‌ای که به تولید می‌رسد

استایل‌دهی ساده سلول در مدل XLSX از طریق مجموعه‌های استخری روی کارپوشه انجام می‌شود. Fonts.Add، Fills.AddSolid و Borders.Add هرکدام یک تعریف ثبت می‌کنند و اندیس آن را در استخر برمی‌گردانند. آن اندیس‌ها صفرپایه‌اند. ویژگی‌های سمت سلول که آن‌ها را مصرف می‌کنند، مانند FontIndex، صفر را برای «پیش‌فرض» رزرو می‌کنند، پس مقداری که به یک سلول تخصیص می‌دهید اندیس استخر به‌علاوه یک است:

دیاگرام خطای یک‌واحدی استخر استایل XLSX در HotXLS: Fonts.Add یک اندیس استخر صفرپایه برمی‌گرداند در حالی که FontIndex سلول یک‌پایه است و صفر برای پیش‌فرض رزرو شده، پس جاانداختن به‌علاوه یک بی‌سروصدا هر سرستون را بدون استایل رندر می‌کند
اندیس‌های استخر از صفر شروع می‌شوند و اندیس‌های سلول صفر را برای پیش‌فرض رزرو می‌کنند، پس سمت سلول همیشه یک واحد اضافه می‌کند
HeaderFont := Book.Fonts.Add('Calibri', 11, True, False);  // اندیس استخر، صفرپایه
for Col := 1 to 6 do
  Sheet.Cells[1, Col].FontIndex := HeaderFont + 1;          // اندیس سلول، یک‌پایه

+ 1 را جا بیندازید و هر سرستون به فونت پیش‌فرض بازمی‌گردد. نه استثنایی هست و نه هشداری، فقط کارپوشه‌ای که انگار هیچ‌کس استایلش نداده است. اشتباه مرتبه دوم درون حلقه پنهان است: فراخوانی Fonts.Add یک بار به ازای هر ردیف. تعریف‌های فونت یکسان تکرارزدایی می‌شوند، پس فایل خراب نمی‌شود، اما کار هدر می‌رود، و استخر ترازبندی به‌طور خاص در هر فراخوانی یک شیء تازه پس می‌دهد به‌جای ادغام تکراری‌ها. آن چند استایل را یک بار پیش از حلقه بسازید و اندیس‌هایشان را دوباره استفاده کنید. روی گزارش‌های صدهزار ردیفی، همین یک تغییر یکی از اهرم‌هایی است که در تنظیم کارایی کارپوشه‌های بزرگ برای HotXLS پوشش داده شده است. وقتی فقط به یک ظاهر معنایی آماده نیاز دارید، هر دو نما ApplyBuiltinStyle را روی بازه‌ها افشا می‌کنند، که به استایل‌های داخلی Good، Bad، Neutral و استایل‌های تأکیدی اکسل نگاشت می‌شود، بدون اینکه اصلاً به استخرها دست بزنید

قالب‌بندی شرطی، متن غنی و استایل‌های استخری آخرین مایل یک گزارش‌اند، که پس از تثبیت مدل داده و چیدمان اعمال می‌شوند، و آن مراحل پیشین موضوع تولید گزارش مبتنی بر قالب با HotXLS هستند. مرجع کامل قاعده‌ها، ران‌ها و استایل‌ها در صفحه محصول HotXLS Delphi Component قرار دارد