relaton-render 3.0.0.pre.alpha.9 → 3.0.0.pre.alpha.11

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: 1b2cc7ff33eb9f42dda9093ce14cfe39a2495d4499d93150e50783da711e8087
4
- data.tar.gz: 8be8a9bea072cd0e69307b5ea8fe3674429e302dd8751f1f399b81a5a154c08f
3
+ metadata.gz: c0b28a1996c4db280d4b0f1f976c91c2dc99926b60839df65e76407df0a491f2
4
+ data.tar.gz: 675de3d7b9821c9838a63730bff1d49f39e6595213d2b9476c6875ed5b76f024
5
5
  SHA512:
6
- metadata.gz: 1101c3d3781e1c51f78728e74a5ea7c3897e2d52407d896057952d6eb0461f7f6dab1344ef7141e0d37bf77348cc203019420e6e4ea9753986746e2261ef344b
7
- data.tar.gz: 4be1e8b804b46e08494db403d5bb9100760415af858dc0a96e2d0db30f82cd00d8d53cdd8447a06d434847d6316389b6b3346e7307a5f5630717105b6a3dab06
6
+ metadata.gz: 8a6d75a746549935b322e7437692938d3a126692de6e074053f1768b97b7a4da3c6ce33e8fb5b182f89f92b388c5bcb54bda1d18a38e429c533e72f0cc03b92b
7
+ data.tar.gz: 0cbb4c77dc482860c76834d3dff1f418ab8726dfd84477e5787d06c9b11df4dff139e22019cf2d6b525d980ae6d907207075d315143afc8f472d2828df634116
@@ -74,6 +74,11 @@ module Relaton
74
74
  # falling back to the reference rendering (the 1.x short-cite)
75
75
  FIRST_DELIM = "<span class='fmt-first-biblio-delim'/>"
76
76
 
77
+ # A creator list alone (flavors' document-history name forms)
78
+ def creator_names(item)
79
+ @renderer.render_creators(to_model(item))
80
+ end
81
+
77
82
  # render_all feeds the bibliography list: a reference not ending
78
83
  # in the biblio terminator takes one (the 1.x render1 behaviour;
79
84
  # single-item render stays verbatim)
@@ -142,6 +147,12 @@ module Relaton
142
147
  EXCLUDED_ID_TYPES = %w(METANORMA METANORMA-ORDINAL AUTHOR-DATE TITLE
143
148
  URN ISO-REFERENCE ISSN ISBN DOI).freeze
144
149
 
150
+ # Flavors extend the authoritative-identifier exclusions by
151
+ # overriding this reader on their facade subclass
152
+ def excluded_id_types
153
+ EXCLUDED_ID_TYPES
154
+ end
155
+
145
156
  def facade_docids(doc)
146
157
  node = doc
147
158
  unless node.respond_to?(:xpath)
@@ -160,7 +171,7 @@ module Relaton
160
171
  ->(_x) { true },
161
172
  ].each do |p|
162
173
  out = ids.select do |x|
163
- p.call(x) && !EXCLUDED_ID_TYPES.include?(facade_id_type(x))
174
+ p.call(x) && !excluded_id_types.include?(facade_id_type(x))
164
175
  end
165
176
  out.empty? or break
166
177
  end
@@ -77,7 +77,7 @@ module Relaton
77
77
  parts = Array(person.name&.forename).map do |f|
78
78
  content = localized(f)
79
79
  next content unless content.empty?
80
- next "#{f.initial}." if f.initial.to_s.length == 1
80
+ next initial(f.initial) if f.initial.to_s.length == 1
81
81
 
82
82
  ""
83
83
  end.reject(&:empty?)
@@ -93,14 +93,23 @@ module Relaton
93
93
  end
94
94
  end
95
95
 
96
- # The declared creator fallback (e.g. the publisher's
97
- # abbreviation for standards with no personal creators)
96
+ # The declared creator fallback for items with no personal
97
+ # creators: the publisher's abbreviation or full name
98
98
  def fallback_name
