csl 2.3.0 → 2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0f6649dcc754264a3546a210a92cc4dd50ed8910ef011d61c0db3818cb1c576
4
- data.tar.gz: 85ebc13c82b78cfefd1898c1e44380acea10fead06a1ac7bb4a34dc09b51d77b
3
+ metadata.gz: c70f967fbf46ada97d0c2b3164d3fc1b843d24d6125f1c6364d2d7f408c5fa7e
4
+ data.tar.gz: 59d241370ed43b45a191e938ebbe20b51ed74419a503feb0c71e37d2770facd3
5
5
  SHA512:
6
- metadata.gz: 1934e44e5893b200dc7ca55d522c3fc0024a0510e2a8d918bc5596f5249adad6c0357cae926ff2d47087404f9d3d58767a8448833d017bd47fbfcb8700922db6
7
- data.tar.gz: cab29314253dd59ef4f669e3e649d6bf562eaa9799b8c6384e48a6d20bbc091b4d9a10079929ef960be2997c3879c775eb34c04faf73014693a3c0a0a0e6a080
6
+ metadata.gz: ee00896662db7e3f7ccdc6644f208428a392ad2a66ae09ad93bb37ac4a6366915d9f4cea1cb274526466e8a0441f6c40afdfcbf953735e2d6a744587f41f6777
7
+ data.tar.gz: 2807a5c96c581af1a9314d75b5b24b09e461c6fbf956b475d9bde1f8c1f7a6bc420095a9af6380ee7879129b80f6dc0ce1505dcd555dafdff7355b89e09979cc
@@ -80,10 +80,11 @@ module CSL
80
80
  return unless has_ordinals?
81
81
 
82
82
  options = Term.specialize(options)
83
+ form, options = options[:form], options.except(:form)
83
84
  number = number.to_i.abs
84
85
 
85
86
  # try to match long-ordinals first
86
- if options.delete(:form).to_s =~ /^long/i
87
+ if form.to_s =~ /^long/i
87
88
  ordinal = lookup_long_ordinal_for number, options
88
89
  return ordinal unless ordinal.nil?
89
90
  end
@@ -94,9 +95,9 @@ module CSL
94
95
  ordinal = send algorithm, number, options
95
96
  return ordinal unless ordinal.nil?
96
97
 
97
- # fallback to non-gendered version
98
- if options.delete(:'gender-form')
99
- ordinal = send algorithm, number, options
98
+ # fallback to neuter
99
+ if options.key?(:'gender-form')
100
+ ordinal = send algorithm, number, options.merge(:'gender-form' => nil)
100
101
  end
101
102
 
102
103
  ordinal
data/lib/csl/locale.rb CHANGED
@@ -55,7 +55,7 @@ module CSL
55
55
  error = nil
56
56
 
57
57
  candidates.each do |candidate|
58
- return super(candidate)
58
+ return super(candidate).tap { |locale| locale.requested = tag }
59
59
  rescue ParseError => e
60
60
  error ||= e
61
61
  end
@@ -119,6 +119,10 @@ module CSL
119
119
 
120
120
  attr_accessor :region, :script
121
121
 
122
+ # @return [String, nil] the IETF tag the locale was loaded for;
123
+ # this differs from the locale's own tag if the locale is a fallback
124
+ attr_accessor :requested
125
+
122
126
  alias_child :metadata, :info
123
127
  alias_child :dates, :date
124
128
  alias_child :options, :style_options
@@ -171,6 +175,7 @@ module CSL
171
175
  def initialize_copy(other)
172
176
  @parent, @ancestors, @descendants, @siblings, @root, @depth = nil
173
177
  initialize(other.attributes.to_hash.merge(:lang => other.to_s))
178
+ @requested = other.requested
174
179
  end
175
180
 
176
181
  def added_to(node)
@@ -266,8 +271,9 @@ module CSL
266
271
  # de.ordinalize(13)
267
272
  # #-> "13."
268
273
  #
269
- # de.ordinalize(3, :form => :long, :gender => :feminine)
270
- # #-> "dritte"
274
+ # fr = Locale.load('fr')
275
+ # fr.ordinalize(1, :'gender-form' => 'feminine')
276
+ # #-> "1ʳᵉ"
271
277
  #
272
278
  # @note
273
279
  # For CSL 1.0 (and older) locales that do not define an "ordinal-00"
@@ -279,8 +285,9 @@ module CSL
279
285
  # @param options [Hash] formatting options
280
286
  #
281
287
  # @option options [:short,:long] :form (:short) which ordinals form to use
