@@ -78,6 +78,19 @@ def _translate_alias(ctxt, path):
7878 return keys
7979
8080
81+ def _sibling_alias_type (alias_elem ) -> str :
82+ """
83+ Extract target type from an alias path pointing at a sibling.
84+
85+ E.g. ``../unit[@type='duration-year']`` -> ``duration-year``.
86+ """
87+ path_attr = alias_elem .attrib ['path' ]
88+ parent , _ , leaf = path_attr .rpartition ('/' )
89+ if parent != ".." :
90+ raise ValueError (f"not a sibling alias: { path_attr } " )
91+ return TYPE_ATTR_RE .match (leaf ).group (1 )
92+
93+
8194def _parse_currency_date (s ):
8295 if not s :
8396 return None
@@ -805,8 +818,20 @@ def parse_decimal_formats(data, tree):
805818 length_type = elem .attrib .get ('type' )
806819 if _should_skip_elem (elem , length_type , decimal_formats ):
807820 continue
808- if elem .findall ('./alias' ):
809- # TODO map the alias to its target
821+ alias_elem = elem .find ('./alias' )
822+ if alias_elem is not None :
823+ if alias_elem .attrib ['path' ] != "../decimalFormatLength[@type='short']" :
824+ raise ValueError ("Unexpected decimal format alias in new CLDR data; this may require hand-holding." )
825+ # In particular: if you're reading this, the tribal knowledge below
826+ # (that this is compact_decimal_format) needs to be double-checked.
827+
828+ # `root.xml` aliases `long` to `short`.
829+ # The typed lengths only carry compact patterns, so the alias lands in `compact_decimal_formats`.
830+ # Only root has this.
831+ compact_decimal_formats = data .setdefault ('compact_decimal_formats' , {})
832+ compact_decimal_formats [length_type ] = Alias (
833+ ('compact_decimal_formats' , _sibling_alias_type (alias_elem )),
834+ )
810835 continue
811836 for pattern_el in elem .findall ('./decimalFormat/pattern' ):
812837 pattern_type = pattern_el .attrib .get ('type' )
@@ -879,11 +904,58 @@ def parse_unit_patterns(data, tree):
879904 unit_patterns = data .setdefault ('unit_patterns' , {})
880905 compound_patterns = data .setdefault ('compound_unit_patterns' , {})
881906 unit_display_names = data .setdefault ('unit_display_names' , {})
907+ # `root.xml` aliases the `long` and `narrow` unit lengths to `short`,
908+ # and some units to others (e.g. `duration-day-person` to `duration-day`).
909+ # This is keyed unit-first instead of length-first (XML),
910+ # so length aliases are expanded into per-unit aliases below.
911+ # Only root has aliases.
912+ length_aliases = {}
882913
883914 for elem in tree .findall ('.//units/unitLength' ):
884915 unit_length_type = elem .attrib ['type' ]
916+ alias_elem = elem .find ('./alias' )
917+ if alias_elem is not None :
918+ length_aliases [unit_length_type ] = _sibling_alias_type (alias_elem )
919+ continue
885920 for unit in elem .findall ('unit' ):
886921 unit_type = unit .attrib ['type' ]
922+ alias_elem = unit .find ('./alias' )
923+ if alias_elem is not None :
924+ target_unit = _sibling_alias_type (alias_elem )
925+ if unit_type .endswith ('-person' ):
926+ # HACK (and this is a mouthful):
927+ # The `duration-*-person` units (used for formatting ages) are
928+ # aliased to their base units. The alias element only exists
929+ # inside `unitLength[short]`, so literal resolution would route
930+ # a long/narrow request through the short chain and lose the
931+ # requested length ("3 y" where "3 years" was wanted).
932+ # ICU considers this a deficiency of the alias data structure
933+ # (https://unicode-org.atlassian.net/browse/ICU-20400) and
934+ # compensates by stripping the `-person` suffix before lookup
935+ # This seems to make sense, so we do that too:
936+ # alias the whole unit, preserving the requested length.
937+ #
938+ # NB: this deliberately bends literal TR35 alias resolution to
939+ # match ICU's output. Should CLDR ever gain a length-preserving
940+ # alias representation, spec compliance is restored by deleting
941+ # this special-case `-person` branch and translating those
942+ # aliases as written, as the branch below does..
943+ unit_patterns [unit_type ] = Alias (('unit_patterns' , target_unit ))
944+ unit_display_names [unit_type ] = Alias (('unit_display_names' , target_unit ))
945+ else :
946+ # The other aliased units (`graphics-dot*` -> `graphics-pixel*`,
947+ # `energy-foodcalorie` -> `energy-kilocalorie`) get no such
948+ # compensation in ICU and follow the literal chain: the alias
949+ # applies within this length only, and a locale's own data for
950+ # the unit at other lengths (plus the length aliases below)
951+ # takes precedence over the target unit's.
952+ unit_patterns .setdefault (unit_type , {})[unit_length_type ] = Alias (
953+ ('unit_patterns' , target_unit , unit_length_type ),
954+ )
955+ unit_display_names .setdefault (unit_type , {})[unit_length_type ] = Alias (
956+ ('unit_display_names' , target_unit , unit_length_type ),
957+ )
958+ continue
887959 unit_and_length_patterns = unit_patterns .setdefault (unit_type , {}).setdefault (unit_length_type , {})
888960 for pattern in unit .findall ('unitPattern' ):
889961 if pattern .attrib .get ('case' , 'nominative' ) != 'nominative' :
@@ -922,11 +994,32 @@ def parse_unit_patterns(data, tree):
922994 compound_unit_info ['compound_variations' ] = compound_variations
923995 compound_patterns .setdefault (unit_type , {})[unit_length_type ] = compound_unit_info
924996
997+ for aliased_length , target_length in length_aliases .items ():
998+ for dict_name , dst in (
999+ ('unit_patterns' , unit_patterns ),
1000+ ('compound_unit_patterns' , compound_patterns ),
1001+ ('unit_display_names' , unit_display_names ),
1002+ ):
1003+ for unit_type , lengths in dst .items ():
1004+ if isinstance (lengths , Alias ):
1005+ # A length-preserving whole-unit alias installed above.
1006+ continue
1007+ lengths .setdefault (aliased_length , Alias ((dict_name , unit_type , target_length )))
1008+
9251009
9261010def parse_date_fields (data , tree ):
9271011 date_fields = data .setdefault ('date_fields' , {})
9281012 for elem in tree .findall ('.//dates/fields/field' ):
9291013 field_type = elem .attrib ['type' ]
1014+ # `root` aliases `x-narrow` to `x-short` and `x-short` to `x`.
1015+ # Locales defining only some length variants inherit these.
1016+ # Only root has these.
1017+ alias_elem = elem .find ('alias' )
1018+ if alias_elem is not None :
1019+ date_fields [field_type ] = Alias (
1020+ _translate_alias (['date_fields' , field_type ], alias_elem .attrib ['path' ]),
1021+ )
1022+ continue
9301023 date_fields .setdefault (field_type , {})
9311024 for rel_time in elem .findall ('relativeTime' ):
9321025 rel_time_type = rel_time .attrib ['type' ]
0 commit comments