يرفض Excel فتح مصنف XLSX يحمل تحقق بيانات من نوع قائمة فيه قائمة لاصقة أطول من 255 محرفًا، وينطبق ذلك حتى على ملف حفظه Excel بنفسه. ويحمّل HotXLS، المكوّن الأصلي لـ Excel لـ Delphi وC++Builder، تلك القوائم ويصوغ قواعد القوائم عبر الأوراق كامتدادات x14، لكن الإصلاح الدائم واحد في كل مكان: احفظ القوائم الطويلة في نطاق ورقة عمل أو اسم معرَّف
تقرير الخطأ يصل عادة من عميل لا من اختباراتك: ينمو في قالب عمود Region أو Product بدأت قائمته المنسدلة بثمانية إدخالات وصارت ستين. وكل شيء لا يزال يعمل في نسخة XLSB التي يحتفظ بها أحد على قرص مشترك. ثم تكتب مهمة تصدير القاعدة نفسها في XLSX، ويرد Excel بطلب إصلاح أو رفض صريح. لم يتغير في كودك شيء سوى طول سلسلة واحدة، وهذا بالضبط سبب استحقاق هذا الحد للفهم قبل أن يجدك
أين ينطبق حد القائمة ذو 255 محرفًا فعلًا؟
ينطبق الحد على الصيغة اللاصقة لتحقق قائمة في XLSX، أي السلسلة المقتبسة المفصولة بفواصل المخزنة في formula1، ويفضه Excel عند فتح الملف، لا فقط حين تكتب القائمة في حوار Data Validation. وأعطى الاختبار المباشر مقابل Excel نتيجة نظيفة: قائمة من 253 محرفًا فتحت، وقائمتا 260 و421 محرفًا رفضتا. وحالة 421 محرفًا هي المفيدة تعليميًا، لأنها جاءت من مصنف ألفه Excel بنفسه: سيكتب Excel ملفًا لن يقرأه Excel مرة أخرى
المخطط (schema) لا ينقذك هنا: يعرف الجزء 1 من ECMA-376 في §18.3.1.32 عنصر dataValidation وصيغه دون ذكر سقف 255 محرفًا، فيمكن أن يكون ملف صالح المخطط ومع ذلك مرفوضًا. ذلك قيد تطبيق، والمرجع الوحيد الموثوق له سلوك Excel نفسه
لا يفرض HotXLS ذلك السقف عنك: تلتف TXLSXWorksheet.AddListValidation على العناصر باقتباس وتخزنها كما أعطيت؛ لا تقطع قائمة طويلة ولا ترفضها، فقائمة من 400 محرف تدخل XLSX المحفوظ تمامًا كما طُلبت ثم يرفض Excel النتيجة. ذلك اختيار مقصود، لأن قص قائمة بصمت يفسد البيانات بطريقة لا ينتبهها أحد، لكنه يضع فحص الطول على عاتقك. ومساعد صغير يبقيه صادقًا:
const
// فتح Excel قائمة من 253 محرفًا ورفض 260 و421.
// أبق هامشًا بدل التصويب على الحد الدقيق.
MaxInlineListChars = 250;
function InlineListLength(const AItems: array of WideString): Integer;
var
I: Integer;
begin
Result := 0;
for I := 0 to High(AItems) do
Inc(Result, Length(AItems[I]));
if Length(AItems) > 1 then
Inc(Result, High(AItems)); // فواصل الفصل
end;
عد الاقتباسات المزدوجة المضمَّنة بمالك محرفين، لأن AddListValidation يضاعفها لإبقاء سلسلة الصيغة صالحة
لماذا يستطيع XLSB حمل قوائم لا يستطيعها XLSX؟
يخزن XLSB القائمة اللاصقة رمز سلسلة PtgStr واحدًا طوله حقل 16 بت، فالصيغة الثنائية بلا جدار 255 محرفًا. عينة أصلية بستين عنصرًا و419 محرفًا تسكن رمزًا واحدًا وتدور في Excel دون شكوى. والقائمة نفسها في XLSX هي حالة 421 محرفًا المذكورة أعلاه
محمل يطبق حد الحوار ذا 255 محرفًا على XLSB سيرفض ذلك الملف. ويحصر فحص رموز التحقق في XLSB لدى HotXLS رمز السلسلة عند 32,767 محرفًا بدلًا، وهو سقف سلسلة خلية Excel نفسه. وتحفّان يستحقان قولًا صريحًا: يسقف المحمل أيضًا صيغة تحقق كاملة عند 16,384 بايتة، فالسقف العملي لقائمة لاصقة واحدة أدنى مما يوحيه فحص الرمز وحده. والقوائم المفصولة بمحارف NUL داخل PtgStr خارج الملف الشخصي المدعوم، فتسجل غير مدعومة بدل التخمين
المصيدة تجلس في التحويل: قائمة طويلة سليمة تمامًا في XLSB تصير ملفًا مرفوضًا لحظة أن يحفظ أي شيء، HotXLS ضمنًا، ذلك المصنف XLSX. وإن كان خط أنابيب يحول ملفات XLSB لعملاء، فشغل فحص الطول على كل قاعدة قائمة قبل حفظ XLSX، لا بعد أن يسجلها عميل
ماذا يحدث للقوائم التي تشير إلى ورقة أخرى؟
يخزن Excel قائمة مصدرها على ورقة عمل مختلفة في امتداد x14:dataValidations لا في عنصر dataValidations العادي: القاعدة تسكن داخل extLst الخاصة بورقة العمل تحت URI الامتداد {CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF} المعرف في [MS-XLSX]، ومرجع مصدرها عنصر xm:f مثل Lists!$A$2:$A$61. ويحفظ XLSB القاعدة نفسها في سجلات الامتداد الخاصة به
يحلل HotXLS قواعد امتداد XLSX تلك إلى مجموعة DataValidations المنمَّطة ويميز كل واحدة بـ TXLSXDataValidation.IsExtension. فتظهر قائمة عبر الأوراق بجوار القواعد العادية بنطاقها ونوعها وصيغتها قابلَين للقراءة، ونجو من التحريرات والحفظ وإعادة الفتح. وعند الحفظ يبقي الكاتب قواعد الامتداد خارج العنصر العادي ويعيد بناء امتداد x14 في موضعه المخططي عند ذيل ورقة العمل. وتحفظ الصيغتان كلتاهما، فقواعد العدد الكامل والعشري وطول النص والتاريخ والوقت التي تستخدم between أو notBetween تحفظ الحدَّين معًا
الملف الشخصي المنمَّط للامتداد يغطي القاعدة كاملة: سوى type وoperator وallowBlank وعلمَي الإظهار والنطاق والصيغتين، تحفظ قاعدة الامتداد عنوان طلب الإدخال ونصه، وعنوان تنبيه الخطأ ونصه، ونمط التنبيه، ووضع IME، وضبط إخفاء القائمة المنسدلة، عبر تحميل وإعادة حفظ، بقواعد التهريب نفسها الخاصة بتحقق عادي
uses
SysUtils, lxHandleX, lxDiagnostics;
procedure AuditListRules(const FileName: WideString);
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Dv: TXLSXDataValidation;
S, I: Integer;
begin
Book := TXLSXWorkbook.Create;
try
if Book.Open(FileName) <> 1 then
raise Exception.Create('Cannot open ' + FileName);
for S := 1 to Book.Sheets.Count do // Sheets[] بترقيم من 1
begin
Sheet := Book.Sheets[S];
for I := 0 to Sheet.DataValidations.Count - 1 do
begin
Dv := Sheet.DataValidations[I];
if Dv.ValidationType <> xlsxDvList then
Continue;
if Dv.IsExtension then
Writeln(Sheet.Name, ' ', Dv.Range, ': x14 list from ', Dv.Formula1)
else if (Dv.Formula1 <> '') and (Dv.Formula1[1] = '"') and
(Length(Dv.Formula1) - 2 > MaxInlineListChars) then
Writeln(Sheet.Name, ' ', Dv.Range, ': inline list of ',
Length(Dv.Formula1) - 2, ' chars, refused by Excel as XLSX');
end;
end;
finally
Book.Free;
end;
end;
أما قراءة العناصر خلف قائمة منسدلة، واختيار الإدخالات، وفحص الإدخال المكتوب مقابل هذه القواعد، فانظر قراءة عناصر القوائم المنسدلة والتحقق من الإدخال مع HotXLS؛ وتبقى هذه المقالة مع صيغ التخزين
كيف يعامل Excel وHotXLS أنواع التحقق المجهولة؟
يفتح Excel dataValidation يحمل وسم type رمزًا غير معروف كقاعدة من نوع none، ويحفظ صيغته، وعند الحفظ يعيد كتابته عنصر dataValidation صريحًا بلا وسم type، ماسقًا الوسم المجهول. وتأتي تلك الرموز من XML محرَّر يدويًا ومن مولدات أطراف ثالثة ومن مزايا Office المستقبلية، وجواب Excel تسامح لا رفض
يتبع HotXLS التسامح نفسه: بدل إسقاط القاعدة، وهو ما يفقدها عند إعادة الحفظ دون أثر، يحفظها القارئ كـ xlsxDvNone بصيغها ونطاقها، ويسجل تشخيص تحذير واحدًا لكل قاعدة موحَّدة بالكود 1307، المكشوف كثابت xlsDiagnosticValidationTypeNormalized في lxDiagnostics. وعند الحفظ تكتب قاعدة none كالعنصر الصريح، الصيغة نفسها التي ينتجها Excel. وقاعدة none لا تتحقق من شيء، فالتشخيص إشارتك إلى أن قاعدة ظن المؤلف أنها نشطة لم تعد تقيد الإدخال
for I := 0 to Book.Diagnostics.Count - 1 do
if Book.Diagnostics[I].Code = xlsDiagnosticValidationTypeNormalized then
Writeln('Untyped rule kept on sheet ', Book.Diagnostics[I].SheetName,
': ', Book.Diagnostics[I].Message);
لماذا قد يرفض Excel مصنفًا بعد جولة ذهاب وعودة؟
يرفض Excel مصنفًا معاد الحفظ حين يستخدم جزء امتداد معاد تشغيله بادئة نطاق أسماء لا يصرح بها الملف الجديد أبدًا، لأن الناتج ليس XML حسن التشكيل: أجزاء الامتداد تحمل اعتياديًا وسومًا مسبوقة مثل xr:uid التي يجلس تصريح xmlns:xr لها على جذر ورقة العمل الأصلية لا داخل الجزء. انسخ الجزء حرفيًا إلى جذر ورقة عمل جديد يفتقر لذلك التصريح فتكسر قواعد نطاقات أسماء XML قبل أن ينظر Excel إلى خلية واحدة
يعالج HotXLS هذا في خطوة الالتقاط: أثناء القراءة يمسح كل جزء معاد تشغيله بحثًا عن بادئات عناصر ووسوم لا يصرح بها الجزء بنفسه، ويحل كل بادئة إلى URI خاصتها عبر القارئ، ويكتب تلك التصريحات على جذر ورقة العمل عند وقت الحفظ. ويبني مسار الحفظ التدفقي الجذر عبر الروتين نفسه، فينتج كلا وضعا الحفظ خرجًا حسن التشكيل، ويعاد فتح ملف معاد الحفظ في Excel بكل قاعدة تحقق موجودة، بما فيها قائمة x14 عبر الأوراق معاد تشغيلها. والصورة الأوسع لكيفية حمل HotXLS محتوى extLst الذي لا يصوغه مغطاة في جولات XLSX بلا فقد للثيمات وextLst وcalcChain، والقواعد العامة خلف طلب إصلاح Excel في لماذا يعرض Excel طلب إصلاح
القوائم الطويلة تعود إلى نطاق أو اسم معرَّف
التصميم المتين لأي قائمة قد تنمو أن تخزن العناصر في خلايا ورقة العمل وتوجه التحقق إليها عبر اسم معرَّف: مصدر النطاق بلا حد 255 محرفًا في أيا من الصيغتين، والعناصر يمكن تحريرها دون لمس القاعدة، والاسم المعرَّف يبقي القاعدة في عنصر dataValidation العادي حتى وحين تجلس الخلايا في ورقة منفصلة، وهو ما يستطيع كل مستهلك للملف قراءته
// Lists ورقة مخصصة، مثل Book.Sheets.Add('Lists')
function AddListRule(Book: TXLSXWorkbook; Target, Lists: TXLSXWorksheet;
ACol: Integer; const ARange, AName: WideString;
const AItems: array of WideString): Integer;
var
I: Integer;
ColRef: WideString;
begin
if InlineListLength(AItems) <= MaxInlineListChars then
begin
Result := Target.AddListValidation(ARange, AItems);
Exit;
end;
for I := 0 to High(AItems) do
Lists.WriteAutoValue(I + 1, ACol, AItems[I]); // صف وعمود بترقيم من 1
ColRef := Chr(Ord('A') + ACol - 1); // الأعمدة من A إلى Z
Book.DefinedNames.Add(AName, Lists.Name + '!$' + ColRef + '$1:$' +
ColRef + '$' + IntToStr(Length(AItems)));
// الـ '=' البادئة تميز مرجعًا؛ دونه يقتبس الاسم نصًا
Result := Target.AddListValidation(ARange, '=' + AName);
end;
علامة التساوي البادئة مهمة: يعامل AddListValidation وسيطًا يبدأ بـ = مرجعًا وينزع العلامة، ويترك وسيطًا يحوي ! دون اقتباس نطاقًا، ويقتبس كل ما سوى ذلك قائمة لاصقة. مرر 'StatusList' دون العلامة فتحصل على قائمة منسدلة بعنصر واحد تقرأ حرفيًا StatusList. ونقطتان أصغر: يحول WriteAutoValue النص كما تفعل إدخال خلايا Excel، فعنصر مثل TRUE يصير Boolean، واسم ورقة يحوي فراغات يحتاج اقتباسات مفردة في المرجع
لقاعدة يجب أن تشير مباشرة إلى ورقة أخرى دون اسم، اضبط IsExtension := True على القاعدة عند الفهرس الذي أعاده AddListValidation، فيكتبها HotXLS كامتداد x14، الصيغة نفسها التي يستخدمها Excel بنفسه. ومسار الاسم المعرَّف أبسط ويظل يعمل لأدوات لا تحلل الامتدادات أبدًا، ولهذا هو التوصية الافتراضية هنا
كل السلوك الموصوف هنا يأتي في مكوّن HotXLS لـ Excel في Delphi لـ Delphi وC++Builder وLazarus، حيث تسرد صفحة المنتج أنواع التحقق المدعومة وصيغ الملفات والمرجع الكامل للتشخيصات