From 7aa8e250b6f32dc287e26b1daa3f2081c018c801 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 9 Jul 2017 21:02:52 +0200 Subject: [PATCH] Improvement to str_in_list template function. Allow a variable number of tests, drastically improving performance in cases where searching a list must return different values depending on what is found. --- manual/template_lang.rst | 2 +- src/calibre/utils/formatter_functions.py | 35 ++++++++++++++++-------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/manual/template_lang.rst b/manual/template_lang.rst index 01cfa3647c..4d6a5acf45 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -132,7 +132,7 @@ The functions available are listed below. Note that the definitive documentation * ``re(pattern, replacement)`` -- return the field after applying the regular expression. All instances of `pattern` are replaced with `replacement`. As in all of calibre, these are Python-compatible regular expressions. * ``select(key)`` -- interpret the field as a comma-separated list of items, with the items being of the form "id:value". Find the pair with the id equal to key, and return the corresponding value. This function is particularly useful for extracting a value such as an isbn from the set of identifiers for a book. * ``shorten(left chars, middle text, right chars)`` -- Return a shortened version of the field, consisting of `left chars` characters from the beginning of the field, followed by `middle text`, followed by `right chars` characters from the end of the string. `Left chars` and `right chars` must be integers. For example, assume the title of the book is `Ancient English Laws in the Times of Ivanhoe`, and you want it to fit in a space of at most 15 characters. If you use ``{title:shorten(9,-,5)}``, the result will be `Ancient E-nhoe`. If the field's length is less than ``left chars`` + ``right chars`` + the length of ``middle text``, then the field will be used intact. For example, the title `The Dome` would not be changed. - * ``str_in_list(val, separator, string, found_val, not_found_val)`` -- treat val as a list of items separated by separator, comparing the string against each value in the list. If the string matches a value, return found_val, otherwise return not_found_val. If the string contains separators, then it is also treated as a list and each value is checked. + * ``str_in_list(val, separator, string, found_val, ..., not_found_val)`` -- treat val as a list of items separated by separator, comparing the string against each value in the list. If the string matches a value (ignoring case), return found_val, otherwise return not_found_val. If the string contains separators, then it is also treated as a list and each value is checked. The string and found_value can be repeated as many times as desired, permitting returning different values depending on the search. The strings are checked in order. The first match is returned. * ``subitems(val, start_index, end_index)`` -- This function is used to break apart lists of tag-like hierarchical items such as genres. It interprets the value as a comma-separated list of tag-like items, where each item is a period-separated list. Returns a new list made by first finding all the period-separated tag-like items, then for each such item extracting the components from `start_index` to `end_index`, then combining the results back together. The first component in a period-separated list has an index of zero. If an index is negative, then it counts from the end of the list. As a special case, an end_index of zero is assumed to be the length of the list. Examples:: Assuming a #genre column containing "A.B.C": diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index c5feaf0688..49cafa91d5 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -544,24 +544,35 @@ class BuiltinInList(BuiltinFormatterFunction): class BuiltinStrInList(BuiltinFormatterFunction): name = 'str_in_list' - arg_count = 5 + arg_count = -1 category = 'List lookup' - __doc__ = doc = _('str_in_list(val, separator, string, found_val, not_found_val) -- ' + __doc__ = doc = _('str_in_list(val, separator, string, found_val, ..., not_found_val) -- ' 'treat val as a list of items separated by separator, ' 'comparing the string against each value in the list. If the ' - 'string matches a value, return found_val, otherwise return ' + 'string matches a value (ignoring case) then return found_val, otherwise return ' 'not_found_val. If the string contains separators, then it is ' - 'also treated as a list and each value is checked.') + 'also treated as a list and each value is checked. The string and ' + 'found_value can be repeated as many times as desired, permitting ' + 'returning different values depending on the search. The strings are ' + 'checked in order. The first match is returned.') - def evaluate(self, formatter, kwargs, mi, locals, val, sep, str, fv, nfv): + def evaluate(self, formatter, kwargs, mi, locals, val, sep, *args): + if (len(args) % 2) != 1: + raise ValueError(_('wstr_in_list requires an odd number of arguments')) l = [v.strip() for v in val.split(sep) if v.strip()] - c = [v.strip() for v in str.split(sep) if v.strip()] - if l: - for v in l: - for t in c: - if strcmp(t, v) == 0: - return fv - return nfv + i = 0 + while i < len(args): + if i + 1 >= len(args): + return args[i] + sf = args[i] + fv = args[i+1] + c = [v.strip() for v in sf.split(sep) if v.strip()] + if l: + for v in l: + for t in c: + if strcmp(t, v) == 0: + return fv + i += 2 class BuiltinIdentifierInList(BuiltinFormatterFunction):