Mode fonction pour Rechercher & remplacer dans l’Éditeur

L’outil Rechercher & remplacer dans l’éditeur supporte un mode fonction. Dans ce mode, vous pouvez combiner des expressions régulières (voir Tout à propos de l’utilisation des expressions régulières dans calibre) avec de puissantes fonctions Python pour faire toutes sortes de traitements avancés de texte.

Dans le mode standard regexp pour rechercher et remplacer, vous spécifiez tant une expression régulière à rechercher qu’un modèle qui est utilisé pour remplacer toutes les correspondances trouvées. Dans le mode fonction, à la place d’utiliser un modèle fixe, vous spécifiez une fonction quelconque, dans le langage de programmation Python. Ceci vous permet de faire beaucoup de choses qui ne sont pas possibles avec de simples modèles.

Les techniques pour utiliser le mode fonction et la syntaxe seront décrites à l’aide d’exemples, vous montrant comment créer des fonctions pour effectuer progressivement des tâches plus complexes.

Le mode Fonction

Réparer automatiquement la casse des titres dans le document

Ici , nous utiliserons l’une des fonctions intégrée dans l’éditeur pour changer automatiquement la casse de tous le texte à l’intérieur des balises titre en casse titre

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

Pour la fonction, choisissez simplement la fonction intégrée Texte en casse de titre(ignorer les balises). Celle-ci changera les titres qui ressemblent à : <h1>certains TITRES</h1> en <h1>Certains Titres</h1>. Elle fonctionnera même s’il y a d’autres balises HTML dans les balises de titre.

Votre première fonction personnalisée - les traits d’union d’embellissement

La vraie puissance du mode fonction vient du fait de pouvoir créer vos propres fonctions pour traiter le texte de manières quelconques. L’outil de Ponctuation Intelligente dans l’éditeur laisse les traits d’union individuels de côté, vous pouvez employer cette fonction pour les remplacer par des tirets fins.

Pour créer une nouvelle fonction, cliquez simplement le bouton Créer/Éditer pour créer une nouvelle fonction et copiez le code Python qui se trouve ci-dessous.

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

Chaque fonction personnalisée Rechercher & remplacer doit avoir un nom unique et consister en une fonction Python nommée replace, qui accepte tous les arguments affichés ci-dessus. Pour le moment, nous ne nous inquiéterons pas à propos de tous les différents arguments de la fonction replace(). Focalisons-nous uniquement sur l’argument match. Il représente une correspondance lors de l’exécution d’un rechercher et remplacer. Sa documentation complète est disponible ici. match.group() renvoie simplement tout le texte correspondant et tout ce que nous faisons est de remplacer les traits d’union dans ce texte par des tirets fins, en remplaçant d’abord les doubles traits d’union et ensuite les traits d’union seuls.

Utilisez cette fonction avec l’expression régulière de recherche

>[^<>]+<

Et il remplacera tous les traits d’union par des tirets fins, mais seulement dans le texte actuel et non dans les définitions de balises HTML.

La force du mode fonction - utiliser un dictionnaire orthographique pour réparer les mots aux mauvais traits d’union

Souvent les livres numériques créés à partir de scans de livres imprimés contiennent des mots avec de mauvais traits d’union – les mots qui sont divisés à la fin de la ligne de la page imprimée. Nous écrirons une fonction simple pour trouver et réparer automatiquement de tels mots.

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

Utilisez cette fonction avec la même expression de recherche que précédemment, à savoir

>[^<>]+<

Et il réparera magiquement tous les mots avec de mauvais traits d’union dans le texte du livre. L’astuce principale est d’utiliser l’un des utiles arguments supplémentaires de la fonction de remplacement, dictionaries. Celui-ci se réfère aux dictionnaires que l’éditeur utilise lui-même pour vérifier orthographiquement le texte dans le livre. Ce que fait cette fonction est de regarder aux mots séparés par un trait d’union, supprimer le trait d’union et vérifier si le dictionnaire reconnait le mot composé, s’il le fait, les mots originaux sont remplacés par le mot composé libre du trait d’union.

