cocina_display 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,122 @@
1
+ require_relative "date"
2
+
3
+ module CocinaDisplay
4
+ module Dates
5
+ # A date range parsed from Cocina structuredValues.
6
+ class DateRange < Date
7
+ attr_reader :start, :stop
8
+
9
+ # Construct a DateRange from Cocina hash with structuredValue.
10
+ # @param cocina [Hash] Cocina date data containing structuredValue
11
+ # @return [CocinaDisplay::DateRange]
12
+ # @return [nil] if no parsable start or stop dates are found
13
+ def self.from_cocina(cocina)
14
+ return unless cocina["structuredValue"].present?
15
+
16
+ dates = cocina["structuredValue"].map { |sv| Date.from_cocina(sv) }
17
+ start = dates.find(&:start?)
18
+ stop = dates.find(&:end?)
19
+ return unless start || stop
20
+
21
+ DateRange.new(cocina, start: start, stop: stop)
22
+ end
23
+
24
+ # Create a new date range using two CocinaDisplay::Date objects.
25
+ # @param cocina [Hash] Cocina date data containing structuredValue
26
+ # @param start [CocinaDisplay::Date, nil] The start date of the range.
27
+ # @param stop [CocinaDisplay::Date, nil] The end date of the range.
28
+ # @return [CocinaDisplay::DateRange]
29
+ def initialize(cocina, start: nil, stop: nil)
30
+ @cocina = cocina
31
+ @start = start
32
+ @stop = stop
33
+ end
34
+
35
+ # The values of the start and stop dates as an array.
36
+ # @see CocinaDisplay::Date#value
37
+ # @return [Array<String>]
38
+ def value
39
+ [start&.value, stop&.value]
40
+ end
41
+
42
+ # Key used to sort this date range. Respects BCE/CE ordering and precision.
43
+ # Ranges are sorted first by their start date, then by their stop date.
44
+ # @see CocinaDisplay::Date#sort_key
45
+ # @return [String]
46
+ def sort_key
47
+ [start&.sort_key, stop&.sort_key].join(" - ")
48
+ end
49
+
50
+ # Base values of start/end as single string. Used for comparison/deduping.
51
+ # @note This is important for uniqueness checks in Imprint display.
52
+ # @return [String]
53
+ def base_value
54
+ "#{@start&.base_value}-#{@stop&.base_value}"
55
+ end
56
+
57
+ # The encoding value for the range.
58
+ # Uses the start encoding, stop encoding, or top-level encoding in that order.
59
+ # @see CocinaDisplay::Date#encoding
60
+ # @return [String, nil]
61
+ def encoding
62
+ start&.encoding || stop&.encoding || super
63
+ end
64
+
65
+ # Is either date in the range qualified in any way?
66
+ # @see CocinaDisplay::Date#qualified?
67
+ # @return [Boolean]
68
+ def qualified?
69
+ start&.qualified? || stop&.qualified? || super
70
+ end
71
+
72
+ # Is either date in the range marked as primary?
73
+ # @see CocinaDisplay::Date#primary?
74
+ # @return [Boolean]
75
+ def primary?
76
+ start&.primary? || stop&.primary? || super
77
+ end
78
+
79
+ # Was either date in the range successfully parsed?
80
+ # @see CocinaDisplay::Date#parsed_date?
81
+ # @return [Boolean]
82
+ def parsed_date?
83
+ start&.parsed_date? || stop&.parsed_date? || false
84
+ end
85
+
86
+ # Decoded version of the range, if it was encoded. Strips leading zeroes.
87
+ # @see CocinaDisplay::Date#decoded_value
88
+ # @return [String]
89
+ def decoded_value(**kwargs)
90
+ [
91
+ start&.decoded_value(**kwargs),
92
+ stop&.decoded_value(**kwargs)
93
+ ].uniq.join(" - ")
94
+ end
95
+
96
+ # Decoded range with "BCE" or "CE" and qualifier markers applied.
97
+ # @see CocinaDisplay::Date#qualified_value
98
+ # @return [String]
99
+ def qualified_value
100
+ if start&.qualifier == stop&.qualifier
101
+ qualifier = start&.qualifier || stop&.qualifier
102
+ date = decoded_value
103
+ return "[ca. #{date}]" if qualifier == "approximate"
104
+ return "[#{date}?]" if qualifier == "questionable"
105
+ return "[#{date}]" if qualifier == "inferred"
106
+
107
+ date
108
+ else
109
+ "#{start&.qualified_value} - #{stop&.qualified_value}"
110
+ end
111
+ end
112
+
113
+ # Express the range as an EDTF::Interval between the start and stop dates.
114
+ # @return [EDTF::Interval]
115
+ def as_interval
116
+ interval_start = start&.date&.edtf || "open"
117
+ interval_stop = stop&.date&.edtf || "open"
118
+ ::Date.edtf("#{interval_start}/#{interval_stop}")
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "edtf"
4
+ require "active_support"
5
+ require "active_support/core_ext/enumerable"
6
+ require "active_support/core_ext/object/blank"
7
+
8
+ require_relative "utils"
9
+ require_relative "marc_country_codes"
10
+ require_relative "dates/date"
11
+ require_relative "dates/date_range"
12
+
13
+ module CocinaDisplay
14
+ # Wrapper for Cocina events used to generate an imprint statement for display.
15
+ class Imprint
16
+ # Parse Cocina dates and convert any non-single dates to DateRange objects.
17
+ # This ensures that we can correctly deduplicate ranges against single dates.
18
+ # @param cocina_dates [Array<Hash>] Array of Cocina date hashes
19
+ # @return [Array<CocinaDisplay::Dates::Date | CocinaDisplay::Dates::DateRange>]
20
+ def self.parse_dates(cocina_dates)
21
+ cocina_dates.map { |cd| CocinaDisplay::Dates::Date.from_cocina(cd) }.filter(&:parsable?).compact
22
+ end
23
+
24
+ attr_reader :cocina, :dates
25
+
26
+ # Initialize the imprint with Cocina event data.
27
+ # @param cocina [Hash] Cocina structured data for a single event
28
+ def initialize(cocina)
29
+ @cocina = cocina
30
+ @dates = self.class.parse_dates(Array(cocina["date"]))
31
+ end
32
+
33
+ # The entire imprint statement formatted as a string for display.
34
+ # @return [String]
35
+ def display_str
36
+ place_pub = Utils.compact_and_join([place_str, publisher_str], delimiter: " : ")
37
+ edition_place_pub = Utils.compact_and_join([edition_str, place_pub], delimiter: " - ")
38
+ Utils.compact_and_join([edition_place_pub, date_str], delimiter: ", ")
39
+ end
40
+
41
+ # Were any of the dates encoded?
42
+ # Used to detect which event(s) most likely represent the actual imprint(s).
43
+ def date_encoding?
44
+ @dates.any?(&:encoding?)
45
+ end
46
+
47
+ private
48
+
49
+ # The date portion of the imprint statement, comprising all unique dates.
50
+ # @return [String]
51
+ def date_str
52
+ Utils.compact_and_join(unique_dates_for_display.map(&:qualified_value))
53
+ end
54
+
55
+ # The editions portion of the imprint statement, combining all edition notes.
56
+ # @return [String]
57
+ def edition_str
58
+ Utils.compact_and_join(Janeway.enum_for("$.note[?@.type == 'edition'].value", cocina))
59
+ end
60
+
61
+ # The place of publication, combining all location values.
62
+ # @return [String]
63
+ def place_str
64
+ Utils.compact_and_join(locations_for_display, delimiter: " : ")
65
+ end
66
+
67
+ # The publisher information, combining all name values for publishers.
68
+ # @return [String]
69
+ def publisher_str
70
+ Utils.compact_and_join(Janeway.enum_for("$.contributor[?@.role[?@.value == 'publisher']].name[*].value", cocina), delimiter: " : ")
71
+ end
72
+
73
+ # Get the place name for a location, decoding from MARC if necessary.
74
+ # Ignores the unknown/"various locations" country codes and returns nil.
75
+ # @param location [Hash] A location hash parsed from Cocina
76
+ # @return [String] The decoded location name
77
+ # @return [nil] If no valid location value is found
78
+ def decoded_location(location)
79
+ return location["value"] if location["value"].present?
80
+
81
+ if location.dig("source", "code") == "marccountry" &&
82
+ location["code"].present? &&
83
+ ["xx", "vp"].exclude?(location["code"])
84
+ CocinaDisplay::MARC_COUNTRY[location["code"]]
85
+ end
86
+ end
87
+
88
+ # Filter locations to display according to predefined rules.
89
+ # 1. Prefer unencoded locations (plain value) over encoded ones
90
+ # 2. If no unencoded locations but there are MARC country codes, decode them
91
+ # 3. Keep only unique locations after decoding
92
+ def locations_for_display
93
+ unencoded_locs, encoded_locs = Array(cocina["location"]).partition { |loc| loc["value"].present? }
94
+ locs_for_display = unencoded_locs.presence || encoded_locs
95
+ locs_for_display.map { |loc| decoded_location(loc) }.compact_blank.uniq
96
+ end
97
+
98
+ # Filter dates for uniqueness using base value according to predefined rules.
99
+ # 1. For a group of dates with the same base value, choose a single one
100
+ # 2. Prefer unencoded dates over encoded ones when choosing a single date
101
+ # 3. Remove date ranges that duplicate any unencoded non-range dates
102
+ # @return [Array<CocinaDisplay::Dates::Date>]
103
+ # @see CocinaDisplay::Dates::Date#base_value
104
+ # @see https://consul.stanford.edu/display/chimera/MODS+display+rules#MODSdisplayrules-3b.%3CoriginInfo%3E
105
+ def unique_dates_for_display
106
+ # Choose a single date for each group with the same base value
107
+ deduped_dates = dates.group_by(&:base_value).map do |base_value, group|
108
+ if (unencoded = group.reject(&:encoding?)).any?
109
+ unencoded.first
110
+ else
111
+ group.first
112
+ end
113
+ end
114
+
115
+ # Remove any ranges that duplicate part of an unencoded non-range date
116
+ ranges, singles = deduped_dates.partition { |date| date.is_a?(CocinaDisplay::Dates::DateRange) }
117
+ unencoded_singles_dates = singles.reject(&:encoding?).flat_map(&:to_a)
118
+ ranges.reject! { |range| unencoded_singles_dates.any? { |date| range.as_interval.include?(date) } }
119
+
120
+ (singles + ranges).sort
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,394 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Represents the Marc Country Codes mapped to names, from http://www.loc.gov/marc/countries/countries_code.html 2013-01-03
4
+ # key - Marc Country code
5
+ # value - Marc Country term
6
+
7
+ # Ported from sul-dlss/mods gem
8
+
9
+ module CocinaDisplay
10
+ MARC_COUNTRY = {
11
+ "aa" => "Albania",
12
+ "abc" => "Alberta",
13
+ "ac" => "Ashmore and Cartier Islands", # discontinued
14
+ "aca" => "Australian Capital Territory",
15
+ "ae" => "Algeria",
16
+ "af" => "Afghanistan",
17
+ "ag" => "Argentina",
18
+ "ai" => "Armenia (Republic)",
19
+ "air" => "Armenian S.S.R.", # discontinued
20
+ "aj" => "Azerbaijan",
21
+ "ajr" => "Azerbaijan S.S.R.", # discontinued
22
+ "aku" => "Alaska",
23
+ "alu" => "Alabama",
24
+ "am" => "Anguilla",
25
+ "an" => "Andorra",
26
+ "ao" => "Angola",
27
+ "aq" => "Antigua and Barbuda",
28
+ "aru" => "Arkansas",
29
+ "as" => "American Samoa",
30
+ "at" => "Australia",
31
+ "au" => "Austria",
32
+ "aw" => "Aruba",
33
+ "ay" => "Antarctica",
34
+ "azu" => "Arizona",
35
+ "ba" => "Bahrain",
36
+ "bb" => "Barbados",
37
+ "bcc" => "British Columbia",
38
+ "bd" => "Burundi",
39
+ "be" => "Belgium",
40
+ "bf" => "Bahamas",
41
+ "bg" => "Bangladesh",
42
+ "bh" => "Belize",
43
+ "bi" => "British Indian Ocean Territory",
44
+ "bl" => "Brazil",
45
+ "bm" => "Bermuda Islands",
46
+ "bn" => "Bosnia and Herzegovina",
47
+ "bo" => "Bolivia",
48
+ "bp" => "Solomon Islands",
49
+ "br" => "Burma",
50
+ "bs" => "Botswana",
51
+ "bt" => "Bhutan",
52
+ "bu" => "Bulgaria",
53
+ "bv" => "Bouvet Island",
54
+ "bw" => "Belarus",
55
+ "bwr" => "Byelorussian S.S.R.", # discontinued
56
+ "bx" => "Brunei",
57
+ "ca" => "Caribbean Netherlands",
58
+ "cau" => "California",
59
+ "cb" => "Cambodia",
60
+ "cc" => "China",
61
+ "cd" => "Chad",
62
+ "ce" => "Sri Lanka",
63
+ "cf" => "Congo (Brazzaville)",
64
+ "cg" => "Congo (Democratic Republic)",
65
+ "ch" => "China (Republic : 1949- )",
66
+ "ci" => "Croatia",
67
+ "cj" => "Cayman Islands",
68
+ "ck" => "Colombia",
69
+ "cl" => "Chile",
70
+ "cm" => "Cameroon",
71
+ "cn" => "Canada", # discontinued
72
+ "co" => "Curaçao",
73
+ "cou" => "Colorado",
74
+ "cp" => "Canton and Enderbury Islands", # discontinued
75
+ "cq" => "Comoros",
76
+ "cr" => "Costa Rica",
77
+ "cs" => "Czechoslovakia", # discontinued
78
+ "ctu" => "Connecticut",
79
+ "cu" => "Cuba",
80
+ "cv" => "Cabo Verde",
81
+ "cw" => "Cook Islands",
82
+ "cx" => "Central African Republic",
83
+ "cy" => "Cyprus",
84
+ "cz" => "Canal Zone", # discontinued
85
+ "dcu" => "District of Columbia",
86
+ "deu" => "Delaware",
87
+ "dk" => "Denmark",
88
+ "dm" => "Benin",
89
+ "dq" => "Dominica",
90
+ "dr" => "Dominican Republic",
91
+ "ea" => "Eritrea",
92
+ "ec" => "Ecuador",
93
+ "eg" => "Equatorial Guinea",
94
+ "em" => "Timor-Leste",
95
+ "enk" => "England",
96
+ "er" => "Estonia",
97
+ "err" => "Estonia", # discontinued
98
+ "es" => "El Salvador",
99
+ "et" => "Ethiopia",
100
+ "fa" => "Faroe Islands",
101
+ "fg" => "French Guiana",
102
+ "fi" => "Finland",
103
+ "fj" => "Fiji",
104
+ "fk" => "Falkland Islands",
105
+ "flu" => "Florida",
106
+ "fm" => "Micronesia (Federated States)",
107
+ "fp" => "French Polynesia",
108
+ "fr" => "France",
109
+ "fs" => "Terres australes et antarctiques françaises",
110
+ "ft" => "Djibouti",
111
+ "gau" => "Georgia",
112
+ "gb" => "Kiribati",
113
+ "gd" => "Grenada",
114
+ "ge" => "Germany (East)", # discontinued
115
+ "gg" => "Guernsey",
116
+ "gh" => "Ghana",
117
+ "gi" => "Gibraltar",
118
+ "gl" => "Greenland",
119
+ "gm" => "Gambia",
120
+ "gn" => "Gilbert and Ellice Islands", # discontinued
121
+ "go" => "Gabon",
122
+ "gp" => "Guadeloupe",
123
+ "gr" => "Greece",
124
+ "gs" => "Georgia (Republic)",
125
+ "gsr" => "Georgian S.S.R.", # discontinued
126
+ "gt" => "Guatemala",
127
+ "gu" => "Guam",
128
+ "gv" => "Guinea",
129
+ "gw" => "Germany",
130
+ "gy" => "Guyana",
131
+ "gz" => "Gaza Strip",
132
+ "hiu" => "Hawaii",
133
+ "hk" => "Hong Kong", # discontinued
134
+ "hm" => "Heard and McDonald Islands",
135
+ "ho" => "Honduras",
136
+ "ht" => "Haiti",
137
+ "hu" => "Hungary",
138
+ "iau" => "Iowa",
139
+ "ic" => "Iceland",
140
+ "idu" => "Idaho",
141
+ "ie" => "Ireland",
142
+ "ii" => "India",
143
+ "ilu" => "Illinois",
144
+ "im" => "Isle of Man",
145
+ "inu" => "Indiana",
146
+ "io" => "Indonesia",
147
+ "iq" => "Iraq",
148
+ "ir" => "Iran",
149
+ "is" => "Israel",
150
+ "it" => "Italy",
151
+ "iu" => "Israel-Syria Demilitarized Zones", # discontinued
152
+ "iv" => "Côte d'Ivoire",
153
+ "iw" => "Israel-Jordan Demilitarized Zones", # discontinued
154
+ "iy" => "Iraq-Saudi Arabia Neutral Zone",
155
+ "ja" => "Japan",
156
+ "je" => "Jersey",
157
+ "ji" => "Johnston Atoll",
158
+ "jm" => "Jamaica",
159
+ "jn" => "Jan Mayen", # discontinued
160
+ "jo" => "Jordan",
161
+ "ke" => "Kenya",
162
+ "kg" => "Kyrgyzstan",
163
+ "kgr" => "Kirghiz S.S.R.", # discontinued
164
+ "kn" => "Korea (North)",
165
+ "ko" => "Korea (South)",
166
+ "ksu" => "Kansas",
167
+ "ku" => "Kuwait",
168
+ "kv" => "Kosovo",
169
+ "kyu" => "Kentucky",
170
+ "kz" => "Kazakhstan",
171
+ "kzr" => "Kazakh S.S.R.", # discontinued
172
+ "lau" => "Louisiana",
173
+ "lb" => "Liberia",
174
+ "le" => "Lebanon",
175
+ "lh" => "Liechtenstein",
176
+ "li" => "Lithuania",
177
+ "lir" => "Lithuania", # discontinued
178
+ "ln" => "Central and Southern Line Islands", # discontinued
179
+ "lo" => "Lesotho",
180
+ "ls" => "Laos",
181
+ "lu" => "Luxembourg",
182
+ "lv" => "Latvia",
183
+ "lvr" => "Latvia", # discontinued
184
+ "ly" => "Libya",
185
+ "mau" => "Massachusetts",
186
+ "mbc" => "Manitoba",
187
+ "mc" => "Monaco",
188
+ "mdu" => "Maryland",
189
+ "meu" => "Maine",
190
+ "mf" => "Mauritius",
191
+ "mg" => "Madagascar",
192
+ "mh" => "Macao", # discontinued
193
+ "miu" => "Michigan",
194
+ "mj" => "Montserrat",
195
+ "mk" => "Oman",
196
+ "ml" => "Mali",
197
+ "mm" => "Malta",
198
+ "mnu" => "Minnesota",
199
+ "mo" => "Montenegro",
200
+ "mou" => "Missouri",
201
+ "mp" => "Mongolia",
202
+ "mq" => "Martinique",
203
+ "mr" => "Morocco",
204
+ "msu" => "Mississippi",
205
+ "mtu" => "Montana",
206
+ "mu" => "Mauritania",
207
+ "mv" => "Moldova",
208
+ "mvr" => "Moldavian S.S.R.", # discontinued
209
+ "mw" => "Malawi",
210
+ "mx" => "Mexico",
211
+ "my" => "Malaysia",
212
+ "mz" => "Mozambique",
213
+ "na" => "Netherlands Antilles", # discontinued
214
+ "nbu" => "Nebraska",
215
+ "ncu" => "North Carolina",
216
+ "ndu" => "North Dakota",
217
+ "ne" => "Netherlands",
218
+ "nfc" => "Newfoundland and Labrador",
219
+ "ng" => "Niger",
220
+ "nhu" => "New Hampshire",
221
+ "nik" => "Northern Ireland",
222
+ "nju" => "New Jersey",
223
+ "nkc" => "New Brunswick",
224
+ "nl" => "New Caledonia",
225
+ "nm" => "Northern Mariana Islands", # discontinued
226
+ "nmu" => "New Mexico",
227
+ "nn" => "Vanuatu",
228
+ "no" => "Norway",
229
+ "np" => "Nepal",
230
+ "nq" => "Nicaragua",
231
+ "nr" => "Nigeria",
232
+ "nsc" => "Nova Scotia",
233
+ "ntc" => "Northwest Territories",
234
+ "nu" => "Nauru",
235
+ "nuc" => "Nunavut",
236
+ "nvu" => "Nevada",
237
+ "nw" => "Northern Mariana Islands",
238
+ "nx" => "Norfolk Island",
239
+ "nyu" => "New York (State)",
240
+ "nz" => "New Zealand",
241
+ "ohu" => "Ohio",
242
+ "oku" => "Oklahoma",
243
+ "onc" => "Ontario",
244
+ "oru" => "Oregon",
245
+ "ot" => "Mayotte",
246
+ "pau" => "Pennsylvania",
247
+ "pc" => "Pitcairn Island",
248
+ "pe" => "Peru",
249
+ "pf" => "Paracel Islands",
250
+ "pg" => "Guinea-Bissau",
251
+ "ph" => "Philippines",
252
+ "pic" => "Prince Edward Island",
253
+ "pk" => "Pakistan",
254
+ "pl" => "Poland",
255
+ "pn" => "Panama",
256
+ "po" => "Portugal",
257
+ "pp" => "Papua New Guinea",
258
+ "pr" => "Puerto Rico",
259
+ "pt" => "Portuguese Timor", # discontinued
260
+ "pw" => "Palau",
261
+ "py" => "Paraguay",
262
+ "qa" => "Qatar",
263
+ "qea" => "Queensland",
264
+ "quc" => "Québec (Province)",
265
+ "rb" => "Serbia",
266
+ "re" => "Réunion",
267
+ "rh" => "Zimbabwe",
268
+ "riu" => "Rhode Island",
269
+ "rm" => "Romania",
270
+ "ru" => "Russia (Federation)",
271
+ "rur" => "Russian S.F.S.R.", # discontinued
272
+ "rw" => "Rwanda",
273
+ "ry" => "Ryukyu Islands, Southern", # discontinued
274
+ "sa" => "South Africa",
275
+ "sb" => "Svalbard", # discontinued
276
+ "sc" => "Saint-Barthélemy",
277
+ "scu" => "South Carolina",
278
+ "sd" => "South Sudan",
279
+ "sdu" => "South Dakota",
280
+ "se" => "Seychelles",
281
+ "sf" => "Sao Tome and Principe",
282
+ "sg" => "Senegal",
283
+ "sh" => "Spanish North Africa",
284
+ "si" => "Singapore",
285
+ "sj" => "Sudan",
286
+ "sk" => "Sikkim", # discontinued
287
+ "sl" => "Sierra Leone",
288
+ "sm" => "San Marino",
289
+ "sn" => "Sint Maarten",
290
+ "snc" => "Saskatchewan",
291
+ "so" => "Somalia",
292
+ "sp" => "Spain",
293
+ "sq" => "Eswatini",
294
+ "sr" => "Surinam",
295
+ "ss" => "Western Sahara",
296
+ "st" => "Saint-Martin",
297
+ "stk" => "Scotland",
298
+ "su" => "Saudi Arabia",
299
+ "sv" => "Swan Islands", # discontinued
300
+ "sw" => "Sweden",
301
+ "sx" => "Namibia",
302
+ "sy" => "Syria",
303
+ "sz" => "Switzerland",
304
+ "ta" => "Tajikistan",
305
+ "tar" => "Tajik S.S.R.", # discontinued
306
+ "tc" => "Turks and Caicos Islands",
307
+ "tg" => "Togo",
308
+ "th" => "Thailand",
309
+ "ti" => "Tunisia",
310
+ "tk" => "Turkmenistan",
311
+ "tkr" => "Turkmen S.S.R.", # discontinued
312
+ "tl" => "Tokelau",
313
+ "tma" => "Tasmania",
314
+ "tnu" => "Tennessee",
315
+ "to" => "Tonga",
316
+ "tr" => "Trinidad and Tobago",
317
+ "ts" => "United Arab Emirates",
318
+ "tt" => "Trust Territory of the Pacific Islands", # discontinued
319
+ "tu" => "Turkey",
320
+ "tv" => "Tuvalu",
321
+ "txu" => "Texas",
322
+ "tz" => "Tanzania",
323
+ "ua" => "Egypt",
324
+ "uc" => "United States Misc. Caribbean Islands",
325
+ "ug" => "Uganda",
326
+ "ui" => "United Kingdom Misc. Islands", # discontinued
327
+ "uik" => "United Kingdom Misc. Islands",
328
+ "uk" => "United Kingdom", # discontinued
329
+ "un" => "Ukraine",
330
+ "unr" => "Ukraine", # discontinued
331
+ "up" => "United States Misc. Pacific Islands",
332
+ "ur" => "Soviet Union", # discontinued
333
+ "us" => "United States", # discontinued
334
+ "utu" => "Utah",
335
+ "uv" => "Burkina Faso",
336
+ "uy" => "Uruguay",
337
+ "uz" => "Uzbekistan",
338
+ "uzr" => "Uzbek S.S.R.", # discontinued
339
+ "vau" => "Virginia",
340
+ "vb" => "British Virgin Islands",
341
+ "vc" => "Vatican City",
342
+ "ve" => "Venezuela",
343
+ "vi" => "Virgin Islands of the United States",
344
+ "vm" => "Vietnam",
345
+ "vn" => "Vietnam, North", # discontinued
346
+ "vp" => "Various places",
347
+ "vra" => "Victoria",
348
+ "vs" => "Vietnam, South", # discontinued
349
+ "vtu" => "Vermont",
350
+ "wau" => "Washington (State)",
351
+ "wb" => "West Berlin", # discontinued
352
+ "wea" => "Western Australia",
353
+ "wf" => "Wallis and Futuna",
354
+ "wiu" => "Wisconsin",
355
+ "wj" => "West Bank of the Jordan River",
356
+ "wk" => "Wake Island",
357
+ "wlk" => "Wales",
358
+ "ws" => "Samoa",
359
+ "wvu" => "West Virginia",
360
+ "wyu" => "Wyoming",
361
+ "xa" => "Christmas Island (Indian Ocean)",
362
+ "xb" => "Cocos (Keeling) Islands",
363
+ "xc" => "Maldives",
364
+ "xd" => "Saint Kitts-Nevis",
365
+ "xe" => "Marshall Islands",
366
+ "xf" => "Midway Islands",
367
+ "xga" => "Coral Sea Islands Territory",
368
+ "xh" => "Niue",
369
+ "xi" => "Saint Kitts-Nevis-Anguilla", # discontinued
370
+ "xj" => "Saint Helena",
371
+ "xk" => "Saint Lucia",
372
+ "xl" => "Saint Pierre and Miquelon",
373
+ "xm" => "Saint Vincent and the Grenadines",
374
+ "xn" => "North Macedonia",
375
+ "xna" => "New South Wales",
376
+ "xo" => "Slovakia",
377
+ "xoa" => "Northern Territory",
378
+ "xp" => "Spratly Island",
379
+ "xr" => "Czech Republic",
380
+ "xra" => "South Australia",
381
+ "xs" => "South Georgia and the South Sandwich Islands",
382
+ "xv" => "Slovenia",
383
+ "xx" => "No place, unknown, or undetermined",
384
+ "xxc" => "Canada",
385
+ "xxk" => "United Kingdom",
386
+ "xxr" => "Soviet Union", # discontinued
387
+ "xxu" => "United States",
388
+ "ye" => "Yemen",
389
+ "ykc" => "Yukon Territory",
390
+ "ys" => "Yemen (People's Democratic Republic)", # discontinued
391
+ "yu" => "Serbia and Montenegro", # discontinued
392
+ "za" => "Zambia"
393
+ }.freeze
394
+ end