mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
The template functions for the new custom column search template feature. There are 4 new functions:
* make_url(path, [query_name, query_value]+). This is the easiest to use. * make_url_extended(...). This gives the user more control over constructing the URL, including user-built query strings. * query_string([query_name, query_value, how_to_encode]+). Constructs a query string, giving more control over how the values are encoded. * encode_for_url(value, use_plus). URL-encodes a single value. As you said earlier, most people will use make_url(). However, I have seen cases where query values must be inserted into the path, and make_url_extended() helps with that. I have also seen cases where query args must not be encoded. And so on. These 4 functions plus other text functions like re() let the user do whatever is necessary. Note that 'item_value' is no longer encoded. There are two more values available: 'item_value_quoted' and 'item_value_no_plus'. I'm not convinced these are particularly useful but it doesn't hurt anything to have them.
This commit is contained in:
parent
3be9172c51
commit
33af676b11
@ -70,7 +70,8 @@ def search_action_with_data(search_term, value, book_id, field=None, **k):
|
||||
def web_search_link(template, mi, value):
|
||||
formatter = SafeFormat()
|
||||
iv = str(value)
|
||||
mi.set('item_value', qquote(iv, True))
|
||||
mi.set('item_value', iv)
|
||||
mi.set('item_value_quoted', qquote(iv, True))
|
||||
mi.set('item_value_no_plus', qquote(iv, False))
|
||||
u = formatter.safe_format(template, mi, 'BOOK DETAILS WEB LINK', mi)
|
||||
if u:
|
||||
|
@ -542,12 +542,17 @@ class CreateCustomColumn(QDialog):
|
||||
l.addWidget(self.web_search_label)
|
||||
wst = self.web_search_template = QLineEdit()
|
||||
wst.setToolTip('<p>' + _(
|
||||
'Fill in this box if you want clicking on the value in book details to do a '
|
||||
"Fill in this box if you want clicking on the value in book details to do a "
|
||||
"web search instead of searching your calibre library. The book's metadata is "
|
||||
"available to the template. Additional fields '{0}' and '{1}' are also available to the "
|
||||
'template. For multiple-valued (tags-like) columns they are the value being examined, '
|
||||
'telling you which value to use to generate the link. These two values are automatically escaped for use in URLs.').format(
|
||||
'item_value', 'item_value_no_plus') + '</p>')
|
||||
"available to the template.</p><p>Additional fields '{0}', `{1}`, and '{2}' are also available "
|
||||
"to the template. For multiple-valued (tags-like) columns they are the value being examined, "
|
||||
"telling you which value to use to generate the link. The two values '{1}' and '{2}' are "
|
||||
"automatically escaped for use in URLs. In '{1}', spaces are replaced by plus signs. In '{2}' "
|
||||
"spaces are replaced by '%20'.</p><p> The template functions '{3}' (the easiest to use), "
|
||||
"'{4}', '{5}', and '{6}' are useful for constructing the desired URL. There are examples in "
|
||||
"the template function documentation.").format(
|
||||
'item_value', 'item_value_quoted', 'item_value_no_plus', 'make_url()', 'make_url_extended()',
|
||||
'query_string()', 'quote_for_url()') + '</p>')
|
||||
l.addWidget(wst)
|
||||
self.web_search_label.setBuddy(wst)
|
||||
wst_tb = self.web_search_toolbutton = QToolButton()
|
||||
@ -563,17 +568,21 @@ class CreateCustomColumn(QDialog):
|
||||
db = self.gui.current_db.new_api
|
||||
lv = self.gui.library_view
|
||||
rows = lv.selectionModel().selectedRows()
|
||||
from calibre.ebooks.metadata.search_internet import qquote
|
||||
if not self.editing_col or not rows:
|
||||
vals = [{'value': _('Value'), 'lookup_name': _('Lookup name'), 'author': _('Author'),
|
||||
vals = [{'item_value': _('Item Value'),
|
||||
'item_value_quoted': qquote(_('Item Value'), True),
|
||||
'item_value_no_plus': qquote(_('Item Value'), False),
|
||||
'lookup_name': _('Lookup name'),'author': _('Author'),
|
||||
'title': _('Title'), 'author_sort': _('Author sort')}]
|
||||
else:
|
||||
from calibre.ebooks.metadata.search_internet import qquote
|
||||
vals = []
|
||||
for row in rows:
|
||||
book_id = lv.model().id(row)
|
||||
mi = db.new_api.get_metadata(book_id)
|
||||
mi.set('item_value', qquote('Item Value', True))
|
||||
mi.set('item_value_no_plus', qquote('Item Value', False))
|
||||
mi.set('item_value', _('Item Value'))
|
||||
mi.set('item_value_quoted', qquote(_('Item Value'), True))
|
||||
mi.set('item_value_no_plus', qquote(_('Item Value'), False))
|
||||
vals.append(mi)
|
||||
d = TemplateDialog(parent=self, text=self.web_search_template.text(), mi=vals)
|
||||
if d.exec() == QDialog.DialogCode.Accepted:
|
||||
@ -682,8 +691,7 @@ class CreateCustomColumn(QDialog):
|
||||
self.comments_type.setVisible(is_comments)
|
||||
self.comments_type_label.setVisible(is_comments)
|
||||
|
||||
has_url_template = not is_comments and col_type in ('text', '*text', 'composite', '*composite',
|
||||
'series', 'enumeration')
|
||||
has_url_template = col_type in ('text', '*text', 'composite', '*composite', 'series', 'enumeration')
|
||||
self.web_search_label.setVisible(has_url_template)
|
||||
self.web_search_template.setVisible(has_url_template)
|
||||
self.web_search_toolbutton.setVisible(has_url_template)
|
||||
@ -962,21 +970,26 @@ class CreateNewCustomColumn:
|
||||
'make_category': True or False -- whether the column is shown in the tag browser
|
||||
'contains_html': True or False -- whether the column is interpreted as HTML
|
||||
'use_decorations': True or False -- should check marks be displayed
|
||||
'search_template': a template used to construct a search URL for book details
|
||||
datetime columns:
|
||||
'date_format': a string specifying the display format
|
||||
enumerated columns
|
||||
'enum_values': a string containing comma-separated valid values for an enumeration
|
||||
'enum_colors': a string containing comma-separated colors for an enumeration
|
||||
'use_decorations': True or False -- should check marks be displayed
|
||||
'search_template': a template used to construct a search URL for book details
|
||||
float columns:
|
||||
'decimals': the number of decimal digits to allow when editing (int). Range: 1 - 9
|
||||
float and int columns:
|
||||
'number_format': the format to apply when displaying the column
|
||||
rating columns:
|
||||
'allow_half_stars': True or False -- are half-stars allowed
|
||||
series columns:
|
||||
'search_template': a template used to construct a search URL for book details
|
||||
text columns:
|
||||
'is_names': True or False -- whether the items are comma or ampersand separated
|
||||
'use_decorations': True or False -- should check marks be displayed
|
||||
'search_template': a template used to construct a search URL for book details
|
||||
|
||||
This method returns a tuple (Result.enum_value, message). If tuple[0] is
|
||||
Result.COLUMN_ADDED then the message is the lookup name including the '#'.
|
||||
|
@ -30,6 +30,7 @@ from calibre.db.constants import DATA_DIR_NAME, DATA_FILE_PATTERN
|
||||
from calibre.db.notes.exim import expand_note_resources, parse_html
|
||||
from calibre.ebooks.metadata import title_sort
|
||||
from calibre.ebooks.metadata.book.base import field_metadata
|
||||
from calibre.ebooks.metadata.search_internet import qquote
|
||||
from calibre.utils.config import tweaks
|
||||
from calibre.utils.date import UNDEFINED_DATE, format_date, now, parse_date
|
||||
from calibre.utils.icu import capitalize, sort_key, strcmp
|
||||
@ -55,6 +56,7 @@ FORMATTING_VALUES = _('Formatting values')
|
||||
CASE_CHANGES = _('Case changes')
|
||||
DATE_FUNCTIONS = _('Date functions')
|
||||
DB_FUNCS = _('Database functions')
|
||||
URL_FUNCTIONS = _('URL functions')
|
||||
|
||||
|
||||
# Class and method to save an untranslated copy of translated strings
|
||||
@ -2780,7 +2782,7 @@ of templates.
|
||||
class BuiltinToHex(BuiltinFormatterFunction):
|
||||
name = 'to_hex'
|
||||
arg_count = 1
|
||||
category = STRING_MANIPULATION
|
||||
category = URL_FUNCTIONS
|
||||
__doc__ = doc = _(
|
||||
r'''
|
||||
``to_hex(val)`` -- returns the string ``val`` encoded into hex.[/] This is useful
|
||||
@ -2794,7 +2796,7 @@ when constructing calibre URLs.
|
||||
class BuiltinUrlsFromIdentifiers(BuiltinFormatterFunction):
|
||||
name = 'urls_from_identifiers'
|
||||
arg_count = 2
|
||||
category = FORMATTING_VALUES
|
||||
category = URL_FUNCTIONS
|
||||
__doc__ = doc = _(
|
||||
r'''
|
||||
``urls_from_identifiers(identifiers, sort_results)`` -- given a comma-separated
|
||||
@ -3213,6 +3215,203 @@ data without converting it to a string first. Example: ``list_count_field('tags'
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class BuiltinMakeUrl(BuiltinFormatterFunction):
|
||||
name = 'make_url'
|
||||
arg_count = -1
|
||||
category = URL_FUNCTIONS
|
||||
__doc__ = doc = _(
|
||||
r'''
|
||||
``make_url(path, [query_name, query_value]+)`` -- this function is the easiest way
|
||||
to construct a query URL. It uses a ``path``, the web site and page you want to
|
||||
query, and ``query_name``, ``query_value`` pairs from which the query is built.
|
||||
In general, the ``query_value`` must be URL-encoded. With this function it is always
|
||||
encoded and spaces are always replaced with ``'+'`` signs.
|
||||
|
||||
At least one ``query_name, query_value`` pair must be provided.
|
||||
|
||||
Example: constructing a Wikipedia search URL for the author `Niccolò Machiavelli`:
|
||||
[CODE]
|
||||
make_url('https://en.wikipedia.org/w/index.php', 'search', 'Niccolò Machiavelli')
|
||||
[/CODE]
|
||||
returns
|
||||
[CODE]
|
||||
https://en.wikipedia.org/w/index.php?search=Niccol%C3%B2+Machiavelli
|
||||
[/CODE]
|
||||
|
||||
If you are writing a custom column book details URL template then use ``$item_name`` or
|
||||
``field('item_name')`` to obtain the value of the field that was clicked on.
|
||||
Example: if `Niccolò Machiavelli` was clicked then you can construct the URL using:
|
||||
[CODE]
|
||||
make_url('https://en.wikipedia.org/w/index.php', 'search', $item_name)
|
||||
[/CODE]
|
||||
|
||||
See also the functions :ref:`make_url_extended`, :ref:`query_string` and :ref:`encode_for_url`.
|
||||
''')
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals, path, *args):
|
||||
if (len(args) % 2) != 0:
|
||||
raise ValueError(_('{} requires an odd number of arguments').format('make_url'))
|
||||
if len(args) < 2:
|
||||
raise ValueError(_('{} requires at least 3 arguments').format('make_url'))
|
||||
query_args = []
|
||||
for i in range(0, len(args), 2):
|
||||
query_args.append(f'{args[i]}={qquote(args[i+1].strip())}')
|
||||
return f'{path}?{"&".join(query_args)}'
|
||||
|
||||
|
||||
class BuiltinMakeUrlExtended(BuiltinFormatterFunction):
|
||||
name = 'make_url_extended'
|
||||
arg_count = -1
|
||||
category = URL_FUNCTIONS
|
||||
__doc__ = doc = _(
|
||||
r'''
|
||||
``make_url_extended(...)`` -- this function is similar to :ref:`make_url` but
|
||||
gives you more control over the URL components. The components of a URL are
|
||||
|
||||
[B]scheme[/B]:://[B]authority[/B]/[B]path[/B]?[B]query string[/B].
|
||||
|
||||
See [URL href="https://en.wikipedia.org/wiki/URL"]Uniform Resource Locater[/URL] on Wikipedia for more detail.
|
||||
|
||||
The function has two variants:
|
||||
[CODE]
|
||||
make_url_extended(scheme, authority, path, [query_name, query_value]+)
|
||||
[/CODE]
|
||||
and
|
||||
[CODE]
|
||||
make_url_extended(scheme, authority, path, query_string)
|
||||
[/CODE]
|
||||
[/]
|
||||
This function returns a URL constructed from the ``scheme``, ``authority``, ``path``,
|
||||
and either the ``query_string`` or a query string constructed from the query argument pairs.
|
||||
You must supply either a ``query_string`` or at least one ``query_name, query_value`` pair.
|
||||
If you supply ``query_string`` and it is empty then the resulting URL will not have a query string section.
|
||||
|
||||
Example 1: constructing a Wikipedia search URL for the author `Niccolò Machiavelli`:
|
||||
[CODE]
|
||||
make_url_extended('https', 'en.wikipedia.org', '/w/index.php', 'search', 'Niccolò Machiavelli')
|
||||
[/CODE]
|
||||
returns
|
||||
[CODE]
|
||||
https://en.wikipedia.org/w/index.php?search=Niccol%C3%B2+Machiavelli
|
||||
[/CODE]
|
||||
|
||||
See the :ref:`query_string`() function for an example using ``make_url_extended()`` with a ``query_string``.
|
||||
|
||||
If you are writing a custom column book details URL template then use ``$item_name`` or
|
||||
``field('item_name')`` to obtain the value of the field that was clicked on.
|
||||
Example: if `Niccolò Machiavelli` was clicked on then you can construct the URL using :
|
||||
[CODE]
|
||||
make_url_extended('https', 'en.wikipedia.org', '/w/index.php', 'search', $item_name')
|
||||
[/CODE]
|
||||
|
||||
See also the functions :ref:`make_url`, :ref:`query_string` and :ref:`encode_for_url`.
|
||||
''')
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals, scheme, host, path, *args):
|
||||
if len(args) != 1:
|
||||
if (len(args) % 2) != 0:
|
||||
raise ValueError(_('{} requires an odd number of arguments').format('make_url_extended'))
|
||||
if len(args) < 2:
|
||||
raise ValueError(_('{} requires at least 5 arguments').format('make_url_extended'))
|
||||
query_args = []
|
||||
for i in range(0, len(args), 2):
|
||||
query_args.append(f'{args[i]}={qquote(args[i+1].strip())}')
|
||||
qs = '&'.join(query_args)
|
||||
else:
|
||||
qs = args[0]
|
||||
if qs:
|
||||
qs = '?' + qs
|
||||
return f"{scheme}://{host}/{path[1:] if path.startswith('/') else path}{qs}"
|
||||
|
||||
|
||||
class BuiltinQueryString(BuiltinFormatterFunction):
|
||||
name = 'query_string'
|
||||
arg_count = -1
|
||||
category = URL_FUNCTIONS
|
||||
__doc__ = doc = _(
|
||||
r'''
|
||||
``query_string([query_name, query_value, how_to_encode]+)``-- returns a URL query string
|
||||
constructed from the ``query_name, query_value, how_to_encode`` triads.
|
||||
A query string is a series of items where each item looks like ``query_name=query_value``
|
||||
where ``query_value`` is URL-encoded as instructed. The query items are separated by
|
||||
``'&'`` (ampersand) characters.
|
||||
|
||||
If ``how_to_encode`` is ``0`` then ``query_value`` is encoded and spaces are replaced
|
||||
with ``'+'`` (plus) signs. If ``how_to_encode`` is ``1`` then ``query_value`` is
|
||||
encoded with spaces replaced by ``%20``. If ``how_to_encode`` is ``2`` then ``query_value``
|
||||
is returned unchanged; no encoding is done and spaces are not replaced. If you want
|
||||
``query_value`` not to be encoded but spaces to be replaced then use the :ref:`re`
|
||||
function, as in ``re($series, ' ', '%20')``
|
||||
|
||||
You use this function if you need specific control over how the parts of the
|
||||
query string are constructed. You could then use the resultingquery string in
|
||||
:ref:`make_url_extended`, as in
|
||||
[CODE]
|
||||
make_url_extended(
|
||||
'https', 'your_host', 'your_path',
|
||||
query_string('encoded', 'Hendrik Bäßler', 0, 'unencoded', 'Hendrik Bäßler', 2))
|
||||
[/CODE]
|
||||
giving you
|
||||
[CODE]
|
||||
https://your_host/your_path?encoded=Hendrik+B%C3%A4%C3%9Fler&unencoded=Hendrik Bäßler
|
||||
[/CODE]
|
||||
|
||||
You must have at least one ``query_name, query_value, how_to_encode`` triad, but can
|
||||
have as many as you wish.
|
||||
|
||||
The returned value is a URL query string with all the specified items, for example:
|
||||
``name1=val1[&nameN=valN]*``. Note that the ``'?'`` `path` / `query string` separator
|
||||
is not included in the returned result.
|
||||
|
||||
If you are writing a custom column book details URL template then use ``$item_name`` or
|
||||
``field('item_name')`` to obtain the unencoded value of the field that was clicked.
|
||||
You also have ``item_value_quoted`` where the value is already encoded with plus signs
|
||||
replacing spaces, and ``item_value_no_plus`` where the value is already encoded
|
||||
with ``%20`` replacing spaces.
|
||||
|
||||
See also the functions :ref:`make_url`, :ref:`make_url_extended` and :ref:`encode_for_url`.
|
||||
''')
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals, *args):
|
||||
if (len(args) % 3) != 0 or len(args) < 3:
|
||||
raise ValueError(_('{} requires at least one group of 3 arguments').format('query_string'))
|
||||
funcs = [
|
||||
partial(qquote, use_plus=True),
|
||||
partial(qquote, use_plus=False),
|
||||
lambda x:x,
|
||||
]
|
||||
query_args = []
|
||||
for i in range(0, len(args), 3):
|
||||
if (f := args[i+2]) not in ('0', '1', '2'):
|
||||
raise ValueError(
|
||||
_('In {} the third argument of a group must be 0, 1, or 2, not {}').format('query_string', f))
|
||||
query_args.append(f'{args[i]}={funcs[int(f)](args[i+1].strip())}')
|
||||
return "&".join(query_args)
|
||||
|
||||
|
||||
class BuiltinEncodeForURL(BuiltinFormatterFunction):
|
||||
name = 'encode_for_url'
|
||||
arg_count = 2
|
||||
category = URL_FUNCTIONS
|
||||
__doc__ = doc = _(
|
||||
r'''
|
||||
``encode_for_url(value, use_plus)`` -- returns the ``value`` encoded for use in a URL as
|
||||
specified by ``use_plus``. The value is first URL-encoded. Next, if ``use_plus`` is ``0`` then
|
||||
spaces are replaced by ``'+'`` (plus) signs. If it is ``1`` then spaces are replaced by ``%20``.
|
||||
|
||||
If you do not want the value to be encoding but to have spaces replaced then use the
|
||||
:ref:`re` function, as in ``re($series, ' ', '%20')``
|
||||
|
||||
See also the functions :ref:`make_url`, :ref:`make_url_extended` and :ref:`query_string`.
|
||||
''')
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals, value, use_plus):
|
||||
if use_plus not in ('0', '1'):
|
||||
raise ValueError(
|
||||
_('In {} the second argument must be 0, or 1, not {}').format('quote_for_url', use_plus))
|
||||
return qquote(value, use_plus=use_plus=='0')
|
||||
|
||||
|
||||
_formatter_builtins = [
|
||||
BuiltinAdd(), BuiltinAnd(), BuiltinApproximateFormats(), BuiltinArguments(),
|
||||
BuiltinAssign(),
|
||||
@ -3222,7 +3421,7 @@ _formatter_builtins = [
|
||||
BuiltinCmp(), BuiltinConnectedDeviceName(), BuiltinConnectedDeviceUUID(), BuiltinContains(),
|
||||
BuiltinCount(), BuiltinCurrentLibraryName(), BuiltinCurrentLibraryPath(),
|
||||
BuiltinCurrentVirtualLibraryName(), BuiltinDateArithmetic(),
|
||||
BuiltinDaysBetween(), BuiltinDivide(), BuiltinEval(),
|
||||
BuiltinDaysBetween(), BuiltinDivide(), BuiltinEncodeForURL(), BuiltinEval(),
|
||||
BuiltinExtraFileNames(), BuiltinExtraFileSize(), BuiltinExtraFileModtime(),
|
||||
BuiltinFieldListCount(), BuiltinFirstNonEmpty(), BuiltinField(), BuiltinFieldExists(),
|
||||
BuiltinFinishFormatting(), BuiltinFirstMatchingCmp(), BuiltinFloor(),
|
||||
@ -3237,9 +3436,10 @@ _formatter_builtins = [
|
||||
BuiltinListitem(), BuiltinListJoin(), BuiltinListRe(),
|
||||
BuiltinListReGroup(), BuiltinListRemoveDuplicates(), BuiltinListSort(),
|
||||
BuiltinListSplit(), BuiltinListUnion(),BuiltinLookup(),
|
||||
BuiltinLowercase(), BuiltinMod(), BuiltinMultiply(), BuiltinNot(), BuiltinOndevice(),
|
||||
BuiltinOr(), BuiltinPrint(), BuiltinRatingToStars(), BuiltinRange(),
|
||||
BuiltinRawField(), BuiltinRawList(),
|
||||
BuiltinLowercase(), BuiltinMakeUrl(), BuiltinMakeUrlExtended(), BuiltinMod(),
|
||||
BuiltinMultiply(), BuiltinNot(), BuiltinOndevice(),
|
||||
BuiltinOr(), BuiltinPrint(), BuiltinQueryString(), BuiltinRatingToStars(),
|
||||
BuiltinRange(), BuiltinRawField(), BuiltinRawList(),
|
||||
BuiltinRe(), BuiltinReGroup(), BuiltinRound(), BuiltinSelect(), BuiltinSeriesSort(),
|
||||
BuiltinSetGlobals(), BuiltinShorten(), BuiltinStrcat(), BuiltinStrcatMax(),
|
||||
BuiltinStrcmp(), BuiltinStrcmpcase(), BuiltinStrInList(), BuiltinStrlen(), BuiltinSubitems(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user