Edgewall Software

Ticket #68: cldr_aliases.diff

File cldr_aliases.diff, 23.0 KB (added by cmlenz, 4 years ago)

In-progress patch for alias support

  • babel/core.py

     
    285285    #{ General Locale Display Names 
    286286 
    287287    def languages(self): 
    288         return self._data['languages'] 
     288        return localedata.AliasResolvingDict(self._data)['languages'] 
    289289    languages = property(languages, doc="""\ 
    290290        Mapping of language codes to translated language names. 
    291291         
     
    297297        """) 
    298298 
    299299    def scripts(self): 
    300         return self._data['scripts'] 
     300        return localedata.AliasResolvingDict(self._data)['scripts'] 
    301301    scripts = property(scripts, doc="""\ 
    302302        Mapping of script codes to translated script names. 
    303303         
     
    309309        """) 
    310310 
    311311    def territories(self): 
    312         return self._data['territories'] 
     312        return localedata.AliasResolvingDict(self._data)['territories'] 
    313313    territories = property(territories, doc="""\ 
    314314        Mapping of script codes to translated script names. 
    315315         
     
    321321        """) 
    322322 
    323323    def variants(self): 
    324         return self._data['variants'] 
     324        return localedata.AliasResolvingDict(self._data)['variants'] 
    325325    variants = property(variants, doc="""\ 
    326326        Mapping of script codes to translated script names. 
    327327         
    328328        >>> Locale('de', 'DE').variants['1901'] 
    329         u'alte deutsche Rechtschreibung' 
     329        u'Alte deutsche Rechtschreibung' 
    330330         
    331331        :type: `dict` 
    332332        """) 
     
    334334    #{ Number Formatting 
    335335 
    336336    def currencies(self): 
    337         return self._data['currency_names'] 
     337        return localedata.AliasResolvingDict(self._data)['currency_names'] 
    338338    currencies = property(currencies, doc="""\ 
    339339        Mapping of currency codes to translated currency names. 
    340340         
     
    347347        """) 
    348348 
    349349    def currency_symbols(self): 
    350         return self._data['currency_symbols'] 
     350        return localedata.AliasResolvingDict(self._data)['currency_symbols'] 
    351351    currency_symbols = property(currency_symbols, doc="""\ 
    352352        Mapping of currency codes to symbols. 
    353353         
     
    360360        """) 
    361361 
    362362    def number_symbols(self): 
    363         return self._data['number_symbols'] 
     363        return localedata.AliasResolvingDict(self._data)['number_symbols'] 
    364364    number_symbols = property(number_symbols, doc="""\ 
    365365        Symbols used in number formatting. 
    366366         
     
    371371        """) 
    372372 
    373373    def decimal_formats(self): 
    374         return self._data['decimal_formats'] 
     374        return localedata.AliasResolvingDict(self._data)['decimal_formats'] 
    375375    decimal_formats = property(decimal_formats, doc="""\ 
    376376        Locale patterns for decimal number formatting. 
    377377         
     
    382382        """) 
    383383 
    384384    def currency_formats(self): 
    385         return self._data['currency_formats'] 
     385        return localedata.AliasResolvingDict(self._data)['currency_formats'] 
    386386    currency_formats = property(currency_formats, doc=r"""\ 
    387387        Locale patterns for currency number formatting. 
    388388         
     
    393393        """) 
    394394 
    395395    def percent_formats(self): 
    396         return self._data['percent_formats'] 
     396        return localedata.AliasResolvingDict(self._data)['percent_formats'] 
    397397    percent_formats = property(percent_formats, doc="""\ 
    398398        Locale patterns for percent number formatting. 
    399399         
     
    404404        """) 
    405405 
    406406    def scientific_formats(self): 
    407         return self._data['scientific_formats'] 
     407        return localedata.AliasResolvingDict(self._data)['scientific_formats'] 
    408408    scientific_formats = property(scientific_formats, doc="""\ 
    409409        Locale patterns for scientific number formatting. 
    410410         
     
    417417    #{ Calendar Information and Date Formatting 
    418418 
    419419    def periods(self): 
    420         return self._data['periods'] 
     420        return localedata.AliasResolvingDict(self._data)['periods'] 
    421421    periods = property(periods, doc="""\ 
    422422        Locale display names for day periods (AM/PM). 
    423423         
     
    428428        """) 
    429429 
    430430    def days(self): 
    431         return self._data['days'] 
     431        return localedata.AliasResolvingDict(self._data)['days'] 
    432432    days = property(days, doc="""\ 
    433433        Locale display names for weekdays. 
    434434         
     
    439439        """) 
    440440 
    441441    def months(self): 
    442         return self._data['months'] 
     442        return localedata.AliasResolvingDict(self._data)['months'] 
    443443    months = property(months, doc="""\ 
    444444        Locale display names for months. 
    445445         
     
    450450        """) 
    451451 
    452452    def quarters(self): 
    453         return self._data['quarters'] 
     453        return localedata.AliasResolvingDict(self._data)['quarters'] 
    454454    quarters = property(quarters, doc="""\ 
    455455        Locale display names for quarters. 
    456456         
     
    461461        """) 
    462462 
    463463    def eras(self): 
    464         return self._data['eras'] 
     464        return localedata.AliasResolvingDict(self._data)['eras'] 
    465465    eras = property(eras, doc="""\ 
    466466        Locale display names for eras. 
    467467         
     
    474474        """) 
    475475 
    476476    def time_zones(self): 
    477         return self._data['time_zones'] 
     477        return localedata.AliasResolvingDict(self._data)['time_zones'] 
    478478    time_zones = property(time_zones, doc="""\ 
    479479        Locale display names for time zones. 
    480480         
    481481        >>> Locale('en', 'US').time_zones['Europe/London']['long']['daylight'] 
    482482        u'British Summer Time' 
    483483        >>> Locale('en', 'US').time_zones['America/St_Johns']['city'] 
    484         u'St. John\u2019s' 
     484        u"St. John's" 
    485485         
    486486        :type: `dict` 
    487487        """) 
    488488 
    489489    def meta_zones(self): 
    490         return self._data['meta_zones'] 
     490        return localedata.AliasResolvingDict(self._data)['meta_zones'] 
    491491    meta_zones = property(meta_zones, doc="""\ 
    492492        Locale display names for meta time zones. 
    493493         
     
    502502        """) 
    503503 
    504504    def zone_formats(self): 
    505         return self._data['zone_formats'] 
     505        return localedata.AliasResolvingDict(self._data)['zone_formats'] 
    506506    zone_formats = property(zone_formats, doc=r"""\ 
    507507        Patterns related to the formatting of time zones. 
    508508         
     
    516516        """) 
    517517 
    518518    def first_week_day(self): 
    519         return self._data['week_data']['first_day'] 
     519        return localedata.AliasResolvingDict(self._data)['week_data']['first_day'] 
    520520    first_week_day = property(first_week_day, doc="""\ 
    521521        The first day of a week. 
    522522         
     
    529529        """) 
    530530 
    531531    def weekend_start(self): 
    532         return self._data['week_data']['weekend_start'] 
     532        return localedata.AliasResolvingDict(self._data)['week_data']['weekend_start'] 
    533533    weekend_start = property(weekend_start, doc="""\ 
    534534        The day the weekend starts. 
    535535         
     
    540540        """) 
    541541 
    542542    def weekend_end(self): 
    543         return self._data['week_data']['weekend_end'] 
     543        return localedata.AliasResolvingDict(self._data)['week_data']['weekend_end'] 
    544544    weekend_end = property(weekend_end, doc="""\ 
    545545        The day the weekend ends. 
    546546         
     
    551551        """) 
    552552 
    553553    def min_week_days(self): 
    554         return self._data['week_data']['min_days'] 
     554        return localedata.AliasResolvingDict(self._data)['week_data']['min_days'] 
    555555    min_week_days = property(min_week_days, doc="""\ 
    556556        The minimum number of days in a week so that the week is counted as the 
    557557        first week of a year or month. 
     
    563563        """) 
    564564 
    565565    def date_formats(self): 
    566         return self._data['date_formats'] 
     566        return localedata.AliasResolvingDict(self._data)['date_formats'] 
    567567    date_formats = property(date_formats, doc="""\ 
    568568        Locale patterns for date formatting. 
    569569         
     
    576576        """) 
    577577 
    578578    def time_formats(self): 
    579         return self._data['time_formats'] 
     579        return localedata.AliasResolvingDict(self._data)['time_formats'] 
    580580    time_formats = property(time_formats, doc="""\ 
    581581        Locale patterns for time formatting. 
    582582         
     
    589589        """) 
    590590 
    591591    def datetime_formats(self): 
    592         return self._data['datetime_formats'] 
     592        return localedata.AliasResolvingDict(self._data)['datetime_formats'] 
    593593    datetime_formats = property(datetime_formats, doc="""\ 
    594594        Locale patterns for datetime formatting. 
    595595         
  • babel/localedata.py

     
    2323    import threading 
    2424except ImportError: 
    2525    import dummy_threading as threading 
     26from UserDict import DictMixin 
    2627 
    2728__all__ = ['exists', 'load'] 
    2829__docformat__ = 'restructuredtext en' 
     
    3132_cache_lock = threading.RLock() 
    3233_dirname = os.path.join(os.path.dirname(__file__), 'localedata') 
    3334 
     35 
    3436def exists(name): 
    3537    """Check whether locale data is available for the given locale. 
    3638     
     
    4244        return True 
    4345    return os.path.exists(os.path.join(_dirname, '%s.dat' % name)) 
    4446 
     47 
    4548def list(): 
    4649    """Return a list of all locale identifiers for which locale data is 
    4750    available. 
     
    5457        os.path.splitext(filename) for filename in os.listdir(_dirname) 
    5558    ] if extension == '.dat' and stem != 'root'] 
    5659 
     60 
    5761def load(name): 
    5862    """Load the locale data for the given locale. 
    5963     
     
    6165    the Common Locale Data Repository (CLDR). This data is stored as a 
    6266    collection of pickle files inside the ``babel`` package. 
    6367     
    64     >>> d = load('en_US') 
    65     >>> d['languages']['sv'] 
     68    >> d = load('en_US') 
     69    >> d['languages']['sv'] 
    6670    u'Swedish' 
    6771     
    6872    Note that the results are cached, and subsequent requests for the same 
    6973    locale return the same dictionary: 
    7074     
    71     >>> d1 = load('en_US') 
    72     >>> d2 = load('en_US') 
    73     >>> d1 is d2 
     75    >> d1 = load('en_US') 
     76    >> d2 = load('en_US') 
     77    >> d1 is d2 
    7478    True 
    7579     
    7680    :param name: the locale identifier string (or "root") 
     
    107111    finally: 
    108112        _cache_lock.release() 
    109113 
     114 
    110115def merge(dict1, dict2): 
    111116    """Merge the data from `dict2` into the `dict1` dictionary, making copies 
    112117    of nested dictionaries. 
    113118     
     119    >>> d = {1: 'foo', 3: 'baz'} 
     120    >>> merge(d, {1: 'Foo', 2: 'Bar'}) 
     121    >>> d 
     122    {1: 'Foo', 2: 'Bar', 3: 'baz'} 
     123     
     124    >>> d = {1: {1: 'FOO', 2: 'BAR'}, 2: 'foo'} 
     125    >>> merge(d, {1: {3: 'BAZ'}}) 
     126    >>> d 
     127    {1: {1: 'FOO', 2: 'BAR', 3: 'BAZ'}, 2: 'foo'} 
     128     
    114129    :param dict1: the dictionary to merge into 
    115130    :param dict2: the dictionary containing the data that should be merged 
    116131    """ 
    117     for key, value in dict2.items(): 
    118         if value is not None: 
    119             if type(value) is dict: 
    120                 dict1[key] = dict1.get(key, {}).copy() 
    121                 merge(dict1[key], value) 
     132    for key, val2 in dict2.items(): 
     133        if val2 is not None: 
     134            if type(val2) is dict: 
     135                val1 = dict1.get(key, {}) 
     136                if type(val1) is Alias: 
     137                    dict1[key] = (val1, val2) 
     138                else: 
     139                    dict1[key] = val1.copy() 
     140                    merge(dict1[key], val2) 
     141            elif type(val2) is Alias: 
     142                dict1[key] = (val2, dict1[key]) 
    122143            else: 
    123                 dict1[key] = value 
     144                val1 = dict1.get(key) 
     145                if type(val1) is Alias: 
     146                    dict1[key] = (val1, val2) 
     147                else: 
     148                    dict1[key] = val2 
     149 
     150 
     151class Alias(object): 
     152 
     153    def __init__(self, keys): 
     154        self.keys = keys 
     155 
     156    def __repr__(self): 
     157        return '<%s %r>' % (type(self).__name__, self.keys) 
     158 
     159    def resolve(self, data): 
     160        base = data 
     161        for key in self.keys: 
     162            data = data[key] 
     163        if type(data) is Alias: 
     164            data = data.resolve(base) 
     165        return data 
     166 
     167 
     168class AliasResolvingDict(DictMixin, object): 
     169 
     170    def __init__(self, data, base=None): 
     171        self.data = data 
     172        if base is None: 
     173            base = self 
     174        self.base = base 
     175 
     176    def __getitem__(self, key): 
     177        val = self.data[key] 
     178        if type(val) is Alias: # resolve an alias 
     179            val = val.resolve(self.base) 
     180        if type(val) is tuple: # Merge a partial dict with an alias 
     181            alias, others = val 
     182            val = alias.resolve(self.base).copy() 
     183            merge(val, others) 
     184        if type(val) is dict: # Return a nested alias-resolving dict 
     185            val = AliasResolvingDict(val, base=self.base) 
     186        return val 
     187 
     188    def __setitem__(self, key, value): 
     189        self.data[key] = value 
     190 
     191    def copy(self): 
     192        return AliasResolvingDict(self.data.copy(), base=self.base) 
     193 
     194    def keys(self): 
     195        return self.data.keys() 
  • babel/tests/localedata.py

     
    1616 
    1717from babel import localedata 
    1818 
     19 
     20class MergeResolveTestCase(unittest.TestCase): 
     21 
     22    def test_merge_items(self): 
     23        d = {1: 'foo', 3: 'baz'} 
     24        localedata.merge(d, {1: 'Foo', 2: 'Bar'}) 
     25        self.assertEqual({1: 'Foo', 2: 'Bar', 3: 'baz'}, d) 
     26 
     27    def test_merge_nested_dict(self): 
     28        d1 = { 
     29            'x': {'a': 1, 'b': 2, 'c': 3} 
     30        } 
     31        d2 = { 
     32            'x': {'a': 1, 'b': 12, 'd': 14} 
     33        } 
     34        localedata.merge(d1, d2) 
     35        self.assertEqual({ 
     36            'x': {'a': 1, 'b': 12, 'c': 3, 'd': 14} 
     37        }, d1) 
     38 
     39 
     40    def test_merge_alias(self): 
     41        alias = localedata.Alias('x') 
     42        d1 = { 
     43            'x': {'a': 1, 'b': 2, 'c': 3}, 
     44            'y': alias 
     45        } 
     46        d2 = { 
     47            'x': {'a': 1, 'b': 12, 'd': 14}, 
     48            'y': {'b': 22, 'e': 25} 
     49        } 
     50        localedata.merge(d1, d2) 
     51        self.assertEqual({ 
     52            'x': {'a': 1, 'b': 12, 'c': 3, 'd': 14}, 
     53            'y': (alias, {'b': 22, 'e': 25}) 
     54        }, d1) 
     55        d = localedata.AliasResolvingDict(d1) 
     56        self.assertEqual({ 
     57            'x': {'a': 1, 'b': 12, 'c': 3, 'd': 14}, 
     58            'y': {'a': 1, 'b': 22, 'c': 3, 'd': 14, 'e': 25} 
     59        }, d) 
     60 
     61 
    1962def suite(): 
    2063    suite = unittest.TestSuite() 
    2164    suite.addTest(doctest.DocTestSuite(localedata)) 
     65    suite.addTest(unittest.makeSuite(MergeResolveTestCase)) 
    2266    return suite 
    2367 
    2468if __name__ == '__main__': 
  • babel/tests/dates.py

     
    2929        fmt = dates.DateTimeFormat(d, locale='cs_CZ') 
    3030        self.assertEqual('1.', fmt['LLL']) 
    3131 
     32    def test_abbreviated_month_alias(self): 
     33        d = date(2006, 3, 8) 
     34        fmt = dates.DateTimeFormat(d, locale='de_DE') 
     35        self.assertEqual(u'Mär', fmt['LLL']) 
     36 
    3237    def test_week_of_year_first(self): 
    3338        d = date(2006, 1, 8) 
    3439        fmt = dates.DateTimeFormat(d, locale='de_DE') 
     
    187192        tz = timezone('Europe/Paris') 
    188193        t = time(15, 30, tzinfo=tz) 
    189194        fmt = dates.DateTimeFormat(t, locale='fr_FR') 
    190         self.assertEqual(u'Heure de l’Europe centrale', fmt['vvvv']) 
     195        self.assertEqual(u'heure d’Europe centrale', fmt['vvvv']) 
    191196 
    192197    def test_hour_formatting(self): 
    193198        l = 'en_US' 
  • babel/numbers.py

     
    165165    >>> format_currency(1099.98, 'USD', locale='en_US') 
    166166    u'$1,099.98' 
    167167    >>> format_currency(1099.98, 'USD', locale='es_CO') 
    168     u'US$ 1.099,98' 
     168    u'US$\\xa01.099,98' 
    169169    >>> format_currency(1099.98, 'EUR', locale='de_DE') 
    170     u'1.099,98 \\u20ac' 
     170    u'1.099,98\\xa0\\u20ac' 
    171171     
    172172    The pattern can also be specified explicitly: 
    173173     
  • babel/dates.py

     
    189189    string is used for GMT: 
    190190     
    191191    >>> get_timezone_gmt(dt, 'long', locale='fr_FR') 
    192     u'HMG-08:00' 
     192    u'UTC-08:00' 
    193193     
    194194    :param datetime: the ``datetime`` object; if `None`, the current date and 
    195195                     time in UTC is used 
  • scripts/import_cldr.py

     
    1616from optparse import OptionParser 
    1717import os 
    1818import pickle 
     19import re 
    1920import sys 
    2021try: 
    2122    from xml.etree.ElementTree import parse 
     
    2627sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), '..')) 
    2728 
    2829from babel import dates, numbers 
     30from babel.localedata import Alias 
    2931 
    3032weekdays = {'mon': 0, 'tue': 1, 'wed': 2, 'thu': 3, 'fri': 4, 'sat': 5, 
    3133            'sun': 6} 
     
    3638    def any(iterable): 
    3739        return filter(None, list(iterable)) 
    3840 
     41 
    3942def _text(elem): 
    4043    buf = [elem.text or ''] 
    4144    for child in elem: 
     
    4346    buf.append(elem.tail or '') 
    4447    return u''.join(filter(None, buf)).strip() 
    4548 
     49 
     50NAME_RE = re.compile(r"^\w+$") 
     51TYPE_ATTR_RE = re.compile(r"^\w+\[@type='(.*?)'\]$") 
     52 
     53 
     54def _translate_alias(ctxt, path): 
     55    parts = path.split('/') 
     56    keys = ctxt[:] 
     57    for part in parts: 
     58        if part == '..': 
     59            keys.pop() 
     60        else: 
     61            match = TYPE_ATTR_RE.match(part) 
     62            if match: 
     63                keys.append(match.group(1)) 
     64            else: 
     65                assert NAME_RE.match(part) 
     66                keys.append(part) 
     67    return keys 
     68 
     69 
    4670def main(): 
    4771    parser = OptionParser(usage='%prog path/to/cldr') 
    4872    options, args = parser.parse_args() 
     
    109133        stem, ext = os.path.splitext(filename) 
    110134        if ext != '.xml': 
    111135            continue 
     136        #if stem != 'root': 
     137        #    break 
    112138 
    113139        tree = parse(os.path.join(srcdir, 'main', filename)) 
    114140        data = {} 
     
    227253 
    228254            months = data.setdefault('months', {}) 
    229255            for ctxt in calendar.findall('months/monthContext'): 
    230                 ctxts = months.setdefault(ctxt.attrib['type'], {}) 
     256                ctxt_type = ctxt.attrib['type'] 
     257                ctxts = months.setdefault(ctxt_type, {}) 
    231258                for width in ctxt.findall('monthWidth'): 
    232                     widths = ctxts.setdefault(width.attrib['type'], {}) 
    233                     for elem in width.findall('month'): 
    234                         if 'draft' in elem.attrib and int(elem.attrib['type']) in widths: 
    235                             continue 
    236                         widths[int(elem.attrib.get('type'))] = unicode(elem.text) 
     259                    width_type = width.attrib['type'] 
     260                    widths = ctxts.setdefault(width_type, {}) 
     261                    for elem in width.getiterator(): 
     262                        if elem.tag == 'month': 
     263                            if 'draft' in elem.attrib and int(elem.attrib['type']) in widths: 
     264                                continue 
     265                            widths[int(elem.attrib.get('type'))] = unicode(elem.text) 
     266                        elif elem.tag == 'alias': 
     267                            assert elem.attrib['source'] == 'locale' 
     268                            ctxts[width_type] = Alias( 
     269                                _translate_alias(['months', ctxt_type, width_type], 
     270                                                 elem.attrib['path']) 
     271                            ) 
    237272 
    238273            days = data.setdefault('days', {}) 
    239274            for ctxt in calendar.findall('days/dayContext'): 
    240                 ctxts = days.setdefault(ctxt.attrib['type'], {}) 
     275                ctxt_type = ctxt.attrib['type'] 
     276                ctxts = days.setdefault(ctxt_type, {}) 
    241277                for width in ctxt.findall('dayWidth'): 
    242                     widths = ctxts.setdefault(width.attrib['type'], {}) 
    243                     for elem in width.findall('day'): 
    244                         dtype = weekdays[elem.attrib['type']] 
    245                         if 'draft' in elem.attrib and dtype in widths: 
    246                             continue 
    247                         widths[dtype] = unicode(elem.text) 
     278                    width_type = width.attrib['type'] 
     279                    widths = ctxts.setdefault(width_type, {}) 
     280                    for elem in width.getiterator(): 
     281                        if elem.tag == 'day': 
     282                            dtype = weekdays[elem.attrib['type']] 
     283                            if 'draft' in elem.attrib and dtype in widths: 
     284                                continue 
     285                            widths[dtype] = unicode(elem.text) 
     286                        elif elem.tag == 'alias': 
     287                            assert elem.attrib['source'] == 'locale' 
     288                            ctxts[width_type] = Alias( 
     289                                _translate_alias(['days', ctxt_type, width_type], 
     290                                                 elem.attrib['path']) 
     291                            ) 
    248292 
    249293            quarters = data.setdefault('quarters', {}) 
    250294            for ctxt in calendar.findall('quarters/quarterContext'): 
     295                ctxt_type = ctxt.attrib['type'] 
    251296                ctxts = quarters.setdefault(ctxt.attrib['type'], {}) 
    252297                for width in ctxt.findall('quarterWidth'): 
    253                     widths = ctxts.setdefault(width.attrib['type'], {}) 
    254                     for elem in width.findall('quarter'): 
    255                         if 'draft' in elem.attrib and int(elem.attrib['type']) in widths: 
    256                             continue 
    257                         widths[int(elem.attrib.get('type'))] = unicode(elem.text) 
     298                    width_type = width.attrib['type'] 
     299                    widths = ctxts.setdefault(width_type, {}) 
     300                    for elem in width.getiterator(): 
     301                        if elem.tag == 'quarter': 
     302                            if 'draft' in elem.attrib and int(elem.attrib['type']) in widths: 
     303                                continue 
     304                            widths[int(elem.attrib.get('type'))] = unicode(elem.text) 
     305                        elif elem.tag == 'alias': 
     306                            assert elem.attrib['source'] == 'locale' 
     307                            ctxts[width_type] = Alias( 
     308                                _translate_alias(['quarters', ctxt_type, width_type], 
     309                                                 elem.attrib['path']) 
     310                            ) 
    258311 
    259312            eras = data.setdefault('eras', {}) 
    260313            for width in calendar.findall('eras/*'): 
     
    272325            # AM/PM 
    273326            periods = data.setdefault('periods', {}) 
    274327            for elem in calendar.findall('am'): 
    275                 if 'draft' in elem.attrib and elem.tag in periods: 
     328                if ('draft' in elem.attrib or 'alt' in elem.attrib) and elem.tag in periods: 
    276329                    continue 
    277330                periods[elem.tag] = unicode(elem.text) 
    278331            for elem in calendar.findall('pm'): 
    279                 if 'draft' in elem.attrib and elem.tag in periods: 
     332                if ('draft' in elem.attrib or 'alt' in elem.attrib) and elem.tag in periods: 
    280333                    continue 
    281334                periods[elem.tag] = unicode(elem.text) 
    282335 
     
    360413        finally: 
    361414            outfile.close() 
    362415 
     416 
    363417if __name__ == '__main__': 
    364418    main()