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

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

Enumeration parsing now returns only values explicitly found, add count of these independent enums

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