Alles over het gebruik van reguliere expressies in calibre

Reguliere expressies zijn functies die op veel plaatsen van calibre worden gebruikt om geavanceerde manipulatie van e-book inhoud en metadata uit te voeren. Deze handleiding is een voorzichtige inleiding om u op weg te helpen met het gebruik van reguliere uitdrukkingen in calibre.

Allereerst een waarschuwing en bemoediging

Dit gaat onvermijdelijk enigszins technisch worden, - reguliere expressies zijn immers een technisch hulpmiddel voor het doen van technische zaken. Ik zal wat jargon en concepten moeten gebruiken die ingewikkeld of ingewikkeld lijken. Ik ga proberen die concepten zo duidelijk mogelijk uit te leggen, maar kan echt niet zonder ze helemaal te gebruiken. Dat gezegd zijnde, wees niet ontmoedigd door enig jargon, omdat ik heb geprobeerd om alles nieuw uit te leggen. En terwijl reguliere expressies zelf misschien een mysterieuze, zwarte magie lijken (of, om prozaïscher te zijn, een willekeurige reeks mumbo-jumbo letters en tekens), beloof ik dat ze niet zo gecompliceerd zijn. Zelfs degenen die reguliere uitdrukkingen echt goed begrijpen, hebben moeite met het lezen van de meer gecompliceerde uitdrukkingen, maar het schrijven ervan is niet zo moeilijk - je construeert de uitdrukking stap voor stap. Dus, neem een stap en volg me naar het konijnenhol.

Waar in calibre kan je reguliere expressies gebruiken?

Er zijn een paar plaatsen waar calibre reguliere expressies gebruikt. Daar is de :guilabel :Zoeken & vervangen in conversie-opties, detectie van metadata uit bestandsnamen in de importinstellingen en Zoeken & vervangen bij het bewerken van de metadata van boeken in bulk. De calibre boek editor kan ook reguliere expressies gebruiken in zijn Zoek en vervang functie. Tenslotte kan u reguliere expressies gebruiken bij doorzoeken van de calibre boekenlijst en bij zoeken in de calibre e-boekviewer.

Wat is een reguliere expressie eigenlijk?

Een reguliere expressie is een manier om een string set te beschrijven. Een enkele reguliere expressie kan overeenkomen met een aantal verschillende strings. Dit maakt reguliere expressie zo krachtig – het is een beknopte manier om een mogelijk groot aantal variaties te beschrijven.

Notitie

Ik gebruik string hier op de manier dat het gebruikt wordt in programmeertalen: een reeks van één of meerdere karakters, inbegrepen letters, cijfers, interpunctie en zogenaamde witruimte (regeleindes, tabs, enz.). Merk a.u.b. op dat in het algemeen hoofd- en kleine letters niet als identiek beschouwd worden, “a” is dus iets anders als “A” en zo verder. In calibre zijn reguliere expressies hoofdletterongevoelig in de Zoekbalk maar niet in de conversie-opties. Er is een manier om elke reguliere expressie hoofdletterongevoelig te maken maar dat bespreken we later. Het wordt gecompliceerd omdat reguliere expressies variaties toelaten in de strings waar ze mee overeen komen, dus een expressie kan met verschillende strings overeen komen, reden dat ze gebruikt worden. Meer daarover later.

Iets meer uitleg graag?

Wel, daarom zijn we hier. Ten eerste, dit is het belangrijkste concept in reguliere expressies: Een string is zelf een reguliere expressie die met zichzelf overeenkomt. Dat wil zeggen, als ik met de string "Hello, World!" wou overeenkomen met een reguliere expressie, de te gebruiken reguliere expressie zou Hello, World! zijn. Ja, het is echt zo simpel. U zal nochtans opmerken dat dit enkel overeenkomt met de exacte string "Hello, World!", niet met bv. "Hello, wOrld!" of "hello, world!" of elke andere variatie.

Dat klinkt niet heel moeilijk. Ga door.

