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

Last change on this file since 4369 was 4369, checked in by cameron, 4 years ago

Fix default Zzzz map for ScriptExtensions?

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