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

Last change on this file since 4367 was 4367, checked in by cameron, 5 years ago

Refactoring to separate UCD parsing from header file generation.

File size: 11.2 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_UCD_codepoint_name_map(mapfile, canonical_name_lookup_map = None):
213   value_map = {}
214   name_list_order = []
215   f = open(UCD_dir + "/" + mapfile)
216   lines = f.readlines()
217   for t in lines:
218      if UCD_skip.match(t):
219        continue  # skip comment and blank lines
220      m = UCD_point_name_regexp.match(t)
221      if m:
222        (codepoint, name) = (int(m.group(1), 16), m.group(2))
223        newset = singleton_uset(codepoint)
224      else: 
225        m = UCD_range_name_regexp.match(t)
226        if not m: raise Exception("Unknown syntax: %s" % t)
227        (cp_lo, cp_hi, name) = (int(m.group(1), 16), int(m.group(2), 16), m.group(3))
228        newset = range_uset(cp_lo, cp_hi)
229      if not canonical_name_lookup_map == None:
230        cname = canonicalize(name)
231        if not canonical_name_lookup_map.has_key(cname):  raise Exception("Unknown property or property value name '%s'" % cname)
232        name = canonical_name_lookup_map[cname]
233      if not value_map.has_key(name):
234        value_map[name] = newset
235        name_list_order.append(name)
236      else: value_map[name] = uset_union(value_map[name], newset)
237   return (name_list_order, value_map)
238
239
Note: See TracBrowser for help on using the repository browser.