Wszystko, co chcielibyście wiedzieć o wyrażeniach regularnych w calibre

Wyrażenia regularne są stosowane w wielu miejscach w calibre, umożliwiając skomplikowane nawet manipulacje zawartością i metadanymi książki. Ten podręcznik wprowadzi cię łagodnie w podstawy użycia wyrażeń regularnych w calibre.

Na początek ostrzeżenie i zachęta.

Niestety, będzie to trochę techniczna część - w końcu wyrażenia regularne są technicznym narzędziem do wykonywania technicznych zadań. Znajdzie się tu trochę żargonu i pojęć, które mogą wydawać się skomplikowane i zawiłe. Postaram się je wyjaśnić jak najlepiej jak mogę, ale, wierzcie mi, bez tego się nie da. Mimo wszystko nie zniechęcaj się żargonem, postaram się wyjaśniać wszystkie nowe pojęcia. I choć same wyrażenia regularne mogą wydawać się wiedzą tajemną, czarną magią (albo, bardziej prozaicznie, losowym ciągiem znaków), obiecuję, że nie będzie to aż tak skomplikowane. Nawet ci, którzy rozumieją wyrażenia regularne miewają kłopoty z odczytaniem tych bardziej skomplikowanych, ale pisanie ich nie jest takie trudne - konstruujesz wyrażenie krok po kroku. Tak więc - zrób pierwszy krok i - podążaj za białym królikiem…

Gdzie w calibre można użyć wyrażeń regularnych?

There are a few places calibre uses regular expressions. There’s the Search & replace in conversion options, metadata detection from filenames in the import settings and Search & replace when editing the metadata of books in bulk. The calibre book editor can also use regular expressions in its Search and replace feature. Finally, you can use regular expressions when searching the calibre book list and when searching inside the calibre E-book viewer.

Czym właściwie jest wyrażenie regularne?

Wyrażenie regularne jest sposobem na opisanie ciągu znaków. Pojedyncze wyrażenie może pasować do wielu różnych ciągów. To właśnie czyni wyrażenia regularne tak potężnym – to zwięzły sposób na opisanie potencjalnie dużej liczby różnych wersji.

Informacja

I’m using string here in the sense it is used in programming languages: a string of one or more characters, characters including actual characters, numbers, punctuation and so-called whitespace (linebreaks, tabulators etc.). Please note that generally, uppercase and lowercase characters are not considered the same, thus „a” being a different character from „A” and so forth. In calibre, regular expressions are case insensitive in the Search bar, but not in the conversion options. There’s a way to make every regular expression case insensitive, but we’ll discuss that later. It gets complicated because regular expressions allow for variations in the strings it matches, so one expression can match multiple strings, which is why people bother using them at all. More on that in a bit.

Możesz to wyjaśnić?

Otóż dlatego właśnie tu jesteśmy. Po pierwsze najważniejsza koncepcja wyrażeń regularnych: Ciąg znaków jest wyrażeniem regularnym, pasującym do samego siebie. To znaczy, jeśli chcę dopasować ciąg "Hello, World!" przy użyciu wyrażeń regularnych, to najprostsze z nich to Hello, World!. Owszem, to jest takie proste. Zauważysz zapewne, że wyrażenie to pasuje wyłącznie do "Hello, World!", a nie na przykład "Hello, wOrld!" czy "hello, world!" ani żadnej innej odmiany.

Nie brzmi źle. Co dalej?

Next is the beginning of the really good stuff. Remember where I said that regular expressions can match multiple strings? This is where it gets a little more complicated. Say, as a somewhat more practical exercise, the e-book you wanted to convert had a nasty footer counting the pages, like „Page 5 of 423”. Obviously the page number would rise from 1 to 423, thus you’d have to match 423 different strings, right? Wrong, actually: regular expressions allow you to define sets of characters that are matched: To define a set, you put all the characters you want to be in the set into square brackets. So, for example, the set [abc] would match either the character „a”, „b” or „c”. Sets will always only match one of the characters in the set. They „understand” character ranges, that is, if you wanted to match all the lower case characters, you’d use the set [a-z] for lower- and uppercase characters you’d use [a-zA-Z] and so on. Got the idea? So, obviously, using the expression Page [0-9] of 423 you’d be able to match the first 9 pages, thus reducing the expressions needed to three: The second expression Page [0-9][0-9] of 423 would match all two-digit page numbers, and I’m sure you can guess what the third expression would look like. Yes, go ahead. Write it down.

