يقرأ HotXLS مصادر .xls و.xlsx و.xlsm و.ods وCSV وTSV عبر مؤشر سحب صفوف واحد، TXLSRowCursor، يتقدم كل من FindFirst وFindNext فيه صفا منطقيا واحدا في كل مرة بينما يبقى ذلك الصف فقط في الذاكرة. آلة حالات من ست قيم تفصل ما قبل الأول عن نهاية الملف والملغى والفاشل، وقارئ رد النداء الأقدم صار الآن مهايئا فوق المؤشر نفسه
السيناريو مألوف لمن شحن ميزة استيراد. يصل ملف .xlsx حجمه 200 MB، وتربط معالج OnCell، وأول متطلب بعد «اقرؤه» هو «توقف بعد أول مئة قيد معكوس». الآن يقاتلك شكل كودك: الحلقة تعيش داخل المكتبة، وعلى معالجك أن يرفع علما، وكل رد نداء لاحق ما زال ينطلق حتى يلاحظ المحلل، والحالة المتراكمة — كم إصابة حتى الآن، أي عمود طابق، ماذا نفعل بعد — يجب أن تعيش في حقول على فئة موجودة فقط لتمنح رد النداء مكانا يجلس فيه. لا شيء من ذلك مشكلة تحليل. إنها مشكلة تدفق تحكم، وهي التي يزيلها مؤشر السحب
ماذا يكلّف رد نداء الدفع فعلا عند 200 MB
الدفع يعكس التحكم، والانعكاس هو بالضبط ما لا يطيقه مستدعٍ يصفّي أو يربط. مع واجهة رد نداء تمتلك المكتبة الحلقة، فلا يستطيع المستدعي استخدام Break، ولا تشبيك مصدرين، ولا تسليم القارئ لإجراء يتوقع أن يُقاد، ولا التعبير عن «ألقِ نظرة على الصف التالي قبل أن تقرر» دون تخزين مؤقت. التكلفة ليست الإنتاجية — مسار رد نداء SAX مكتوبا جيدا يتدفق على ما يرام — بل أن كل مستهلك غير تافه ينمّي آلة حالات صغيرة خاصة به لمحاكاة الحلقة التي لم يُسمح له بكتابتها. اضرب ذلك في أربع صيغ ملفات، لكل منها تاريخيا نقطة دخول مسح خاصة بها، وتبدأ دلالات التصفية والصيغ والأخطاء تتباعد بينها، وهو بالضبط التباعد الذي انطلق HotXLS لإغلاقه
كيف يغيّر مؤشر السحب كود الاستدعاء لديك؟
إنه يعيد الحلقة إليك، ومعها تدفق تحكم Pascal اعتيادي. TXLSRowCursor.Open يقبل اسم ملف أو TStream، ويكتشف الصيغة، ويحمّل السلاسل المشتركة وبيانات أنماط التواريخ مرة واحدة، ويختار الورقة 1. SelectSheet (ذو أساس أحادي) أو SelectSheetByName يعيدان الاستهداف إلى ورقة عمل أخرى ويصفّران المؤشر إلى ما قبل الأول. ثم يضع FindFirst وFindNext الموضع على الصف المعبأ التالي — الصفوف بلا خلايا قابلة للفك تُتخطى، لذا يمكن لـRowIndex أن يقفز — ويُكشف الصف الحالي بوصفه CellCount وCells[] وValueByCol[]، وكلها ذات أساس أحادي على محور الأعمدة. مغادرة الحلقة هي Break
var
Cursor: TXLSRowCursor;
Hits: Integer;
begin
Cursor := TXLSRowCursor.Create;
try
Cursor.FirstRow := 2; // تخطَّ شريط الترويسة
Cursor.IncludeColumn(1); // افك ترميز هذين العمودين فقط
Cursor.IncludeColumn(7);
if not Cursor.Open('postings-200mb.xlsx') then
Exit;
if not Cursor.SelectSheetByName('Ledger') then
Exit;
Hits := 0;
if Cursor.FindFirst then
repeat
if VarToStr(Cursor.ValueByCol[7]) = 'REVERSED' then
begin
Inc(Hits);
if Hits = 100 then
Break; // Break اعتيادي؛ لا علم إجهاض، لا قيمة حراسة
end;
until not Cursor.FindNext;
finally
Cursor.Free; // المدمِر ينهي المرور
end;
end;
الإسقاط والمدى يُضبطان قبل المرور، لا يُصفَّيان بعده. FirstRow وLastRow وIncludeColumn وClearColumnProjection وIncludeFormulaText وDetectDates وDetectTextTypes كلها تُحترم داخل الخلفيات، لذا لا يخصّص عمود غير محدد قيمته أو سلسلة صيغته أو حمولة نصه المنسق أصلا — وتثبت مجموعة الانحدار ذلك بصيغ من 16 KiB وسلاسل مخزنة مؤقتا لا تتجسد أبدا حين لا يُسقَط عمودها. هذه الخيارات مجمّدة عمدا أثناء نشاط المرور وتصبح قابلة للكتابة من جديد عند نهاية الملف، أو عند SelectSheet، أو بعد Close، كي لا يخلط مسح واحد عقدَي فك ترميز. وإن كنت تحتاج فقط إلى جرد الأوراق لا الصفوف، فإن تحميل البيانات الوصفية فقط والتحميل الانتقائي للأوراق هو نقطة الدخول الأرخص
خلفية واحدة لكل صيغة، وحلقة مسح واحدة لكل منها
لكل صيغة ماسح أمامي واحد بالضبط داخل HotXLS، وكل من مؤشر السحب وقارئ رد النداء يقودان ذلك الماسح نفسه. TXLSXForwardRowBackend هو آلة حالات SAX الوحيدة لأجزاء أوراق ECMA-376 Part 1 §18.3، ويحمل قارئ XML وجدول الصيغ المشتركة ومحلل النص المنسق، ويتقدم إلى حد <row> مادي واحد بالضبط في كل استدعاء. TXLSBiffForwardParser يملك الإعدادات العامة واختيار الورقة وتقديم الصف لتدفق سجلات [MS-XLS]؛ وجعله قابلا للإيقاف المؤقت أنتج أحدّ قيد في التصميم كله، لأن صيغة السلسلة المخزنة مؤقتا هي سجل Formula يليه مباشرة سجل String، لذا يجب ألا تقع نقطة تعليق لكل صف بين الاثنين أبدا. TXLSForwardTextBackend يحمل قارئا مدركا لـBOM، والفاصل النشط، وسجلا منطقيا واحدا — CSV يستنشق الفاصلة أو الفاصلة المنقوطة أو الجدولة أو الأنبوب من السجل الأول متجاهلا الأحرف المزدوجة، والحقول المزدوجة متعددة الأسطر تُربط بـ#10 كي يتتبع رقم الصف السجلات المنطقية لا الأسطر الفعلية. TXLSForwardOdsBackend يحمل قالب صف مادي واحدا لجداول OpenDocument §9، ويعامل table:number-rows-repeated كعدد متبقٍ بدلا من توسيع، ويتقدم عبر الخلايا المغطاة دون إصدار قيم. والقارئ المباشر المتدفق يشارك المحمِّل نفسه للسلاسل المشتركة وأنماط التواريخ
لماذا ست حالات بدلا من علم Eof واحد؟
لأن قيمة بوليانية واحدة تجعل أربع مواقف مختلفة غير قابلة للتمييز، ويخمّن المستدعون خطأ في كلها. TXLSRowCursorState يسميها صراحة
xrcsClosed— لا مصدر مفتوحxrcsBeforeFirst— فُتح أو أعيد استهدافه، ولم يُقرأ أي صف بعدxrcsActive— واقف على صف صالحxrcsEof— استُهلكت الورقة حتى النهايةxrcsCancelled— أوقف المستدعي المرور عمداxrcsFaulted— فشل المرور وأُثير الاستثناء الأصلي
ذلك التمييز الأخير هو ما يهم في الإنتاج. جزء ورقة عمل مفقود أو بداية مرور فاشلة تحتفظ بـEReadError الخاص بها وتنقل المؤشر إلى xrcsFaulted؛ ولا تُخفَّض أبدا إلى False مجردة يقرؤها المستدعي على أنها «كانت هذه الورقة فارغة». Cancel أضيق عمدا من Close: فهي تغلق خلفية ورقة العمل الحالية وتدفقها الفرعي للفك الضغط وتبطل الصف الحالي، لكنها لا تحرر أرشيف ZIP أو تدفق المصدر، واستدعاؤها مرتين عملية بلا أثر. بعد الإلغاء تستأنف باستدعاء SelectSheet صراحة — لن يعيد المؤشر تشغيل مرور بهدوء نيابة عنك. ملكية التدفق تتبع القاعدة الدفاعية نفسها: xsoBorrowed هي الافتراضي وتستعيد موضع التدفق عند الإغلاق، وxsoOwned تنقل الملكية فقط بعد أن ينجح Open بالفعل، كي لا يحرر فتح فاشل تدفقا ما زال المستدعي يحتفظ به
var
Cursor: TXLSRowCursor;
Src: TFileStream;
begin
Src := TFileStream.Create('quarter.ods', fmOpenRead or fmShareDenyWrite);
try
Cursor := TXLSRowCursor.Create;
try
// xsoBorrowed: المؤشر لا يحرر Src أبدا، وClose يستعيد
// الموضع الذي كان عليه التدفق حين استُدعي Open
if not Cursor.Open(Src, xffAuto, xsoBorrowed) then
Exit;
if Cursor.FindFirst then
repeat
if UserPressedStop then
begin
Cursor.Cancel; // تغلق خلفية ورقة العمل وتدفقها
Break; // الفرعي للفك الضغط فقط؛ عديمة الأثر عند التكرار
end;
until not Cursor.FindNext;
case Cursor.State of
xrcsEof: Log('sheet consumed to the end');
xrcsCancelled: Log('stopped by the operator');
xrcsFaulted: Log('pass failed; the EReadError was already raised');
end;
finally
Cursor.Free;
end;
finally
Src.Free; // ما زال لنا، ما زال صالحا، الموضع مُستعاد
end;
end;
استعارة الصف الحالي دون نسخه
يسلّم IXLSRowCursorView صفا إلى إجراء آخر دون تكرار مصفوفة الخلايا. يخزن العرض حارسا مشتركا يحمل مؤشر المؤشر بالإضافة إلى عدّاد جيل من نوع UInt64؛ التقديم واختيار ورقة والإلغاء والإغلاق وإتلاف المؤشر كلها تزيد ذلك الجيل، والإتلاف يمسح مالك الحارس إضافة إلى ذلك. فلا يمكن لعرض بالٍ أن يقرأ ذاكرة محرَّرة: Valid مسبار بلا استثناءات يمكنك استدعاؤه في أي وقت، بينما كل عضو آخر يتحقق أولا ويثير EXLSRowCursorViewInvalidated. كن صادقا بشأن ماهية هذا العقد — إنه فشل سريع لمدة الحياة، لا ضمان أمان خيوط، ولا يرخّص بقراءة صف من خيط ثانٍ بينما يتقدم الأول بالمؤشر
var
View: IXLSRowCursorView;
Cell: TXLSRowCursorCell;
I: Integer;
begin
if Cursor.FindFirst then
repeat
View := Cursor.CurrentRowView; // يستعير؛ لا تُنسخ أي مصفوفة خلايا
for I := 0 to View.CellCount - 1 do
begin
Cell := View.Cells[I];
if Cell.HasFormula and not Cell.FormulaTextAvailable then
UseCachedResult(Cell.Value) // قراءات BIFF الأمامية تبقي النتيجة
else if Cell.Kind = xdkEmpty then // المخزنة مؤقتا، لا الرموز
UseStyleOnly(Cell.StyleIndex) // Blank / MulBlank خلايا حقيقية
else
UseValue(Cell.Col, Cell.Value);
end;
until not Cursor.FindNext;
// الواجهة تعمر بعد الحلقة، لكن الصف خلفها لا يعمر
if not View.Valid then // Valid لا يثير أبدا؛ Cells[] ستثير الآن
View := nil; // EXLSRowCursorViewInvalidated
end;
PeakRowBufferedBytes، وما يُسمح لها بإثباته
وُجدت PeakRowBufferedBytes لتُثبت أن الذاكرة تتبع عرض الصف لا عدد الصفوف. وهي تتراكم سجلات الخلايا والمتغيرات وسلاسل الصيغ وحمولات النص المنسق لصف الإخراج الحالي وتطوي فيها مجموعة العمل الخاصة بالصيغة — السجل المنطقي لـCSV، أو قالب الصف المادي لـODS، أو ذروة سجل BIFF، أو خلية XLSX الخام التي تُفك ترميزها الآن. اقرأها مع SheetPassesStarted، التي تعدّ كم مرور على أوراق العمل بدأ فعلا. تحفّظان يبقيان هذا صادقا: الرقم تقدير لا محاسبة دقيقة للكومة، وهو رتيب منذ أحدث Open، لذا فهو أداة تصحيح أخطاء وانحدار لا مقياس حي. وللصورة الأوسع عن أين يذهب الوقت والبايتات في الكتب الضخمة، انظر أداء المصنفات الكبيرة في Delphi
صار قارئ الدفع مهايئا، وما الذي لن يفعله المؤشر
لم يعد TXLSForwardReader يحمل نقاط دخول مسح منفصلة لـXLSX وBIFF والنص. إنه يهيئ مؤشرا، ويجتازه، ويترجم الصف الحالي إلى أحداث OnSheet وOnCell، ولهذا لم يعد بإمكان الواجهتين أن تتباعدا في التصفية أو حالة الصيغة أو معالجة الأخطاء. نتيجتان جديرتان بالمعرفة قبل الترقية: SheetIndex في رد النداء صار موحّدا ذا أساس أحادي على TXLSForwardReader (يحتفظ TXLSDirectReader بعقد أحداثه الحالي ذي الأساس الصفري)، وOnSheet تنطلق قبل SelectSheet، لذا ضبط SkipSheet يعني أن جزء ورقة العمل لا يُفتح أو يُفك ضغطه إطلاقا. الحدود صريحة بالقدر نفسه: يجب ألا يُعدَّل المصنف أثناء نشاط مرور، والإلغاء يتطلب إعادة تشغيل صريحة، ومسار BIFF الأمامي لا يفك رموز الصيغة أبدا، لذا تُبلغ خلايا الصيغ الكلاسيكية HasFormula بقيمة true مع FormulaTextAvailable بقيمة false وتسلّمك النتيجة المخزنة مؤقتا بدلا من اختراع سلسلة صيغة فارغة. اجتاز مؤشر الصفوف ومهايئه 1,298 فحصا على Delphi Win32 وWin64 بالإضافة إلى حزمة C++Builder 37.0 Win64 الساكنة
إن كنت توزن مؤشر سحب مقابل المحمِّل الذي لديك الآن، فالسؤال الذي يجب طرحه ليس أيهما يحلل أسرع بل أيهما يتيح لك كتابة شرط الخروج الذي تحتاجه فعلا. تفاصيل المكوّن الكاملة وإصدارات IDE المدعومة والترخيص على صفحة مكوّن HotXLS لجداول البيانات في Delphi