From 2349f4507a847b213964a3f918e680a7091934e8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 Nov 2014 22:14:40 +0530 Subject: [PATCH] Start documenting the new function mode --- manual/edit.rst | 11 +++ manual/function_mode.rst | 110 +++++++++++++++++++++++++++++ manual/images/function_replace.png | Bin 0 -> 12039 bytes 3 files changed, 121 insertions(+) create mode 100644 manual/function_mode.rst create mode 100644 manual/images/function_replace.png diff --git a/manual/edit.rst b/manual/edit.rst index f85b888149..80aa705eb9 100644 --- a/manual/edit.rst +++ b/manual/edit.rst @@ -247,6 +247,13 @@ that you can apply. You can even select multiple entries in the list by holding down the Ctrl Key while clicking so as to run multiple search and replace expressions in a single operation. +Function mode +^^^^^^^^^^^^^^^^^^^^^ + +Function mode allows you to write arbitrarily powerful python functions that +are run on every Find/replace. You can do pretty much any text manipulation you +like in function mode. For more information, see :doc:`function_mode`. + Automated tools ------------------- @@ -693,3 +700,7 @@ particularly useful to directly create EPUB files from your own hand-edited HTML files. You can do this via :guilabel:`File->Import an HTML or DOCX file as a new book`. +.. toctree:: + :hidden: + + function_mode diff --git a/manual/function_mode.rst b/manual/function_mode.rst new file mode 100644 index 0000000000..4793c90593 --- /dev/null +++ b/manual/function_mode.rst @@ -0,0 +1,110 @@ +Function Mode for Search & Replace in the Editor +======================================================================= + +The Search & Replace tool in the editor support a *function mode*. In this +mode, you can combine regular expressions (see :doc:`regexp`) with +arbitrarily powerful python functions to do all sorts of advanced text +processing. + +In the standard *regexp* mode for search and replace, you specify both a +regular expression to search for as well as a template that is used to replace +all found matches. In function mode, instead of using a fixed template, you +specify an arbitrary function, in the +`python programming language `_. This allows +you to do lots of things that are not possible with simple templates. + +Techniques for using function mode and the syntax will be described by means of +examples, showing you how to create functions to perform progressively more +complex tasks. + + +.. image:: images/function_replace.png + :alt: The Function mode + :align: center + +Automatically fixing the case of headings in the document +------------------------------------------------------------- + +Here, we will leverage one of the builtin functions in the editor to +automatically change the case of all text inside heading tags to title case:: + + Find expression: <[Hh][1-6][^>]*>([^<>]+) + +For the function, simply choose the :guilabel:`Title-case text` builtin +function. The will change titles that look like: ``

some TITLE

`` to +``

Some Title

``. + + +Your first custom function - smartening hyphens +------------------------------------------------------------------ + +The real power of function mode comes from being able to create your own +functions to process text in arbitrary ways. The Smarten Punctuation tool in +the editor leaves individual hyphens alone, so you can use the this function to +replace them with em-dashes. + +To create a new function, simply click the Create/Edit button to create a new +function and copy the python code from below. + +.. code-block:: python + + def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs): + return match.group().replace('--', '—').replace('-', '—') + +Every Search & Replace custom function must have a unique name and consist of a +python function named replace, that accepts all the arguments shown above. +For the moment, we wont worry about all the different arguments to +``replace()`` function. Just focus on the ``match`` argument. It represents a +match when running a search and replace. Its full documentation in available +`here `_. +``match.group()`` simply returns all the matched text and all we do is replace +hyphens in that text with em-dashes, first replacing double hyphens and +then single hyphens. + +Use this function with the find regular expression:: + + >[^<>]+< + +And it will replace all hyphens with em-dashes, but only in actual text and not +inside HTML tag definitions. + + +The power of function mode - using a spelling dictionary to fix mis-hyphenated words +---------------------------------------------------------------------------------------- + +Often, ebooks created from scans of printed books contain mis-hyphenated words +-- words that were split at the end of the line on the printed page. We will +write a simple function to automatically find and fix such words. + +.. code-block:: python + + import regex + + 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 + return regex.sub(r'(\w+)\s*-\s*(\w+)', replace_word, match.group(), flags=regex.VERSION1 | regex.UNICODE) + +Use this function with the same find expressions as before, namely:: + + >[^<>]+< + +And it will magically fix all mis-hyphenated words in the text of the book. The +main trick is to use one of the useful extra arguments to the replace function, +``dictionaries``. This refers to the dictionaries the editor itself uses to +spell check text in the book. What this function does is look for words +separated by a hyphen, remove the hyphen and check if the dictionary recognizes +the composite word, if it does, the original words are replaced by the hyphen +free composite word. + +Note that one limitation of this technique is it will only work for +mono-lingual books, because, by default, ``dictionaries.recognized()`` uses the +main language of the book. diff --git a/manual/images/function_replace.png b/manual/images/function_replace.png new file mode 100644 index 0000000000000000000000000000000000000000..0b84d1b3f193956ccedbe92592a3b3bac9a6ac35 GIT binary patch literal 12039 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 E02NhurT_o{ literal 0 HcmV?d00001