#!/usr/bin/env python
# Copyright 2019 The Chromium Authors
# 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
# Skip attributes that are already implemented in print preview plus job-priority (b/172208667).
NOOP_ATTRS = [
'copies',
'job-hold-until',
'job-copies',
'job-password',
'job-password-encryption',
'job-priority',
'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(r'^[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 <string_view>
#include "base/functional/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<std::string_view, int>& CapabilityLocalizationMap() {
static const base::NoDestructor<std::map<std::string_view, 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(r'[-/\.]')
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)
# media-source and media-type are only handled for localization because
# they're inside of the media-col collection and we don't handle
# collections otherwise.
supported_items.add("media-source")
add_l10n(l10n_file, "media-source")
supported_items.add("media-type")
add_l10n(l10n_file, "media-type")
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()