Režim funkcí pro Hledat a nahradit v editoru

Nástroj Hledat a nahradit v editoru podporuje režim funkcí. V tomto režimu můžete kombinovat regulární výrazy (viz: Vše o používání regulárních výrazů v Calibre) s libovolnými výkonnými funkcemi Pythonu pro provádění všech druhů rozšířeného zpracování textu.

Ve standardním režimu regulární výraz pro hledání a nahrazení zadáváte jak regulární výraz pro hledání, tak šablonu, která je použita pro nahrazení všech nalezených shod. V režimu funkcí, namísto použití pevné šablony, zadáte libovolnou funkci v programovacím jazyce Python. Umožní vám to udělat spoustu věcí, které nejsou možné u jednoduchých šablon.

Techniky pro použití režimu funkcí a syntaxe budou popsány pomocí příkladů, které vám ukáží, jak vytvořit funkce pro provádění postupně složitějších úloh.

The Function mode

Automatická oprava velikosti písmen nadpisů v dokumentu

Zde budeme využívat jednu z vestavěných funkcí v editoru pro automatickou změnu velikosti písmen celého textu uvnitř značek nadpisů, aby byla všechna první písmena velká:

Find expression: <([Hh][1-6])[^>]*>.+?</\1>

Pro funkci jednoduše zvolte vestavěnou funkci Všechna první velká (ignorovat značky). Toto změní nadpisy, které vypadají takto: <h1>nějaký NADPIS</h1> na <h1>Nějaký Nadpis</h1>. Bude to fungovat, i když jsou jiné značky HTML uvnitř značek nadpisů.

Vaše první vlastní funkce – vylepšení pomlček

Skutečná síla režimu funkcí pochází ze schopnosti vytvářet vaše vlastní funkce pro zpracování textu libovolnými způsoby. Nástroj Vylepšit interpunkci v editoru nechává samostatné pomlčky být, takže můžete použít tuto funkci pro jejich nahrazení dlouhými pomlčkami.

Pro vytvoření nové funkce jednoduše klikněte na tlačítko Vytvořit nebo upravit pro vytvoření nové funkce a zkopírujte níže uvedený kód Pythonu.

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
    return match.group().replace('--', '—').replace('-', '—')

Každá vlastní funkce Najít a nahradit musí mít jedinečný název a skládat se z funkce Pythonu s názvem replace, která přijímá všechny výše uvedené parametry. Pro tuto chvíli se nebudeme starat o všechny různé parametry funkce replace(). Soustředíme se pouze na parametr match. Zastupuje shodu při spuštění hledat a nahradit. Jeho úplná dokumentace je dostupná zde. match.group() jednoduše vrátí všechny odpovídající texty a vše, co uděláme je, že nahradíme pomlčky v tomto textu dlouhými pomlčkami, nejdříve se dvojité pomlčky a potom jednotlivé pomlčky.

Použijte tuto funkci s hledáním regulárního výrazu:

>[^<>]+<

A nahradí to všechny pomlčky dlouhými pomlčkami, ale pouze ve skutečném textu a ne uvnitř definicí značek HTML.

Síla režimu funkcí – používání pravopisného slovníku pro opravu chybně rozdělených slov spojovníkem

E-knihy vytvořené naskenováním tištěných knih často obsahují chybně rozdělená slova spojovníkem – slova, která byla rozdělena na konci řádku na vytištěné stránce. Napíšeme si jednoduchou funkci pro automatické nalezení a opravení takových slov.

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 &amp;
    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

Použijte tuto funkci se stejným vyhledávacím výrazem jako předtím, a to:

>[^<>]+<

A jako kouzlem to opraví všechna chybně rozdělená slova v textu knihy. Hlavní trik je použít jeden z užitečných dodatečných parametrů pro funkci nahrazení, dictionaries. Ten odkazuje na slovníky, které samotný editor používá pro kontrolu pravopisu textu v knize. Tato funkce hledá slova oddělená spojovníkem, odebere spojovník a zkontrolujte, zda slovník rozpozná složené slovo. Pokud ano, původní slova jsou nahrazena složeným slovem bez spojovníku.