99
- return "" unless name_form.creator_fallback == "publisher_abbrev"
100
-
101
- contributors("publisher").map do |c|
102
- localized(c.organization&.abbreviation)
103
- end.reject(&:empty?).first.to_s
99
+ orgs = contributors("publisher")
100
+ name = case name_form.creator_fallback
101
+ when "publisher_abbrev"
102
+ orgs.map do |c|
103
+ localized(c.organization&.abbreviation)
104
+ end
105
+ when "publisher_name"
106
+ orgs.map do |c|
107
+ localized(Array(c.organization&.name).first)
108
+ end
109
+ else
110
+ []
111
+ end
112
+ name.reject(&:empty?).first.to_s
104
113
  end
105
114
 
106
115
  def names
@@ -15,9 +15,18 @@ module Relaton
15
15
 
16
16
 
17
17
  def render
18
- d = identifiers.first or return ""
18
+ return "" if identifiers.empty?
19
19
 
20
- IdentifierKinds.render(d)
20
+ ids = @style.templates.identifier_mode == "first" ?
21
+ [identifiers.first] : identifiers
22
+ ids.map { |d| render_id(d) }
23
+ .join(@i18n.punct_fetch("identifier_join", ". "))
24
+ end
25
+
26
+ # A flavor's identifier kinds subclass this element and
27
+ # override here
28
+ def render_id(docidentifier)
29
+ IdentifierKinds.render(docidentifier)
21
30
  end
22
31
 
23
32
  private
@@ -12,13 +12,20 @@ module Relaton
12
12
  end
13
13
 
14
14
  def render
15
+ text = uri
15
16
  form = uri_form
17
+ # an access location cites its own label ("At: Library"); a
18
+ # uri carries the availability label
19
+ if @from_accesslocation
20
+ return "#{@i18n.label('at_url')} #{text}".strip
21
+ end
22
+
16
23
  text = if form && !form.empty?
17
24
  ::Relaton::Render::Iso690::Template.new(form).evaluate(
18
- "uri" => Template::Field[!uri.empty?, uri],
25
+ "uri" => Template::Field[!text.empty?, text],
19
26
  )
20
27
  else
21
- uri
28
+ text
22
29
  end
23
30
 
24
31
  label = @short ? "" : "#{@i18n.label('available_from')} "
@@ -44,6 +51,7 @@ module Relaton
44
51
  from_accesslocation =
45
52
  Array(@model.accesslocation).map(&:to_s).reject(&:empty?)
46
53
  unless from_accesslocation.empty?
54
+ @from_accesslocation = true
47
55
  return from_accesslocation.first.to_s
48
56
  end
49
57
 
@@ -13,13 +13,10 @@ module Relaton
13
13
  !render.nil?
14
14
  end
15
15
 
16
+ # The bare date; the style's dateForm wraps it in the
17
+ # dategroup slot (Fields#dategroup_field)
16
18
  def render
17
- text = range || year&.to_s
18
- return text if (form = @style.templates.date_form).empty?
19
-
20
- ::Relaton::Render::Iso690::Template.new(form).evaluate(
21
- "date" => Template::Field[!text.to_s.empty?, text.to_s],
22
- )
19
+ range || year&.to_s
23
20
  end
24
21
 
25
22
  private
@@ -27,7 +24,9 @@ module Relaton
27
24
  def publication_date
28
25
  Array(@model.date).find(&:published) ||
29
26
  Array(@model.date).find { |d| d.type.nil? } ||
30
- Array(@model.date).find { |d| d.type == "created" }
27
+ Array(@model.date).find { |d| d.type == "created" } ||
28
+ Array(@model.date).find { |d| d.type == "issued" } ||
29
+ Array(@model.date).find { |d| d.type == "circulated" }
31
30
  end
32
31
 
33
32
  def publication_date_value
@@ -39,7 +38,9 @@ module Relaton
39
38
  d = publication_date or return nil
40
39
  dash = @i18n.label("date_range")
41
40
  if d.from && d.to
42
- "#{d.from.to_date.year}#{dash}#{d.to.to_date.year}"
41
+ fy = d.from.to_date.year
42
+ ty = d.to.to_date.year
43
+ fy == ty ? fy.to_s : "#{fy}#{dash}#{ty}"
43
44
  elsif d.from