Nu komt het echt leuke gedeelte. Herinnert u zich dat ik zei dat reguliere expressies met meerdere strings kunnen overeenkomen? Nu wordt het een beetje ingewikkelder. Neem, als een iets meer praktische oefening, dat het e-boek dat u wou converteren een gemene voettekst had om de pagina’s te tellen, zoals “Pagina 5 van 423”. Duidelijk dat het pagina nummer zal stijgen van 1 tot 423, dus u zou met 423 verschillende strings moeten overeenkomen, klopt? Fout, eigenlijk: reguliere expressies laten u karaktersets definiëren die overeenkomen: Om een set te definiëren, zet u alle karakters die u in de set wilt tussen vierkante haken. Dus, bv., de set [abc] komt overeen met ofwel karakter “a”, “b” of “c”. Sets komen altijd maar overeen met een van de karakters in de set. Ze “begrijpen” karakterbereiken, dat wil zeggen, als u met alle kleine letters wil overeenkomen, gebruikt u de set [a-z], voor hoofd- en kleine letters gebruikt u [a-zA-Z] en zo voort. Bent u mee? Dus, duidelijk, gebruik makend van de expressie Page [0-9] of 423 komt u overeen met de eerste 9 pagina’s, daarbij de vereiste expressies tot drie reducerend: De tweede expressie Page [0-9][0-9] of 423  komt overeen met alle tweecijferige paginanummers en ik ben er zeker van dat u kan raden hoe de derde expressie eruit zou zien. Ja, vooruit, schrijf het op.

Prachtig! Nu begint het ergens op te lijken!

Ik hoopte al dat u dat zou zeggen. Maar hou u vast, het wordt nog beter! We zagen net dat met sets we konden zoeken naar één of meerdere karakters ineens. Maar men kan zelfs een karakter of set herhalen en zo het aantal expressies nodig voor het paginanummer voorbeeld boven verminderen tot één. Ja, ÉÉN! Spannend? Absoluut! Zo werkt het: Bepaalde zogenaamde speciale karakters, “+”, “?” en “*”, herhalen het voorafgaande element. (Element betekent ofwel een enkel karakter, ofwel een karakterset, ofwel een escape sequentie ofwel een groep (we leren later meer over die twee laatste)- kortom, elke eenheid in een reguliere expressie). Deze karakters heten wildcards of quantifiers. Nauwkeuriger, “?” komt overeen met 0 of 1 van het voorafgaande element, “*” komt overeen met 0 of meer van het voorafgaande element en “+” komt overeen met 1 of meer van het voorafgaande element. Een paar voorbeelden: De expressie a? komt overeen met ofwel “” (de lege string, niet echt bruikbaar in dit geval) ofwel “a”, de expressie a* komt overeen met “”, “a”, “aa” of elk aantal a’s op een rij, en, tenslotte, de expressie a+ komt overeen met “a”, “aa” of elk aantal a’s op een rij (Merk op: het komt niet overeen met de lege string!). Zelfde geval met sets: De expressie [0-9]+ komt overeen met elk bestaand geheel getal! Ik weet wat u denkt en u hebt gelijk: Als u dat in het geval boven met overeenkomende pagina nummers gebruikt, zou dat niet die enkele expressie zijn die met alle paginanummers overeen komt? Ja, de expressie Page [0-9]+ of 423 komt overeen met elk paginanummer in dat boek!

Notitie

Iets over die quantifiers: Ze proberen gewoonlijk met zoveel mogelijk tekst overeen te komen, voorzichtig gebruiken dus. Dit wordt “gulzig gedrag” genoemd - Ik ben er zeker van dat u dat snapt. Het wordt problematisch als u, bv. probeert met een tag overeen te komen. Beschouw bv. de string "<p class="calibre2">Title here</p>" en dat u met de openingstag wilt overeenkomen (het deel tussen het eerste paar punthaakjes, meer over tags later). Men zou denken dat de expressie <p.*> dat doet maar eigenlijk komt dat overeen met de hele string! (Het karakter “.” is nog een speciaal karakter dat met alles overeenkomt behalve een nieuwe ijn, dus de expressie .* komt overeen met elke regel die u zich kan inbeelden). I.p.d.v. gebruik <p.*?> wat de quantifier "*" niet-gulzig maakt. Die expressie komt enkel overeen met de openingstag, zoals bedoeld. Er is nog een manier om dit te bereiken: De expressie <p[^>]*> komt met dezelfde tag overeen - u ziet waarom na het volgende onderdeel. Merk gewoon op dat er dikwijls meerdere manieren zijn om een reguliere expressie te schrijven.

Nou, deze speciale karakters zijn erg netjes en zo maar wat als ik een overeenkomst met een punt of vraagteken wil?