Pamatujte, že jediné omezení této techniky je, že to bude fungovat pouze pro jednojazyčné knihy, protože dictionaries.recognized() ve výchozím nastavení používá hlavní jazyk knihy.

Automatické číslování oddílů

Nyní uvidíme něco trochu jiného. Předpokládejme, že váš soubor HTML má mnoho oddílů, každý s nadpisem ve značce <h2>, která vypadají takto <h2>Nějaký text</h2>. Můžete si vytvořit vlastní funkci, která automaticky očísluje tyto nadpisy za sebou následujícími čísly oddílů, takže budou vypadat takto <h2>1. Nějaký 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'

Použijte vyhledávací výraz:

(?s)(<h2[^<>]*>)(.+?</h2>)

Umístěte kurzor na začátek souboru a klikněte na Nahradit vše.

Tato funkce využívá další z užitečných dodatečných parametrů pro replace(): parametr number. Při provádění Nahradit vše je číslo automaticky zvýšeno pro každou následující shodu.

Další novinkou je použití replace.file_order – nastavení, které pro 'páteř' znamená, že pokud je toto hledání spuštěno na více souborech HTML, soubory jsou zpracovávány v pořadí, v jakém se objevují v knize. Pro podrobnosti viz Volba pořadí souborů při spuštění na více souborech HTML.

Automatické vytvoření obsahu

Nakonec zkusme něco trochu náročnějšího. Předpokládejme, že vaše kniha má nadpisy ve značkách h1 a h2, které vypadají takto <h1 id="nejakeid">Nějaký text</h1>. Automaticky vygenerujeme HTML obsah na základě těchto nadpisů. Vytvořte níže uvedenou vlastní funkci:

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'

A použijte ji s vyhledávacím výrazem:

<(h[12]) [^<>]* id=['"]([^'"]+)['"][^<>]*>([^<>]+)

Spusťte hledání nad Všechny textové soubory a na konci vyhledávání se otevře okno s „Výstup ladění z vaší funkce“, které bude mít HTML obsah připravený k vložení do toc.html.

Výše uvedená funkce je silně okomentovaná, takže by mělo být snadné ji pochopit. Klíčovou novinkou je použití dalšího užitečného dodatečného parametru pro funkci replace(), objektu data. Objekt data je dict Pythonu, který přetrvává mezi všemi po sobě jdoucími vyvoláními replace() během jediné operace Nahradit vše.

Další novinkou je použití call_after_last_match – nastavení, které pro True nad funkcí replace() znamená, že editor vyvolá replace() ještě jednou poté, co byly nalezeny všechny shody. Pro toto dodatečné vyvolání bude shoda objektu None.

To byla jen ukázka, která vám předvedla sílu režimu funkcí, pokud opravdu potřebujete vygenerovat obsah z nadpisů ve vaší knize, měli byste raději použít vyhrazený nástroj pro obsah v Nástroje → Obsah.

API pro režim funkcí

Všechny funkce režimu funkcí musí být funkce Pythonu pojmenované replace s následujícím podpisem:

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
    return a_string

Když je spuštěno najít a nahradit, pro každou shodu, která je nalezena, bude vyvolána funkce replace(), která musí vrátit nahrazovací řetězec pro tuto shodu. Pokud není třeba provést žádná nahrazení, mělo by se vrátit match.group(), což je původní řetězec. Jednotlivé parametry pro funkci replace() jsou zdokumentovány níže.

Parametr match

Parametr match představuje aktuálně nalezenou shodu. Je to objekt Match Pythonu. Jeho nejužitečnější metoda je group(), která může být použita k získání textu odpovídajícího jednotlivým skupin zachycování v hledání regulárního výrazu.

Parametr number

Parametr number je číslo aktuální shody. Když spustíte Nahradit vše, každá po sobě jdoucí shoda způsobí, že bude vyvoláno``replace()`` se zvyšujícím se číslem. První shoda má číslo 1.

Parametr file_name

