source: proto/charsetcompiler/UCD/UCD_parser.py @ 4948

Last change on this file since 4948 was 4948, checked in by cameron, 3 years ago

Generator for UnicodeNameData?.cpp

File size: 14.0 KB
Line 
1#
2# UCD_parser.py - parsing Unicode Character Database (UCD) files
3#
4# Robert D. Cameron
5# December 28, 2014
6#
7# Licensed under Open Software License 3.0.
8#
9#
10import re, string, os.path
11from unicode_set import *
12
13UCD_dir = "8.0.0"
14def set_UCD_dir(d):
15    global UCD_dir
16    UCD_dir = d
17
18trivial_name_char_re = re.compile('[-_\s]')
19def canonicalize(property_string):
20    return trivial_name_char_re.sub('', property_string.lower())
21
22#
23#  Processing files of the UCD
24#
25#  General format for skippable comments, blank lines
26UCD_skip = re.compile("^#.*$|^\s*$")
27
28#
29#  UCD Property File Format 1: property aliases
30#  PropertyAliases.txt
31#
32UCD_property_section_regexp = re.compile("^#\s*([-A-Za-z_0-9]+)\s*Properties\s*$")
33UCD_property_alias_regexp = re.compile("^([-A-Za-z_0-9]+)\s*;\s*([-A-Za-z_0-9]+)([^#]*)")
34
35def parse_PropertyAlias_txt():
36    property_enum_name_list = []
37    full_name_map = {}
38    property_lookup_map = {}
39    property_kind_map = {}
40    property_kind = "unspecified"
41    f = open(UCD_dir + "/" + 'PropertyAliases.txt')
42    lines = f.readlines()
43    for t in lines:
44        m = UCD_property_section_regexp.match(t)
45        if m:
46            property_kind = m.group(1)
47        if UCD_skip.match(t): continue  # skip comment and blank lines
48        m = UCD_property_alias_regexp.match(t)
49        if not m: raise Exception("Unknown property alias syntax: %s" % t)
50        (prop_enum, prop_preferred_full_name, prop_extra) = (m.group(1), m.group(2), m.group(3))
51        prop_aliases = re.findall("[-A-Za-z_0-9]+", prop_extra)
52        property_enum_name_list.append(prop_enum)
53        full_name_map[prop_enum] = prop_preferred_full_name
54        property_lookup_map[canonicalize(prop_enum)] = prop_enum
55        property_lookup_map[canonicalize(prop_preferred_full_name)] = prop_enum
56        for a in prop_aliases: property_lookup_map[canonicalize(a)] = prop_enum
57        property_kind_map[prop_enum] = property_kind
58    #
59    # Override the property kind for scx
60    property_kind_map['scx'] = 'Extension'
61    return (property_enum_name_list, full_name_map, property_lookup_map, property_kind_map)
62
63
64UCD_property_value_missing_regexp = re.compile("^#\s*@missing:\s*([0-9A-F]{4,6})[.][.]([0-9A-F]{4,6})\s*;\s*([-A-Za-z_0-9.]+)\s*;\s*([-A-Za-z_0-9.<> ]+)\s*([^#]*)")
65#
66#  UCD Property File Format 2: property value aliases
67#  PropertyValueAliases.txt
68#
69#  This file records value aliases for property values for
70#  each enumerated property, with the following additional notes:
71#  (1) The corresponding integer value of the enum constant is
72#      also specified for ccc (second field).
73#  (2) The Age property is a numeric type which has decimal float
74#      values as the enum constants: these won't be legal in enum syntax.
75#  (3) Binary properties also have enumerated values and aliases listed,
76#      although this is redundant, because all binary properties have the
77#      same value space.
78#  (4) @missing lines provide default value information, primarily for some
79#      non-enumerated types
80
81def parse_PropertyValueAlias_txt(property_lookup_map):
82    UCD_property_value_alias_regexp = re.compile("^([-A-Za-z_0-9.]+)\s*;\s*([-A-Za-z_0-9.]+)\s*;\s*([-A-Za-z_0-9.]+)([^#]*)")
83    property_value_list = {}
84    property_value_enum_integer = {}
85    property_value_full_name_map = {}
86    property_value_lookup_map = {}
87    missing_specs = {}
88    f = open(UCD_dir + "/" + 'PropertyValueAliases.txt')
89    lines = f.readlines()
90    for t in lines:
91        if UCD_skip.match(t):
92            m = UCD_property_value_missing_regexp.match(t)
93            if m:
94                if m.group(1) != '0000' or m.group(2) != '10FFFF': raise Exception("Bad missing spec: " + s)
95                cname = canonicalize(m.group(3))
96                if not property_lookup_map.has_key(cname): raise Exception("Bad missing property: " + s)
97                missing_specs[property_lookup_map[cname]] = m.group(4)
98            continue  # skip comment and blank lines
99        m = UCD_property_value_alias_regexp.match(t)
100        if not m: raise Exception("Unknown property value alias syntax: %s" % t)
101        prop_code = canonicalize(m.group(1))
102        if not property_lookup_map.has_key(prop_code): raise Exception("Property code: '%s' is unknown" % prop_code)
103        else: prop_code = property_lookup_map[prop_code]
104        if not property_value_list.has_key(prop_code):
105            property_value_list[prop_code] = []
106            property_value_enum_integer[prop_code] = {}
107            property_value_full_name_map[prop_code] = {}
108            property_value_lookup_map[prop_code] = {}
109            enum_integer = 0
110        # Special case for ccc: second field is enum integer value
111        if prop_code == 'ccc':
112            enum_integer = int(m.group(2))
113            value_enum = m.group(3)
114            extra = m.group(4)
115            extra_list = re.findall("[-A-Za-z_0-9.]+", extra)
116            value_preferred_full_name = extra_list[0]
117            # Treat integer string as an alias
118            value_aliases = [m.group(2)] + extra_list[1:]
119        # Special case for age: second field is numeric, third field is enum
120        # treat numeric value as an alias string
121        elif prop_code == 'age':
122            value_enum = m.group(3)
123            value_preferred_full_name = m.group(3)
124            extra = m.group(4)
125            value_aliases = [m.group(2)] + re.findall("[-A-Za-z_0-9]+", extra)
126        else:
127            value_enum = m.group(2)
128            value_preferred_full_name = m.group(3)
129            extra = m.group(4)
130            value_aliases = re.findall("[-A-Za-z_0-9]+", extra)
131        property_value_list[prop_code].append(value_enum)
132        property_value_enum_integer[prop_code][value_enum] = enum_integer
133        enum_integer += 1
134        property_value_full_name_map[prop_code][value_enum] = value_preferred_full_name
135        property_value_lookup_map[prop_code][value_enum] = value_enum
136        property_value_lookup_map[prop_code][canonicalize(value_enum)] = value_enum
137        property_value_lookup_map[prop_code][canonicalize(value_preferred_full_name)] = value_enum
138        for a in value_aliases: property_value_lookup_map[prop_code][canonicalize(a)] = value_enum
139    # Special case for scx:
140    property_value_list['scx'] = property_value_list['sc']
141    property_value_enum_integer['scx'] = property_value_enum_integer['sc']
142    property_value_full_name_map['scx'] = property_value_full_name_map['sc']
143    property_value_lookup_map['scx'] = property_value_lookup_map['sc']
144    return (property_value_list, property_value_enum_integer, property_value_full_name_map, property_value_lookup_map, missing_specs)
145
146
147
148#
149#  Union of a list of sets
150#
151def union_of_all(uset_list):
152    if uset_list == []: return empty_uset()
153    else:
154        accum_set = uset_list[0]
155        for s in uset_list[1:]:
156            accum_set = uset_union(accum_set, s)
157        return accum_set
158
159#
160#  UCD Property File Format 3:  codepoint -> name maps
161#
162UCD_skip = re.compile("^#.*$|^\s*$")
163UCD_missing_regexp1 = re.compile("^#\s*@missing:\s*([0-9A-F]{4,6})[.][.]([0-9A-F]{4,6})\s*;\s*([-A-Za-z0-9_]+)\s*(?:[;#]|$)")
164UCD_point_name_regexp = re.compile("^([0-9A-F]{4,6})\s*;\s*((?:[-A-Za-z0-9_.]+\s+)*[-A-Za-z0-9_.]+)\s*(?:[;#]|$)")
165UCD_range_name_regexp = re.compile("^([0-9A-F]{4,6})[.][.]([0-9A-F]{4,6})\s*;\s*((?:[-A-Za-z0-9_.]+\s+)*[-A-Za-z0-9_.]+)\s*(?:[;#]|$)")
166
167def parse_UCD_enumerated_property_map(property_code, vlist, canon_map, mapfile, default_value = None):
168    value_map = {}
169    name_list_order = []
170    f = open(UCD_dir + "/" + mapfile)
171    lines = f.readlines()
172    for t in lines:
173        if UCD_skip.match(t):
174            m = UCD_missing_regexp1.match(t)
175            if m:
176                if default_value != None:
177                    raise Exception("Default value already specified, extraneous @missing spec: %s" % t)
178                (missing_lo, missing_hi, default_value) = (int(m.group(1), 16), int(m.group(2), 16), m.group(3))
179                default_value = canonicalize(default_value)
180                if not canon_map.has_key(default_value):  raise Exception("Unknown default property value name '%s'" % default_value)
181                if missing_lo != 0 or missing_hi != 0x10FFFF: raise Exception("Unexpected missing data range '%x, %x'" % (missing_lo, missing_hi))
182                default_value = canon_map[default_value]
183            continue  # skip comment and blank lines
184        m = UCD_point_name_regexp.match(t)
185        if m:
186            (codepoint, name) = (int(m.group(1), 16), m.group(2))
187            newset = singleton_uset(codepoint)
188        else:
189            m = UCD_range_name_regexp.match(t)
190            if not m: raise Exception("Unknown syntax: %s" % t)
191            (cp_lo, cp_hi, name) = (int(m.group(1), 16), int(m.group(2), 16), m.group(3))
192            newset = range_uset(cp_lo, cp_hi)
193        cname = canonicalize(name)
194        if not canon_map.has_key(cname):  raise Exception("Unknown property or property value name '%s'" % cname)
195        name = canon_map[cname]
196        if not value_map.has_key(name):
197            value_map[name] = newset
198            name_list_order.append(name)
199        else: value_map[name] = uset_union(value_map[name], newset)
200    if property_code == 'gc':
201        # special logic for derived categories
202        value_map['LC'] = union_of_all([value_map[v] for v in ['Lu', 'Ll', 'Lt']])
203        value_map['L'] = union_of_all([value_map[v] for v in ['Lu', 'Ll', 'Lt', 'Lm', 'Lo']])
204        value_map['M'] = union_of_all([value_map[v] for v in ['Mn', 'Mc', 'Me']])
205        value_map['N'] = union_of_all([value_map[v] for v in ['Nd', 'Nl', 'No']])
206        value_map['P'] = union_of_all([value_map[v] for v in ['Pc', 'Pd', 'Ps', 'Pe', 'Pi', 'Pf', 'Po']])
207        value_map['S'] = union_of_all([value_map[v] for v in ['Sm', 'Sc', 'Sk', 'So']])
208        value_map['Z'] = union_of_all([value_map[v] for v in ['Zs', 'Zl', 'Zp']])
209        value_map['C'] = union_of_all([value_map[v] for v in ['Cc', 'Cf', 'Cs', 'Co', 'Cn']])
210        name_list_order = ['LC', 'L', 'M', 'N', 'P', 'S', 'Z', 'C']+ name_list_order
211    for v in vlist:
212        if not v in name_list_order:
213            #raise Exception("Property %s value %s missing" % (self.full_name_map[property_code], v))
214            #print("Warning: property %s has no instance of value %s" % (property_code, v))
215            value_map[v] = empty_uset()
216            name_list_order.append(v)
217    explicitly_defined_cps = empty_uset()
218    for k in value_map.keys(): explicitly_defined_cps = uset_union(explicitly_defined_cps, value_map[k])
219    need_default_value = uset_complement(explicitly_defined_cps)
220    if default_value != None:
221        if value_map.has_key(default_value):
222            value_map[default_value] = uset_union(value_map[default_value], need_default_value)
223        else:
224            value_map[default_value] = need_default_value
225            name_list_order.append(default_value)
226    elif uset_popcount(need_default_value) > 0:
227        print "Warning no default value, but %i codepoints not specified" % uset_popcount(need_default_value)
228    return (name_list_order, value_map)
229
230def parse_ScriptExtensions_txt(scripts, canon_map):
231    filename_root = 'ScriptExtensions'
232    property_code = 'scx'
233    (scriptlist, script_map) = parse_UCD_enumerated_property_map('sc', scripts, canon_map, 'Scripts.txt')
234    (scx_sets, scx_set_map) = parse_UCD_codepoint_name_map('ScriptExtensions.txt')
235    value_map = {}
236    explicitly_defined_set = empty_uset()
237    for scx_list in scx_sets:
238        scx_items = scx_list.split(" ")
239        for scx in scx_items:
240            # sc = canonical_property_value_map[canonicalize(scx)]
241            sc = scx
242            if value_map.has_key(sc):
243                value_map[sc] = uset_union(value_map[sc], scx_set_map[scx_list])
244            else: value_map[sc] = scx_set_map[scx_list]
245        explicitly_defined_set = uset_union(explicitly_defined_set, scx_set_map[scx_list])
246    for v in scripts:
247        if value_map.has_key(v):
248            value_map[v] = uset_union(value_map[v], uset_difference(script_map[v], explicitly_defined_set))
249        elif script_map.has_key(v):
250            value_map[v] = script_map[v]
251        else: value_map[v] = empty_uset()
252    return (scripts, value_map)
253
254
255def parse_UCD_codepoint_name_map(mapfile, canon_map = None):
256    value_map = {}
257    name_list_order = []
258    f = open(UCD_dir + "/" + mapfile)
259    lines = f.readlines()
260    for t in lines:
261        if UCD_skip.match(t):
262            continue  # skip comment and blank lines
263        m = UCD_point_name_regexp.match(t)
264        if m:
265            (codepoint, name) = (int(m.group(1), 16), m.group(2))
266            newset = singleton_uset(codepoint)
267        else:
268            m = UCD_range_name_regexp.match(t)
269            if not m: raise Exception("Unknown syntax: %s" % t)
270            (cp_lo, cp_hi, name) = (int(m.group(1), 16), int(m.group(2), 16), m.group(3))
271            newset = range_uset(cp_lo, cp_hi)
272        if not canon_map == None:
273            cname = canonicalize(name)
274            if not canon_map.has_key(cname):
275                raise Exception("Unknown property or property value name '%s'" % cname)
276            name = canon_map[cname]
277        if not value_map.has_key(name):
278            value_map[name] = newset
279            name_list_order.append(name)
280        else: value_map[name] = uset_union(value_map[name], newset)
281    return (name_list_order, value_map)
282
283
284UnicodeData_txt_regexp = re.compile("^([0-9A-F]{4,6});([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);(.*)$")
285
286def parse_UnicodeData_txt():
287   data_records = []
288   f = open(UCD_dir + "/UnicodeData.txt")
289   lines = f.readlines()
290   for t in lines:
291      if UCD_skip.match(t):
292        continue  # skip comment and blank lines
293      m = UnicodeData_txt_regexp.match(t)
294      if not m: raise Exception("Unknown syntax: %s" % t)
295      (cp, name, gc) = (m.group(1), m.group(2), m.group(3))
296      (ccc, bidic, decomp, bidim) = (m.group(4), m.group(5), m.group(6), m.group(10))
297      (decval, digitval, numval) = (m.group(7), m.group(8), m.group(9))
298      # Unicode 1 name and ISO comment are obolete
299      (uc, lc, tc) = (m.group(13), m.group(14), m.group(15))
300      data_records.append((cp, name, gc, ccc, bidic, decomp, decval, digitval, numval, bidim, uc, lc, tc))
301   return data_records
302
Note: See TracBrowser for help on using the repository browser.