مقال تقني

HotXLS Delphi Component: comments, hyperlinks, and review workflows في Delphi

تفرّق النسخة المحدثة بين تعليقات الخلايا كسجلات مراجعة تكتبها الآلة وبين الروابط الخارجية والقفزات داخل المصنف، ولكل منها API مختلف. كما تضيف قواعد لفهرس المراجعة، ومسار XLS القديم، وفحص الروابط قبل التسليم

أعد تسمية ورقة من "Summary" إلى "Overview" في مصنف مولَّد، وكل رابط تشعبي داخلي كان يشير إلى Summary!A1 يتوقف عن الإشارة إلى أي مكان. لا استثناء عند الحفظ، ولا عند الفتح. الرابط لا يزال يُعرَض، ولا يزال يبدو قابلًا للنقر، ويُحل بهدوء إلى لا شيء. نفس نوع الكسر يظهر بعد تحويل حفظ باسم، أو رحلة ذهاب وإياب بين ‎.xls‎ و‎.xlsx‎، عندما يهبط تعليق بعيدًا عمودًا واحدًا أو يفقد رابط نسبي هدفه. كلتا الميزتين تحملان حالة مراجعة يتصرف بناءً عليها أشخاص حقيقيون، فعندما تنكسران يكون الفشل غير مرئي حتى ينقر مراجع ولا يحدث شيء

هذا هو السبب العملي الذي يجعل التعليقات والروابط التشعبية تستحق عناية أكثر مما يوحي به مظهرها التجميلي. يمنح HotXLS كود Delphi وC++Builder وصولًا مباشرًا بالكتابة إلى كليهما، في XLS وXLSX، دون أي أتمتة لـExcel في الحلقة. الوجه الآخر لتلك السيطرة هو المسؤولية: تكتب المكتبة بالضبط الأهداف التي تسلّمها إياها ولا تتحقق من صحة أي منها، فإبقاء سير عمل المراجعة سليمًا هو مهمة كودك أنت، لا مهمة Excel

مخطط لإعادة محاولة تعليق في HotXLS داخل Delphi حيث يحدّث استطلاع FindAt ملاحظة الخلية القائمة بينما يكدّس إعادة محاولة AddComment عمياء نسخة مكررة
إعادة محاولة تستدعي AddComment بلا تمييز ترصّ ملاحظة ثانية على الخلية نفسها، بينما يعدّل فحص FindAt الملاحظة الموجودة أصلاً

تعليقات الخلايا كسجلات مراجعة مكتوبة آليًا

في نموذج صنف XLSX، التعليق كائن على مستوى ورقة العمل: يعرف صفه، وعموده، ومؤلفه، وجسم نصه. حقل المؤلف يستحق مكانه. عندما يمر مصنف أنشأه كودك عبر سلسلة مراجعة، فإن أول سؤال يطرحه المدقق هو من كتب ملاحظة معينة، وملاحظة تُركت دون مؤلف تجيب عن ذلك السؤال بفراغ. اختم التعليقات المولَّدة بهوية خدمة حتى لا يكون المصدر غامضًا أبدًا

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Note: TXLSXComment;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('reconciliation.xlsx');
    Sheet := Book.Sheets[0];

    // ملاحظة موقّعة على الرقم المعدَّل
    Sheet.AddComment(14, 4, 'Manual adjustment: late FX rate, see ticket FIN-2214',
      'recon-service');

    // تحديث ملاحظة موجودة بدلًا من تكديس ملاحظة ثانية
    Note := Sheet.Comments.FindAt(14, 4);
    if Note <> nil then
      Note.Text := Note.Text + ' [verified 2026-06-11]';

    Book.SaveAs('reconciliation-reviewed.xlsx');
  finally
    Book.Free;
  end;
end;

