1'''
2Created on 23 Sep 2010
3
4@author: charles
5'''
6
7__license__   = 'GPL v3'
8__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
9__docformat__ = 'restructuredtext en'
10
11import re, string, traceback, numbers
12from functools import partial
13from math import modf
14
15from calibre import prints
16from calibre.constants import DEBUG
17from calibre.ebooks.metadata.book.base import field_metadata
18from calibre.utils.formatter_functions import formatter_functions
19from calibre.utils.icu import strcmp
20from polyglot.builtins import error_message
21
22
23class Node:
24    NODE_RVALUE = 1
25    NODE_IF = 2
26    NODE_ASSIGN = 3
27    NODE_FUNC = 4
28    NODE_COMPARE_STRING = 5
29    NODE_COMPARE_NUMERIC = 6
30    NODE_CONSTANT = 7
31    NODE_FIELD = 8
32    NODE_RAW_FIELD = 9
33    NODE_CALL = 10
34    NODE_ARGUMENTS = 11
35    NODE_FIRST_NON_EMPTY = 12
36    NODE_FOR = 13
37    NODE_GLOBALS = 14
38    NODE_SET_GLOBALS = 15
39    NODE_CONTAINS = 16
40    NODE_BINARY_LOGOP = 17
41    NODE_UNARY_LOGOP = 18
42    NODE_BINARY_ARITHOP = 19
43    NODE_UNARY_ARITHOP = 20
44    NODE_PRINT = 21
45    NODE_BREAK = 22
46    NODE_CONTINUE = 23
47    NODE_RETURN = 24
48    NODE_CHARACTER = 25
49    NODE_STRCAT = 26
50
51    def __init__(self, line_number, name):
52        self.my_line_number = line_number
53        self.my_node_name = name
54
55    @property
56    def node_name(self):
57        return self.my_node_name
58
59    @property
60    def line_number(self):
61        return self.my_line_number
62
63
64class IfNode(Node):
65    def __init__(self, line_number, condition, then_part, else_part):
66        Node.__init__(self, line_number, 'if ...')
67        self.node_type = self.NODE_IF
68        self.condition = condition
69        self.then_part = then_part
70        self.else_part = else_part
71
72
73class ForNode(Node):
74    def __init__(self, line_number, variable, list_field_expr, separator, block):
75        Node.__init__(self, line_number, 'for ...:')
76        self.node_type = self.NODE_FOR
77        self.variable = variable
78        self.list_field_expr = list_field_expr
79        self.separator = separator
80        self.block = block
81
82
83class BreakNode(Node):
84    def __init__(self, line_number):
85        Node.__init__(self, line_number, 'break')
86        self.node_type = self.NODE_BREAK
87
88
89class ContinueNode(Node):
90    def __init__(self, line_number):
91        Node.__init__(self, line_number, 'continue')
92        self.node_type = self.NODE_CONTINUE
93
94
95class ReturnNode(Node):
96    def __init__(self, line_number, expr):
97        Node.__init__(self, line_number, 'return')
98        self.expr = expr
99        self.node_type = self.NODE_RETURN
100
101
102class AssignNode(Node):
103    def __init__(self, line_number, left, right):
104        Node.__init__(self, line_number, 'assign to ' + left)
105        self.node_type = self.NODE_ASSIGN
106        self.left = left
107        self.right = right
108
109
110class FunctionNode(Node):
111    def __init__(self, line_number, function_name, expression_list):
112        Node.__init__(self, line_number, function_name + '()')
113        self.node_type = self.NODE_FUNC
114        self.name = function_name
115        self.expression_list = expression_list
116
117
118class CallNode(Node):
119    def __init__(self, line_number, name, function, expression_list):
120        Node.__init__(self, line_number, 'call template: ' + name)
121        self.node_type = self.NODE_CALL
122        self.function = function
123        self.expression_list = expression_list
124
125
126class ArgumentsNode(Node):
127    def __init__(self, line_number, expression_list):
128        Node.__init__(self, line_number, 'arguments()')
129        self.node_type = self.NODE_ARGUMENTS
130        self.expression_list = expression_list
131
132
133class GlobalsNode(Node):
134    def __init__(self, line_number, expression_list):
135        Node.__init__(self, line_number, 'globals()')
136        self.node_type = self.NODE_GLOBALS
137        self.expression_list = expression_list
138
139
140class SetGlobalsNode(Node):
141    def __init__(self, line_number, expression_list):
142        Node.__init__(self, line_number, 'set_globals()')
143        self.node_type = self.NODE_SET_GLOBALS
144        self.expression_list = expression_list
145
146
147class StringCompareNode(Node):
148    def __init__(self, line_number, operator, left, right):
149        Node.__init__(self, line_number, 'comparision: ' + operator)
150        self.node_type = self.NODE_COMPARE_STRING
151        self.operator = operator
152        self.left = left
153        self.right = right
154
155
156class NumericCompareNode(Node):
157    def __init__(self, line_number, operator, left, right):
158        Node.__init__(self, line_number, 'comparison: ' + operator)
159        self.node_type = self.NODE_COMPARE_NUMERIC
160        self.operator = operator
161        self.left = left
162        self.right = right
163
164
165class LogopBinaryNode(Node):
166    def __init__(self, line_number, operator, left, right):
167        Node.__init__(self, line_number, 'binary operator: ' + operator)
168        self.node_type = self.NODE_BINARY_LOGOP
169        self.operator = operator
170        self.left = left
171        self.right = right
172
173
174class LogopUnaryNode(Node):
175    def __init__(self, line_number, operator, expr):
176        Node.__init__(self, line_number, 'unary operator: ' + operator)
177        self.node_type = self.NODE_UNARY_LOGOP
178        self.operator = operator
179        self.expr = expr
180
181
182class NumericBinaryNode(Node):
183    def __init__(self, line_number, operator, left, right):
184        Node.__init__(self, line_number, 'binary operator: ' + operator)
185        self.node_type = self.NODE_BINARY_ARITHOP
186        self.operator = operator
187        self.left = left
188        self.right = right
189
190
191class NumericUnaryNode(Node):
192    def __init__(self, line_number, operator, expr):
193        Node.__init__(self, line_number, 'unary operator: '+ operator)
194        self.node_type = self.NODE_UNARY_ARITHOP
195        self.operator = operator
196        self.expr = expr
197
198
199class ConstantNode(Node):
200    def __init__(self, line_number, value):
201        Node.__init__(self, line_number, 'constant: ' + value)
202        self.node_type = self.NODE_CONSTANT
203        self.value = value
204
205
206class VariableNode(Node):
207    def __init__(self, line_number, name):
208        Node.__init__(self, line_number, 'variable: ' + name)
209        self.node_type = self.NODE_RVALUE
210        self.name = name
211
212
213class FieldNode(Node):
214    def __init__(self, line_number, expression):
215        Node.__init__(self, line_number, 'field()')
216        self.node_type = self.NODE_FIELD
217        self.expression = expression
218
219
220class RawFieldNode(Node):
221    def __init__(self, line_number, expression, default=None):
222        Node.__init__(self, line_number, 'raw_field()')
223        self.node_type = self.NODE_RAW_FIELD
224        self.expression = expression
225        self.default = default
226
227
228class FirstNonEmptyNode(Node):
229    def __init__(self, line_number, expression_list):
230        Node.__init__(self, line_number, 'first_non_empty()')
231        self.node_type = self.NODE_FIRST_NON_EMPTY
232        self.expression_list = expression_list
233
234
235class ContainsNode(Node):
236    def __init__(self, line_number, arguments):
237        Node.__init__(self, line_number, 'contains()')
238        self.node_type = self.NODE_CONTAINS
239        self.value_expression = arguments[0]
240        self.test_expression = arguments[1]
241        self.match_expression = arguments[2]
242        self.not_match_expression = arguments[3]
243
244
245class PrintNode(Node):
246    def __init__(self, line_number, arguments):
247        Node.__init__(self, line_number, 'print')
248        self.node_type = self.NODE_PRINT
249        self.arguments = arguments
250
251
252class CharacterNode(Node):
253    def __init__(self, line_number, expression):
254        Node.__init__(self, line_number, 'character()')
255        self.node_type = self.NODE_CHARACTER
256        self.expression = expression
257
258
259class StrcatNode(Node):
260    def __init__(self, line_number, expression_list):
261        Node.__init__(self, line_number, 'strcat()')
262        self.node_type = self.NODE_STRCAT
263        self.expression_list = expression_list
264
265
266class _Parser:
267    LEX_OP = 1
268    LEX_ID = 2
269    LEX_CONST = 3
270    LEX_EOF = 4
271    LEX_STRING_INFIX = 5
272    LEX_NUMERIC_INFIX = 6
273    LEX_KEYWORD = 7
274    LEX_NEWLINE = 8
275
276    def error(self, message):
277        ln = None
278        try:
279            tval = "'" + self.prog[self.lex_pos-1][1] + "'"
280        except Exception:
281            tval = _('Unknown')
282        if self.lex_pos > 0 and self.lex_pos < self.prog_len:
283            location = tval
284            ln = self.line_number
285        else:
286            location = _('the end of the program')
287        if ln:
288            raise ValueError(_('{0}: {1} near {2} on line {3}').format(
289                                          'Formatter', message, location, ln))
290        else:
291            raise ValueError(_('{0}: {1} near {2}').format(
292                                          'Formatter', message, location))
293
294    def check_eol(self):
295        while self.lex_pos < len(self.prog) and self.prog[self.lex_pos] == self.LEX_NEWLINE:
296            self.line_number += 1
297            self.consume()
298
299    def token(self):
300        self.check_eol()
301        try:
302            token = self.prog[self.lex_pos][1]
303            self.lex_pos += 1
304            return token
305        except:
306            return None
307
308    def consume(self):
309        self.lex_pos += 1
310
311    def token_op_is(self, op):
312        self.check_eol()
313        try:
314            token = self.prog[self.lex_pos]
315            return token[1] == op and token[0] == self.LEX_OP
316        except:
317            return False
318
319    def token_op_is_string_infix_compare(self):
320        self.check_eol()
321        try:
322            return self.prog[self.lex_pos][0] == self.LEX_STRING_INFIX
323        except:
324            return False
325
326    def token_op_is_numeric_infix_compare(self):
327        self.check_eol()
328        try:
329            return self.prog[self.lex_pos][0] == self.LEX_NUMERIC_INFIX
330        except:
331            return False
332
333    def token_is_newline(self):
334        return self.lex_pos < len(self.prog) and self.prog[self.lex_pos] == self.LEX_NEWLINE
335
336    def token_is_id(self):
337        self.check_eol()
338        try:
339            return self.prog[self.lex_pos][0] == self.LEX_ID
340        except:
341            return False
342
343    def token_is(self, candidate):
344        self.check_eol()
345        try:
346            token = self.prog[self.lex_pos]
347            return token[1] == candidate and token[0] == self.LEX_KEYWORD
348        except:
349            return False
350
351    def token_is_keyword(self):
352        self.check_eol()
353        try:
354            return self.prog[self.lex_pos][0] == self.LEX_KEYWORD
355        except:
356            return False
357
358    def token_is_constant(self):
359        self.check_eol()
360        try:
361            return self.prog[self.lex_pos][0] == self.LEX_CONST
362        except:
363            return False
364
365    def token_is_eof(self):
366        self.check_eol()
367        try:
368            return self.prog[self.lex_pos][0] == self.LEX_EOF
369        except:
370            return True
371
372    def token_text(self):
373        self.check_eol()
374        try:
375            return self.prog[self.lex_pos][1]
376        except:
377            return _("'End of program'")
378
379    def program(self, parent, funcs, prog):
380        self.line_number = 1
381        self.lex_pos = 0
382        self.parent = parent
383        self.funcs = funcs
384        self.func_names = frozenset(set(self.funcs.keys()))
385        self.prog = prog[0]
386        self.prog_len = len(self.prog)
387        if prog[1] != '':
388            self.error(_("Failed to scan program. Invalid input '{0}'").format(prog[1]))
389        tree = self.expression_list()
390        if not self.token_is_eof():
391            self.error(_("Expected end of program, found '{0}'").format(self.token_text()))
392        return tree
393
394    def expression_list(self):
395        expr_list = []
396        while True:
397            while self.token_is_newline():
398                self.line_number += 1
399                self.consume()
400            if self.token_is_eof():
401                break
402            expr_list.append(self.top_expr())
403            if self.token_op_is(';'):
404                self.consume()
405            else:
406                break
407        return expr_list
408
409    def if_expression(self):
410        self.consume()
411        line_number = self.line_number
412        condition = self.top_expr()
413        if not self.token_is('then'):
414            self.error(_("{0} statement: expected '{1}', "
415                         "found '{2}'").format('if', 'then', self.token_text()))
416        self.consume()
417        then_part = self.expression_list()
418        if self.token_is('elif'):
419            return IfNode(line_number, condition, then_part, [self.if_expression(),])
420        if self.token_is('else'):
421            self.consume()
422            else_part = self.expression_list()
423        else:
424            else_part = None
425        if not self.token_is('fi'):
426            self.error(_("{0} statement: expected '{1}', "
427                         "found '{2}'").format('if', 'fi', self.token_text()))
428        self.consume()
429        return IfNode(line_number, condition, then_part, else_part)
430
431    def for_expression(self):
432        line_number = self.line_number
433        self.consume()
434        if not self.token_is_id():
435            self.error(_("'{0}' statement: expected an identifier").format('for'))
436        variable = self.token()
437        if not self.token_is('in'):
438            self.error(_("{0} statement: expected '{1}', "
439                         "found '{2}'").format('for', 'in', self.token_text()))
440        self.consume()
441        list_expr = self.top_expr()
442        if self.token_is('separator'):
443            self.consume()
444            separator = self.expr()
445        else:
446            separator = None
447        if not self.token_op_is(':'):
448            self.error(_("{0} statement: expected '{1}', "
449                         "found '{2}'").format('for', ':', self.token_text()))
450        self.consume()
451        block = self.expression_list()
452        if not self.token_is('rof'):
453            self.error(_("{0} statement: expected '{1}', "
454                         "found '{2}'").format('for', 'rof', self.token_text()))
455        self.consume()
456        return ForNode(line_number, variable, list_expr, separator, block)
457
458    def top_expr(self):
459        return self.or_expr()
460
461    def or_expr(self):
462        left = self.and_expr()
463        while self.token_op_is('||'):
464            self.consume()
465            right = self.and_expr()
466            left = LogopBinaryNode(self.line_number, 'or', left, right)
467        return left
468
469    def and_expr(self):
470        left = self.not_expr()
471        while self.token_op_is('&&'):
472            self.consume()
473            right = self.not_expr()
474            left = LogopBinaryNode(self.line_number, 'and', left, right)
475        return left
476
477    def not_expr(self):
478        if self.token_op_is('!'):
479            self.consume()
480            return LogopUnaryNode(self.line_number, 'not', self.not_expr())
481        return self.compare_expr()
482
483    def compare_expr(self):
484        left = self.add_subtract_expr()
485        if (self.token_op_is_string_infix_compare() or
486                self.token_is('in') or self.token_is('inlist')):
487            operator = self.token()
488            return StringCompareNode(self.line_number, operator, left, self.add_subtract_expr())
489        if self.token_op_is_numeric_infix_compare():
490            operator = self.token()
491            return NumericCompareNode(self.line_number, operator, left, self.add_subtract_expr())
492        return left
493
494    def add_subtract_expr(self):
495        left = self.times_divide_expr()
496        while self.token_op_is('+') or self.token_op_is('-'):
497            operator = self.token()
498            right = self.times_divide_expr()
499            left = NumericBinaryNode(self.line_number, operator, left, right)
500        return left
501
502    def times_divide_expr(self):
503        left = self.unary_plus_minus_expr()
504        while self.token_op_is('*') or self.token_op_is('/'):
505            operator = self.token()
506            right = self.unary_plus_minus_expr()
507            left = NumericBinaryNode(self.line_number, operator, left, right)
508        return left
509
510    def unary_plus_minus_expr(self):
511        if self.token_op_is('+'):
512            self.consume()
513            return NumericUnaryNode(self.line_number, '+', self.unary_plus_minus_expr())
514        if self.token_op_is('-'):
515            self.consume()
516            return NumericUnaryNode(self.line_number, '-', self.unary_plus_minus_expr())
517        return self.expr()
518
519    def call_expression(self, name, arguments):
520        subprog = self.funcs[name].cached_parse_tree
521        if subprog is None:
522            text = self.funcs[name].program_text
523            if not text.startswith('program:'):
524                self.error(_("A stored template must begin with '{0}'").format('program:'))
525            text = text[len('program:'):]
526            subprog = _Parser().program(self, self.funcs,
527                                        self.parent.lex_scanner.scan(text))
528            self.funcs[name].cached_parse_tree = subprog
529        return CallNode(self.line_number, name, subprog, arguments)
530
531    # {keyword: tuple(preprocessor, node builder) }
532    keyword_nodes = {
533            'if':       (lambda self:None, if_expression),
534            'for':      (lambda self:None, for_expression),
535            'break':    (lambda self: self.consume(), lambda self: BreakNode(self.line_number)),
536            'continue': (lambda self: self.consume(), lambda self: ContinueNode(self.line_number)),
537            'return':   (lambda self: self.consume(), lambda self: ReturnNode(self.line_number, self.expr())),
538    }
539
540    # {inlined_function_name: tuple(constraint on number of length, node builder) }
541    inlined_function_nodes = {
542        'field':            (lambda args: len(args) == 1,
543                             lambda ln, args: FieldNode(ln, args[0])),
544        'raw_field':        (lambda args: len(args) == 1,
545                             lambda ln, args: RawFieldNode(ln, *args)),
546        'test':             (lambda args: len(args) == 3,
547                             lambda ln, args: IfNode(ln, args[0], (args[1],), (args[2],))),
548        'first_non_empty':  (lambda args: len(args) >= 1,
549                             lambda ln, args: FirstNonEmptyNode(ln, args)),
550        'assign':           (lambda args: len(args) == 2 and len(args[0]) == 1 and args[0][0].node_type == Node.NODE_RVALUE,
551                             lambda ln, args: AssignNode(ln, args[0][0].name, args[1])),
552        'contains':         (lambda args: len(args) == 4,
553                             lambda ln, args: ContainsNode(ln, args)),
554        'character':        (lambda args: len(args) == 1,
555                             lambda ln, args: CharacterNode(ln, args[0])),
556        'print':            (lambda _: True,
557                             lambda ln, args: PrintNode(ln, args)),
558        'strcat':           (lambda _: True,
559                             lambda ln, args: StrcatNode(ln, args))
560    }
561
562    def expr(self):
563        if self.token_op_is('('):
564            self.consume()
565            rv = self.expression_list()
566            if not self.token_op_is(')'):
567                self.error(_("Expected '{0}', found '{1}'").format(')', self.token_text()))
568            self.consume()
569            return rv
570
571        # Check if we have a keyword-type expression
572        if self.token_is_keyword():
573            t = self.token_text()
574            kw_tuple = self.keyword_nodes.get(t, None)
575            if kw_tuple:
576                # These are keywords, so there can't be ambiguity between these,
577                # ids, and functions.
578                kw_tuple[0](self)
579                return kw_tuple[1](self)
580
581        # Not a keyword. Check if we have an id reference or a function call
582        if self.token_is_id():
583            # We have an identifier. Check if it is a shorthand field reference
584            line_number = self.line_number
585            id_ = self.token()
586            if len(id_) > 1 and id_[0] == '$':
587                if id_[1] == '$':
588                    return RawFieldNode(line_number, ConstantNode(self.line_number, id_[2:]))
589                return FieldNode(line_number, ConstantNode(self.line_number, id_[1:]))
590
591            # Do we have a function call?
592            if not self.token_op_is('('):
593                # Nope. We must have an lvalue (identifier) or an assignment
594                if self.token_op_is('='):
595                    # classic assignment statement
596                    self.consume()
597                    return AssignNode(line_number, id_, self.top_expr())
598                return VariableNode(line_number, id_)
599
600            # We have a function.
601            # Check if it is a known one. We do this here so error reporting is
602            # better, as it can identify the tokens near the problem.
603            id_ = id_.strip()
604            if id_ not in self.func_names:
605                self.error(_('Unknown function {0}').format(id_))
606
607            # Eat the opening paren, parse the argument list, then eat the closing paren
608            self.consume()
609            arguments = list()
610            while not self.token_op_is(')'):
611                # parse an argument expression (recursive call)
612                arguments.append(self.expression_list())
613                if not self.token_op_is(','):
614                    break
615                self.consume()
616            t = self.token()
617            if t != ')':
618                self.error(_("Expected a '{0}' for function call, "
619                             "found '{1}'").format(')', t))
620
621            # Check for an inlined function
622            function_tuple = self.inlined_function_nodes.get(id_, None)
623            if function_tuple and function_tuple[0](arguments):
624                return function_tuple[1](line_number, arguments)
625            # More complicated special cases
626            if id_ == 'arguments' or id_ == 'globals' or id_ == 'set_globals':
627                new_args = []
628                for arg_list in arguments:
629                    arg = arg_list[0]
630                    if arg.node_type not in (Node.NODE_ASSIGN, Node.NODE_RVALUE):
631                        self.error(_("Parameters to '{0}' must be "
632                                     "variables or assignments").format(id_))
633                    if arg.node_type == Node.NODE_RVALUE:
634                        arg = AssignNode(line_number, arg.name, ConstantNode(self.line_number, ''))
635                    new_args.append(arg)
636                if id_ == 'arguments':
637                    return ArgumentsNode(line_number, new_args)
638                if id_ == 'set_globals':
639                    return SetGlobalsNode(line_number, new_args)
640                return GlobalsNode(line_number, new_args)
641            # Check for calling a stored template
642            if id_ in self.func_names and not self.funcs[id_].is_python:
643                return self.call_expression(id_, arguments)
644            # We must have a reference to a formatter function. Check if
645            # the right number of arguments were supplied
646            cls = self.funcs[id_]
647            if cls.arg_count != -1 and len(arguments) != cls.arg_count:
648                self.error(_('Incorrect number of arguments for function {0}').format(id_))
649            return FunctionNode(line_number, id_, arguments)
650        elif self.token_is_constant():
651            # String or number
652            return ConstantNode(self.line_number, self.token())
653        else:
654            # Who knows what?
655            self.error(_("Expected an expression, found '{0}'").format(self.token_text()))
656
657
658class ExecutionBase(Exception):
659    def __init__(self, name):
660        super().__init__(_('{0} outside of for loop').format(name) if name else '')
661        self.value = ''
662
663    def set_value(self, v):
664        self.value = v
665
666    def get_value(self):
667        return self.value
668
669
670class ContinueExecuted(ExecutionBase):
671    def __init__(self):
672        super().__init__('continue')
673
674
675class BreakExecuted(ExecutionBase):
676    def __init__(self):
677        super().__init__('break')
678
679
680class ReturnExecuted(ExecutionBase):
681    def __init__(self):
682        super().__init__('return')
683
684
685class StopException(Exception):
686    def __init__(self):
687        super().__init__('Template evaluation stopped')
688
689
690class _Interpreter:
691    def error(self, message, line_number):
692        m = _('Interpreter: {0} - line number {1}').format(message, line_number)
693        raise ValueError(m)
694
695    def program(self, funcs, parent, prog, val, is_call=False, args=None,
696                global_vars=None, break_reporter=None):
697        self.parent = parent
698        self.parent_kwargs = parent.kwargs
699        self.parent_book = parent.book
700        self.funcs = funcs
701        self.locals = {'$':val}
702        self.override_line_number = None
703        self.global_vars = global_vars if isinstance(global_vars, dict) else {}
704        if break_reporter:
705            self.break_reporter = self.call_break_reporter
706            self.real_break_reporter = break_reporter
707        else:
708            self.break_reporter = None
709
710        try:
711            if is_call:
712                ret =  self.do_node_call(CallNode(1, prog, None), args=args)
713            else:
714                ret = self.expression_list(prog)
715        except ReturnExecuted as e:
716            ret = e.get_value()
717        return ret
718
719    def call_break_reporter(self, txt, val, line_number):
720        self.real_break_reporter(txt, val, self.locals,
721                                 self.override_line_number if self.override_line_number
722                                     else line_number)
723
724    def expression_list(self, prog):
725        val = ''
726        try:
727            for p in prog:
728                val = self.expr(p)
729        except (BreakExecuted, ContinueExecuted) as e:
730            e.set_value(val)
731            raise e
732        return val
733
734    INFIX_STRING_COMPARE_OPS = {
735        "==": lambda x, y: strcmp(x, y) == 0,
736        "!=": lambda x, y: strcmp(x, y) != 0,
737        "<": lambda x, y: strcmp(x, y) < 0,
738        "<=": lambda x, y: strcmp(x, y) <= 0,
739        ">": lambda x, y: strcmp(x, y) > 0,
740        ">=": lambda x, y: strcmp(x, y) >= 0,
741        "in": lambda x, y: re.search(x, y, flags=re.I),
742        "inlist": lambda x, y: list(filter(partial(re.search, x, flags=re.I),
743                                           [v.strip() for v in y.split(',') if v.strip()]))
744        }
745
746    def do_node_string_infix(self, prog):
747        try:
748            left = self.expr(prog.left)
749            right = self.expr(prog.right)
750            res = '1' if self.INFIX_STRING_COMPARE_OPS[prog.operator](left, right) else ''
751            if (self.break_reporter):
752                self.break_reporter(prog.node_name, res, prog.line_number)
753            return res
754        except (StopException, ValueError) as e:
755            raise e
756        except:
757            self.error(_("Error during string comparison: "
758                         "operator '{0}'").format(prog.operator), prog.line_number)
759
760    INFIX_NUMERIC_COMPARE_OPS = {
761        "==#": lambda x, y: x == y,
762        "!=#": lambda x, y: x != y,
763        "<#": lambda x, y: x < y,
764        "<=#": lambda x, y: x <= y,
765        ">#": lambda x, y: x > y,
766        ">=#": lambda x, y: x >= y,
767        }
768
769    def float_deal_with_none(self, v):
770        # Undefined values and the string 'None' are assumed to be zero.
771        # The reason for string 'None': raw_field returns it for undefined values
772        return float(v if v and v != 'None' else 0)
773
774    def do_node_numeric_infix(self, prog):
775        try:
776            left = self.float_deal_with_none(self.expr(prog.left))
777            right = self.float_deal_with_none(self.expr(prog.right))
778            res = '1' if self.INFIX_NUMERIC_COMPARE_OPS[prog.operator](left, right) else ''
779            if (self.break_reporter):
780                self.break_reporter(prog.node_name, res, prog.line_number)
781            return res
782        except (StopException, ValueError) as e:
783            raise e
784        except:
785            self.error(_("Value used in comparison is not a number: "
786                         "operator '{0}'").format(prog.operator), prog.line_number)
787
788    def do_node_if(self, prog):
789        line_number = prog.line_number
790        test_part = self.expr(prog.condition)
791        if self.break_reporter:
792            self.break_reporter("'if': condition value", test_part, line_number)
793        if test_part:
794            v = self.expression_list(prog.then_part)
795            if self.break_reporter:
796                self.break_reporter("'if': then-block value", v, line_number)
797            return v
798        elif prog.else_part:
799            v = self.expression_list(prog.else_part)
800            if self.break_reporter:
801                self.break_reporter("'if': else-block value", v, line_number)
802            return v
803        return ''
804
805    def do_node_rvalue(self, prog):
806        try:
807            if (self.break_reporter):
808                self.break_reporter(prog.node_name, self.locals[prog.name], prog.line_number)
809            return self.locals[prog.name]
810        except:
811            self.error(_("Unknown identifier '{0}'").format(prog.name), prog.line_number)
812
813    def do_node_func(self, prog):
814        args = list()
815        for arg in prog.expression_list:
816            # evaluate the expression (recursive call)
817            args.append(self.expr(arg))
818        # Evaluate the function.
819        id_ = prog.name.strip()
820        cls = self.funcs[id_]
821        res = cls.eval_(self.parent, self.parent_kwargs,
822                        self.parent_book, self.locals, *args)
823        if (self.break_reporter):
824            self.break_reporter(prog.node_name, res, prog.line_number)
825        return res
826
827    def do_node_call(self, prog, args=None):
828        if (self.break_reporter):
829            self.break_reporter(prog.node_name, _('before evaluating arguments'), prog.line_number)
830        if args is None:
831            args = []
832            for arg in prog.expression_list:
833                # evaluate the expression (recursive call)
834                args.append(self.expr(arg))
835        saved_locals = self.locals
836        self.locals = {}
837        for dex, v in enumerate(args):
838            self.locals['*arg_'+ str(dex)] = v
839        if (self.break_reporter):
840            self.break_reporter(prog.node_name, _('after evaluating arguments'), prog.line_number)
841            saved_line_number = self.override_line_number
842            self.override_line_number = (self.override_line_number if self.override_line_number
843                                         else prog.line_number)
844        else:
845            saved_line_number = None
846        try:
847            val = self.expression_list(prog.function)
848        except ReturnExecuted as e:
849            val = e.get_value()
850        self.override_line_number = saved_line_number
851        self.locals = saved_locals
852        if (self.break_reporter):
853            self.break_reporter(prog.node_name + _(' returned value'), val, prog.line_number)
854        return val
855
856    def do_node_arguments(self, prog):
857        for dex, arg in enumerate(prog.expression_list):
858            self.locals[arg.left] = self.locals.get('*arg_'+ str(dex), self.expr(arg.right))
859        if (self.break_reporter):
860            self.break_reporter(prog.node_name, '', prog.line_number)
861        return ''
862
863    def do_node_globals(self, prog):
864        res = ''
865        for arg in prog.expression_list:
866            res = self.locals[arg.left] = self.global_vars.get(arg.left, self.expr(arg.right))
867        if (self.break_reporter):
868            self.break_reporter(prog.node_name, res, prog.line_number)
869        return res
870
871    def do_node_set_globals(self, prog):
872        res = ''
873        for arg in prog.expression_list:
874            res = self.global_vars[arg.left] = self.locals.get(arg.left, self.expr(arg.right))
875        if (self.break_reporter):
876            self.break_reporter(prog.node_name, res, prog.line_number)
877        return res
878
879    def do_node_constant(self, prog):
880        if (self.break_reporter):
881            self.break_reporter(prog.node_name, prog.value, prog.line_number)
882        return prog.value
883
884    def do_node_field(self, prog):
885        try:
886            name = self.expr(prog.expression)
887            try:
888                res = self.parent.get_value(name, [], self.parent_kwargs)
889                if (self.break_reporter):
890                    self.break_reporter(prog.node_name, res, prog.line_number)
891                return res
892            except:
893                self.error(_("Unknown field '{0}'").format(name), prog.line_number)
894        except (StopException, ValueError) as e:
895            raise e
896        except:
897            self.error(_("Unknown field '{0}'").format('internal parse error'),
898                       prog.line_number)
899
900    def do_node_raw_field(self, prog):
901        try:
902            name = self.expr(prog.expression)
903            name = field_metadata.search_term_to_field_key(name)
904            res = getattr(self.parent_book, name, None)
905            if res is None and prog.default is not None:
906                res = self.expr(prog.default)
907                if (self.break_reporter):
908                    self.break_reporter(prog.node_name, res, prog.line_number)
909                return res
910            if res is not None:
911                if isinstance(res, list):
912                    fm = self.parent_book.metadata_for_field(name)
913                    if fm is None:
914                        res = ', '.join(res)
915                    else:
916                        res = fm['is_multiple']['list_to_ui'].join(res)
917                else:
918                    res = str(res)
919            else:
920                res = str(res)  # Should be the string "None"
921            if (self.break_reporter):
922                self.break_reporter(prog.node_name, res, prog.line_number)
923            return res
924        except (StopException, ValueError) as e:
925            raise e
926        except:
927            self.error(_("Unknown field '{0}'").format('internal parse error'),
928                       prog.line_number)
929
930    def do_node_assign(self, prog):
931        t = self.expr(prog.right)
932        self.locals[prog.left] = t
933        if (self.break_reporter):
934            self.break_reporter(prog.node_name, t, prog.line_number)
935        return t
936
937    def do_node_first_non_empty(self, prog):
938        for expr in prog.expression_list:
939            v = self.expr(expr)
940            if v:
941                if self.break_reporter:
942                    self.break_reporter(prog.node_name, v, prog.line_number)
943                return v
944        if (self.break_reporter):
945            self.break_reporter(prog.node_name, '', prog.line_number)
946        return ''
947
948    def do_node_strcat(self, prog):
949        res = ''.join([self.expr(expr) for expr in prog.expression_list])
950        if self.break_reporter:
951            self.break_reporter(prog.node_name, res, prog.line_number)
952        return res
953
954    def do_node_for(self, prog):
955        line_number = prog.line_number
956        try:
957            separator = ',' if prog.separator is None else self.expr(prog.separator)
958            v = prog.variable
959            f = self.expr(prog.list_field_expr)
960            res = getattr(self.parent_book, f, f)
961            if res is not None:
962                if not isinstance(res, list):
963                    res = [r.strip() for r in res.split(separator) if r.strip()]
964                ret = ''
965                if self.break_reporter:
966                    self.break_reporter("'for' list value", separator.join(res), line_number)
967                try:
968                    for x in res:
969                        try:
970                            self.locals[v] = x
971                            ret = self.expression_list(prog.block)
972                        except ContinueExecuted as e:
973                            ret = e.get_value()
974                except BreakExecuted as e:
975                    ret = e.get_value()
976                if (self.break_reporter):
977                    self.break_reporter("'for' block value", ret, line_number)
978            elif self.break_reporter:
979                # Shouldn't get here
980                self.break_reporter("'for' list value", '', line_number)
981                ret = ''
982            return ret
983        except (StopException, ValueError) as e:
984            raise e
985        except Exception as e:
986            self.error(_("Unhandled exception '{0}'").format(e), line_number)
987
988    def do_node_break(self, prog):
989        if (self.break_reporter):
990            self.break_reporter(prog.node_name, '', prog.line_number)
991        raise BreakExecuted()
992
993    def do_node_continue(self, prog):
994        if (self.break_reporter):
995            self.break_reporter(prog.node_name, '', prog.line_number)
996        raise ContinueExecuted()
997
998    def do_node_return(self, prog):
999        v = self.expr(prog.expr)
1000        if (self.break_reporter):
1001            self.break_reporter(prog.node_name, v, prog.line_number)
1002        e = ReturnExecuted()
1003        e.set_value(v)
1004        raise e
1005
1006    def do_node_contains(self, prog):
1007        v = self.expr(prog.value_expression)
1008        t = self.expr(prog.test_expression)
1009        if re.search(t, v, flags=re.I):
1010            res = self.expr(prog.match_expression)
1011        else:
1012            res = self.expr(prog.not_match_expression)
1013        if (self.break_reporter):
1014            self.break_reporter(prog.node_name, res, prog.line_number)
1015        return res
1016
1017    LOGICAL_BINARY_OPS = {
1018        'and': lambda self, x, y: self.expr(x) and self.expr(y),
1019        'or': lambda self, x, y: self.expr(x) or self.expr(y),
1020    }
1021
1022    def do_node_logop(self, prog):
1023        try:
1024            res = ('1' if self.LOGICAL_BINARY_OPS[prog.operator](self, prog.left, prog.right) else '')
1025            if (self.break_reporter):
1026                self.break_reporter(prog.node_name, res, prog.line_number)
1027            return res
1028        except (StopException, ValueError) as e:
1029            raise e
1030        except:
1031            self.error(_("Error during operator evaluation: "
1032                         "operator '{0}'").format(prog.operator), prog.line_number)
1033
1034    LOGICAL_UNARY_OPS = {
1035        'not': lambda x: not x,
1036    }
1037
1038    def do_node_logop_unary(self, prog):
1039        try:
1040            expr = self.expr(prog.expr)
1041            res = ('1' if self.LOGICAL_UNARY_OPS[prog.operator](expr) else '')
1042            if (self.break_reporter):
1043                self.break_reporter(prog.node_name, res, prog.line_number)
1044            return res
1045        except (StopException, ValueError) as e:
1046            raise e
1047        except:
1048            self.error(_("Error during operator evaluation: "
1049                         "operator '{0}'").format(prog.operator), prog.line_number)
1050
1051    ARITHMETIC_BINARY_OPS = {
1052        '+': lambda x, y: x + y,
1053        '-': lambda x, y: x - y,
1054        '*': lambda x, y: x * y,
1055        '/': lambda x, y: x / y,
1056    }
1057
1058    def do_node_binary_arithop(self, prog):
1059        try:
1060            answer = self.ARITHMETIC_BINARY_OPS[prog.operator](
1061                            self.float_deal_with_none(self.expr(prog.left)),
1062                            self.float_deal_with_none(self.expr(prog.right)))
1063            res = str(answer if modf(answer)[0] != 0 else int(answer))
1064            if (self.break_reporter):
1065                self.break_reporter(prog.node_name, res, prog.line_number)
1066            return res
1067        except (StopException, ValueError) as e:
1068            raise e
1069        except:
1070            self.error(_("Error during operator evaluation: "
1071                         "operator '{0}'").format(prog.operator), prog.line_number)
1072
1073    ARITHMETIC_UNARY_OPS = {
1074        '+': lambda x: x,
1075        '-': lambda x: -x,
1076    }
1077
1078    def do_node_unary_arithop(self, prog):
1079        try:
1080            expr = self.ARITHMETIC_UNARY_OPS[prog.operator](float(self.expr(prog.expr)))
1081            res = str(expr if modf(expr)[0] != 0 else int(expr))
1082            if (self.break_reporter):
1083                self.break_reporter(prog.node_name, res, prog.line_number)
1084            return res
1085        except (StopException, ValueError) as e:
1086            raise e
1087        except:
1088            self.error(_("Error during operator evaluation: "
1089                         "operator '{0}'").format(prog.operator), prog.line_number)
1090
1091    characters = {
1092        'return':    '\r',
1093        'newline':   '\n',
1094        'tab':       '\t',
1095        'backslash': '\\',
1096    }
1097
1098    def do_node_character(self, prog):
1099        try:
1100            key = self.expr(prog.expression)
1101            ret = self.characters.get(key, None)
1102            if ret is None:
1103                self.error(_("Function {0}: invalid character name '{1}")
1104                           .format('character', key), prog.line_number)
1105            if (self.break_reporter):
1106                self.break_reporter(prog.node_name, ret, prog.line_number)
1107        except (StopException, ValueError) as e:
1108            raise e
1109        return ret
1110
1111    def do_node_print(self, prog):
1112        res = []
1113        for arg in prog.arguments:
1114            res.append(self.expr(arg))
1115        print(res)
1116        return res[0] if res else ''
1117
1118    NODE_OPS = {
1119        Node.NODE_IF:             do_node_if,
1120        Node.NODE_ASSIGN:         do_node_assign,
1121        Node.NODE_CONSTANT:       do_node_constant,
1122        Node.NODE_RVALUE:         do_node_rvalue,
1123        Node.NODE_FUNC:           do_node_func,
1124        Node.NODE_FIELD:          do_node_field,
1125        Node.NODE_RAW_FIELD:      do_node_raw_field,
1126        Node.NODE_COMPARE_STRING: do_node_string_infix,
1127        Node.NODE_COMPARE_NUMERIC:do_node_numeric_infix,
1128        Node.NODE_ARGUMENTS:      do_node_arguments,
1129        Node.NODE_CALL:           do_node_call,
1130        Node.NODE_FIRST_NON_EMPTY:do_node_first_non_empty,
1131        Node.NODE_FOR:            do_node_for,
1132        Node.NODE_GLOBALS:        do_node_globals,
1133        Node.NODE_SET_GLOBALS:    do_node_set_globals,
1134        Node.NODE_CONTAINS:       do_node_contains,
1135        Node.NODE_BINARY_LOGOP:   do_node_logop,
1136        Node.NODE_UNARY_LOGOP:    do_node_logop_unary,
1137        Node.NODE_BINARY_ARITHOP: do_node_binary_arithop,
1138        Node.NODE_UNARY_ARITHOP:  do_node_unary_arithop,
1139        Node.NODE_PRINT:          do_node_print,
1140        Node.NODE_BREAK:          do_node_break,
1141        Node.NODE_CONTINUE:       do_node_continue,
1142        Node.NODE_RETURN:         do_node_return,
1143        Node.NODE_CHARACTER:      do_node_character,
1144        Node.NODE_STRCAT:         do_node_strcat,
1145        }
1146
1147    def expr(self, prog):
1148        try:
1149            if isinstance(prog, list):
1150                return self.expression_list(prog)
1151            return self.NODE_OPS[prog.node_type](self, prog)
1152        except (ValueError, ExecutionBase, StopException) as e:
1153            raise e
1154        except Exception as e:
1155            if (DEBUG):
1156                traceback.print_exc()
1157            self.error(_("Internal error evaluating an expression: '{0}'").format(str(e)),
1158                       prog.line_number)
1159
1160
1161class TemplateFormatter(string.Formatter):
1162    '''
1163    Provides a format function that substitutes '' for any missing value
1164    '''
1165
1166    _validation_string = 'This Is Some Text THAT SHOULD be LONG Enough.%^&*'
1167
1168    # Dict to do recursion detection. It is up to the individual get_value
1169    # method to use it. It is cleared when starting to format a template
1170    composite_values = {}
1171
1172    def __init__(self):
1173        string.Formatter.__init__(self)
1174        self.book = None
1175        self.kwargs = None
1176        self.strip_results = True
1177        self.column_name = None
1178        self.template_cache = None
1179        self.global_vars = {}
1180        self.locals = {}
1181        self.funcs = formatter_functions().get_functions()
1182        self._interpreters = []
1183        self._template_parser = None
1184        self.recursion_stack = []
1185        self.recursion_level = -1
1186
1187    def _do_format(self, val, fmt):
1188        if not fmt or not val:
1189            return val
1190        if val == self._validation_string:
1191            val = '0'
1192        typ = fmt[-1]
1193        if typ == 's':
1194            pass
1195        elif 'bcdoxXn'.find(typ) >= 0:
1196            try:
1197                val = int(val)
1198            except Exception:
1199                raise ValueError(
1200                    _('format: type {0} requires an integer value, got {1}').format(typ, val))
1201        elif 'eEfFgGn%'.find(typ) >= 0:
1202            try:
1203                val = float(val)
1204            except:
1205                raise ValueError(
1206                    _('format: type {0} requires a decimal (float) value, got {1}').format(typ, val))
1207        return str(('{0:'+fmt+'}').format(val))
1208
1209    def _explode_format_string(self, fmt):
1210        try:
1211            matches = self.format_string_re.match(fmt)
1212            if matches is None or matches.lastindex != 3:
1213                return fmt, '', ''
1214            return matches.groups()
1215        except:
1216            if DEBUG:
1217                traceback.print_exc()
1218            return fmt, '', ''
1219
1220    format_string_re = re.compile(r'^(.*)\|([^\|]*)\|(.*)$', re.DOTALL)
1221    compress_spaces = re.compile(r'\s+')
1222    backslash_comma_to_comma = re.compile(r'\\,')
1223
1224    arg_parser = re.Scanner([
1225                (r',', lambda x,t: ''),
1226                (r'.*?((?<!\\),)', lambda x,t: t[:-1]),
1227                (r'.*?\)', lambda x,t: t[:-1]),
1228        ])
1229
1230    # ################# Template language lexical analyzer ######################
1231
1232    lex_scanner = re.Scanner([
1233            (r'(==#|!=#|<=#|<#|>=#|>#)', lambda x,t: (_Parser.LEX_NUMERIC_INFIX, t)),  # noqa
1234            (r'(==|!=|<=|<|>=|>)',       lambda x,t: (_Parser.LEX_STRING_INFIX, t)),  # noqa
1235            (r'(if|then|else|elif|fi)\b',lambda x,t: (_Parser.LEX_KEYWORD, t)),  # noqa
1236            (r'(for|in|rof|separator)\b',lambda x,t: (_Parser.LEX_KEYWORD, t)),  # noqa
1237            (r'(break|continue)\b',      lambda x,t: (_Parser.LEX_KEYWORD, t)),  # noqa
1238            (r'(return|inlist)\b',       lambda x,t: (_Parser.LEX_KEYWORD, t)),  # noqa
1239            (r'(\|\||&&|!)',             lambda x,t: (_Parser.LEX_OP, t)),  # noqa
1240            (r'[(),=;:\+\-*/]',          lambda x,t: (_Parser.LEX_OP, t)),  # noqa
1241            (r'-?[\d\.]+',               lambda x,t: (_Parser.LEX_CONST, t)),  # noqa
1242            (r'\$\$?#?\w+',              lambda x,t: (_Parser.LEX_ID, t)),  # noqa
1243            (r'\$',                      lambda x,t: (_Parser.LEX_ID, t)),  # noqa
1244            (r'\w+',                     lambda x,t: (_Parser.LEX_ID, t)),  # noqa
1245            (r'".*?((?<!\\)")',          lambda x,t: (_Parser.LEX_CONST, t[1:-1])),  # noqa
1246            (r'\'.*?((?<!\\)\')',        lambda x,t: (_Parser.LEX_CONST, t[1:-1])),  # noqa
1247            (r'\n#.*?(?:(?=\n)|$)',      lambda x,t: _Parser.LEX_NEWLINE),  # noqa
1248            (r'\s',                      lambda x,t: _Parser.LEX_NEWLINE if t == '\n' else None),  # noqa
1249        ], flags=re.DOTALL)
1250
1251    def _eval_program(self, val, prog, column_name, global_vars, break_reporter):
1252        if column_name is not None and self.template_cache is not None:
1253            tree = self.template_cache.get(column_name, None)
1254            if not tree:
1255                tree = self.gpm_parser.program(self, self.funcs, self.lex_scanner.scan(prog))
1256                self.template_cache[column_name] = tree
1257        else:
1258            tree = self.gpm_parser.program(self, self.funcs, self.lex_scanner.scan(prog))
1259        return self.gpm_interpreter.program(self.funcs, self, tree, val,
1260                                global_vars=global_vars, break_reporter=break_reporter)
1261
1262    def _eval_sfm_call(self, template_name, args, global_vars):
1263        func = self.funcs[template_name]
1264        tree = func.cached_parse_tree
1265        if tree is None:
1266            tree = self.gpm_parser.program(self, self.funcs,
1267                           self.lex_scanner.scan(func.program_text[len('program:'):]))
1268            func.cached_parse_tree = tree
1269        return self.gpm_interpreter.program(self.funcs, self, tree, None,
1270                                            is_call=True, args=args,
1271                                            global_vars=global_vars)
1272    # ################# Override parent classes methods #####################
1273
1274    def get_value(self, key, args, kwargs):
1275        raise Exception('get_value must be implemented in the subclass')
1276
1277    def format_field(self, val, fmt):
1278        # ensure we are dealing with a string.
1279        if isinstance(val, numbers.Number):
1280            if val:
1281                val = str(val)
1282            else:
1283                val = ''
1284        # Handle conditional text
1285        fmt, prefix, suffix = self._explode_format_string(fmt)
1286
1287        # Handle functions
1288        # First see if we have a functional-style expression
1289        if fmt.startswith('\''):
1290            p = 0
1291        else:
1292            p = fmt.find(':\'')
1293            if p >= 0:
1294                p += 1
1295        if p >= 0 and fmt[-1] == '\'':
1296            val = self._eval_program(val, fmt[p+1:-1], None, self.global_vars, None)
1297            colon = fmt[0:p].find(':')
1298            if colon < 0:
1299                dispfmt = ''
1300            else:
1301                dispfmt = fmt[0:colon]
1302        else:
1303            # check for old-style function references
1304            p = fmt.find('(')
1305            dispfmt = fmt
1306            if p >= 0 and fmt[-1] == ')':
1307                colon = fmt[0:p].find(':')
1308                if colon < 0:
1309                    dispfmt = ''
1310                    colon = 0
1311                else:
1312                    dispfmt = fmt[0:colon]
1313                    colon += 1
1314
1315                fname = fmt[colon:p].strip()
1316                if fname in self.funcs:
1317                    func = self.funcs[fname]
1318                    if func.arg_count == 2:
1319                        # only one arg expected. Don't bother to scan. Avoids need
1320                        # for escaping characters
1321                        args = [fmt[p+1:-1]]
1322                    else:
1323                        args = self.arg_parser.scan(fmt[p+1:])[0]
1324                        args = [self.backslash_comma_to_comma.sub(',', a) for a in args]
1325                    if not func.is_python:
1326                        args.insert(0, val)
1327                        val = self._eval_sfm_call(fname, args, self.global_vars)
1328                    else:
1329                        if (func.arg_count == 1 and (len(args) != 1 or args[0])) or \
1330                                (func.arg_count > 1 and func.arg_count != len(args)+1):
1331                            raise ValueError(
1332                                _('Incorrect number of arguments for function {0}').format(fname))
1333                        if func.arg_count == 1:
1334                            val = func.eval_(self, self.kwargs, self.book, self.locals, val)
1335                            if self.strip_results:
1336                                val = val.strip()
1337                        else:
1338                            val = func.eval_(self, self.kwargs, self.book, self.locals, val, *args)
1339                            if self.strip_results:
1340                                val = val.strip()
1341                else:
1342                    return _('%s: unknown function')%fname
1343        if val:
1344            val = self._do_format(val, dispfmt)
1345        if not val:
1346            return ''
1347        return prefix + val + suffix
1348
1349    def evaluate(self, fmt, args, kwargs, global_vars, break_reporter=None):
1350        if fmt.startswith('program:'):
1351            ans = self._eval_program(kwargs.get('$', None), fmt[8:],
1352                                     self.column_name, global_vars, break_reporter)
1353        else:
1354            ans = self.vformat(fmt, args, kwargs)
1355            if self.strip_results:
1356                ans = self.compress_spaces.sub(' ', ans)
1357        if self.strip_results:
1358            ans = ans.strip(' ')
1359        return ans
1360
1361    # It is possible for a template to indirectly invoke other templates by
1362    # doing field references of composite columns. If this happens then the
1363    # reference can use different parameters when calling safe_format(). Because
1364    # the parameters are saved as instance variables they can possibly affect
1365    # the 'calling' template. To avoid this problem, save the current formatter
1366    # state when recursion is detected. There is no point in saving the level
1367    # 0 state.
1368
1369    def save_state(self):
1370        self.recursion_level += 1
1371        if self.recursion_level > 0:
1372            return (
1373                (self.strip_results,
1374                 self.column_name,
1375                 self.template_cache,
1376                 self.kwargs,
1377                 self.book,
1378                 self.global_vars,
1379                 self.funcs,
1380                 self.locals))
1381        else:
1382            return None
1383
1384    def restore_state(self, state):
1385        self.recursion_level -= 1
1386        if state is not None:
1387            (self.strip_results,
1388             self.column_name,
1389             self.template_cache,
1390             self.kwargs,
1391             self.book,
1392             self.global_vars,
1393             self.funcs,
1394             self.locals) = state
1395
1396    # Allocate an interpreter if the formatter encounters a GPM or TPM template.
1397    # We need to allocate additional interpreters if there is composite recursion
1398    # so that the templates are evaluated by separate instances. It is OK to
1399    # reuse already-allocated interpreters because their state is initialized on
1400    # call. As a side effect, no interpreter is instantiated if no TPM/GPM
1401    # template is encountered.
1402
1403    @property
1404    def gpm_interpreter(self):
1405        while len(self._interpreters) <= self.recursion_level:
1406            self._interpreters.append(_Interpreter())
1407        return self._interpreters[self.recursion_level]
1408
1409    # Allocate a parser if needed. Parsers cannot recurse so one is sufficient.
1410
1411    @property
1412    def gpm_parser(self):
1413        if self._template_parser is None:
1414            self._template_parser = _Parser()
1415        return self._template_parser
1416
1417    # ######### a formatter that throws exceptions ############
1418
1419    def unsafe_format(self, fmt, kwargs, book, strip_results=True, global_vars=None):
1420        state = self.save_state()
1421        try:
1422            self.strip_results = strip_results
1423            self.column_name = self.template_cache = None
1424            self.kwargs = kwargs
1425            self.book = book
1426            self.composite_values = {}
1427            self.locals = {}
1428            self.global_vars = global_vars if isinstance(global_vars, dict) else {}
1429            return self.evaluate(fmt, [], kwargs, self.global_vars)
1430        finally:
1431            self.restore_state(state)
1432
1433    # ######### a formatter guaranteed not to throw an exception ############
1434
1435    def safe_format(self, fmt, kwargs, error_value, book,
1436                    column_name=None, template_cache=None,
1437                    strip_results=True, template_functions=None,
1438                    global_vars=None, break_reporter=None):
1439        state = self.save_state()
1440        if self.recursion_level == 0:
1441            # Initialize the composite values dict if this is the base-level
1442            # call. Recursive calls will use the same dict.
1443            self.composite_values = {}
1444        try:
1445            self.strip_results = strip_results
1446            self.column_name = column_name
1447            self.template_cache = template_cache
1448            self.kwargs = kwargs
1449            self.book = book
1450            self.global_vars = global_vars if isinstance(global_vars, dict) else {}
1451            if template_functions:
1452                self.funcs = template_functions
1453            else:
1454                self.funcs = formatter_functions().get_functions()
1455            self.locals = {}
1456            try:
1457                ans = self.evaluate(fmt, [], kwargs, self.global_vars, break_reporter=break_reporter)
1458            except StopException as e:
1459                ans = error_message(e)
1460            except Exception as e:
1461                if DEBUG:  # and getattr(e, 'is_locking_error', False):
1462                    traceback.print_exc()
1463                    if column_name:
1464                        prints('Error evaluating column named:', column_name)
1465                ans = error_value + ' ' + error_message(e)
1466            return ans
1467        finally:
1468            self.restore_state(state)
1469
1470
1471class ValidateFormatter(TemplateFormatter):
1472    '''
1473    Provides a formatter that substitutes the validation string for every value
1474    '''
1475
1476    def get_value(self, key, args, kwargs):
1477        return self._validation_string
1478
1479    def validate(self, x):
1480        from calibre.ebooks.metadata.book.base import Metadata
1481        return self.safe_format(x, {}, 'VALIDATE ERROR', Metadata(''))
1482
1483
1484validation_formatter = ValidateFormatter()
1485
1486
1487class EvalFormatter(TemplateFormatter):
1488    '''
1489    A template formatter that uses a simple dict instead of an mi instance
1490    '''
1491
1492    def get_value(self, key, args, kwargs):
1493        if key == '':
1494            return ''
1495        key = key.lower()
1496        return kwargs.get(key, _('No such variable {0}').format(key))
1497
1498
1499# DEPRECATED. This is not thread safe. Do not use.
1500eval_formatter = EvalFormatter()
1501