282
- # @option options [:feminine,:masculine,:neutral] :gender (:neutral)
283
- # which ordinals gender-form to use
288
+ # @option options ['feminine','masculine',nil] :'gender-form' (nil)
289
+ # which ordinals gender-form to use; falls back to the neuter
290
+ # ordinals (without gender-form)
284
291
  #
285
292
  # @raise [ArgumentError] if number cannot be converted to an integer
286
293
  #
@@ -300,8 +307,7 @@ module CSL
300
307
 
301
308
  # @return [Boolean] true when the option limit-day-ordinals-to-day-1 is true
302
309
  def limit_day_ordinals?
303
- return false unless has_options? && options.attribute?(:'limit-day-ordinals-to-day-1')
304
- !!(options[:'limit-day-ordinals-to-day-1'].to_s =~ /^true$/i)
310
+ has_options? && options.boolean_attribute(:'limit-day-ordinals-to-day-1')
305
311
  end
306
312
 
307
313
  def limit_day_ordinals!
@@ -314,8 +320,7 @@ module CSL
314
320
 
315
321
  # @return [Boolean] true when the option punctuation-in-quote is true
316
322
  def punctuation_in_quote?
317
- return false unless has_options? && options.attribute?(:'punctuation-in-quote')
318
- !!(options[:'punctuation-in-quote'].to_s =~ /^true$/i)
323
+ has_options? && options.boolean_attribute(:'punctuation-in-quote')
319
324
  end
320
325
  alias punctuation_in_quotes? punctuation_in_quote?
321
326
 
@@ -399,13 +404,13 @@ module CSL
399
404
 
400
405
  # @returns [Boolean] whether or not the Locale is the default locale
401
406
  def default?
402
- to_s == Locale.default
407
+ default_tag?(language, script, region)
403
408
  end
404
409
 
405
410
  # @return [Boolean] whehter or not the Locale's region is the default
406
411
  # region for its language
407
412
  def default_region?
408
- region && region == Locale.regions[language]
413
+ default_region_for?(language, region)
409
414
  end
410
415
 
411
416
  # @return [Boolean] whether or not the Locale's language is the default
@@ -414,11 +419,25 @@ module CSL
414
419
  language && language == Locale.languages[region]
415
420
  end
416
421
 
422
+ # Whether or not the passed-in locale (e.g., an in-style locale)
423
+ # matches this locale. This is the case for universal locales and
424
+ # for locales of the language this locale was requested for.
417
425
  def like?(other)
418
426
  return false unless other.is_a? Locale
419
427
  return true if other.universal?
420
428
 
421
- language == other.language
429
+ requested_language == other.language
430
+ end
431
+
432
+ # @return [Symbol, nil] the language this locale was requested for
433
+ def requested_language
434
+ requested.nil? ? language : requested.split('-')[0].to_sym
435
+ end
436
+
437
+ # @return [Boolean] whether or not the locale was loaded as a
438
+ # fallback for a different locale
439
+ def fallback?
440
+ !requested.nil? && requested != to_s
422
441
  end
423
442
 
424
443
  def universal?
@@ -461,44 +480,76 @@ module CSL
461
480
  # de-DE will come before de-AT even though the alphabetical order
462
481
  # would be different).
463
482
  #
483
+ # Fallback locales are compared as the locale they were requested
484
+ # for (see #requested); locale data (terms, dates, and options) is
485
+ # not compared.
486
+ #
464
487
  # @param other [Locale] the locale used for comparison
465
488
  # @return [1,0,-1,nil] the result of the comparison
466
489
  def <=>(other)
490
+ return nil unless other.is_a?(Locale)
491
+ compare(identity, other.identity)
492
+ end
493
+
494
+ # @return [String] the Locale's IETF tag
495
+ def to_s
496
+ [language, script, region].compact.join('-')
497
+ end
498
+
499
+ # @return [String] a string representation of the Locale
500
+ def inspect
501
+ "#<#{self.class.name} #{to_s}>"
502
+ end
503
+
504
+ protected
505
+
506
+ # @return [Array<Symbol, nil>] language, script, and region of the
507
+ # locale or, for fallback locales, of the requested tag
508
+ def identity
509
+ return [language, script, region] unless fallback?
510
+
511
+ language, *rs = requested.split('-').map(&:to_sym)
512
+ region, script = rs.reverse
513
+
514
+ [language, script, region]
515
+ end
516
+
517
+ private
518
+
519
+ # Compares two locale identities (see #identity).
520
+ def compare(a, b)
521
+ language, script, region = a
522
+ other_language, other_script, other_region = b
523
+
467
524
  case