فحص FindAt يحمل وزنًا أكبر مما يبدو عليه. مهمة دفعية تعيد المحاولة بعد فشل عابر ستستدعي بسعادة AddComment مرة ثانية على خلية علّقت عليها بالفعل، وتنتهي الخلية بملاحظتين متكدستين لم يطلبهما أحد. افحص بـFindAt أولًا، وحدّث الكائن الذي تعيده. مجموعة Comments تكشف أيضًا DeleteAt وDeleteInRange. تلك النسخة الخاصة بالنطاق هي ما تلجأ إليه عندما تطهّر مصنفًا قبل مغادرته المبنى: مسح تعليقات ضمان الجودة الداخلية من منطقة كاملة استدعاء واحد بدلًا من حلقة مكتوبة يدويًا عبر الخلايا

عناوين URL الخارجية والقفزات داخل المصنف واجهتان مختلفتان

تحفظ OOXML نوعي الروابط في مكانين مختلفين. عنوان URL خارجي يصبح إدخال علاقة في جزء ‎.rels‎ الخاص بالورقة، والخلية تشير إلى العلاقة بمعرّفها. أما القفزة الداخلية فلا تلمس طبقة العلاقات على الإطلاق؛ إنها سلسلة موقع نصية عادية مثل Summary!A1 مخزَّنة مباشرة على الرابط. يبقي HotXLS ذلك التمييز مرئيًا في الواجهة بدلًا من إثقال طريقة واحدة بمعانٍ متعددة، وهذا يعني أنك تختار الاستدعاء الصحيح بمعرفة أين يعيش الهدف:

مخطط يقابل تخزين HotXLS عنوان URL خارجيًا كعلاقة في جزء rels وقفزة داخلية كسلسلة موقع عادية في مصنفات مولَّدة من Delphi
يسافر URL خارجي عبر طبقة العلاقات بينما القفزة الداخلية نص مجرد، فيفشل كل نوع بطريقته ويحتاج قاعدة تدقيق خاصة
Sheet.Cells[2, 1].Value := 'Source record';
Sheet.AddHyperlink(2, 1, 'https://intranet.example.com/records/2214',
  'Open record 2214', 'ERP source entry');

Sheet.Cells[3, 1].Value := 'Totals';
Sheet.AddHyperlinkToCell(3, 1, 'Overview!B12', 'Jump to totals');

على كائن TXLSXHyperlink الناتج، Url وLocation متنافيتان، وIsInternal تخبرك أيهما مُعبَّأة. تلك العلامة هي ما تفحصه عندما تجرد الروابط في مصنف مفتوح وتحتاج إلى معاملة "يغادر الملف" و"يبقى داخل الملف" بقواعد مختلفة: مضيف خارجي قد يواجه قائمة سماح بينما هدف داخلي لا يحتاج إلا تسمية ورقة موجودة فعلًا. الروابط الداخلية لا تحمل خلفها أي أجزاء علاقة، وهذا أيضًا ما يجعل إعادة كتابتها بالجملة أرخص

الكسر من الافتتاحية يعيش بالكامل في الجهة الداخلية، وهو يتبع حقيقة واحدة: سلسلة الموقع ليست مرجعًا مُحلَّلًا. يكتب HotXLS النص بالضبط كما سلّمته إياه، ولا شيء يعيد توجيه ذلك النص عندما تُعاد تسمية ورقة لاحقًا. دفاعان يصمدان عمليًا. الأول انضباط بشأن الترتيب: أعد تسمية كل ورقة قبل توليد رابط واحد، ثم عامل أسماء الأوراق كمعرّفات مجمَّدة. الثاني أمتن وينجو من إعادة التسمية التي تحدث لاحقًا. وجّه الرابط إلى اسم معرَّف على مستوى المصنف بدلًا من عنوان Sheet!Cell خام، لأن Excel يعيد كتابة تعريف الاسم عندما تتغير الورقة الأساسية، فيركب الرابط تلقائيًا. هذا النهج الثاني يقترن طبيعيًا بالتقنيات في الأسماء المعرَّفة والصيغ بين الأوراق في HotXLS

جهة XLS: نفس المفاهيم، سباكة أقدم