Toto je název souboru, ve kterém byla nalezena aktuální shoda. Při hledání uvnitř označeného textu je file_name prázdný. file_name je v kanonické podobě, cesta relativní ke kořenu knihy, s / jako oddělovačem cesty.

Parametr metadata

Toto představuje metadata aktuální knihy, jako je název, autoři, jazyk atd. Je to objekt třídy: calibre.ebooks.metadata.book.base.Metadata. Užitečné atributy zahrnují title, authors (seznam autorů) a language (kód jazyka).

Parametr dictionaries

Toto představuje sbírku slovníků používaných ke kontrole pravopisu aktuální knihy. Jeho nejužitečnější metoda je dictionaries.recognized(word), která vrátí True, pokud je předané slovo rozpoznáno slovníkem pro aktuální jazyk knihy.

Parametr data

Toto je jednoduchý dict Pythonu. Když spustíte Nahradit vše, každá po sobě jdoucí shoda způsobí, že bude vyvoláno replace() se stejným dict jako data. Můžete to tak použít pro ukládání libovolných dat mezi vyvoláními replace() během operace Nahradit vše.

Parametr functions

Parametr functions vám dá přístup ke všem ostatním uživatelem definovaným funkcím. To je užitečné pro opětovné použití kódu. Můžete definovat obslužné funkce na jednom místě a znovu je použít ve všech svých ostatních funkcích. Předpokládejme například, že vytvoříte funkci pojmenovanou My Function takto:

def utility():
   # do something

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
    ...

Potom v jiné funkci můžete přistupovat k funkci utility() takto:

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
    utility = functions['My Function']['utility']
    ...

Můžete také použít objekt funkce pro ukládání trvalých dat, která mohou být znovu použita jinými funkcemi. Například byste mohli mít jednu funkci, která při spuštění s Nahradit vše shromažďuje některá data, a další funkci, která je používá, když je spuštěna později. Vezměme si následující dvě funkce:

# 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']
    ...

Ladění vašich funkcí

Funkce, které vytvoříte, můžete ladit pomocí standardní funkce print() z Pythonu. Výstup tisku se zobrazí v automaticky otevíraném okně po dokončení Najít a nahradit. Příklad použití print() pro výstup celého obsahu jste viděli výše.

Volba pořadí souborů při spuštění na více souborech HTML

Když spustíte Nahradit vše na více souborech HTML, pořadí, ve kterém jsou soubory zpracovány, závisí na tom, jaké soubory máte otevřené pro úpravy. Můžete vynutit, aby vyhledávání zpracovalo soubory v pořadí, ve kterém se objeví, nastavením parametru file_order ve vaší funkci takto:

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
    ...

replace.file_order = 'spine'

file_order přijímá dvě hodnoty, spine a spine-reverse, které způsobují, že hledání zpracuje více souborů v pořadí, v jakém jsou uvedeny v knize, a to buď vpřed nebo zpět v uvedeném pořadí.

Vyvolání vaší funkce ještě jednou poté, co je nalezena poslední shoda

Někdy, jako ve výše uvedeném příkladu automatického generování obsahu, je užitečné vyvolat vaši funkci ještě jednou poté, co je nalezena poslední shoda. To můžete provést nastavením parametru ``call_after_last_match` ve vaší funkci takto:

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
    ...

replace.call_after_last_match = True

Připojování výstupu z funkce pro označený text

Při spuštění hledání a nahrazení na označeném textu je někdy užitečné připojit nějaký text na konec označeného textu. Můžete to udělat nastavením atributu append_final_output_to_marked na vaši funkci (pamatujte, že také musíte nastavit call_after_last_match) takto:

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

Potlačení dialogového okna výsledku při provádění vyhledávání na označeném textu

Můžete také potlačit dialogové okno výsledku (což může zpomalit opakovanou aplikaci hledání a nahrazení na mnoha blocích textu) nastavením atributu suppress_result_dialog na vaší funkci takto:

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
    ...

replace.suppress_result_dialog = True

Další příklady

Další užitečné příklady, které vytvořili uživatelé Calibre, můžete nalézt ve fóru Editoru Calibre.