44
45
  "#{d.from.to_date.year}#{dash}"
45
46
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ module Elements
7
+ # The item's updated date, nested in the creator date group
8
+ # ("(2018 (updated November 2018))")
9
+ class Updated < Element
10
+ def present?
11
+ !render.empty?
12
+ end
13
+
14
+ def render
15
+ d = Array(@model.date).find { |x| x.type == "updated" } or
16
+ return ""
17
+ value = d.at || d.from || d.to or return ""
18
+
19
+ date = value.to_date
20
+ " (#{label('updated')} #{month(date.month)} #{date.year})"
21
+ rescue ArgumentError, TypeError
22
+ ""
23
+ end
24
+
25
+ private
26
+
27
+ def label(key)
28
+ @i18n.label(key)
29
+ end
30
+
31
+ def month(num)
32
+ @i18n.label("month_#{num}")
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -22,6 +22,7 @@ module Relaton
22
22
  autoload :Stddoc, "relaton/render/iso690/elements/stddoc"
23
23
  autoload :Status, "relaton/render/iso690/elements/status"
24
24
  autoload :Citeid, "relaton/render/iso690/elements/citeid"
25
+ autoload :Updated, "relaton/render/iso690/elements/updated"
25
26
 
26
27
  CLASSES = {
27
28
  creator: Creator,
@@ -41,11 +42,22 @@ module Relaton
41
42
  stddoc: Stddoc,
42
43
  status: Status,
43
44
  citeid: Citeid,
45
+ updated: Updated,
44
46
  }.freeze
45
47
 
46
- def self.build(name, model, style:, i18n:, short: false)
47
- CLASSES.fetch(name).new(model, style: style, i18n: i18n,
48
- short: short)
48
+ class << self
49
+ # Flavors extend the vocabulary through the renderer's
50
+ # element map: a flavor slot resolves before the built-ins,
51
+ # scoped to that renderer alone (never process-wide)
52
+ def resolve(name, elements = {})
53
+ elements[name.to_sym] || CLASSES.fetch(name, nil)
54
+ end
55
+ end
56
+
57
+ def self.build(name, model, style:, i18n:, short: false,
58
+ elements: {})
59
+ klass = resolve(name, elements) or return nil
60
+ klass.new(model, style: style, i18n: i18n, short: short)
49
61
  end
50
62
  end
51
63
  end
@@ -11,40 +11,89 @@ module Relaton
11
11
  ELEMENT_SLOTS = %i[
12
12
  creator title edition medium series production
13
13
  date numeration component_part identifier location size
14
- extent access stddoc status citeid
14
+ extent access stddoc status citeid updated
15
15
  ].freeze
16
16
 
17
17
  def initialize(model, style:, i18n:, disambiguator: nil,
18
- short: false)
18
+ short: false, elements: {})
19
19
  @model = model
20
20
  @style = style
21
21
  @i18n = i18n
22
22
  @disambiguator = disambiguator.to_s
23
23
  @short = short
24
+ @elements = elements
24
25
  end
25
26
 
26
27
  def to_h
27
28
  table = {}
28
- ELEMENT_SLOTS.each do |slot|
29
+ slots.each do |slot|
29
30
  key = slot.to_s.delete("_").downcase
30
31
  table[key] = element_field(slot)
31
32
  end
33
+ table["dategroup"] = dategroup_field
34
+ unless table["dategroup"].present?
35
+ table["dategroup"] = fallback_field(:dategroup)
36
+ end
32
37
  table.merge(name_fields)
33
38
  end
34
39
 
40
+ def fallback_field(slot)
41
+ fallback = @style.fallback_for(item_kind, slot)
42
+ fallback ? Template::Field[true, fallback] :
43
+ Template::Field[false, ""]
44
+ end
45
+
35
46
  private
36
47
 
