وضع الدالة للبحث والاستبدال في المحرر¶
تدعم أداة البحث والاستبدال في المحرر وضع الدالة. في هذا الوضع، يمكنك دمج التعبيرات العادية (راجع كل شيء عن استخدام التعبيرات العادية في calibre) مع دوال بايثون قوية بشكل تعسفي للقيام بجميع أنواع معالجة النصوص المتقدمة.
في وضع regexp القياسي للبحث والاستبدال، تحدد تعبيرًا عاديًا للبحث عنه بالإضافة إلى قالب يستخدم لاستبدال جميع التطابقات التي تم العثور عليها. في وضع الدالة، بدلاً من استخدام قالب ثابت، تحدد دالة عشوائية، بلغة برمجة بايثون. يتيح لك هذا القيام بالعديد من الأشياء التي لا يمكن القيام بها باستخدام القوالب البسيطة.
سيتم وصف تقنيات استخدام وضع الدالة وبناء الجملة عن طريق الأمثلة، مما يوضح لك كيفية إنشاء دوال لأداء مهام أكثر تعقيدًا بشكل تدريجي.

تصحيح حالة أحرف العناوين تلقائيًا في المستند¶
هنا، سنستفيد من إحدى الدوال المدمجة في المحرر لـ تغيير حالة جميع النصوص داخل علامات العناوين تلقائيًا إلى حالة العنوان:
Find expression: <([Hh][1-6])[^>]*>.+?</\1>
بالنسبة للدالة، ما عليك سوى اختيار دالة نص بحالة العنوان (تجاهل العلامات) المدمجة. ستغير العناوين التي تبدو كـ: <h1>some titLE</h1>
إلى <h1>Some Title</h1>
. ستعمل حتى لو كانت هناك علامات HTML أخرى داخل علامات العناوين.
دالتك المخصصة الأولى - تحسين الواصلات¶
تأتي القوة الحقيقية لوضع الدالة من القدرة على إنشاء دوال خاصة بك لمعالجة النص بطرق عشوائية. أداة تحسين علامات الترقيم في المحرر تترك الواصلات الفردية كما هي، لذا يمكنك استخدام هذه الدالة لاستبدالها بشرطات طويلة (em-dashes).
لإنشاء دالة جديدة، ما عليك سوى النقر على زر إنشاء/تحرير لـ إنشاء دالة جديدة ونسخ كود بايثون من أدناه.
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
return match.group().replace('--', '—').replace('-', '—')
يجب أن يكون لكل دالة مخصصة لـ البحث والاستبدال اسم فريد وتتكون من دالة بايثون تسمى replace، تقبل جميع الوسائط الموضحة أعلاه. في الوقت الحالي، لن نقلق بشأن جميع الوسائط المختلفة لدالة replace()
. ركز فقط على وسيطة match
. إنها تمثل تطابقًا عند تشغيل بحث واستبدال. وثائقها الكاملة متاحة هنا. match.group()
ببساطة يعيد جميع النصوص المطابقة وكل ما نفعله هو استبدال الواصلات في ذلك النص بشرطات طويلة (em-dashes)، أولاً استبدال الواصلات المزدوجة ثم الواصلات الفردية.
استخدم هذه الدالة مع التعبير العادي للبحث:
>[^<>]+<
وسوف تستبدل جميع الواصلات بشرطات طويلة (em-dashes)، ولكن فقط في النص الفعلي وليس داخل تعريفات علامات HTML.
قوة وضع الدالة - استخدام قاموس إملائي لإصلاح الكلمات ذات الواصلات الخاطئة¶
غالبًا ما تحتوي الكتب الإلكترونية التي تم إنشاؤها من مسح الكتب المطبوعة على كلمات ذات واصلات خاطئة -- كلمات تم تقسيمها في نهاية السطر على الصفحة المطبوعة. سنكتب دالة بسيطة للعثور على هذه الكلمات وإصلاحها تلقائيًا.
import regex
from calibre import replace_entities
from calibre import prepare_string_for_xml
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
def replace_word(wmatch):
# Try to remove the hyphen and replace the words if the resulting
# hyphen free word is recognized by the dictionary
without_hyphen = wmatch.group(1) + wmatch.group(2)
if dictionaries.recognized(without_hyphen):
return without_hyphen
return wmatch.group()
# Search for words split by a hyphen
text = replace_entities(match.group()[1:-1]) # Handle HTML entities like &
corrected = regex.sub(r'(\w+)\s*-\s*(\w+)', replace_word, text, flags=regex.VERSION1 | regex.UNICODE)
return '>%s<' % prepare_string_for_xml(corrected) # Put back required entities
استخدم هذه الدالة مع نفس تعبير البحث السابق، وهو:
>[^<>]+<
وسوف تقوم بتصحيح جميع الكلمات ذات الواصلات الخاطئة في نص الكتاب بطريقة سحرية. الخدعة الرئيسية هي استخدام إحدى الوسائط الإضافية المفيدة لدالة الاستبدال، dictionaries
. يشير هذا إلى القواميس التي يستخدمها المحرر نفسه للتحقق الإملائي للنص في الكتاب. ما تفعله هذه الدالة هو البحث عن الكلمات المفصولة بواصلة، وإزالة الواصلة والتحقق مما إذا كان القاموس يتعرف على الكلمة المركبة، إذا كان الأمر كذلك، يتم استبدال الكلمات الأصلية بالكلمة المركبة الخالية من الواصلة.
لاحظ أن أحد قيود هذه التقنية هو أنها ستعمل فقط مع الكتب أحادية اللغة، لأنه، افتراضيًا، تستخدم dictionaries.recognized()
اللغة الرئيسية للكتاب.
ترقيم الأقسام تلقائيًا¶
الآن سنرى شيئًا مختلفًا قليلاً. لنفترض أن ملف HTML الخاص بك يحتوي على العديد من الأقسام، كل منها يحتوي على عنوان في علامة <h2>
تبدو كـ <h2>Some text</h2>
. يمكنك إنشاء دالة مخصصة ستقوم بترقيم هذه العناوين تلقائيًا بأرقام أقسام متتالية، بحيث تبدو كـ <h2>1. Some text</h2>
.
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
section_number = '%d. ' % number
return match.group(1) + section_number + match.group(2)
# Ensure that when running over multiple files, the files are processed
# in the order in which they appear in the book
replace.file_order = 'spine'
استخدمها مع تعبير البحث:
(?s)(<h2[^<>]*>)(.+?</h2>)
ضع المؤشر في أعلى الملف وانقر على استبدال الكل.
تستخدم هذه الدالة وسيطة إضافية مفيدة أخرى لدالة replace()
: الوسيطة number
. عند إجراء استبدال الكل، يتم زيادة الرقم تلقائيًا لكل تطابق متتالي. التطابق الأول يحمل الرقم 1.
ميزة جديدة أخرى هي استخدام replace.file_order
-- تعيينها إلى 'spine'
يعني أنه إذا تم تشغيل هذا البحث على ملفات HTML متعددة، فإن الملفات تتم معالجتها بالترتيب الذي تظهر به في الكتاب. راجع اختر ترتيب الملفات عند التشغيل على ملفات HTML متعددة للحصول على التفاصيل.
إنشاء جدول محتويات تلقائيًا¶
أخيرًا، دعنا نجرب شيئًا أكثر طموحًا. لنفترض أن كتابك يحتوي على عناوين في علامات h1
و h2
تبدو كـ <h1 id="someid">Some Text</h1>
. سنقوم بإنشاء جدول محتويات HTML تلقائيًا بناءً على هذه العناوين. أنشئ الدالة المخصصة أدناه:
from calibre import replace_entities
from calibre.ebooks.oeb.polish.toc import TOC, toc_to_html
from calibre.gui2.tweak_book import current_container
from calibre.ebooks.oeb.base import xml2str
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
if match is None:
# All matches found, output the resulting Table of Contents.
# The argument metadata is the metadata of the book being edited
if 'toc' in data:
toc = data['toc']
root = TOC()
for (file_name, tag_name, anchor, text) in toc:
parent = root.children[-1] if tag_name == 'h2' and root.children else root
parent.add(text, file_name, anchor)
toc = toc_to_html(root, current_container(), 'toc.html', 'Table of Contents for ' + metadata.title, metadata.language)
print(xml2str(toc))
else:
print('No headings to build ToC from found')
else:
# Add an entry corresponding to this match to the Table of Contents
if 'toc' not in data:
# The entries are stored in the data object, which will persist
# for all invocations of this function during a 'Replace All' operation
data['toc'] = []
tag_name, anchor, text = match.group(1), replace_entities(match.group(2)), replace_entities(match.group(3))
data['toc'].append((file_name, tag_name, anchor, text))
return match.group() # We don't want to make any actual changes, so return the original matched text
# Ensure that we are called once after the last match is found so we can
# output the ToC
replace.call_after_last_match = True
# Ensure that when running over multiple files, this function is called,
# the files are processed in the order in which they appear in the book
replace.file_order = 'spine'
واستخدمها مع تعبير البحث:
<(h[12]) [^<>]* id=['"]([^'"]+)['"][^<>]*>([^<>]+)
قم بتشغيل البحث على جميع الملفات النصية وفي نهاية البحث، ستظهر نافذة منبثقة بعنوان "إخراج تصحيح الأخطاء من دالتك" والتي ستحتوي على جدول محتويات HTML، جاهز للصق في toc.html
.
الدالة أعلاه مشروحة بشكل مكثف، لذا يجب أن يكون من السهل متابعتها. الميزة الجديدة الرئيسية هي استخدام وسيطة إضافية مفيدة أخرى لدالة replace()
، وهي كائن data
. كائن data
هو قاموس بايثون يستمر بين جميع الاستدعاءات المتتالية لـ replace()
أثناء عملية استبدال الكل الواحدة.
ميزة جديدة أخرى هي استخدام call_after_last_match
-- تعيين هذا إلى True
في دالة replace()
يعني أن المحرر سيستدعي replace()
مرة إضافية بعد العثور على جميع التطابقات. لهذا الاستدعاء الإضافي، سيكون كائن المطابقة None
.
كان هذا مجرد عرض توضيحي لإظهار قوة وضع الدالة، إذا كنت تحتاج حقًا إلى إنشاء جدول محتويات من العناوين في كتابك، فمن الأفضل استخدام أداة جدول المحتويات المخصصة في أدوات → جدول المحتويات.
واجهة برمجة تطبيقات وضع الدالة¶
يجب أن تكون جميع دوال وضع الدالة دوال بايثون تسمى replace، مع التوقيع التالي:
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
return a_string
عند تشغيل بحث/استبدال، لكل تطابق يتم العثور عليه، سيتم استدعاء دالة replace()
، ويجب أن تعيد سلسلة الاستبدال لذلك التطابق. إذا لم يتم إجراء أي استبدالات، يجب أن تعيد match.group()
وهي السلسلة الأصلية. يتم توثيق الوسائط المختلفة لدالة replace()
أدناه.
وسيطة match
¶
تمثل وسيطة match
التطابق الذي تم العثور عليه حاليًا. إنها كائن مطابقة بايثون. أكثر طرقها فائدة هي group()
التي يمكن استخدامها للحصول على النص المطابق للمجموعات الفردية التي تم التقاطها في تعبير البحث العادي.
وسيطة number
¶
وسيطة number
هي رقم التطابق الحالي. عند تشغيل استبدال الكل، سيؤدي كل تطابق متتالي إلى استدعاء replace()
برقم متزايد. التطابق الأول يحمل الرقم 1.
وسيطة file_name
¶
هذا هو اسم الملف الذي تم العثور على التطابق الحالي فيه. عند البحث داخل النص المحدد، يكون file_name
فارغًا. يكون file_name
في شكل قانوني، وهو مسار نسبي لجذر الكتاب، باستخدام /
كفاصل للمسار.
وسيطة metadata
¶
يمثل هذا البيانات الوصفية للكتاب الحالي، مثل العنوان، المؤلفين، اللغة، إلخ. وهو كائن من الفئة calibre.ebooks.metadata.book.base.Metadata
. تتضمن السمات المفيدة title
، authors
(قائمة من المؤلفين) و language
(رمز اللغة).
وسيطة dictionaries
¶
يمثل هذا مجموعة القواميس المستخدمة للتدقيق الإملائي في الكتاب الحالي. أكثر طرقه فائدة هي dictionaries.recognized(word)
التي ستعيد True
إذا تم التعرف على الكلمة الممررة بواسطة القاموس الخاص بلغة الكتاب الحالي.
وسيطة data
¶
هذا قاموس بايثون بسيط. عندما تقوم بتشغيل استبدال الكل، كل تطابق متتالي سيؤدي إلى استدعاء replace()
بنفس dictionary
كبيانات. وبالتالي يمكنك استخدامه لتخزين بيانات عشوائية بين استدعاءات replace()
أثناء عملية استبدال الكل.
وسيطة functions
¶
تمنحك وسيطة functions
إمكانية الوصول إلى جميع الدوال الأخرى المعرفة من قبل المستخدم. هذا مفيد لإعادة استخدام الكود. يمكنك تعريف دوال مساعدة في مكان واحد وإعادة استخدامها في جميع الدوال الأخرى. على سبيل المثال، لنفترض أنك أنشأت دالة باسم My Function
كالتالي:
def utility():
# do something
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...
ثم، في دالة أخرى، يمكنك الوصول إلى دالة utility()
كالتالي:
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
utility = functions['My Function']['utility']
...
يمكنك أيضًا استخدام كائن الدوال لتخزين بيانات مستمرة، يمكن إعادة استخدامها بواسطة دوال أخرى. على سبيل المثال، يمكنك أن يكون لديك دالة عند تشغيلها مع استبدال الكل تجمع بعض البيانات ودالة أخرى تستخدمها عند تشغيلها لاحقًا. ضع في اعتبارك الدالتين التاليتين:
# Function One
persistent_data = {}
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...
persistent_data['something'] = 'some data'
# Function Two
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
persistent_data = functions['Function One']['persistent_data']
...
تصحيح أخطاء الدوال الخاصة بك¶
يمكنك تصحيح أخطاء الدوال التي تنشئها باستخدام دالة print()
القياسية من بايثون. سيتم عرض إخراج الطباعة في نافذة منبثقة بعد اكتمال البحث/الاستبدال. لقد رأيت مثالاً على استخدام print()
لإخراج جدول محتويات كامل أعلاه.
اختر ترتيب الملفات عند التشغيل على ملفات HTML متعددة¶
عند تشغيل استبدال الكل على ملفات HTML متعددة، فإن الترتيب الذي تتم به معالجة الملفات يعتمد على الملفات التي فتحتها للتحرير. يمكنك إجبار البحث على معالجة الملفات بالترتيب الذي تظهر به عن طريق تعيين السمة file_order
على دالتك، كالتالي:
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...
replace.file_order = 'spine'
تقبل file_order
قيمتين، spine
و spine-reverse
مما يتسبب في معالجة البحث لملفات متعددة بالترتيب الذي تظهر به في الكتاب، إما للأمام أو للخلف، على التوالي.
استدعاء دالتك مرة إضافية بعد العثور على آخر تطابق¶
أحيانًا، كما هو الحال في مثال إنشاء جدول المحتويات التلقائي أعلاه، يكون من المفيد استدعاء دالتك مرة إضافية بعد العثور على آخر تطابق. يمكنك القيام بذلك عن طريق تعيين السمة call_after_last_match
على دالتك، كالتالي:
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...
replace.call_after_last_match = True
إلحاق الإخراج من الدالة بالنص المحدد¶
عند تشغيل البحث والاستبدال على نص محدد، يكون من المفيد أحيانًا إلحاق نص إلى نهاية النص المحدد. يمكنك القيام بذلك عن طريق تعيين السمة append_final_output_to_marked
على دالتك (لاحظ أنك تحتاج أيضًا إلى تعيين call_after_last_match
)، كالتالي:
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...
return 'some text to append'
replace.call_after_last_match = True
replace.append_final_output_to_marked = True
إخفاء مربع حوار النتائج عند إجراء عمليات بحث على نص محدد¶
يمكنك أيضًا إخفاء مربع حوار النتائج (الذي يمكن أن يبطئ التطبيق المتكرر للبحث/الاستبدال على العديد من كتل النص) عن طريق تعيين السمة suppress_result_dialog
على دالتك، كالتالي:
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...
replace.suppress_result_dialog = True
المزيد من الأمثلة¶
يمكن العثور على المزيد من الأمثلة المفيدة، التي ساهم بها مستخدمو calibre، في منتدى محرر الكتب الإلكترونية في calibre.