U kan natuurlijk dit doen: Zet gewoon een backslash voor elk speciaal karakter en het wordt geïnterpreteerd als letterlijk dat karakter, zonder speciale betekenis. Dit paar backslashes gevolgd door een enkel karakter wordt een escape sequentie genoemd en een backslash voor een speciaal karakter zetten heet dat karakter escapen. Een escape sequentie wordt geïnterpreteerd als een enkel element. Er zijn natuurlijk escape sequenties die meer doen dan speciale karakters escapen, "\t" bv. betekent een tabulator. We zien later enkele escape sequenties. Oh, enne, wat betreft die speciale karakters: Beschouw elk karakter dat we bespreken in deze introductie als hebbende een zekere functie die speciaal kan zijn en dus een nood om te escapen als u het letterlijke karakter wilt hebben.

Wat zijn dan de meest nuttige sets?

Ik wist dat u het zou vragen. Enkele nuttige sets zijn [0-9] komt overeen met een enkel getal, [a-z] met een enkele kleine letter, [A-Z] met een enkele hoofdletter, [a-zA-Z] met een enkele letter en [a-zA-Z0-9] komt overeen met een enkele letter of cijfer.

\d

is gelijkwaardig aan [0-9]

\w

is gelijkwaardig aan [a-zA-Z0-9_]

\s

is gelijkwaardig aan elke spatie

Notitie

“Witruimte” is een term voor alles dat niet afgedrukt wordt. Deze karakters zijn onder andere spatie, tab, regelinvoerteken (LF), paginainvoerteken (FF), regeleinde (CR), niet-afbrekende spaties, enz.

Notitie

The upper and lower case sets may match both upper and lowercase if the setting to make searches case insensitive is enabled. Such settings are found, for instance in Preferences->Searching in calibre itself and on the Search panel in the calibre E-book viewer as well as the calibre Edit book tool.

Al laatste woord over sets, u kan een set ook definiëren als elk karakter behalve die in de set. U doet dat door het karakter "^" als allereerste karakter in de set te zetten. Dus, [^a] komt overeen met elk karakter behalve “a”. Dat heet de set aanvullen. Die verkorte escapereeksen die we eerder zagen kunnen ook aangevuld worden: "\D" betekent elk niet-numeriek karakter, equivalent dus met [^0-9]. De andere verkortingen kunnen aangevuld worden door, u raadde het, gebruik van de respectieve hoofdletter i.p.v. de kleine letter. Dus, terug naar het voorbeeld <p[^>]*> van de vorige sectie, u ziet nu dat de gebruikte karakterset probeert overeen te komen met elk karakter behalve een sluitend vierkant haakje.

Maar als ik een paar verschillende strings had die ik wilde laten overeenkomen worden dingen ingewikkeld?

Vrees niet, ‘t leven is nog altijd eenvoudig en goed. Overweeg dit voorbeeld: In het boek dat converteert staat “Titel” op elke oneven pagina en “Auteur” op elke even pagina. Ziet er goed uit in druk, niet? Maar in e-boeken is het vervelend. U kan hele expressies groeperen tussen aanhalingstekens en het karakter "|" laat overeenkomen met ofwel de expressie rechts ofwel de expressie links. Combineer deze en u bent klaar. Te snel voor u? Oké, om te beginnen groeperen we de expressies voor even en oneven pagina’s en krijgen (Title)(Author) als de twee benodigde expressies. Nu maken we het simpeler met gebruik van het pijp symbool ("|" wordt het pijp of sluissymbool genoemd): Als u de expressie (Title|Author) gebruikt krijgt u ofwel een overeenkomst voor “Titel” (op de oneven pagina’s) ofwel “Auteur” (op de even pagina’s). Wel, was dat niet gemakkelijk?

U kan natuurlijk het pijp symbool ook zonder groeperende haakjes gebruiken. Weet u nog dat ik zei dat quantifiers het element voor zich herhalen? Wel, het pijp symbool werkt enigszins anders: De expressie “Titel|Auteur” komt ook overeen met ofwel de string “Titel” ofwel de string “Auteur”, net zoals het voorbeeld boven met groeperen. Het pijp symbool selecteert tussen de volledige expressie ervoor en erna. Dus als u met de strings “Calibre” en “calibre” wou overeenkomen en enkel wou selecteren tussen de hoofd- en kleine letter “c”, moet u de expressie (c|C)alibre gebruiken, waar de groepering ervoor zorgt dat enkel de “c” wordt geselecteerd. Als u c|Calibre zou gebruiken, zou u een overeenkomst krijgen op de string “c” of op de string “Calibre”, wat niet is wat we wilden. Samengevat: Bij twijfel, gebruik groeperen met het pijp symbool.