48
+ # The date with the updated date nested ("(2018 (updated
49
+ # November 2018))"); the bare date slot stays untouched
50
+ def dategroup_field
51
+ date = raw_field(:date)
52
+ updated = raw_field(:updated)
53
+ return Template::Field[false, ""] if !date.present? &&
54
+ !updated.present?
55
+
56
+ inner = updated.present? ? updated.text : date.text
57
+ inner = "#{date.text}#{updated.text}" if date.present? &&
58
+ updated.present?
59
+ Template::Field[true, "(#{inner})"]
60
+ end
61
+
62
+ private
63
+
64
+ def slots
65
+ ELEMENT_SLOTS | @elements.keys
66
+ end
67
+
68
+ # The element's own render, before any absent-slot fallback:
69
+ # the creator-date group cites real dates only
70
+ def raw_field(slot)
71
+ element_field = build_field(slot)
72
+ return element_field if element_field.present?
73
+
74
+ Template::Field[false, ""]
75
+ end
76
+
37
77
  def element_field(slot)
78
+ field = build_field(slot)
79
+ return field if field.present?
80
+
81
+ fallback = @style.fallback_for(item_kind, slot)
82
+ return Template::Field[true, fallback] if fallback
83
+
84
+ Template::Field[false, ""]
85
+ end
86
+
87
+ def build_field(slot)
38
88
  element = Elements.build(slot, @model, style: @style,
39
- i18n: @i18n, short: @short)
89
+ i18n: @i18n, short: @short,
90
+ elements: @elements) or
91
+ return Template::Field[false, ""]
40
92
  text = element.render.to_s
41
93
  if element.present? && !text.empty?
42
94
  return Template::Field[true, text]
43
95
  end
44
96
 
45
- fallback = @style.fallback_for(item_kind, slot)
46
- return Template::Field[true, fallback] if fallback
47
-
48
97
  Template::Field[false, ""]
49
98
  end
50
99
 
@@ -53,7 +102,8 @@ module Relaton
53
102
  end
54
103
 
55
104
  def name_fields
56
- creator = Elements.build(:creator, @model, style: @style, i18n: @i18n)
105
+ creator = Elements.build(:creator, @model, style: @style,
106
+ i18n: @i18n, elements: @elements)
57
107
  {
58
108
  "surname" => Template::Field[creator.present?, creator.in_text],
59
109
  "givennames" => Template::Field[!creator.principal_given.empty?,
@@ -26,6 +26,7 @@ labels:
26
26
  series_no: "no."
27
27
  report_no: "Report no."
28
28
  available_from: "Available from:"
29
+ at_url: "At:"
29
30
  in: "In:"
30
31
  and: "and"
31
32
  oxford_comma: ","
@@ -16,19 +16,10 @@ module Relaton
16
16
  "MRN" => ->(content) { content },
17
17
  }.freeze
18
18
 
19
- # Flavor-registered kinds, consulted before the built-ins; the
20
- # registry is process-wide by design
21
- OVERRIDES = {}
22
-
23
19
  class << self
24
- def register(type, kind)
25
- OVERRIDES[type] = kind
26
- kind
27
- end
28
-
29
20
  def render(docidentifier)
30
21
  content = docidentifier.content.to_s
31
- kind = OVERRIDES[docidentifier.type] || KINDS[docidentifier.type]
22
+ kind = KINDS[docidentifier.type]
32
23
  kind ? kind.call(content) : content
33
24
  end
34
25
  end
@@ -23,7 +23,8 @@ module Relaton
23
23
  end
24
24
 
25
25
  def initialize(style: DEFAULT_STYLE, lang: "en", script: "Latn",
26
- labels: {})
26
+ labels: {}, elements: {})
27
+ @elements = elements
27
28
  @style = Style.load(style)
28
29
  @i18n = I18n.load(lang)
29
30
  @i18n.overlay_hash!(labels) unless labels.empty?
@@ -37,7 +38,8 @@ module Relaton
37
38
  kind = Kinds.kind_for(model.type)
38
39
  template = @style.template_for(kind, home: home_docid?(model))
39
40
  out = Template.new(template).evaluate(
40
- Fields.new(model, style: @style, i18n: @i18n).to_h,
41
+ Fields.new(model, style: @style, i18n: @i18n,
42
+ elements: @elements).to_h,
41
43
  )
42
44
  raise ::Relaton::Render::Unrenderable,
43
45
  "no renderable elements" if out.strip.empty?
