Tryb funkcji Wyszukaj i zamień w Edytorze¶
The Search & replace tool in the editor support a function mode. In this mode, you can combine regular expressions (see Wszystko, co chcielibyście wiedzieć o wyrażeniach regularnych w calibre) with arbitrarily powerful Python functions to do all sorts of advanced text processing.
In the standard regexp mode for search and replace, you specify both a regular expression to search for as well as a template that is used to replace all found matches. In function mode, instead of using a fixed template, you specify an arbitrary function, in the Python programming language. This allows you to do lots of things that are not possible with simple templates.
Techniki wykorzystania trybu funkcji oraz używana do tego składnia zostaną przedstawione w serii przykładów, pokazując jak tworzyć funkcje, by wykonywać coraz bardziej złożone zadania.
Automatyczne poprawianie wielkości liter w nagłówkach dokumentu¶
Tu wykorzystamy jedną z wbudowanych funkcji edytora do automatycznej zmiany wielkości liter we wszystkich znacznikach tytułów tak, by każda pierwsza litera wyrazu była wielka:
Find expression: <([Hh][1-6])[^>]*>.+?</\1>
For the function, simply choose the Title-case text (ignore tags) builtin
function. The will change titles that look like: <h1>some titLE</h1>
to
<h1>Some Title</h1>
. It will work even if there are other HTML tags inside
the heading tags.
Twoja pierwsza własna funkcja - poprawianie myślników¶
Prawdziwy potencjał trybu funkcji ujawnia się wraz z możliwością pisania własnych funkcji do dowolnego przetwarzania tekstu. Inteligentna Interpunkcja w edytorze pozostawia pojedyncze myślniki, możesz jej więc użyć do zamiany ich na znak pauzy.
To create a new function, simply click the Create/edit button to create a new function and copy the Python code from below.
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
return match.group().replace('--', '—').replace('-', '—')
Every Search & replace custom function must have a unique name and consist of a
Python function named replace, that accepts all the arguments shown above.
For the moment, we won’t worry about all the different arguments to
replace()
function. Just focus on the match
argument. It represents a
match when running a search and replace. Its full documentation in available
here.
match.group()
simply returns all the matched text and all we do is replace
hyphens in that text with em-dashes, first replacing double hyphens and
then single hyphens.
Użyj tej funkcji z wyrażeniem regularnym:
>[^<>]+<
Zamieni ona wszystkie myślniki na pauzy, ale tylko we właściwym tekście, nie wewnątrz definicji znaczników.
Potęga trybu funkcji - użycie słownika do poprawienia błędnie podzielonych słów.¶
Often, e-books created from scans of printed books contain mis-hyphenated words – words that were split at the end of the line on the printed page. We will write a simple function to automatically find and fix such words.
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
Użyj tej funkcji z tym samym poszukiwanym wyrażeniem jak poprzednio, mianowicie:
>[^<>]+<
W magiczny sposób poprawi to wszystkie błędnie podzielone słowa w tekście. Trik polega na użyciu jednego z argumentów funkcji, dictionaries
. Stanowi on odniesienie do słowników, których używa edytor do sprawdzania pisowni. Funkcja odszukuje słowo z myślnikiem w środku, usuwa ów myślnik i sprawdza, czy tak połączony wyraz jest w słowniku. Jeśli tak to oryginalny wyraz jest zastępowany nowym, połączonym.
Należy pamiętać, że działanie tej funkcji jest ograniczone do książek w jednym języku, bowiem domyślnie dictionaries.recognized()
używa głównego języka książki.
Autonumerowanie rozdziałów¶
Teraz coś innego. Przypuśćmy, że plik HTML ma wiele rozdziałów, każdy rozpoczynający się znacznikiem <h2>
w postaci <h2>Jakiś tekst</h2>
. Można napisać funkcję, która ponumeruje te rozdziały kolejnymi numerami, by wyglądały następująco: <h2>1. Jakiś tekst</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'
Użyj jej, wyszukując wyrażenie:
(?s)(<h2[^<>]*>)(.+?</h2>)
Ustaw kursor na górze pliku i kliknij Zamień wszystkie
Ta funkcja wykorzystuje inny przydatny argument funkcji replace()
: number
. Przy operacji Zamień wszystkie liczba jest automatycznie inkrementowana przy każdym dopasowaniu.
Kolejną nowością jest użycie replace.file_order
– ustawienie tej opcji na spine
oznacza, że jeśli przeszukiwanych jest wiele plików to będą one przetwarzane w takiej kolejności, w jakiej występują w książce. Szczegóły znajdziesz w Wybierz kolejność plików, jeśli przetwarzanych jest wiele plików HTML.
Automatyczne tworzenie spisu treści¶
Na koniec spróbujmy czegoś bardziej ambitnego. Przypuśćmy, że w książce tytuły są w znacznikach h1
i h2
, które wyglądają następująco: <h1 id="jakiesid">Jakiś tekst</h1>
. Wygenerujemy automatycznie spis treści w HTML, bazując na tych znacznikach. Stwórz funkcję jak poniżej:
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'
Użyj takiego szukanego wyrażenia:
<(h[12]) [^<>]* id=['"]([^'"]+)['"][^<>]*>([^<>]+)
Run the search on All text files and at the end of the search, a
window will popup with „Debug output from your function” which will have the
HTML Table of Contents, ready to be pasted into toc.html
.
The function above is heavily commented, so it should be easy to follow. The
key new feature is the use of another useful extra argument to the
replace()
function, the data
object. The data
object is a Python
dictionary that persists between all successive invocations of replace()
during
a single Replace All operation.
Kolejna rzecz to właściwość call_after_last_match
– ustawienie jej na True
oznacza, że edytor wywoła funkcję replace()
dodatkowo jeden raz po odnalezieniu wszystkich dopasowań. W tym wywołaniu obiekt dopasowania jest równy None
.
To tylko demonstracja możliwości trybu funkcji, jeśli rzeczywiście chcesz wygenerować spis treści z nagłówków, lepiej będzie posłużyć się dedykowanym temu narzędziem Narzędzia → Spis treści.
API trybu funkcji¶
All function mode functions must be Python functions named replace, with the following signature:
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
return a_string
Kiedy uruchamiane jest wyszukiwanie/zamiana, dla każdego odnalezionego dopasowania, dla którego zostanie wywołana funkcja replace()
, musi ona zwrócić tekst zastępujący owo dopasowanie. Jeśli zamiana nie zostanie wykonana, powinna zwrócić march.group()
, który jest oryginalnym tekstem. Różne argumenty funkcji replace()
zostały opisane poniżej.
Argument match
¶
The match
argument represents the currently found match. It is a
Python Match object.
Its most useful method is group()
which can be used to get the matched
text corresponding to individual capture groups in the search regular
expression.
Argument number
¶
Argument number
jest numerem aktualnego dopasowania. Kiedy uruchamiasz Zamień wszystko, każde kolejne dopasowanie będzie wywoływać replace()
z następnym numerem. Pierwsze dopasowanie ma numer 1.
Argument file_name
¶
To jest nazwa pliku, w którym odnaleziono bieżące dopasowanie. Przy wyszukiwaniu w zaznaczeniu file_name
jest puste. file_name
ma postać kanonicznej względnej ścieżki, od katalogu głównego książki, do oddzielania katalogów służy /
.
Argument metadata
¶
Ten argument reprezentuje metadane aktualnej książki, takie jak tytuł, autor, język itp. Jest to obiekt klasy calibre.ebooks.metadata.book.base.Metadata
. Najbardziej przydatne właściwości to title
, authors
(lista autorów) i language
(kod języka).
Argument dictionaries
¶
This represents the collection of dictionaries used for spell checking the
current book. Its most useful method is dictionaries.recognized(word)
which will return True
if the passed in word is recognized by the dictionary
for the current book’s language.
Argument data
¶
This a simple Python dictionary
. When you run
Replace all, every successive match will cause replace()
to be
called with the same dictionary
as data. You can thus use it to store arbitrary
data between invocations of replace()
during a Replace all
operation.
Argument functions
¶
Ten argument daje ci dostęp do wszystkich innych zdefiniowanych przez ciebie funkcji. Przydaje się to do wielokrotnego użycia tego samego kodu. Możesz zdefiniować jakąś funkcję w jednym miejscu i wykorzystać ją w innych twoich funkcjach. Na przykład przypuśćmy, że stworzyłeś taką funkcję o nazwie My function
:
def utility():
# do something
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...
Potem, w innej funkcji, możesz uzyskać dostęp do utility()
w ten sposób:
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
utility = functions['My Function']['utility']
...
Można również użyć funkcji do przechowywania trwałych danych, które mogą być ponownie wykorzystane przez inne funkcje. Na przykład jedna funkcja zbiera dane przy uruchomieniu Zamień wszystko, a następna, uruchomiona później, wykorzystuje je. Przyjrzyjmy się dwóm następującym funkcjom:
# 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']
...
Debugowanie twoich funkcji¶
You can debug the functions you create by using the standard print()
function from Python. The output of print will be displayed in a popup window
after the Find/replace has completed. You saw an example of using print()
to output an entire table of contents above.
Wybierz kolejność plików, jeśli przetwarzanych jest wiele plików HTML¶
When you run a Replace all on multiple HTML files, the order in
which the files are processes depends on what files you have open for editing.
You can force the search to process files in the order in which the appear by
setting the file_order
attribute on your function, like this:
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...
replace.file_order = 'spine'
file_order
może przyjąć dwie wartości, spine
i spine-reverse
, które powodują przeszukanie plików, odpowiednio, w kolejności w jakiej są wyświetlane lub odwrotnie.
Dodatkowe wywołanie funkcji po znalezieniu ostatniego dopasowania¶
Czasem, jak w przykładzie ze spisem treści powyżej, przydatne jest wywołanie funkcji dodatkowo, po ostatnim dopasowaniu. Można to zrobić, ustawiając właściwość call_after_last_match
, o, tak:
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...
replace.call_after_last_match = True
Dołączanie wyniku z funkcji do zaznaczonego tekstu¶
When running search and replace on marked text, it is sometimes useful to
append so text to the end of the marked text. You can do that by setting
the append_final_output_to_marked
attribute on your function (note that you
also need to set call_after_last_match
), like this:
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
Pomija okno dialogowe wyników podczas wyszukiwania zaznaczonego tekstu¶
You can also suppress the result dialog (which can slow down the repeated
application of a search/replace on many blocks of text) by setting
the suppress_result_dialog
attribute on your function, like this:
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...
replace.suppress_result_dialog = True
Więcej przykładów¶
More useful examples, contributed by calibre users, can be found in the calibre E-book editor forum.