U miste…

…wacht nog even, er is nog één, heel tof ding dat u kan doen met groepen. Als u een groep hebt die vroeger overeenkwam, kan u verder in de expressie verwijzingen naar die groep gebruiken: Groepen worden genummerd vanaf 1, en u verwijst ernaar door het getal van de groep waarnaar u wilt verwijzen te escapen, dus naar de vijfde groep wordt verwezen met \5. Dus, als u zocht naar ([^ ]+) \1 in de string “Test Test”, komt dat overeen met de hele string!

In het begin zei u dat er een manier was om een reguliere expressie hoofdletterongevoelig te maken?

Inderdaad, bedankt voor uw aandacht en om me eraan te herinneren. U kan calibre zeggen hoe u bepaalde dingen gedaan wilt hebben met vlaggen. U voegt vlaggen in in uw expressie via de speciale constructie (?vlaggen hier) waar u, natuurlijk, “vlaggen hier” vervangt door de specifieke vlag die u wilt. Om hoofdlettergebruik te negeren is de vlag i dus u voegt (?i) in in uw expressie. Dus, (?i)test komt overeen met “Test”, “tEst”, “TEst” en elke hoofd/kleine letter variatie ie u kunt bedenken.

Een andere nuttige vlag laat het punt overeenkomen met eender welk karakter, inbegrepen de newline, de vlag s. Als u meerdere vlaggen wil gebruiken in een expressie, zet ze gewoon in dezelfde verklaring: (?is) negeert hoofdlettergebruik en laat het punt met alles overeenkomen. Het maakt niet uit welke vlag u eerst zet, (?si) is gelijk aan het bovenstaande.

Ik denk dat ik deze reguliere expressies begin te begrijpen nu … hoe gebruik ik ze in calibre?

Omzettingen

Laten we beginnen met de conversie instellingen, wat echt handig is. In het Zoeken & vervangen deel kan u een regexp (afkorting voor reguliere expressie) ingeven die de string beschrijft die vervangen wordt tijdens de conversie. Het handige deel is de wizard. Klik op de toverstaf en u krijgt een voorbeeld van wat calibre “ziet” tijdens de conversie. Scrol naar de string die u wilt verwijderen, selecteer en kopieer hem, plak helm in het regexp veld boven in het venster. Als er variabele delen zijn, zoals pagina nummers of zo, gebruik daarvoor sets en quantifiers en terwijl u bezig bent, denk eraan speciale karakters te escapen, als er zijn. Klik op de Test knop en calibre accentueert de delen die het zou vervangen als u de regexp zou gebruiken. Wanneer u tevreden bent, klik op OK en converteer. Weer voorzichtig als uw conversie bron tags heeft zoals deze:

Maybe, but the cops feel like you do, Anita. What's one more dead vampire?
New laws don't change that. </p>
<p class="calibre4"> <b class="calibre2">Generated by ABC Amber LIT Conv
<a href="http://www.processtext.com/abclit.html" class="calibre3">erter,
http://www.processtext.com/abclit.html</a></b></p>
<p class="calibre4"> It had only been two years since Addison v. Clark.
The court case gave us a revised version of what life was

(schaamteloos geripped uit deze thread). U moet enkele tags verwijderen ook. In dit voorbeeld raad ik aan te beginnen met de tag <b class="calibre2">, nu moet u eindigen met de overeenkomende sluittag (openingstags zijn <tag>, sluittags zijn </tag>), welke gewoon de volgende </b> in dit geval. (Gebruik een goede HTML manual of vraag in het forum als u hierover twijfelt). De openingstag kan beschreven worden met <b.*?>, de sluittag met </b>, dus we kunnen alles tussen deze tags verwijderen met <b.*?>.*?</b>. Maar deze expressie gebruiken is een slecht idee want ze verwijdert alles omsloten door <b> tags (die, by the way, de omsloten tekst vet weergeeft), en waarschijnlijk verwijderen we delen va het boek op deze manier. In plaats daarvan, geef ook het begin van de omsloten string mee, de reguliere expressie wordt dan <b.*?>\s*Generated\s+by\s+ABC\s+Amber\s+LIT.*?</b> De \s met quantifiers is inbegrepen hier in plaats van expliciet gebruik van de spaties zoals in de string om eventuele variaties van de string op te vangen. Denk eraan te controleren wat calibre gaat verwijderen zodat u zeker bent dat u geen delen verwijdert die u wilt behouden als u een nieuwe expressie test. Als u maar één voorkomen test, kan u ergens anders in de tekst een mismatch over het hoofd zien. Merk ook op dat als per ongeluk meer of minder tags verwijdert dan u eigenlijk wou dat calibre de beschadigde code probeert te herstellen na het verwijderen.