Notez qu’une limitation à cette technique est qu’elle ne fonctionnera uniquement qu’avec les livres unilingues, car, par défaut, dictionaries.recognized() utilise la langue principale du livre.

Auto numérotation des sections

Maintenant nous allons voir quelque chose d’un peu différent. Supposons que votre fichier HTML ait beaucoup de sections, chacune avec un titre dans une balise <h2> ressemblant à <h2>Un certain texte</h2>. Vous pouvez créer une fonction personnalisée qui numérotera automatiquement ces titres avec des numéros de section consécutifs, afin qu’elles ressemblent à <h2>1. Un certain texte</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'

Utilisez la avec l’expression de recherche

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

Placez le curseur en haut du fichier et cliquez Remplacez tout

Cette fonction utilise l’un des utiles arguments supplémentaires pour replace() l’argument ``number`. Lorsque vous faites un Remplacer Tout le nombre est incrémenté automatiquement pour chaque correspondante successive .

Une autre nouvelle fonctionnalité est l’utilisation de replace.file_order` – régler cela à spine signifie que si cette recherche est exécutée sur de multiples fichiers HTML, ces fichiers seront traités dans l’ordre dans lequel ils apparaissent dans le livre. Voir Choisissez l’ordre de fichier lors de l’exécution sur de multiples fichiers HTML pour des détails.

Auto créer une Table des Matières

Finalement, essayons quelque chose d’un peu plus ambitieux. Supposons que votre livre à des titres dans des balises h1 et h2 qui ressemblent à <h1 id= »someid »>Un certain texte</h1>``. Nous auto générerons une Table des Matières HTML basée sur ces titres. Créez la fonction personnalisée suivante :

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'

Et utilisez là pour trouver l’expression:

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

Lancez la recherche sur Tous les fichiers textes et à la fin de la recherche, une fenêtre apparaîtra avec « Résultat de débogage pour votre fonction » qui contiendra la Table des Matières HTML, prête à être collée dans toc.html.

La fonction ci-dessus est fortement commentée, aussi elle devrait être facile à suivre. La nouvelle fonctionnalité clé est l’utilisation d’un autre argument supplémentaire utile à la fonction replace(), l’objet data. L’objet data est un dictionnaire Python qui persiste entre les invocations successives de replace() pendant une seule opération Remplacer Tout.

Une autre nouvelle fonctionnalité est l’utilisation de call_after_last_match – paramétrer cela à True sur la fonction replace() signifie que l’éditeur appellera replace() une fois de plus après que toutes les correspondances aient été trouvées. Pour cet appel supplémentaire, l’objet correspondant sera``None``

Ceci était juste une démonstration pour vous montrer la puissance du mode fonction. Si vous avez réellement besoin de générer une Table des Matières à partir des titres dans votre livre, vous aurez mieux en utilisant l’outil Table des Matières dédié dans Tools → Table des Matières.

L’API pour le mode fonction

Toutes les fonctions du mode fonction doivent être des fonctions Python nommées replace, avec la signature suivante

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

Quand un rechercher/remplacer est exécuté, pour chaque correspondance qui est trouvée, la fonction replace() sera appelée, elle doit retourner la chaîne de remplacement pour cette correspondance. Si aucuns remplacements ne doivent être effectués, elle devrait retourner match.group() qui est la chaîne originale. Les divers arguments de la fonction replace() sont documentées ci-dessous.

L’argument match

L’argument match représente la correspondance actuellement trouvée. C’est un objet Python Match. Sa méthode la plus utile est group() qui peut être utilisée pour récupérer le texte apparié correspondant à la capture de groupes individuels dans l’expression régulière de recherche.

L’argument number

L’argument number est le nombre de l’actuelle correspondance. Lorsque vous exécutez Remplacer Tout, chaque correspondance successive entraînera replace() à être appelé avec un nombre incrémenté. La première correspondance porte le numéro 1.

L’argument file_name

Ceci est le nom du fichier dans lequel la correspondance actuelle a été trouvée. Lors d’une recherche à l’intérieur d’un texte marqué, le file_name est vide. Le file_name est de forme reconnue, une chemin relatif à la racine du livre, utilisant / comme séparateur de chemin.

L’argument metadata