Hej, nieźle! To zaczyna mieć sens!

I was hoping you’d say that. But brace yourself, now it gets even better! We just saw that using sets, we could match one of several characters at once. But you can even repeat a character or set, reducing the number of expressions needed to handle the above page number example to one. Yes, ONE! Excited? You should be! It works like this: Some so-called special characters, „+”, „?” and „*”, repeat the single element preceding them. (Element means either a single character, a character set, an escape sequence or a group (we’ll learn about those last two later)- in short, any single entity in a regular expression). These characters are called wildcards or quantifiers. To be more precise, „?” matches 0 or 1 of the preceding element, „*” matches 0 or more of the preceding element and „+” matches 1 or more of the preceding element. A few examples: The expression a? would match either „” (which is the empty string, not strictly useful in this case) or „a”, the expression a* would match „”, „a”, „aa” or any number of a’s in a row, and, finally, the expression a+ would match „a”, „aa” or any number of a’s in a row (Note: it wouldn’t match the empty string!). Same deal for sets: The expression [0-9]+ would match every integer number there is! I know what you’re thinking, and you’re right: If you use that in the above case of matching page numbers, wouldn’t that be the single one expression to match all the page numbers? Yes, the expression Page [0-9]+ of 423 would match every page number in that book!

Informacja

A note on these quantifiers: They generally try to match as much text as possible, so be careful when using them. This is called „greedy behaviour”- I’m sure you get why. It gets problematic when you, say, try to match a tag. Consider, for example, the string "<p class="calibre2">Title here</p>" and let’s say you’d want to match the opening tag (the part between the first pair of angle brackets, a little more on tags later). You’d think that the expression <p.*> would match that tag, but actually, it matches the whole string! (The character „.” is another special character. It matches anything except linebreaks, so, basically, the expression .* would match any single line you can think of). Instead, try using <p.*?> which makes the quantifier "*" non-greedy. That expression would only match the first opening tag, as intended. There’s actually another way to accomplish this: The expression <p[^>]*> will match that same opening tag- you’ll see why after the next section. Just note that there quite frequently is more than one way to write a regular expression.

No tak, te znaki specjalne są bardzo fajne i w ogóle, ale co jeśli chcemy dopasować kropkę albo znak zapytania?

Możesz oczywiście zrobić tak: poprzedź znak specjalny backslashem i będzie on interpretowany jak każdy inny znak, bez specjalnego znaczenia. Taka para backslasha i znaku specjalnego jest nazywana sekwencją unikową, a umieszczanie backslasha przed znakiem to maskowanie znaków. Sekwencja unikowa jest interpretowana jak jeden element. Rzecz jasna są sekwencje unikowe, które mają znaczenie inne niż tylko maskowanie znaków specjalnych. Na przykład "\t" oznacza tabulator. Więcej o sekwencjach unikowych powiemy później. A przy okazji, co do znaków specjalnych: potraktuj te znaki, o których mowa jako znaki mające specjalne funkcje i tym samym konieczne jest maskowanie jeśli chcesz uzyskać literalnie konkretny znak.

A jakie są najbardziej przydatne zestawy?

Knew you’d ask. Some useful sets are [0-9] matching a single number, [a-z] matching a single lowercase letter, [A-Z] matching a single uppercase letter, [a-zA-Z] matching a single letter and [a-zA-Z0-9] matching a single letter or number. You can also use an escape sequence as shorthand:

\d

is equivalent to [0-9]

\w

is equivalent to [a-zA-Z0-9_]

\s

is equivalent to any whitespace

Informacja

„Whitespace” is a term for anything that won’t be printed. These characters include space, tabulator, line feed, form feed, carriage return, non-breaking spaces, etc.

Informacja

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.

