| #!/usr/bin/env python |
| # Copyright 2019 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| '''python %(prog)s [options] |
| Generate mapping from IPP attribute name to appropriate handler based on its |
| type as described in IPP registration files.''' |
| |
| import argparse |
| import csv |
| import re |
| |
| # We skip attributes that are already implemented in print preview. |
| NOOP_ATTRS = [ |
| 'copies', |
| 'job-hold-until', |
| 'job-copies', |
| 'job-password', |
| 'job-password-encryption', |
| 'media', |
| 'media-col', |
| 'multiple-document-handling', |
| 'number-up', |
| 'orientation-requested', |
| 'page-ranges', |
| 'presentation-direction-number-up', |
| 'print-color-mode', |
| 'print-scaling', |
| 'printer-resolution', |
| 'sheet-collate', |
| 'sides', |
| ] |
| |
| # RFC 8011 (5.1.4) requires keywords to start with a letter. There's however at |
| # least one keyword that starts with a digit. |
| KEYWORD_PATTERN = re.compile('^[a-z1-9][a-z0-9\._-]*$') |
| |
| HANDLER_HEADER = """// DO NOT MODIFY |
| // Generated by printing/backend/tools/code_generator.py |
| |
| #include "printing/backend/ipp_handler_map.h" |
| |
| #include "base/bind.h" |
| #include "printing/backend/ipp_handlers.h" |
| |
| namespace printing { |
| |
| HandlerMap GenerateHandlers() { |
| HandlerMap result; |
| """ |
| |
| HANDLER_FOOTER = """ return result; |
| } |
| |
| } // namespace printing |
| """ |
| |
| L10N_HEADER = """// DO NOT MODIFY |
| // Generated by printing/backend/tools/code_generator.py |
| |
| #include "chrome/common/printing/ipp_l10n.h" |
| |
| #include "base/no_destructor.h" |
| #include "components/strings/grit/components_strings.h" |
| |
| const std::map<base::StringPiece, int>& CapabilityLocalizationMap() { |
| static const base::NoDestructor<std::map<base::StringPiece, int>> l10n_map({ |
| """ |
| |
| L10N_FOOTER = """ }); |
| return *l10n_map; |
| } |
| """ |
| |
| |
| def get_handler(syntax, name): |
| if syntax.startswith('1setOf'): |
| handler = get_handler(syntax[6:].strip(), name) |
| if handler == 'EnumHandler': |
| # 3 is 'none' value for finishings. |
| # IPP enums always use positive numbers so we can use 0 in other cases. |
| default = 3 if name.endswith('finishings') else 0 |
| return 'MultivalueEnumHandler, %d' % default |
| |
| # TODO(crbug.com/964919): Add other multivalue handlers. |
| return '' |
| |
| if syntax == 'collection': |
| # TODO(crbug.com/964919): Add collection handler. |
| return '' |
| |
| if syntax.startswith('type1') or syntax.startswith('type2'): |
| # ignore prefix |
| return get_handler(syntax[5:].strip(), name) |
| |
| if syntax.startswith('keyword'): |
| return 'KeywordHandler' |
| |
| if syntax.startswith('enum'): |
| return 'EnumHandler' |
| |
| if syntax == 'boolean': |
| return 'BooleanHandler' |
| |
| if syntax.startswith('integer'): |
| # TODO(crbug.com/964919): Add integer handler. |
| return 'NumberHandler' |
| |
| if syntax.startswith('name') or syntax.startswith('text'): |
| return 'TextHandler' |
| |
| return '' |
| |
| |
| # Remove annotations like '(obsolete)', '(deprecated)' etc. |
| def remove_annotation(keyword): |
| parenthesis = keyword.find('(') |
| return keyword if parenthesis == -1 else keyword[:parenthesis].strip() |
| |
| |
| SPECIAL_CHARS = re.compile('[-/\.]') |
| |
| |
| def add_l10n(l10n_file, ipp_id, grit_id=None): |
| if not grit_id: |
| grit_id = ipp_id |
| |
| l10n_file.write(' {"%s", IDS_PRINT_%s},\n' % |
| (ipp_id, SPECIAL_CHARS.sub('_', grit_id.upper()))) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser(usage=__doc__) |
| parser.add_argument( |
| '-a', |
| '--attributes-file', |
| dest='attributes_file', |
| help='path to ipp-registrations-2.csv input file', |
| metavar='FILE', |
| required=True) |
| parser.add_argument( |
| '-k', |
| '--keyword-values-file', |
| dest='keyword_values_file', |
| help='path to ipp-registrations-4.csv input file', |
| metavar='FILE', |
| required=True) |
| parser.add_argument( |
| '-e', |
| '--enum-values-file', |
| dest='enum_values_file', |
| help='path to ipp-registrations-6.csv input file', |
| metavar='FILE', |
| required=True) |
| parser.add_argument( |
| '-i', |
| '--ipp-handler-map', |
| dest='ipp_handler_map', |
| help='path to ipp_handler_map.cc output file', |
| metavar='FILE') |
| parser.add_argument( |
| '-l', |
| '--localization-map', |
| dest='localization_map', |
| help='path to ipp_l10n.cc output file', |
| metavar='FILE') |
| args = parser.parse_args() |
| |
| if not (args.ipp_handler_map or args.localization_map): |
| parser.error('No output file selected') |
| |
| handlers = [] |
| supported_items = set() |
| with open(args.attributes_file, 'r') as attr_file: |
| attr_reader = csv.reader(attr_file) |
| |
| for attr in attr_reader: |
| # Filter out by attribute group. |
| if attr[0] != 'Job Template': |
| continue |
| |
| # Skip sub-attributes. |
| if attr[2] != '': |
| continue |
| |
| attr_name = remove_annotation(attr[1]) |
| # Skip duplicates |
| if attr_name in supported_items: |
| continue |
| |
| if not KEYWORD_PATTERN.match(attr_name): |
| print('Warning: attribute name %s is invalid' % attr_name) |
| continue |
| |
| syntax = attr[4] |
| handler = 'NoOpHandler' |
| if attr_name not in NOOP_ATTRS: |
| handler = get_handler(syntax.strip(), attr_name) |
| |
| if handler == '': |
| continue |
| |
| handlers.append((attr_name, handler)) |
| if handler != 'NoOpHandler': |
| supported_items.add(attr_name) |
| |
| if args.ipp_handler_map: |
| handler_file = open(args.ipp_handler_map, 'w') |
| handler_file.write(HANDLER_HEADER) |
| |
| for (attr_name, handler) in handlers: |
| handler_file.write(' result.emplace("%s", base::BindRepeating(&%s));\n' |
| % (attr_name, handler)) |
| |
| handler_file.write(HANDLER_FOOTER) |
| handler_file.close() |
| |
| if args.localization_map: |
| l10n_file = open(args.localization_map, 'w') |
| l10n_file.write(L10N_HEADER) |
| |
| for (attr_name, handler) in handlers: |
| if handler != 'NoOpHandler': |
| if args.localization_map and not handler.startswith('Multivalue'): |
| add_l10n(l10n_file, attr_name) |
| |
| with open(args.keyword_values_file, 'r') as keyword_file: |
| keyword_reader = csv.reader(keyword_file) |
| for keyword_item in keyword_reader: |
| attr_name = keyword_item[0] |
| if attr_name in supported_items: |
| keyword_value = remove_annotation(keyword_item[1]) |
| # TODO(crbug.com/964919): Also handle some plain English cases. |
| if KEYWORD_PATTERN.match(keyword_value): |
| l10n_key = '%s/%s' % (attr_name, keyword_value) |
| # Skip duplicates. |
| if l10n_key not in supported_items: |
| supported_items.add(l10n_key) |
| add_l10n(l10n_file, l10n_key) |
| |
| with open(args.enum_values_file, 'r') as enum_file: |
| enum_reader = csv.reader(enum_file) |
| for enum_item in enum_reader: |
| attr_name = enum_item[0] |
| if attr_name in supported_items: |
| enum_value = enum_item[1] |
| try: |
| int(enum_value) |
| l10n_key = attr_name + '/' + enum_value |
| # Skip duplicates and finishings 'none' value. |
| if l10n_key not in supported_items and l10n_key != 'finishings/3': |
| supported_items.add(l10n_key) |
| add_l10n(l10n_file, l10n_key, |
| attr_name + '_' + remove_annotation(enum_item[2])) |
| except ValueError: |
| # TODO(crbug.com/964919): Handle some plain English cases. |
| pass |
| |
| l10n_file.write(L10N_FOOTER) |
| l10n_file.close() |
| |
| |
| if __name__ == '__main__': |
| main() |