engine: Generate simple.xml with denylist

simple.xml was generated by allowlist (whitelist) and it will be
done by denylist (blacklist).

BUG=https://github.com/ibus/ibus/issues/2153
This commit is contained in:
fujiwarat 2020-08-21 09:07:39 +09:00
Родитель 6042974152
Коммит 508527daaf
3 изменённых файлов: 407 добавлений и 7 удалений

Просмотреть файл

@ -4,6 +4,7 @@
# #
# Copyright (c) 2010-2016, Google Inc. All rights reserved. # Copyright (c) 2010-2016, Google Inc. All rights reserved.
# Copyright (c) 2007-2016 Peng Huang <shawn.p.huang@gmail.com> # Copyright (c) 2007-2016 Peng Huang <shawn.p.huang@gmail.com>
# Copyright (c) 2013-2020 Takao Fujiwara <takao.fujiwara1@gmail.com>
# #
# This library is free software; you can redistribute it and/or # This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public # modify it under the terms of the GNU Lesser General Public
@ -78,20 +79,25 @@ component_DATA = \
componentdir = $(pkgdatadir)/component componentdir = $(pkgdatadir)/component
CLEANFILES = \ MAINTAINERCLEANFILES = \
simple.xml \ simple.xml \
$(NULL) $(NULL)
EXTRA_DIST = \ EXTRA_DIST = \
gensimple.py \
iso639converter.py \ iso639converter.py \
simple.xml.in \ simple.xml \
$(NULL) $(NULL)
simple.xml: simple.xml.in simple.xml:
$(AM_V_GEN) sed \ $(srcdir)/gensimple.py \
-e 's|@VERSION[@]|$(VERSION)|g' \ --input=$(datarootdir)/X11/xkb/rules/evdev.xml \
-e 's|@libexecdir[@]|$(libexecdir)|g' $< > $@.tmp && \ --output=$@ \
mv $@.tmp $@ --version=$(VERSION).`date '+%Y%m%d'` \
--exec-path=$(libexecdir)/ibus-engine-simple \
--iso-path=$(datarootdir)/xml/iso-codes/iso_639.xml \
--first-language \
$(NULL)
$(libibus): $(libibus):
$(MAKE) -C $(top_builddir)/src $(MAKE) -C $(top_builddir)/src

27
engine/denylist.txt Normal file
Просмотреть файл

@ -0,0 +1,27 @@
# vim:set fileencoding=utf-8 et sts=4 sw=4:
#
# ibus - Intelligent Input Bus for Linux / Unix OS
#
# Copyright © 2020 Takao Fujiwara <takao.fujiwara1@gmail.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
# This file is a deny list (black list) and used by gensimple.py.
# gensimple.py generates the engine list with evdev.xml and if an engine name
# is matched with any entries in this file, the engine is excluded from the
# engine list.
# Asterisk(*) character can be used to match any engines.
# E.g. xkb:cn:*:* excludes xkb:cn::zho and xkb:cn:mon_trad:mvf
xkb:cn:*:*
xkb:nec_vndr/jp:*:*

367
engine/gensimple.py Normal file → Executable file
Просмотреть файл