Na koniec informacji o zestawach - możesz również zdefiniować wszystkie znaki oprócz tych ujętych w zestawie. Robi się to poprzez dodanie znaku "^" jako pierwszego znaku w zestawie. Tak więc [^a] dopasuje każdy znak oprócz „a”. Nazywane jest to uzupełnianiem zestawu. Sekwencje unikowe, o których pisaliśmy wcześniej również mogą być w ten sposób uzupełniane: "\D" oznacza dowolny znak nienumeryczny, jest więc ekwiwalentem [^0-9]. Inne skróty, jak się zapewne spodziewasz, mogą również być uzupełniane poprzez wielką literę zamiast małej. Powracając do przykładu <p[^>]*> z poprzedniego rozdziału rozumiesz już, że w tym przypadku zestaw jest używany do dopasowania dowolnego znaku oprócz zamykającego nawiasu ostrego.

A gdybym miał kilka różnych ciągów, które chciałbym dopasować, to czy byłoby to skomplikowane?

Fear not, life still is good and easy. Consider this example: The book you’re converting has „Title” written on every odd page and „Author” written on every even page. Looks great in print, right? But in e-books, it’s annoying. You can group whole expressions in normal parentheses, and the character "|" will let you match either the expression to its right or the one to its left. Combine those and you’re done. Too fast for you? Okay, first off, we group the expressions for odd and even pages, thus getting (Title)(Author) as our two needed expressions. Now we make things simpler by using the vertical bar ("|" is called the vertical bar character): If you use the expression (Title|Author) you’ll either get a match for „Title” (on the odd pages) or you’d match „Author” (on the even pages). Well, wasn’t that easy?

Możesz oczywiście użyć pionowej kreski bez grupujących nawiasów. Pamiętasz, że kwantyfikatory powtarzają poprzedzający je znak? Pionowa kreska działa nieco inaczej: wyrażenie „Tytuł|Autor” także dopasuje ciąg „Tytuł” albo ciąg „Autor”, tak jak w poprzednim przykładzie, gdzie użyliśmy nawiasów. Pionowa kreska obejmuje swoim działaniem całe wyrażenie przed i za nią. Tak więc jeśli chcesz odnaleźć ciąg „Calibre” i „calibre”, wybierając pomiędzy małym a wielkim „C”, musisz użyć wyrażenia (c|C)alibre, w którym grupowanie oznacza, że wybór dotyczy wyłącznie „c”. Gdybyś użył c|Calibre to dopasowany zostałby ciąg „c” albo ciąg „Calibre”, co jest nieprawidłowe. W skrócie: jeśli masz wątpliwości, grupuj wyrażenia, jeśli używasz pionowej kreski.

Zapomniałem…

…moment, jest jeszcze jedna, naprawdę użyteczna funkcja grup. Jeśli w wyrażeniu jest grupa, która została dopasowana, możesz się do niej odnieść później w tym wyrażeniu: grupy są numerowane, począwszy od 1, możesz użyć tych numerów, poprzedzając je backslashem, tak więc piąta grupa będzie oznaczona przez \5. Jeśli więc szukasz ([^ ]+)\1 otrzymasz cały ciąg!

Na początku powiedziałeś, że w wyrażeniach regularnych wielkość liter może nie mieć znaczenia?

Yes, I did, thanks for paying attention and reminding me. You can tell calibre how you want certain things handled by using something called flags. You include flags in your expression by using the special construct (?flags go here) where, obviously, you’d replace „flags go here” with the specific flags you want. For ignoring case, the flag is i, thus you include (?i) in your expression. Thus, (?i)test would match „Test”, „tEst”, „TEst” and any case variation you could think of.

Another useful flag lets the dot match any character at all, including the newline, the flag s. If you want to use multiple flags in an expression, just put them in the same statement: (?is) would ignore case and make the dot match all. It doesn’t matter which flag you state first, (?si) would be equivalent to the above.

Zaczynam chyba rozumieć, o co chodzi w wyrażeniach regularnych, a teraz… jak ich użyć w calibre?

Konwertowanie

