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