Functie modus voor zoek & vervangen in de editor¶
De :guilabel: Zoek & vervangen tool in de editor ondersteunt een functiemodus. In deze modus kunt u reguliere expressies combineren (zie :doc: regexp) met willekeurig krachtige Python-functies om allerlei geavanceerde tekstverwerking uit te voeren.
In de standaard regexp modus voor zoeken en vervangen, geeft u zowel een reguliere expressie op om naar te zoeken als een sjabloon, om alle gevonden overeenkomsten te vervangen. In functiemodus geeft u in plaats van een vast sjabloon een willekeurige functie op in de Python programmeertaal. Hiermee kunt u veel dingen doen die niet mogelijk zijn met eenvoudige sjablonen.
Technieken voor het gebruik van de functie modus en de syntaxis worden beschreven aan de hand van voorbeelden, waarin u wordt uitgelegd hoe u functies kunt maken om steeds complexere taken uit te voeren.
Automatisch het hoofdlettergebruik van titels in documenten corrigeren¶
Hier zullen we gebruik maken van een van de ingebouwde functies in de editor om automatisch het hoofdlettergebruik van alle tekst binnen heading-tags naar elk woord met hoofdletters te veranderen:
Find expression: <([Hh][1-6])[^>]*>.+?</\1>
Voor de functie kiest u gewoon de ingebouwde functie Titeltekst (tags negeren). Deze functie verandert titels die er als volgt uitzien: <h1>some titLE</h1> in <h1>Some Title</h1>. Dit werkt zelfs als er andere HTML-tags in de kopteksttags staan.
Uw eerste aangepaste functie - koppeltekens slim maken¶
De echte kracht van de functiemodus komt voort uit de mogelijkheid om je eigen functies te creëren om tekst op willekeurige manieren te verwerken. De Smarten Punctuation-tool in de editor laat individuele koppeltekens ongemoeid, zodat je deze functie kunt gebruiken om ze te vervangen door em-dashes.
Om een nieuwe functie te maken, klik je gewoon op de knop Maken/bewerken om een nieuwe functie te maken en kopieer je de Python-code hieronder.
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
return match.group().replace('--', '—').replace('-', '—')
Elke aangepaste functie Zoeken en vervangen moet een unieke naam hebben en bestaan uit een Python-functie met de naam replace, die alle hierboven weergegeven argumenten accepteert. Voorlopig houden we ons niet bezig met alle verschillende argumenten voor de functie replace(). Concentreer je gewoon op het argument match. Dit vertegenwoordigt een overeenkomst bij het uitvoeren van een zoek- en vervangactie. De volledige documentatie is beschikbaar hier. match.group() retourneert eenvoudigweg alle overeenkomende tekst en het enige wat we doen is de koppeltekens in die tekst vervangen door em-streepjes, waarbij we eerst dubbele koppeltekens vervangen en daarna enkele koppeltekens.
Gebruik deze functie met de zoek reguliere expressie:
>[^<>]+<
En het vervangt alle koppeltekens met em-streepjes maar enkel in echte tekst en niet in HTML tag definities.
De kracht van functie modus - een spellingswoordenboek gebruiken om fout koppeltekengebruik te herstellen¶
E-boeken gecreëerd van scans van gedrukte boeken bevatten dikwijls fout koppeltekengebruik – woorden gesplitst aan het einde van een regel in d gedrukte tekst. We gaan een eenvoudige functie schrijven om zulke woorden automatisch te vinden en herstellen.
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
Gebruik deze functie met dezelfde zoek reguliere expressie als voorheen, namelijk:
>[^<>]+<
En het zal op magische wijze alle verkeerd gespelde woorden in de tekst van het boek corrigeren. De belangrijkste truc is om een van de handige extra argumenten voor de vervangingsfunctie te gebruiken, namelijk “woordenboeken”. Dit verwijst naar de woordenboeken die de editor zelf gebruikt om de tekst in het boek op spelling te controleren. Deze functie zoekt naar woorden die door een koppelteken worden gescheiden, verwijdert het koppelteken en controleert of het woordenboek het samengestelde woord herkent. Als dat het geval is, worden de oorspronkelijke woorden vervangen door het samengestelde woord zonder koppelteken.
Houd er rekening mee dat een beperking van deze techniek is dat deze alleen werkt voor eentalige boeken, omdat dictionaries.recognized() standaard de hoofdtaal van het boek gebruikt.
Automatische nummering secties¶
Nu gaan we iets anders bekijken. Stel dat uw HTML-bestand veel secties bevat, elk met een koptekst in een <h2> tag die eruitziet als <h2>Enige tekst</h2>. Je kunt een aangepaste functie maken die deze koppen automatisch nummert met opeenvolgende sectienummers, zodat ze eruit zien als <h2>1. Enige 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'
Gebruik het met de find expresse:
(?s)(<h2[^<>]*>)(.+?</h2>)
Plaats de cursor aan het begin van het bestand en klik op Alle vervangen.
Deze functie maakt gebruik van een ander handig extra argument van replace(): het argument number. Bij het uitvoeren van een Replace All wordt het getal automatisch verhoogd voor elke opeenvolgende overeenkomst.
Een andere nieuwe functie is het gebruik van de replace.file_order – instelling die voor 'spine' betekent dat als deze zoekopdracht draait op meerdere HTML files, de bestanden verwerkt worden in de volgorde waarin ze verschijnen in het book. Bekijk Kies bestandsvolgorde bij uitvoeren meerdere HTML bestanden voor details.
Maak automatische inhoudsopgave aan¶
Laten we tot slot iets ambitieuzer proberen. Stel dat uw boek kopteksten heeft in h1- en h2-tags die er als volgt uitzien: <h1 id="someid">Some Text</h1>. We gaan automatisch een HTML-inhoudsopgave genereren op basis van deze kopteksten. Maak de onderstaande aangepaste functie:
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'
En gebruik het met de find expressie:
<(h[12]) [^<>]* id=['"]([^'"]+)['"][^<>]*>([^<>]+)
Voer de zoekopdracht uit op Alle tekstbestanden en aan het einde van de zoekopdracht verschijnt er een venster met “Debug-uitvoer van uw functie” met de HTML-inhoudsopgave, klaar om te worden geplakt in toc.html.
De bovenstaande functie is uitgebreid van commentaar voorzien, dus deze zou gemakkelijk te volgen moeten zijn. De belangrijkste nieuwe functie is het gebruik van een ander nuttig extra argument voor de functie replace(), het object data. Het object data is een Python woordenboek dat blijft bestaan tussen alle opeenvolgende aanroepen van replace() tijdens een enkele Replace All-bewerking.
Een andere nieuwe functie is het gebruik van call_after_last_match – als je dat instelt op True in de functie replace(), betekent dit dat de editor replace() nog een keer extra aanroept nadat alle overeenkomsten zijn gevonden. Voor deze extra aanroep is het overeenkomstobject None.
Dit was slechts een demonstratie om u de kracht van de functiemodus te laten zien. Als u echt een inhoudsopgave wilt genereren op basis van de kopjes in uw boek, kunt u beter de speciale inhoudsopgavetool gebruiken in Tools → Table of Contents.
De API voor de functie modus¶
Alle functiemodusfuncties moeten Python-functies zijn met de naam replace en de volgende handtekening:
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
return a_string
Wanneer een zoek-/vervangactie wordt uitgevoerd, wordt voor elke gevonden overeenkomst de functie replace() aangeroepen, die de vervangende tekenreeks voor die overeenkomst moet retourneren. Als er geen vervangingen moeten worden uitgevoerd, moet deze match.group() retourneren, wat de oorspronkelijke tekenreeks is. De verschillende argumenten voor de functie replace() worden hieronder beschreven.
Het argument match¶
Het argument match vertegenwoordigt de momenteel gevonden overeenkomst. Het is een Python Match-object. De meest bruikbare methode is group(), die kan worden gebruikt om de overeenkomende tekst op te halen die overeenkomt met individuele capture-groepen in de reguliere zoekuitdrukking.
The number argument¶
Het argument number is het nummer van de huidige overeenkomst. Wanneer u Replace All uitvoert, zal elke opeenvolgende overeenkomst ervoor zorgen dat replace() wordt aangeroepen met een oplopend nummer. De eerste overeenkomst heeft nummer 1.
Het file_name argument¶
Dit is de bestandsnaam van het bestand waarin de huidige overeenkomst is gevonden. Bij het zoeken in gemarkeerde tekst is de bestandsnaam leeg. De bestandsnaam is in canonieke vorm, een pad ten opzichte van de root van het boek, waarbij / als padscheidingsteken wordt gebruikt.
Het metadata argument¶
Dit vertegenwoordigt de metadata van het huidige boek, zoals titel, auteurs, taal, enz. Het is een object van klasse calibre.ebooks.metadata.book.base.Metadata. Nuttige attributen zijn onder andere title, authors (een lijst met auteurs) en language (de taalcode).
Het dictionaries argument¶
Dit vertegenwoordigt de verzameling woordenboeken die worden gebruikt voor spellingcontrole van het huidige boek. De meest bruikbare methode is dictionaries.recognized(word), die True retourneert als het doorgegeven woord wordt herkend door het woordenboek voor de taal van het huidige boek.
Het data argument¶
Dit is een eenvoudige Python-woordenlijst. Wanneer u Alles vervangen uitvoert, zal elke opeenvolgende overeenkomst ervoor zorgen dat replace() wordt aangeroepen met dezelfde woordenlijst als gegevens. U kunt dit dus gebruiken om willekeurige gegevens op te slaan tussen aanroepen van replace() tijdens een Alles vervangen-bewerking.
Het functions argument¶
Het argument functions geeft u toegang tot alle andere door de gebruiker gedefinieerde functies. Dit is handig voor het hergebruik van code. U kunt hulpprogrammafuncties op één plek definiëren en ze in al uw andere functies hergebruiken. Stel bijvoorbeeld dat u een functie met de naam My Function als volgt maakt:
def utility():
# do something
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...
Dan, in een andere functie, hebt u toegang tot de utility() functie, zo:
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
utility = functions['My Function']['utility']
...
U kunt het functiesobject ook gebruiken om permanente gegevens op te slaan, die door andere functies opnieuw kunnen worden gebruikt. U kunt bijvoorbeeld één functie hebben die bij uitvoering met Replace All bepaalde gegevens verzamelt, en een andere functie die deze gegevens gebruikt wanneer deze daarna wordt uitgevoerd. Bekijk de volgende twee functies eens:
# 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']
...
Foutzoeken in uw functies¶
U kunt de functies die u maakt debuggen met behulp van de standaardfunctie print() van Python. De uitvoer van print wordt weergegeven in een pop-upvenster nadat het zoeken/vervangen is voltooid. Hierboven zag u een voorbeeld van het gebruik van print() om een volledige inhoudsopgave weer te geven.
Kies bestandsvolgorde bij uitvoeren meerdere HTML bestanden¶
Wanneer u een Replace all uitvoert op meerdere HTML-bestanden, hangt de volgorde waarin de bestanden worden verwerkt af van welke bestanden u hebt geopend om te bewerken. U kunt ervoor zorgen dat de zoekopdracht bestanden verwerkt in de volgorde waarin ze verschijnen door het attribuut file_order in te stellen op uw functie, zoals hier:
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...
replace.file_order = 'spine'
file_order accepteert twee waarden, spine en spine-reverse, waardoor de zoekopdracht meerdere bestanden verwerkt in de volgorde waarin ze in het boek voorkomen, respectievelijk voorwaarts of achterwaarts.
Je functie wordt nog een keer aangeroepen nadat de laatste wedstrijd is gevonden.¶
Soms, zoals in het voorbeeld van het automatisch genereren van een inhoudsopgave hierboven, is het handig om uw functie nog een keer aan te roepen nadat de laatste overeenkomst is gevonden. U kunt dit doen door het attribuut call_after_last_match in te stellen op uw functie, zoals hier:
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...
replace.call_after_last_match = True
De uitvoer van de functie toevoegen aan gemarkeerde tekst¶
Wanneer u zoeken en vervangen uitvoert op gemarkeerde tekst, is het soms handig om tekst toe te voegen aan het einde van de gemarkeerde tekst. U kunt dat doen door het kenmerk append_final_output_to_marked in te stellen op uw functie (let op: u moet ook call_after_last_match instellen), zoals hier:
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
Het resultaatvenster onderdrukken bij het zoeken in gemarkeerde tekst¶
U kunt ook het resultatendialoogvenster onderdrukken (wat het herhaaldelijk toepassen van een zoek-/vervangactie op veel tekstblokken kan vertragen) door het kenmerk suppress_result_dialog in uw functie in te stellen, zoals hier:
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...
replace.suppress_result_dialog = True
Meer voorbeelden¶
Meer nuttige voorbeelden, aangeleverd door Calibre-gebruikers, zijn te vinden op het Calibre E-book editor forum.