468
- when !other.is_a?(Locale)
469
- nil
470
- when [language, region] == [other.language, other.region]
471
- script <=> other.script
472
- when default?
525
+ when [language, region] == [other_language, other_region]
526
+ script <=> other_script
527
+ when default_tag?(*a)
473
528
  -1
474
- when other.default?
529
+ when default_tag?(*b)
475
530
  1
476
- when language == other.language
531
+ when language == other_language
477
532
  case
478
- when default_region?
533
+ when default_region_for?(language, region)
479
534
  -1
480
- when other.default_region?
535
+ when default_region_for?(other_language, other_region)
481
536
  1
482
537
  else
483
- region.to_s <=> other.region.to_s
538
+ region.to_s <=> other_region.to_s
484
539
  end
485
540
  else
486
- language.to_s <=> other.language.to_s
541
+ language.to_s <=> other_language.to_s
487
542
  end
488
543
  end
489
544
 
490
- # @return [String] the Locale's IETF tag
491
- def to_s
492
- [language, script, region].compact.join('-')
545
+ def default_tag?(language, script, region)
546
+ [language, script, region].compact.join('-') == Locale.default
493
547
  end
494
548
 
495
- # @return [String] a string representation of the Locale
496
- def inspect
497
- "#<#{self.class.name} #{to_s}>"
549
+ def default_region_for?(language, region)
550
+ region && region == Locale.regions[language]
498
551
  end
499
552
 
500
- private
501
-
502
553
  alias original_locale_attribute_assignments attribute_assignments
503
554
 
504
555
  def attribute_assignments
@@ -549,6 +600,9 @@ module CSL
549
600
  def merge_terms(other)
550
601
  return self unless other.has_terms?
551
602
 
603
+ # Ordinals are replaced as a set
604
+ terms.drop_ordinals if has_terms? && other.terms.has_ordinals?
605
+
552
606
  other.each_term do |term|
553
607
  store term.deep_copy
554
608
  end
data/lib/csl/node.rb CHANGED
@@ -168,13 +168,10 @@ module CSL
168
168
  end
169
169
 
170
170
  def fetch(key, default = nil)
171
- value = keys.include?(key.to_sym) && send(:'[]', key)
171
+ value = send(:'[]', key) if keys.include?(key.to_sym)
172
+ return value unless value.nil?
172
173
 
173
- if block_given?
174
- value || yield(key)
175
- else
176
- value || default
177
- end
174
+ block_given? ? yield(key) : default
178
175
  end
179
176
 
180
177
  # Merges the current with the passed-in attributes.
@@ -307,6 +304,17 @@ module CSL
307
304
  attributes.fetch(name, false)
308
305
  end
309
306
 
307
+ # @param name [Symbol] the name of an xsd:boolean attribute
308
+ # @param default [Boolean] the value if the attribute is not set
309
+ # @return [Boolean] the value of the boolean attribute
310
+ def boolean_attribute(name, default = false)
311
+ case attributes.fetch(name, nil).to_s
312
+ when 'true', '1' then true
313
+ when 'false', '0' then false
314
+ else default
315
+ end
316
+ end
317
+
310
318
  # @param [[String]] names list of attribute names
311
319
  # @return [Boolean] true if the node contains attributes for all
312
320
  # passed-in names; false otherwise.
@@ -466,11 +474,11 @@ module CSL
466
474
  end
467
475
 
468
476
  def strip_periods?
469
- attribute?(:'strip-periods') && !!(attributes[:'strip-periods'].to_s =~ /^true$/i)
477
+ boolean_attribute(:'strip-periods')
470
478
  end
471
479
 
472
480
  def quotes?
473
- attribute?(:'quotes') && !!(attributes[:'quotes'].to_s =~ /^true$/i)
481
+ boolean_attribute(:quotes)
474
482
  end
475
483
 
476
484
 
@@ -573,17 +581,6 @@ module CSL
573
581
  CSL.encode_xml_text text.to_s.strip
574
582
  end
575
583
 
576
- # TextNodes quack like a string.
577
- # def_delegators :to_s, *String.instance_methods(false).reject do |m|
578
- # m.to_s =~ /^\W|!$|(?:^(?:hash|eql?|to_s|length|size|inspect)$)/
579
- # end
580
- #
581
- # String.instance_methods(false).select { |m| m.to_s =~ /!$/ }.each do |m|
582
- # define_method(m) do
583
- # content.send(m) if content.respond_to?(m)
584
- # end
585
- # end
586
-
587
584
  def initialize(argument = '')