Boeken toevoegen

Iets anders waar u reguliere expressies voor kan gebruiken is metadata uit bestandsnamen halen. U vindt deze functie in “Boeken toevoegen” in de instellingen. Er is hier een speciale functie: U kan veldnamen gebruiken voor metadata velden, bv. (?P<title>) geeft aan dat calibre dit deel van de string gebruikt als boektitel. De toegelaten veldnamen zijn opgelijst in de vensters, samen met een ander leuk testveld. Bv. Stel u wilt een hoop bestanden importeren genaamd zoals Classical Texts: The Divine Comedy by Dante Alighieri.mobi. (Dit is natuurlijk al in uw bibliotheek, we houden allemaal van klassieke Italiaanse poëzie) of Science Fiction epics: The Foundation Trilogy by Isaac Asimov.epub. Dit is duidelijk een naamgevingsschema waar calibre geen nuttige data kan uithalen - z’n standaard expressie om metadata te extraheren is (?P2.+) - (?P3[^_]+). Een reguliere expressie die hier werkt zou [a-zA-Z]+: (?P4.+) by (?P5.+) zijn. Merk a.u.b. op dat, in de groep voor het metadata veld, u expressies moet gebruiken die beschrijven waar het veld met overeenkomt. En merk ook op dat bij gebruik van het door calibre voorziene testveld, u de extensie moet toevoegen aan uw testbestandsnaam of u krijgt helemaal geen overeenkomsten, ook niet met een werkende expressie.

Massabewerking van metadata

The last part is regular expression Search and replace in metadata fields. You can access this by selecting multiple books in the library and using bulk metadata edit. Be very careful when using this last feature, as it can do Very Bad Things to your library! Doublecheck that your expressions do what you want them to using the test fields, and only mark the books you really want to change! In the regular expression search mode, you can search in one field, replace the text with something and even write the result into another field. A practical example: Say your library contained the books of Frank Herbert’s Dune series, named after the fashion Dune 1 - Dune, Dune 2 - Dune Messiah and so on. Now you want to get Dune into the series field. You can do that by searching for (.*?) \d+ - .* in the title field and replacing it with \1 in the series field. See what I did there? That’s a reference to the first group you’re replacing the series field with. Now that you have the series all set, you only need to do another search for .*? - in the title field and replace it with "" (an empty string), again in the title field, and your metadata is all neat and tidy. Isn’t that great? By the way, instead of replacing the entire field, you can also append or prepend to the field, so, if you wanted the book title to be prepended with series info, you could do that as well. As you by now have undoubtedly noticed, there’s a checkbox labeled Case sensitive, so you won’t have to use flags to select behaviour here.

Wel, tot zover de zeer korte inleiding tot reguliere expressies. Hopelijk heb ik u genoeg laten zien om u tenminste te laten beginnen en het mogelijk te maken om zelfstandig verder te leren - een goed vertrekpunt is de Python documentatie voor reguliere expressies.

Toch een laatste verwittiging: Reguliere expressies zijn krachtig maar ook foutgevoelig. calibre voorziet echt super testmogelijkheden om te zien of uw expressies doen wat u verwacht van hen. Gebruik ze. Probeer uzelf niet in de voet te schieten. (Dzjee, wat een prachtige uitdrukking…). Doet u het toch ondanks de waarschuwing (of in een ander lichaamsdeel), probeer ervan te leren.

Snelle referentie

Credits

Dank voor hulp met tips, correcties en dergelijke:

  • ldolse

  • kovidgoyal

  • chaley

  • dwanthny

  • kacir

  • Starson17

  • Orpheu

Voor meer over reguliere expressies, bekijk The Python User Manual. De actuele reguliere expressie bibliotheek gebruikt door calibre is: regex welke verscheidene nuttige verbeteringen ondersteunt bovenop de Python standaard bibliotheek.