Zacznijmy od ustawień konwertowania, co jest dość przyjemne. W części :guilabel:„Znajdź i zamień” możesz wprowadzić wyrażenie regularne opisujące napis, który zostanie podmieniony w trakcie konwertowania książki. Tu z pomocą przychodzi kreator. Naciśnij przycisk kreatora, a zobaczysz podgląd tego, jak calibre „widzi” książkę podczas konwertowania. Przewiń tekst do napisu, który chcesz usunąć, zaznacz go, skopiuj i wklej do pola wyrażenia regularnego na górze okna. Jeśli napis zawiera części zmienne, takie jak numery stron itp., możesz użyć zbiorów i kwantyfikatorów, by je wychwycić. Skoro już przy tym jesteśmy, pamiętaj, by poprzedzić jakiekolwiek znaki specjalne znakiem modyfikacji (tj. odwrotnym ukośnikiem). Naciśnij przycisk :guilabel:„Test”, a calibre wyróżni części, które zastąpiłby podczas konwertowania. Kiedy już będziesz gotowy, naciśnij OK, by rozpocząć konwertowanie. Pamiętaj, że jeżeli źródło konwertowania zawiera znaczniki, jak w poniższym przykładzie:

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

(shamelessly ripped out of this thread). You’d have to remove some of the tags as well. In this example, I’d recommend beginning with the tag <b class="calibre2">, now you have to end with the corresponding closing tag (opening tags are <tag>, closing tags are </tag>), which is simply the next </b> in this case. (Refer to a good HTML manual or ask in the forum if you are unclear on this point). The opening tag can be described using <b.*?>, the closing tag using </b>, thus we could remove everything between those tags using <b.*?>.*?</b>. But using this expression would be a bad idea, because it removes everything enclosed by <b>- tags (which, by the way, render the enclosed text in bold print), and it’s a fair bet that we’ll remove portions of the book in this way. Instead, include the beginning of the enclosed string as well, making the regular expression <b.*?>\s*Generated\s+by\s+ABC\s+Amber\s+LIT.*?</b> The \s with quantifiers are included here instead of explicitly using the spaces as seen in the string to catch any variations of the string that might occur. Remember to check what calibre will remove to make sure you don’t remove any portions you want to keep if you test a new expression. If you only check one occurrence, you might miss a mismatch somewhere else in the text. Also note that should you accidentally remove more or fewer tags than you actually wanted to, calibre tries to repair the damaged code after doing the removal.

Dodawanie książek

Another thing you can use regular expressions for is to extract metadata from filenames. You can find this feature in the „Adding books” part of the settings. There’s a special feature here: You can use field names for metadata fields, for example (?P<title>) would indicate that calibre uses this part of the string as book title. The allowed field names are listed in the windows, together with another nice test field. An example: Say you want to import a whole bunch of files named like Classical Texts: The Divine Comedy by Dante Alighieri.mobi. (Obviously, this is already in your library, since we all love classical italian poetry) or Science Fiction epics: The Foundation Trilogy by Isaac Asimov.epub. This is obviously a naming scheme that calibre won’t extract any meaningful data out of - its standard expression for extracting metadata is (?P<title>.+) - (?P<author>[^_]+). A regular expression that works here would be [a-zA-Z]+: (?P<title>.+) by (?P<author>.+). Please note that, inside the group for the metadata field, you need to use expressions to describe what the field actually matches. And also note that, when using the test field calibre provides, you need to add the file extension to your testing filename, otherwise you won’t get any matches at all, despite using a working expression.

Masowa edycja metadanych

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.

Well, that just about concludes the very short introduction to regular expressions. Hopefully I’ll have shown you enough to at least get you started and to enable you to continue learning by yourself- a good starting point would be the Python documentation for regexps.

One last word of warning, though: Regexps are powerful, but also really easy to get wrong. calibre provides really great testing possibilities to see if your expressions behave as you expect them to. Use them. Try not to shoot yourself in the foot. (God, I love that expression…). But should you, despite the warning, injure your foot (or any other body parts), try to learn from it.

Szybkie odniesienie

Uznanie

Dzięki za pomocne rady, korekty itp.:

  • Idolse

  • kovidgoyal

  • chaley

  • dwanthny

  • kacir

  • Starson17

  • Orpheu

For more about regexps see The Python User Manual. The actual regular expression library used by calibre is: regex which supports several useful enhancements over the Python standard library one.