واجهة BIFF8 تعلّق التعليقات بالنطاقات بدلًا من مجموعة على مستوى ورقة العمل. تستدعي AddComment على IXLSRange وتستعيد TXLSComment؛ خاصية Comment الخاصة بالنطاق تقرأ ملاحظة موجودة، وClearComments تمحوها. الحافة الحادة هنا موضعية. TXLSComment لا تكشف علنًا صفها وعمودها الخاصين، فالحلقة الطبيعية، "اجتز كل تعليق وأبلغ عن مكانه"، تعمل بالعكس ضد الواجهة. عليك أن تبدأ من الخلايا. إما أن تشغّل التدقيق من قائمة العناوين التي علّقت عليها، أو تحتفظ بسجل مواضع خاص بك أثناء الكتابة، لأن كائن التعليق لن يخبرك لاحقًا أين يعيش

var
  Book: IXLSWorkbook;
  Sheet: IXLSWorksheet;
  Remark: TXLSComment;
begin
  Book := TXLSWorkbook.Create;
  Sheet := Book.Sheets.Add;
  Sheet.Name := 'Review';
  Sheet.Cells.Item[5, 2].Value := 4821.50;

  Remark := Sheet.Cells.Item[5, 2].AddComment('Awaiting sign-off from controller');
  Remark.Visible := True;   // اجعل الملاحظة تنبثق مفتوحة من أول مشاهدة

  Sheet.AddHyperlink(7, 2, 'https://intranet.example.com/signoff/4821',
    'Sign-off form', 'Opens the controller queue');
  Book.SaveAs('review.xls');
end;

ضبط Visible على True هو الطريقة القديمة لجعل ملاحظة يستحيل إغفالها: يبقى المربع الأصفر مفتوحًا على الورقة بدلًا من انتظار التمرير فوقها. TXLSComment تذهب خطوة أبعد من نظيرتها في XLSX بكشف TextRuns، فيمكن لملاحظة واحدة أن تحمل تحذيرًا بخط عريض إلى جانب شرح عادي، تنسيق لا تكشفه واجهة تعليقات XLSX بنفس الطريقة. الروابط التشعبية في هذه الجهة تصل عبر ثلاث حمولات زائدة متدرجة (العنوان وحده، ثم مع نص عرض، ثم مع تلميح شاشة) وتُقرأ مجددًا عبر مجموعة HyperLinks الخاصة بورقة العمل، حيث يعرض كل رابط Address وSubAddress وDisplayText وScreenTip

ورقة فهرس مراجعة تتفوق على ملاحظات متناثرة

بعد نحو عشر إضافات توضيحية، يتوقف التمرير للقراءة بهدوء عن التوسع. تتكدس الملاحظات على أوراق لا يفتحها مراجع أبدًا، والملاحظات الأهم هي بالضبط الأسهل تفويتًا. البنية التي أثبتت جدارتها هي ورقة فهرس مولَّدة: صف واحد لكل موقع مُعلَّق عليه، يسرد اسم ورقته، وعنوان خليته، ومؤلفه، ومقتطفًا قصيرًا من الملاحظة. العمود الأخير يحمل رابطًا تشعبيًا داخليًا مبنيًا بـAddHyperlinkToCell يقفز مباشرة إلى الخلية المُعلَّق عليها. الآن يقرأ المراجع قائمة للأسفل بدلًا من البحث عبر شبكة، وعدد صفوف ذلك الفهرس يخدم في آن واحد كجرد تعليقاتك لمرحلة التدقيق أدناه

الفهرس رخيص البناء لأن مولِّدك يعرف بالفعل كل موضع لمسه. ألحق مجموعة (ورقة، صف، عمود، مؤلف، ملخص) بقائمة أثناء كتابة كل تعليق، ثم أصدر ورقة الفهرس أخيرًا حتى يكون عدد صفوفها نهائيًا قبل الحفظ. تحسينان يستحقان العناء: رتّب الفهرس حسب الخطورة أو حسب الورقة بدلًا من تسلسل الإدراج، وضع رابط عودة في ترويسة الفهرس حتى يستطيع المراجع الارتداد إلى الأعلى بعد كل عنصر. ولأن الروابط الداخلية سلاسل موقع نصية عادية دون أي شيء في طبقة العلاقات خلفها، حتى فهرس بألف صف لا يضيف تقريبًا شيئًا يُذكر إلى حجم الملف أو وقت الحفظ