588
585
  case
589
586
  when argument.respond_to?(:each_pair)
@@ -229,7 +229,7 @@ module CSL
229
229
  # be inserted between before et-al depending on the
230
230
  # number of names rendered
231
231
  def delimiter_contextually_precedes_et_al?
232
- return true unless attribute?[:'delimiter-precedes-et-al']
232
+ return true unless attribute?(:'delimiter-precedes-et-al')
233
233
  !!(attributes[:'delimiter-precedes-et-al'].to_s =~ /^contextual/i)
234
234
  end
235
235
 
@@ -327,7 +327,7 @@ module CSL
327
327
  end
328
328
 
329
329
  def ellipsis?
330
- attributes[:'et-al-use-last'].to_s =~ /^true$/
330
+ boolean_attribute(:'et-al-use-last')
331
331
  end
332
332
 
333
333
  def ellipsis
data/lib/csl/style.rb CHANGED
@@ -74,6 +74,15 @@ module CSL
74
74
  @errors = Schema.validate self
75
75
  end
76
76
 
77
+ # Merges the style's overrides into the given locale.
78
+ #
79
+ # @param locale [Locale]
80
+ # @return [Locale]
81
+ def localize(locale)
82
+ overrides = locales.sort.reverse.select { |other| locale.like?(other) }
83
+ overrides.empty? ? locale : locale.merge(*overrides)
84
+ end
85
+
77
86
  def valid?
78
87
  validate.empty?
79
88
  end
@@ -127,7 +136,7 @@ module CSL
127
136
  alias demote_particle? demote_non_dropping_particle?
128
137
 
129
138
  def initialize_without_hyphen?
130
- attribute?(:'initialize-with-hyphen') && !attributes[:'initialize-with-hyphen']
139
+ !boolean_attribute(:'initialize-with-hyphen', true)
131
140
  end
132
141
 
133
142
  def has_page_range_format?
data/lib/csl/treelike.rb CHANGED
@@ -33,12 +33,17 @@ module CSL
33
33
  end
34
34
  alias delete delete_children
35
35
 
36
- # Deletes child nodes that are equal to the passed-in node. Returns all
37
- # deleted children. If no children were deleted, returns nil. If the
38
- # optional block is given, returns the result block if no children were
39
- # deleted.
36
+ # Deletes the passed-in child node.
37
+ # Returns the deleted child.
38
+ # If the node is not a child, returns nil or,
39
+ # if the optional block is given, the result of the block.
40
40
  def delete_child(child)
41
- deleted = children.delete child
41
+ deleted = if children.is_a?(Array)
42
+ index = children.index { |node| node.equal?(child) }
43
+ children.delete_at(index) unless index.nil?
44
+ else
45
+ children.delete child
46
+ end
42
47
 
43
48
  case
44
49
  when deleted.nil? && block_given?
@@ -423,18 +428,19 @@ module CSL
423
428
 
424
429
  alias << push
425
430
 
426
- # Delete items from self that are equal to node. If any items are
427
- # found, returns the deleted items. If the items is not found,
428
- # returns nil. If the optional code block is given, returns the
429
- # result og block if the item is not found.
431
+ # Deletes the passed-in node; other, equal nodes are not deleted.
432
+ # Returns the deleted node or nil if the node is not found.
433
+ # If the optional code block is given,
434
+ # returns the result of the block if the node is not found.
430
435
  def delete(node)
431
436
  return nil unless node.respond_to?(:nodename)
432
437
 
433
438
  deleted = resolve(node.nodename)
434
439
  if deleted.kind_of?(Array)
435
- deleted = deleted.delete(node)
440
+ index = deleted.index { |other| other.equal?(node) }
441
+ deleted = index.nil? ? nil : deleted.delete_at(index)
436
442
  else
437
- if deleted == node
443
+ if deleted.equal?(node)
438
444
  self[node.nodename] = nil
439
445
  else
440
446
  deleted = nil
@@ -442,7 +448,7 @@ module CSL
442
448
  end
443
449
 
444
450
  # Delete node from ordered list as well
445
- @order.delete(node)
451
+ @order.delete_if { |other| other.equal?(node) }
446
452
 
447
453
  if deleted.nil? && block_given?
448
454
  yield
data/lib/csl/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module CSL
2
- VERSION = '2.3.0'.freeze
2
+ VERSION = '2.4.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: csl
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sylvester Keil
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-10-05 00:00:00.000000000 Z
10
+ date: 2026-10-07 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: namae