@@ -52,13 +54,20 @@ module Relaton
52
54
  Array(model.docidentifier).any? { |d| types.include?(d.type) }
53
55
  end
54
56
 
57
+ # A creator list alone (flavors' document-history name forms)
58
+ def render_creators(model)
59
+ Elements.build(:creator, model, style: @style, i18n: @i18n,
60
+ short: true, elements: @elements).render
61
+ end
62
+
55
63
  # The short cite: the reference with the first-biblio marker
56
64
  def render_short(model, delim)
57
65
  kind = Kinds.kind_for(model.type)
58
66
  template = @style.type_template_for(kind)&.short ||
59
67
  @style.template_for(kind, home: home_docid?(model))
60
68
  Template.new(template).evaluate_short(
61
- Fields.new(model, style: @style, i18n: @i18n, short: true).to_h,
69
+ Fields.new(model, style: @style, i18n: @i18n, short: true,
70
+ elements: @elements).to_h,
62
71
  delim,
63
72
  )
64
73
  rescue ::Relaton::Render::Unrenderable
@@ -69,7 +78,8 @@ module Relaton
69
78
  def citation(model, disambiguator: nil)
70
79
  Template.new(@style.templates.citation).evaluate(
71
80
  Fields.new(model, style: @style, i18n: @i18n,
72
- disambiguator: disambiguator).to_h,
81
+ disambiguator: disambiguator,
82
+ elements: @elements).to_h,
73
83
  )
74
84
  end
75
85
  end
@@ -18,6 +18,9 @@ module Relaton
18
18
  attribute :creators, :string, default: ""
19
19
  attribute :uri_short, :string, default: ""
20
20
  attribute :date_form, :string, default: ""
21
+ # "first": cite only the first identifier (1.x
22
+ # authoritative_identifier|first styles)
23
+ attribute :identifier_mode, :string, default: "all"
21
24
 
22
25
  key_value do
23
26
  map "titleOpen", to: :title_open
@@ -30,6 +33,7 @@ module Relaton
30
33
  map "creators", to: :creators
31
34
  map "uriShort", to: :uri_short
32
35
  map "dateForm", to: :date_form
36
+ map "identifierMode", to: :identifier_mode
33
37
  end
34
38
  end
35
39
  end
@@ -87,8 +87,14 @@ module Relaton
87
87
  def terminate(body)
88
88
  # 1.x punctuation cleanup: an element ending in a colon
89
89
  # carries its own separator (the following sentence period
90
- # collapses), and spaces collapse before a comma
90
+ # collapses); a sentence period followed by a spaced comma
91
+ # or another period collapses into one (", 2013" over
92
+ # ". , 2013"; "n.d.." into "n.d.") — an initials period
93
+ # before a comma ("P.,") stands; spaces collapse before a
94
+ # comma
91
95
  body = body.gsub(/:\s*\.\s*/, ": ")
96
+ body = body.gsub(/\.\s+,/, ",")
97
+ body = body.gsub(/\.\s*\./, ".")
92
98
  body = body.gsub(/ +/, " ").gsub(" ,", ",")
93
99
  return body if @terminator.strip.empty?
94
100
  return body if body.rstrip.end_with?(@terminator.strip)
@@ -1,5 +1,5 @@
1
1
  module Relaton
2
2
  module Render
3
- VERSION = "3.0.0.pre.alpha.9".freeze
3
+ VERSION = "3.0.0.pre.alpha.11".freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: relaton-render
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0.pre.alpha.9
4
+ version: 3.0.0.pre.alpha.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-05 00:00:00.000000000 Z
11
+ date: 2026-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: htmlentities
@@ -137,6 +137,7 @@ files:
137
137
  - lib/relaton/render/iso690/elements/status.rb
138
138
  - lib/relaton/render/iso690/elements/stddoc.rb
139
139
  - lib/relaton/render/iso690/elements/title.rb
140
+ - lib/relaton/render/iso690/elements/updated.rb
140
141
  - lib/relaton/render/iso690/fields.rb
141
142
  - lib/relaton/render/iso690/i18n.rb
142
143
  - lib/relaton/render/iso690/i18n/en.yml