تلك الورقة نفسها تسدد ثمنها مجددًا في رحلة العودة. عندما يعود المصنف المُراجَع، يقرأ كودك قيم حالة كُتبت في خلايا بجانب صفوف الفهرس بدلًا من إعادة مسح كل ورقة بحثًا عن تعليقات ربما تغيّرت. عمود من خلايا حالة منظَّمة يُحلَّل بنظافة؛ تناثر من ملاحظات نصية حرة لا يُحلَّل كذلك

مرحلة تدقيق قبل التسليم تلتقط الكسر فعليًا

لا شيء من هذه الواجهات يتحقق من صحة هدف. رابط إلى ورقة حذفتها، مضيف إنترانت مكتوب خطأ، مشاركة ملفات أُلغي تشغيلها الربع الماضي: كل هذه تُحفظ دون أي همسة. تحدد ECMA-376 كيفية تخزين رابط، لا أنه يُحل إلى شيء ما. لذا فإن مصنفًا يحمل بيانات وصفية للمراجعة يستحق مرحلة تدقيق قصيرة خاصة به، تُشغَّل قبل SaveAs مباشرة:

مخطط لتمريرة التدقيق قبل التسليم في HotXLS التي تفحص الأهداف الداخلية وقوائم URL المسموحة وعدد التعليقات وتنظيف المستلمين قبل SaveAs في Delphi
يجري أربعة فحوص قبيل SaveAs مباشرة، وكل واحد منها يلتقط إخفاقاً لن ترفعه المكتبة نفسها أبداً
  • اجمع كل موقع داخلي كُتب أثناء التوليد وتأكد أن اسم الورقة قبل علامة التعجب لا يزال موجودًا في مجموعة أوراق المصنف
  • افحص عناوين URL الخارجية مقابل قائمة سماح من المخططات والمضيفين. مسارات file:// وUNC العارية تسرّب تفاصيل بيئة وتنكسر في اللحظة التي يغادر فيها الملف شبكتك
  • عدّ التعليقات لكل ورقة وقارنها بما نوى مولِّدك كتابته. إعادة محاولة ضاعفت الملاحظات تظهر هنا بدلًا من صندوق وارد المراجع
  • اشطب الإضافات التوضيحية الداخلية فقط بـDeleteInRange كلما كان المستلم خارج المؤسسة

الفرق التي تبني مصنفاتها من طبقة بيانات يمكنها طي هذه المرحلة داخل خطوة خط الأنابيب نفسها التي تتحقق أصلًا من صحة البيانات، فيركب فحص البيانات الوصفية مجانًا. الآليات هي نفسها الموصوفة في تصدير نتائج استعلامات قاعدة البيانات إلى تقارير Excel، موجَّهة نحو الروابط والتعليقات بدلًا من الصفوف

تفصيل اقتباس واحد يوقع الناس عندما يبنون سلاسل مواقع يدويًا. ورقة يحتوي اسمها على مسافة يجب اقتباسها داخل الموقع، تمامًا كما يقتبسها شريط الصيغة: 'Quarterly Totals'!A1، لا Quarterly Totals!A1. يطبّق HotXLS نفس القواعد التي يستخدمها محرك الصيغ للمراجع بين الأوراق، فإذا كان رابط يعمل في صيغة ورقة عمل، فإن اقتباسه سيعمل هنا أيضًا. سلّمه اسمًا غير مقتبس بمسافة وستحصل على نفس الرابط الميت الصامت الذي حذرت منه الافتتاحية

التعليقات والروابط التشعبية هي الأجزاء من مصنف مولَّد التي يتصرف المراجعون بناءً عليها دون نظرة ثانية، وهذا بالضبط سبب أن هدفًا يشير إلى لا شيء يسبب ضررًا حقيقيًا قبل أن ينتبه أحد. ابنِ مرحلة التحقق مرة واحدة، وشغّلها على كل مصنف قبل شحنه، ويبقى سير عمل المراجعة سليمًا عبر إعادات التسمية والتحويلات. سطح API الكامل لكلتا واجهتَي XLS وXLSX موثَّق على صفحة منتج HotXLS Delphi Component