Ceci représente les métadonnées du livre actuel, comme le titre, les auteurs, la langue, etc. C’est un objet de la classe calibre.ebooks.metadata.book.base.Metadata. Les attributs utiles incluent, title, authors (une liste d’auteur) et language (le code de langue).

L’argument dictionaries

Ceci représente la collection de dictionnaires utilisés pour la vérification orthographique du livre actuel. Sa méthode la plus utile est dictionaries.recognized(word) qui renverra ``True```si le mot analysé est reconnu par le dictionnaire de la langue actuelle du livre.

L’argument data

Ceci est un un simple dictionnaire Python. Lorsque vous exécutez Remplacer tout, toutes les correspondances suivantes entraîneront un appel de replace() avec le même dictionnaire en tant que données. Vous pouvez donc l’utiliser pour stocker arbitrairement des données entre des invocations de replace() pendant une opération Remplacer tout.

L’argument functions

L’argument functions vous donne accès à toutes les autres fonctions définies par l’utilisateur. Ceci est utile pour ré-utiliser du code. Vous pouvez définir les fonctions utilitaires à un seul endroit et les ré-utiliser dans toutes vos autres fonctions. Par exemple, supposons la création d’une fonction nommée My Function comme ceci :

def utility():
   # do something

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

Alors, dans une autre fonction, vous pouvez accéder à la fonction utility() comme ceci :

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

Vous pouvez aussi utiliser les objets fonctions pour stocker des données persistantes, qui peuvent être ré-utilisées par d’autres fonctions. Par exemple, vous pourriez avoir une fonction qui lorsqu’elle s’exécute avec Remplacer Tout collecte des données et une autre fonction qui les utilisera lorsqu’elle sera lancée plus tard. Considérez les deux fonctions suivantes :

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

Dépanner vos fonctions

Vous pouvez dépanner les fonctions que vous créez en utilisant la fonction standard print() de Python. Le résultat de print sera affiché dans une fenêtre popup après que le Rechercher/remplacer soit terminé. Nous avons vu un exemple de l’utilisation de print() pour sortir une table des matières entière plus haut.

Choisissez l’ordre de fichier lors de l’exécution sur de multiples fichiers HTML

Lorsque vous lancez Remplacer tout sur de multiples fichiers HTML, l’ordre dans lequel les fichiers sont traités dépend de quels fichiers vous avez ouvert pour l’édition. Vous pouvez forcer la recherche à traiter les fichiers dans l’ordre dans lequel ils apparaissent en paramétrant l’attribut file_order de votre fonction, comme ceci :

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

replace.file_order = 'spine'

file_order accepte deux valeurs, spine et spine-reverse qui entraîne le traitement de multiples fichiers dans l’ordre où ils apparaissent dans le livre, soit descendant soit ascendant, respectivement.

Appelle votre fonction une dernière fois après que la dernière correspondance ait été trouvée.

Parfois, comme dans l’exemple de la table des matières auto-générée ci-dessus, il est utile que votre fonction soit appelée une nouvelle fois après que la dernière correspondance ait été trouvée. Vous pouvez faire ceci en paramétrant l’attribut call_after_last_match dans votre fonction, comme ceci :

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

replace.call_after_last_match = True

Ajouter le résultat de la fonction au texte marqué

Lorsque vous effectuez un rechercher et remplacer sur du texte marqué, il est quelquefois utile d’ajouter du texte à la fin du texte marqué. Vous pouvez faire cela en paramétrant l’attribut append_final_output_to_marked sur votre fonction (notez que vous aurez également besoin de paramétrer ``call_after_last_match`), comme ceci :

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

Suppression du dialogue résultant lorsque des recherches sont effectuées sur du texte marqué

Vous pouvez également supprimer le résultat du dialogue (qui peut ralentir l’application répétée d’un rechercher/remplacer sur beaucoup de blocs de texte) en paramétrant l’attribut suppress_result_dialog sur votre fonction, comme ceci :

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

replace.suppress_result_dialog = True

Plus d’exemples

Plus d’exemples utiles, fournis par des utilisateurs de calibre, peuvent être trouvés dans le Forum Éditeur de livre numérique de calibre.