mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Unreported bug: the V4 template language treated None values as zero. These changes make the new processor do the same thing. Also update the documentation,
This commit is contained in:
parent
aa1482d782
commit
fb9a388c08
@ -403,7 +403,7 @@ Program mode also supports the classic relational (comparison) operators: ``==``
|
|||||||
* ``program: if field('series') != 'foo' then 'bar' else 'mumble' fi`` returns 'bar' if the book's series is not 'foo', else 'mumble'.
|
* ``program: if field('series') != 'foo' then 'bar' else 'mumble' fi`` returns 'bar' if the book's series is not 'foo', else 'mumble'.
|
||||||
* ``program: if or(field('series') == 'foo', field('series') == '1632') then 'yes' else 'no' fi`` returns 'yes' if series is either 'foo' or '1632', otherwise 'no'.
|
* ``program: if or(field('series') == 'foo', field('series') == '1632') then 'yes' else 'no' fi`` returns 'yes' if series is either 'foo' or '1632', otherwise 'no'.
|
||||||
* ``program: if '11' > '2' then 'yes' else 'no' fi`` returns 'no' because it is doing a lexical comparison.
|
* ``program: if '11' > '2' then 'yes' else 'no' fi`` returns 'no' because it is doing a lexical comparison.
|
||||||
If you want numeric comparison, use the operators ``==#``, ``!=#``, ``<#``, ``<=#``, ``>#``, ``>=#``. These operators return '' if either the left or the right side are the empty string, '1' if the operator evaluates to True, otherwise ''.
|
If you want numeric comparison instead of lexical comparison, use the operators ``==#``, ``!=#``, ``<#``, ``<=#``, ``>#``, ``>=#``. In this case the left and right values are set to zero if they are undefined or the empty string. If they are not numbers then an error is raised.
|
||||||
|
|
||||||
The following example is a `program:` mode implementation of a recipe on the MobileRead forum: "Put series into the title, using either initials or a shortened form. Strip leading articles from the series name (any)." For example, for the book The Two Towers in the Lord of the Rings series, the recipe gives `LotR [02] The Two Towers`. Using standard templates, the recipe requires three custom columns and a plugboard, as explained in the following:
|
The following example is a `program:` mode implementation of a recipe on the MobileRead forum: "Put series into the title, using either initials or a shortened form. Strip leading articles from the series name (any)." For example, for the book The Two Towers in the Lord of the Rings series, the recipe gives `LotR [02] The Two Towers`. Using standard templates, the recipe requires three custom columns and a plugboard, as explained in the following:
|
||||||
|
|
||||||
|
@ -23,10 +23,11 @@ class Node(object):
|
|||||||
NODE_IF = 2
|
NODE_IF = 2
|
||||||
NODE_ASSIGN = 3
|
NODE_ASSIGN = 3
|
||||||
NODE_FUNC = 4
|
NODE_FUNC = 4
|
||||||
NODE_INFIX = 5
|
NODE_STRING_INFIX = 5
|
||||||
NODE_CONSTANT = 6
|
NODE_NUMERIC_INFIX = 6
|
||||||
NODE_FIELD = 7
|
NODE_CONSTANT = 7
|
||||||
NODE_RAW_FIELD = 8
|
NODE_FIELD = 8
|
||||||
|
NODE_RAW_FIELD = 9
|
||||||
|
|
||||||
|
|
||||||
class IfNode(Node):
|
class IfNode(Node):
|
||||||
@ -54,10 +55,19 @@ class FunctionNode(Node):
|
|||||||
self.expression_list = expression_list
|
self.expression_list = expression_list
|
||||||
|
|
||||||
|
|
||||||
class InfixNode(Node):
|
class StringInfixNode(Node):
|
||||||
def __init__(self, operator, left, right):
|
def __init__(self, operator, left, right):
|
||||||
Node.__init__(self)
|
Node.__init__(self)
|
||||||
self.node_type = self.NODE_INFIX
|
self.node_type = self.NODE_STRING_INFIX
|
||||||
|
self.operator = operator
|
||||||
|
self.left = left
|
||||||
|
self.right = right
|
||||||
|
|
||||||
|
|
||||||
|
class NumericInfixNode(Node):
|
||||||
|
def __init__(self, operator, left, right):
|
||||||
|
Node.__init__(self)
|
||||||
|
self.node_type = self.NODE_NUMERIC_INFIX
|
||||||
self.operator = operator
|
self.operator = operator
|
||||||
self.left = left
|
self.left = left
|
||||||
self.right = right
|
self.right = right
|
||||||
@ -92,12 +102,13 @@ class RawFieldNode(Node):
|
|||||||
|
|
||||||
|
|
||||||
class _Parser(object):
|
class _Parser(object):
|
||||||
LEX_OP = 1
|
LEX_OP = 1
|
||||||
LEX_ID = 2
|
LEX_ID = 2
|
||||||
LEX_CONST = 3
|
LEX_CONST = 3
|
||||||
LEX_EOF = 4
|
LEX_EOF = 4
|
||||||
LEX_INFIX = 5
|
LEX_STRING_INFIX = 5
|
||||||
LEX_KEYWORD = 6
|
LEX_NUMERIC_INFIX = 6
|
||||||
|
LEX_KEYWORD = 7
|
||||||
|
|
||||||
def error(self, message):
|
def error(self, message):
|
||||||
try:
|
try:
|
||||||
@ -131,9 +142,15 @@ class _Parser(object):
|
|||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def token_op_is_infix_compare(self):
|
def token_op_is_string_infix_compare(self):
|
||||||
try:
|
try:
|
||||||
return self.prog[self.lex_pos][0] == self.LEX_INFIX
|
return self.prog[self.lex_pos][0] == self.LEX_STRING_INFIX
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def token_op_is_numeric_infix_compare(self):
|
||||||
|
try:
|
||||||
|
return self.prog[self.lex_pos][0] == self.LEX_NUMERIC_INFIX
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -251,10 +268,13 @@ class _Parser(object):
|
|||||||
|
|
||||||
def infix_expr(self):
|
def infix_expr(self):
|
||||||
left = self.expr()
|
left = self.expr()
|
||||||
if not self.token_op_is_infix_compare():
|
if self.token_op_is_string_infix_compare():
|
||||||
return left
|
operator = self.token()
|
||||||
operator = self.token()
|
return StringInfixNode(operator, left, self.expr())
|
||||||
return InfixNode(operator, left, self.expr())
|
if self.token_op_is_numeric_infix_compare():
|
||||||
|
operator = self.token()
|
||||||
|
return NumericInfixNode(operator, left, self.expr())
|
||||||
|
return left
|
||||||
|
|
||||||
def expr(self):
|
def expr(self):
|
||||||
if self.token_is_if():
|
if self.token_is_if():
|
||||||
@ -324,25 +344,44 @@ class _Interpreter(object):
|
|||||||
val = self.expr(p)
|
val = self.expr(p)
|
||||||
return val
|
return val
|
||||||
|
|
||||||
INFIX_OPS = {
|
INFIX_STRING_OPS = {
|
||||||
"==": lambda x, y: strcmp(x, y) == 0,
|
"==": lambda x, y: strcmp(x, y) == 0,
|
||||||
"!=": lambda x, y: strcmp(x, y) != 0,
|
"!=": lambda x, y: strcmp(x, y) != 0,
|
||||||
"<": lambda x, y: strcmp(x, y) < 0,
|
"<": lambda x, y: strcmp(x, y) < 0,
|
||||||
"<=": lambda x, y: strcmp(x, y) <= 0,
|
"<=": lambda x, y: strcmp(x, y) <= 0,
|
||||||
">": lambda x, y: strcmp(x, y) > 0,
|
">": lambda x, y: strcmp(x, y) > 0,
|
||||||
">=": lambda x, y: strcmp(x, y) >= 0,
|
">=": lambda x, y: strcmp(x, y) >= 0,
|
||||||
"==#": lambda x, y: float(x) == float(y) if x and y else False,
|
|
||||||
"!=#": lambda x, y: float(x) != float(y) if x and y else False,
|
|
||||||
"<#": lambda x, y: float(x) < float(y) if x and y else False,
|
|
||||||
"<=#": lambda x, y: float(x) <= float(y) if x and y else False,
|
|
||||||
">#": lambda x, y: float(x) > float(y) if x and y else False,
|
|
||||||
">=#": lambda x, y: float(x) >= float(y) if x and y else False,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def do_node_infix(self, prog):
|
def do_node_string_infix(self, prog):
|
||||||
left = self.expr(prog.left)
|
try:
|
||||||
right = self.expr(prog.right)
|
left = self.expr(prog.left)
|
||||||
return '1' if self.INFIX_OPS[prog.operator](left, right) else ''
|
right = self.expr(prog.right)
|
||||||
|
return ('1' if self.INFIX_STRING_OPS[prog.operator](left, right) else '')
|
||||||
|
except:
|
||||||
|
self.error(_('Error during string comparison. Operator {0}').format(prog.operator))
|
||||||
|
|
||||||
|
INFIX_NUMERIC_OPS = {
|
||||||
|
"==#": lambda x, y: x == y,
|
||||||
|
"!=#": lambda x, y: x != y,
|
||||||
|
"<#": lambda x, y: x < y,
|
||||||
|
"<=#": lambda x, y: x <= y,
|
||||||
|
">#": lambda x, y: x > y,
|
||||||
|
">=#": lambda x, y: x >= y,
|
||||||
|
}
|
||||||
|
|
||||||
|
def float_deal_with_none(self, v):
|
||||||
|
# Undefined values and the string 'None' are assumed to be zero.
|
||||||
|
# The reason for string 'None': raw_field returns it for undefined values
|
||||||
|
return float(v if v and v != 'None' else 0)
|
||||||
|
|
||||||
|
def do_node_numeric_infix(self, prog):
|
||||||
|
try:
|
||||||
|
left = self.float_deal_with_none(self.expr(prog.left))
|
||||||
|
right = self.float_deal_with_none(self.expr(prog.right))
|
||||||
|
return '1' if self.INFIX_NUMERIC_OPS[prog.operator](left, right) else ''
|
||||||
|
except:
|
||||||
|
self.error(_('Value used in comparison is not a number. Operator {0}').format(prog.operator))
|
||||||
|
|
||||||
def do_node_if(self, prog):
|
def do_node_if(self, prog):
|
||||||
test_part = self.expr(prog.condition)
|
test_part = self.expr(prog.condition)
|
||||||
@ -406,14 +445,15 @@ class _Interpreter(object):
|
|||||||
return t
|
return t
|
||||||
|
|
||||||
NODE_OPS = {
|
NODE_OPS = {
|
||||||
Node.NODE_IF: do_node_if,
|
Node.NODE_IF: do_node_if,
|
||||||
Node.NODE_ASSIGN: do_node_assign,
|
Node.NODE_ASSIGN: do_node_assign,
|
||||||
Node.NODE_CONSTANT: do_node_constant,
|
Node.NODE_CONSTANT: do_node_constant,
|
||||||
Node.NODE_RVALUE: do_node_rvalue,
|
Node.NODE_RVALUE: do_node_rvalue,
|
||||||
Node.NODE_FUNC: do_node_func,
|
Node.NODE_FUNC: do_node_func,
|
||||||
Node.NODE_FIELD: do_node_field,
|
Node.NODE_FIELD: do_node_field,
|
||||||
Node.NODE_RAW_FIELD:do_node_raw_field,
|
Node.NODE_RAW_FIELD: do_node_raw_field,
|
||||||
Node.NODE_INFIX: do_node_infix,
|
Node.NODE_STRING_INFIX: do_node_string_infix,
|
||||||
|
Node.NODE_NUMERIC_INFIX: do_node_numeric_infix,
|
||||||
}
|
}
|
||||||
|
|
||||||
def expr(self, prog):
|
def expr(self, prog):
|
||||||
@ -494,17 +534,17 @@ class TemplateFormatter(string.Formatter):
|
|||||||
# ################# 'Functional' template language ######################
|
# ################# 'Functional' template language ######################
|
||||||
|
|
||||||
lex_scanner = re.Scanner([
|
lex_scanner = re.Scanner([
|
||||||
(r'(==#|!=#|<=#|<#|>=#|>#|==|!=|<=|<|>=|>)',
|
(r'(==#|!=#|<=#|<#|>=#|>#)', lambda x,t: (_Parser.LEX_NUMERIC_INFIX, t)),
|
||||||
lambda x,t: (_Parser.LEX_INFIX, t)),
|
(r'(==|!=|<=|<|>=|>)', lambda x,t: (_Parser.LEX_STRING_INFIX, t)),
|
||||||
(r'(if|then|else|fi)\b', lambda x,t: (_Parser.LEX_KEYWORD, t)), # noqa
|
(r'(if|then|else|fi)\b', lambda x,t: (_Parser.LEX_KEYWORD, t)), # noqa
|
||||||
(r'[(),=;]', lambda x,t: (_Parser.LEX_OP, t)), # noqa
|
(r'[(),=;]', lambda x,t: (_Parser.LEX_OP, t)), # noqa
|
||||||
(r'-?[\d\.]+', lambda x,t: (_Parser.LEX_CONST, t)), # noqa
|
(r'-?[\d\.]+', lambda x,t: (_Parser.LEX_CONST, t)), # noqa
|
||||||
(r'\$', lambda x,t: (_Parser.LEX_ID, t)), # noqa
|
(r'\$', lambda x,t: (_Parser.LEX_ID, t)), # noqa
|
||||||
(r'\w+', lambda x,t: (_Parser.LEX_ID, t)), # noqa
|
(r'\w+', lambda x,t: (_Parser.LEX_ID, t)), # noqa
|
||||||
(r'".*?((?<!\\)")', lambda x,t: (_Parser.LEX_CONST, t[1:-1])), # noqa
|
(r'".*?((?<!\\)")', lambda x,t: (_Parser.LEX_CONST, t[1:-1])), # noqa
|
||||||
(r'\'.*?((?<!\\)\')', lambda x,t: (_Parser.LEX_CONST, t[1:-1])), # noqa
|
(r'\'.*?((?<!\\)\')', lambda x,t: (_Parser.LEX_CONST, t[1:-1])), # noqa
|
||||||
(r'\n#.*?(?:(?=\n)|$)', None),
|
(r'\n#.*?(?:(?=\n)|$)', None),
|
||||||
(r'\s', None),
|
(r'\s', None),
|
||||||
], flags=re.DOTALL)
|
], flags=re.DOTALL)
|
||||||
|
|
||||||
def _eval_program(self, val, prog, column_name):
|
def _eval_program(self, val, prog, column_name):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user