source: icGREP/icgrep-devel/UCD-scripts/UCD_parser.py @ 5653

Last change on this file since 5653 was 5653, checked in by cameron, 22 months ago

Updates for Python 3; some refactoring

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