@ -0,0 +1,367 @@
#!/usr/bin/python
# vim:set fileencoding=utf-8 et sts=4 sw=4:
#
# ibus - Intelligent Input Bus for Linux / Unix OS
#
# Copyright © 2020 Takao Fujiwara <takao.fujiwara1@gmail.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
# This script generates simple.xml with /usr/share/X11/xkb/rules/evdev.xml,
# /usr/share/xml/iso-codes/iso_639.xml and denylist.txt
from xml.dom import minidom
from xml.sax import make_parser as sax_make_parser
from xml.sax.handler import feature_namespaces as sax_feature_namespaces
from xml.sax.saxutils import XMLFilterBase, XMLGenerator, escape
from xml.sax.xmlreader import AttributesImpl
from xml.sax._exceptions import SAXParseException
import codecs
import getopt
import io
import os
import sys
VERSION='0.1'
EVDEV_XML = '/usr/share/X11/xkb/rules/evdev.xml'
EXEC_PATH='/usr/lib/ibus-engine-simple'
ISO_PATH='/usr/share/xml/iso-codes/iso_639.xml'
PY3K = sys.version_info >= (3, 0)
if PY3K:
from io import StringIO
else:
# io.StringIO does not work with XMLGenerator
from cStringIO import StringIO
# iso_639.xml includes UTF-8
reload(sys)
sys.setdefaultencoding('utf-8')
def usage(prgname):
print('''\
%s Version %s
Usage:
%s [OPTION...]
Options:
-h, --help Show this message
-i, --input=EVDEV_XML Load EVDEV_XML file (default is:
%s)
-o, --output=FILE Output FILE (default is stdout)
-V, --version=VERSION Set IBus VERSION (default is %s)
-e, --exec-path=EXEC_PATH Set EXEC_PATH file (default is:
%s)
-I, --iso-path=ISO_PATH Load ISO_PATH file (default is:
%s)
-1, --first-language Pull first language only in language list
''' % (prgname, VERSION, prgname, EVDEV_XML, VERSION, EXEC_PATH, ISO_PATH))
class EvdevXML(XMLFilterBase):
def __init__(self, parser=None, downstream=None, iso639=None,
denylist=None, author=None, first=False):
XMLFilterBase.__init__(self, parser)
self.__downstream = downstream
self.__iso639 = iso639
self.__denylist = denylist
self.__author = author
self.__first = first
self.__is_layout = False
self.__is_description = False
self.__is_config_item = False
self.__is_variant = False
self.__is_iso639 = False
self.__is_name = False
self.__layout = ''
self.__description = ''
self.__variant = ''
self.__list_iso639 = []
def startDocument(self):
if self.__downstream:
self.__downstream.startDocument()
self.__downstream.startElement('engines', AttributesImpl({}))
def endDocument(self):
if self.__downstream:
self.__downstream.endElement('engines')
self.__downstream.endDocument()
def startElement(self, name, attrs):
if name == 'layout':
self.__is_layout = True
elif name == 'description':
self.__is_description = True
elif name == 'configItem':
self.__is_config_item = True
elif name == 'languageList':
self.__list_iso639 = []
elif name == 'iso639Id':
self.__is_iso639 = True
elif name == 'variant':
self.__is_variant = True
elif name == 'name':
self.__is_name = True
def endElement(self, name):
if name == 'layout':
self.__is_layout = False
self.__layout = ''
self.__description = ''
self.__variant = ''
self.__list_iso639 = []
elif name == 'description':
self.__is_description = False
elif name == 'configItem':
self.save()
self.__is_config_item = False
elif name == 'iso639Id':
self.__is_iso639 = False
elif name == 'variant':
self.__is_variant = False
elif name == 'name':
self.__is_name = False
def characters(self, text):
if self.__is_description:
self.__description = text
elif self.__is_name:
if self.__is_variant and self.__is_config_item:
self.__variant = text
elif self.__is_layout and self.__is_config_item:
self.__layout = text
elif self.__is_iso639:
self.__list_iso639.append(text)
def save(self):
if not self.__downstream:
return
for iso in self.__list_iso639:
do_deny = False
for [xkb, layout, variant, lang] in self.__denylist:
if xkb == 'xkb' \
and ( layout == self.__layout or layout == '*' ) \
and ( variant == self.__variant or variant == '*' ) \
and ( lang == iso or variant == '*' ):
do_deny = True
break
if do_deny:
continue
self.__downstream.startElement('engine', AttributesImpl({}))
self.__downstream.startElement('name', AttributesImpl({}))
name = 'xkb:%s:%s:%s' % (
self.__layout,
self.__variant,
iso
)
self.__downstream.characters(name)
self.__downstream.endElement('name')
self.__downstream.startElement('language', AttributesImpl({}))
iso639_1 = self.__iso639.code2to1(iso)
if iso639_1 != None:
iso = iso639_1
self.__downstream.characters(iso)
self.__downstream.endElement('language')
self.__downstream.startElement('license', AttributesImpl({}))
self.__downstream.characters('GPL')
self.__downstream.endElement('license')
if self.__author != None:
self.__downstream.startElement('author', AttributesImpl({}))
self.__downstream.characters(self.__author)
self.__downstream.endElement('author')
self.__downstream.startElement('layout', AttributesImpl({}))
self.__downstream.characters(self.__layout)
self.__downstream.endElement('layout')
self.__downstream.startElement('longname', AttributesImpl({}))
self.__downstream.characters(self.__description)
self.__downstream.endElement('longname')
self.__downstream.startElement('description', AttributesImpl({}))
self.__downstream.characters(self.__description)
self.__downstream.endElement('description')
self.__downstream.startElement('icon', AttributesImpl({}))
self.__downstream.characters('ibus-keyboard')
self.__downstream.endElement('icon')
self.__downstream.startElement('rank', AttributesImpl({}))
if self.__variant == '':
self.__downstream.characters('50')
else:
self.__downstream.characters('1')
self.__downstream.endElement('rank')
self.__downstream.endElement('engine')
if self.__first:
break
class GenerateEngineXML():
_NAME = 'org.freedesktop.IBus.Simple'
_DESCRIPTION = 'A table based simple engine'
_AUTHOR = 'Peng Huang <shawn.p.huang@gmail.com>'
_HOMEPAGE = 'https://github.com/ibus/ibus/wiki'
_DOMAIN = 'ibus'
def __init__(self, path, iso639=None, denylist='', version='', exec='',
first=False):
self.__path = path
self.__iso639 = iso639
self.__denylist = denylist
self.__version = version
self.__exec = exec
self.__first = first
self.__result = StringIO()
downstream = XMLGenerator(self.__result, 'utf-8')
self.__load(downstream)
def __load(self, downstream=None):
parser = sax_make_parser()
parser.setFeature(sax_feature_namespaces, 0)
self.__handler = EvdevXML(parser,
downstream,
self.__iso639,
self.__denylist,
self._AUTHOR,
self.__first)
parser.setContentHandler(self.__handler)
f = codecs.open(self.__path, 'r', encoding='utf-8')
try:
parser.parse(f)
except SAXParseException:
print('Error: Invalid file format: %s' % path)
finally:
f.close()
def write(self, output=None):
if output != None:
od = codecs.open(output, 'w', encoding='utf-8')
else:
if PY3K:
od = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
else:
od = codecs.getwriter('utf-8')(sys.stdout)
contents = self.__result.getvalue()
index = contents.find('<engines>')
if index >= 0:
author = escape(self._AUTHOR)
contents = '%s<component><name>%s</name>\
<description>%s</description><exec>%s</exec><version>%s</version>\
<author>%s</author><license>%s</license><homepage>%s</homepage>\
<textdomain>%s</textdomain>%s</component>' % (
contents[:index],
self._NAME, self._DESCRIPTION,
self.__exec, self.__version, author, 'GPL',
self._HOMEPAGE, self._DOMAIN, contents[index:] )
parsed = minidom.parseString(contents)
# format with indent and encoding attribute in header
xml = parsed.toprettyxml(indent=' ', encoding='utf-8')
# convert byte to str
od.write(str(xml, 'utf-8'))
#od.write(contents)
class ISO639XML(XMLFilterBase):
def __init__(self, parser=None):
self.__code2to1 = {}
self.__codetoname = {}
XMLFilterBase.__init__(self, parser)
def startElement(self, name, attrs):
if name != 'iso_639_entry':
return
n = attrs.get('name')
iso639_1 = attrs.get('iso_639_1_code')
iso639_2b = attrs.get('iso_639_2B_code')
iso639_2t = attrs.get('iso_639_2T_code')
if iso639_1 != None:
self.__codetoname[iso639_1] = n
if iso639_2b != None:
self.__code2to1[iso639_2b] = iso639_1
self.__codetoname[iso639_2b] = n
if iso639_2t != None and iso639_2b != iso639_2t:
self.__code2to1[iso639_2t] = iso639_1
self.__codetoname[iso639_2t] = n
def code2to1(self, iso639_2):
try:
return self.__code2to1[iso639_2]
except KeyError:
return None
def parse_iso639(path):
f = codecs.open(path, 'r', encoding='utf-8')
parser = sax_make_parser()
parser.setFeature(sax_feature_namespaces, 0)
handler = ISO639XML(parser)
parser.setContentHandler(handler)
try:
parser.parse(f)
except SAXParseException:
print('Error: Invalid file format: %s' % path)
finally:
f.close()
return handler
def parse_denylist(denyfile):
denylist = []
f = codecs.open(denyfile, 'r', encoding='utf-8')
for line in f.readlines():
if line == '\n' or line[0] == '#':
continue
line = line.rstrip()
entry = line.split(':')
if len(entry) != 4:
print('WARNING: format error: \'%s\' against \'%s\'' \
% (line, 'xkb:layout:variant:lang'))
continue
denylist.append(entry)
f.close()
return denylist
if __name__ == '__main__':
prgname = os.path.basename(sys.argv[0])
mydir = os.path.dirname(sys.argv[0])
try:
opts, args = getopt.getopt(sys.argv[1:],
'hi:o:V:e:I:1',
['help', 'input=', 'output=', 'version=',
'exec-path=', 'iso-path=',
'first-language'])
except getopt.GetoptError as err:
print(err)
usage(prgname)
sys.exit(2)
if len(args) > 0:
usage(prgname)
sys.exit(2)
input = EVDEV_XML
output = None
version=VERSION
exec_path=EXEC_PATH
iso_path=ISO_PATH
first=False
for opt, arg in opts:
if opt in ('-h', '--help'):
usage(prgname)
sys.exit()
elif opt in ('-i', '--input'):
input = arg
elif opt in ('-o', '--output'):
output = arg
elif opt in ('-V', '--version'):
version = arg
elif opt in ('-e', '--exec-path'):
exec_path = arg
elif opt in ('-I', '--iso-path'):
iso_path = arg
elif opt in ('-1', '--first-langauge'):
first=True
iso639 = parse_iso639(iso_path)
denylist = parse_denylist('%s/%s' % ( mydir, 'denylist.txt'))
xml = GenerateEngineXML(input, iso639, denylist, version, exec_path, first)
xml.write(output)