relaton-render 3.0.0.pre.alpha.3 → 3.0.0.pre.alpha.4

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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/lib/relaton/render/general.rb +5 -1
  3. data/lib/relaton/render/iso690/element.rb +52 -1
  4. data/lib/relaton/render/iso690/elements/access.rb +44 -0
  5. data/lib/relaton/render/iso690/elements/component_part.rb +59 -13
  6. data/lib/relaton/render/iso690/elements/creator.rb +40 -30
  7. data/lib/relaton/render/iso690/elements/edition.rb +18 -2
  8. data/lib/relaton/render/iso690/elements/extent.rb +51 -0
  9. data/lib/relaton/render/iso690/elements/location.rb +17 -2
  10. data/lib/relaton/render/iso690/elements/medium.rb +7 -2
  11. data/lib/relaton/render/iso690/elements/production.rb +10 -2
  12. data/lib/relaton/render/iso690/elements/pub_date.rb +2 -1
  13. data/lib/relaton/render/iso690/elements/series.rb +21 -0
  14. data/lib/relaton/render/iso690/elements/size.rb +37 -0
  15. data/lib/relaton/render/iso690/elements/stddoc.rb +40 -0
  16. data/lib/relaton/render/iso690/elements/title.rb +16 -1
  17. data/lib/relaton/render/iso690/elements.rb +8 -0
  18. data/lib/relaton/render/iso690/fields.rb +2 -1
  19. data/lib/relaton/render/iso690/i18n/en.yml +22 -0
  20. data/lib/relaton/render/iso690/kinds.rb +6 -2
  21. data/lib/relaton/render/iso690/renderer.rb +11 -4
  22. data/lib/relaton/render/iso690/style/locale.rb +6 -1
  23. data/lib/relaton/render/iso690/style/name_form.rb +10 -0
  24. data/lib/relaton/render/iso690/style/scheme.rb +2 -0
  25. data/lib/relaton/render/iso690/style/template_map.rb +4 -0
  26. data/lib/relaton/render/iso690/style/type_template.rb +6 -0
  27. data/lib/relaton/render/iso690/style.rb +14 -4
  28. data/lib/relaton/render/version.rb +1 -1
  29. data/lib/relaton-render.rb +4 -0
  30. metadata +5 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1d41488064d32008b3f44f81635f4bf8e096341adb5434eb270b440b9f7bfbe
4
- data.tar.gz: 7b3b00fdadc97f66749d98f028684016a7d917977a3900c2d852a6127e4126f2
3
+ metadata.gz: ee63b0602a2be7f4c00c3d0fee7795387cac5b695037ffec618271b380ce6098
4
+ data.tar.gz: 1bd28b75e32a979ba82cedc675851d61c26769010c62823a85180a5c8236a8a5
5
5
  SHA512:
6
- metadata.gz: 1c73e86c124a5cb571305da9b9dc8ac0a35eeabadc89f2d9fa819ca95edd3bbe097356c4e5e4f2199542c94a0939338bfd3b04d6f1f3933708b266751b5b75c9
7
- data.tar.gz: 539376d05813f377ca9d8fa36dff0c023e477daf4c7fb435b487a4e2da22bb98e185f65a1ff3a2e17b838e881028a8f7a3c7de1a27f7c5d7f5f616274f71c349
6
+ metadata.gz: '070696e6d0f15855bb40365c0e5953f05a80ea0fc0b453b2c1b62da371d9bfa0459c9d7e593ba79add4227c4ecc97af247561cd78345d835a5262f9be30ee651'
7
+ data.tar.gz: 660e8b5815000dd129e2893f39584d64999eda4d863c704ba9a4ee1e8cda9d68bdefbdfb8675a2aea25309a649709d7fa24458e5682c27c5932344cbe5e8ceed
@@ -56,7 +56,11 @@ module Relaton
56
56
  def render_all(bib, type: "author-date")
57
57
  items = facade_bibitems(bib) or return nil
58
58
  items.each_with_object({}).with_index do |(item, m), i|
59
- ref = @renderer.render(item)
59
+ ref = begin
60
+ @renderer.render(item)
61
+ rescue ::Relaton::Render::Unrenderable
62
+ next
63
+ end
60
64
  m[item.id] = {
61
65
  id: item.id, ord: i, formattedref: ref,
62
66
  citation: {
@@ -8,10 +8,11 @@ module Relaton
8
8
  # predicate, #render the element's formatting method. Punctuation
9
9
  # between elements belongs to the kind/style, not the element.
10
10
  class Element
11
- def initialize(model, style:, i18n:)
11
+ def initialize(model, style:, i18n:, kind: nil)
12
12
  @model = model
13
13
  @style = style
14
14
  @i18n = i18n
15
+ @kind = kind
15
16
  end
16
17
 
17
18
  def present?
@@ -37,6 +38,56 @@ module Relaton
37
38
  r.is_a?(String) ? r == role : r.type == role
38
39
  end
39
40
  end
41
+
42
+ def org_name(contributor)
43
+ name = Array(contributor.organization&.name)
44
+ .map { |n| localized(n).upcase }.join(", ")
45
+ abbrev = localized(contributor.organization&.abbreviation)
46
+ return name if name.empty? || abbrev.empty?
47
+
48
+ "#{name} (#{abbrev})"
49
+ end
50
+
51
+ def item_kind
52
+ @kind || Kinds.kind_for(@model.type)
53
+ end
54
+
55
+ def person_completename(person)
56
+ localized(person.name&.completename)
57
+ end
58
+
59
+ def person_surname(person)
60
+ localized(person.name&.surname)
61
+ end
62
+
63
+ def person_given(person)
64
+ parts = Array(person.name&.forename).map do |f|
65
+ content = localized(f)
66
+ next content unless content.empty?
67
+ next "#{f.initial}." if f.initial.to_s.length == 1
68
+
69
+ ""
70
+ end.reject(&:empty?)
71
+ return localized(person.name&.formatted_initials) if parts.empty?
72
+ return parts.map { |p| "#{p[0]}." }.join(" ") if initials?
73
+
74
+ parts.join(" ")
75
+ end
76
+
77
+ def initials?
78
+ @style.scheme.name_form.initials
79
+ end
80
+
81
+ def join_names(names)
82
+ case names.size
83
+ when 0 then ""
84
+ when 1 then names.first
85
+ when 2 then names.join(" #{@i18n.label('and')} ")
86
+ else
87
+ "#{names[0..-2].join(', ')}#{@i18n.label('oxford_comma')} " \
88
+ "#{@i18n.label('and')} #{names.last}"
89
+ end
90
+ end
40
91
  end
41
92
  end
42
93
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module Relaton
6
+ module Render
7
+ module Iso690
8
+ module Elements
9
+ # Date of access (ISO 690 clause 7.12): "[viewed: September 3,
10
+ # 2019]" for online resources consulted.
11
+ class Access < Element
12
+ def present?
13
+ !date_text.empty?
14
+ end
15
+
16
+ def render
17
+ "[#{@i18n.label('viewed')}: #{date_text}]"
18
+ end
19
+
20
+ private
21
+
22
+ def date_text
23
+ d = Array(@model.date).find { |x| x.type == "accessed" } or return ""
24
+ value = (d.at || d.from || d.to).to_s
25
+ parts = value.split("-")
26
+ case parts.size
27
+ when 3
28
+ date = ::Date.parse(value)
29
+ "#{month(date.month)} #{date.day}, #{date.year}"
30
+ when 2
31
+ "#{month(parts[1].to_i)} #{parts[0]}"
32
+ else
33
+ value
34
+ end
35
+ end
36
+
37
+ def month(num)
38
+ @i18n.label("month_#{num}")
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -4,44 +4,90 @@ module Relaton
4
4
  module Render
5
5
  module Iso690
6
6
  module Elements
7
- # Component part within a host (ISO 690 clause 7.4): "In: host
8
- # title, edition of host". Host data comes from the typed
9
- # partOf relation.
7
+ # Component part within a host (ISO 690 clause 7.4): host title
8
+ # (emphasised), host editors, host edition, host place and
9
+ # publisher. Host data comes from the typed partOf/includedIn
10
+ # relation.
10
11
  class ComponentPart < Element
12
+ HOST_RELATION_TYPES = %w[partOf includedIn].freeze
13
+
11
14
  def present?
12
- !host_title.empty?
15
+ !host_title_text.empty?
13
16
  end
14
17
 
15
18
  def render
16
- "#{@i18n.label('in')} " \
17
- "#{@style.templates.title_open}#{host_title}" \
18
- "#{@style.templates.title_close}" \
19
- "#{host_edition}"
19
+ "#{lead}#{host_title}#{host_editors}#{host_edition}" \
20
+ "#{host_production}"
20
21
  end
21
22
 
22
23
  private
23
24
 
25
+ def lead
26
+ label = @i18n.label("in")
27
+ label.empty? ? "" : "#{label} "
28
+ end
29
+
24
30
  def relation
25
- Array(@model.relation).find { |r| r.type == "partOf" }
31
+ Array(@model.relation).find do |r|
32
+ HOST_RELATION_TYPES.include?(r.type)
33
+ end
26
34
  end
27
35
 
28
36
  def host
29
37
  relation&.bibitem
30
38
  end
31
39
 
32
- def host_title
40
+ def host_title_text
33
41
  return localized(relation&.description) if host.nil?
34
42
 
35
43
  Array(host.title).map { |t| localized(t) }.join(" ").strip
36
44
  end
37
45
 
46
+ def host_title
47
+ return "" if host_title_text.empty?
48
+
49
+ "#{@style.templates.title_open}#{host_title_text}" \
50
+ "#{@style.templates.title_close}"
51
+ end
52
+
53
+ def host_editors
54
+ eds = Array(host&.contributor).select { |c| has_role?(c, "editor") }
55
+ names = eds.each_with_index
56
+ .map { |c, i| host_person_name(c, first: i.zero?) }
57
+ .reject(&:empty?)
58
+ return "" if names.empty?
59
+
60
+ " (#{@i18n.label(eds.one? ? 'ed' : 'eds')} #{join_names(names)})"
61
+ end
62
+
63
+ # ISO 690 clause 7.2 host editors: the first name inverted
64
+ # (surname, initials), subsequent names in direct order, all in
65
+ # mixed case
66
+ def host_person_name(contributor, first:)
67
+ person = contributor.person or return ""
68
+
69
+ surname = person_surname(person)
70
+ given = person_given(person)
71
+ if first
72
+ [surname, given].reject(&:empty?).join(", ")
73
+ else
74
+ [given, surname].reject(&:empty?).join(" ")
75
+ end
76
+ end
77
+
38
78
  def host_edition
39
79
  return "" if host.nil?
40
80
 
41
- edition = Edition.new(host, style: @style, i18n: @i18n)
42
- return "" unless edition.present?
81
+ edition = Edition.new(host, style: @style, i18n: @i18n,
82
+ kind: item_kind)
83
+ edition.present? ? ". #{edition.render}" : ""
84
+ end
85
+
86
+ def host_production
87
+ return "" if host.nil?
43
88
 
44
- ". #{edition.render}"
89
+ production = Production.new(host, style: @style, i18n: @i18n)
90
+ production.present? ? ". #{production.render}" : ""
45
91
  end
46
92
  end
47
93
  end
@@ -15,7 +15,32 @@ module Relaton
15
15
  end
16
16
 
17
17
  def render
18
- "#{join(names)}#{role_suffix}"
18
+ case role_placement
19
+ when "afterPeriod"
20
+ # the in-slot trailing space separates the (eds.) marker
21
+ # from the next element, which follows directly in the
22
+ # template; a lone editor is unmarked
23
+ if editors? && creators.size > 1
24
+ "#{period(join(names))} (#{role_word}) "
25
+ else
26
+ "#{period(join(names))} "
27
+ end
28
+ else
29
+ marker = editors? && creators.size > 1 ? " (#{role_word})" : ''
30
+ "#{join(names)}#{marker}"
31
+ end
32
+ end
33
+
34
+ def role_placement
35
+ @style.scheme.name_form.role_placement || "glue"
36
+ end
37
+
38
+ def period(text)
39
+ text.end_with?(".") ? text : "#{text}."
40
+ end
41
+
42
+ def role_word
43
+ @i18n.label(creators.one? ? "ed" : "eds")
19
44
  end
20
45
 
21
46
  # In-text (name-and-date) form: the principal creator only
@@ -55,11 +80,19 @@ module Relaton
55
80
 
56
81
  def format(contributor, first:)
57
82
  person = contributor.person or return org_name(contributor)
58
- name = completename(person) ||
83
+ name = completename_name(person, first: first) ||
59
84
  personal_name(person, first: first)
60
85
  name.empty? ? org_name(contributor) : name
61
86
  end
62
87
 
88
+ def completename_name(person, first:)
89
+ complete = completename(person) or return nil
90
+ return complete unless name_form.completename_upcase
91
+
92
+ first ? @style.render_name(surname: complete, given: "") :
93
+ complete.upcase
94
+ end
95
+
63
96
  def personal_name(person, first:)
64
97
  surname = surname(person).to_s
65
98
  given = given_names(person)
@@ -75,21 +108,7 @@ module Relaton
75
108
  end
76
109
 
77
110
  def join(names)
78
- case names.size
79
- when 0 then ""
80
- when 1 then names.first
81
- when 2 then names.join(" #{@i18n.label('and')} ")
82
- else
83
- "#{names[0..-2].join(', ')}#{@i18n.label('oxford_comma')} " \
84
- "#{@i18n.label('and')} #{names.last}"
85
- end
86
- end
87
-
88
- def role_suffix
89
- return "" unless editors?
90
-
91
- suffix = creators.one? ? @i18n.label("ed") : @i18n.label("eds")
92
- " (#{suffix})"
111
+ join_names(names)
93
112
  end
94
113
 
95
114
  def editors?
@@ -97,30 +116,21 @@ module Relaton
97
116
  end
98
117
 
99
118
  def completename(person)
100
- value = localized(person.name&.completename)
119
+ value = person_completename(person)
101
120
  value.empty? ? nil : value
102
121
  end
103
122
 
104
123
  def surname(person)
105
- value = localized(person.name&.surname)
124
+ value = person_surname(person)
106
125
  value.empty? ? nil : value
107
126
  end
108
127
 
109
128
  def given_names(person)
110
- names = Array(person.name&.forename).map { |f| localized(f) }
111
- .reject(&:empty?)
112
- return names.map { |n| "#{n[0]}." }.join(" ") if name_form.initials
113
-
114
- names.join(" ")
129
+ person_given(person)
115
130
  end
116
131
 
117
132
  def org_name(contributor)
118
- name = Array(contributor.organization&.name)
119
- .map { |n| localized(n).upcase }.join(", ")
120
- abbrev = localized(contributor.organization&.abbreviation)
121
- return name if name.empty? || abbrev.empty?
122
-
123
- "#{name} (#{abbrev})"
133
+ super
124
134
  end
125
135
  end
126
136
  end
@@ -8,17 +8,33 @@ module Relaton
8
8
  # edition through Edition#number (falling back to its content).
9
9
  class Edition < Element
10
10
  def present?
11
- !number.zero?
11
+ !raw.to_s.empty?
12
12
  end
13
13
 
14
14
  def render
15
+ return "#{@i18n.label('version')} #{raw}" unless word_edition?
16
+
17
+ word = @i18n.label("edition_#{number}")
18
+ return word unless word == "edition_#{number}"
19
+
15
20
  "#{ordinalize(number)} #{@i18n.label('edition')}"
16
21
  end
17
22
 
18
23
  private
19
24
 
25
+ # Worded editions are document editions; online resources
26
+ # carry numbered builds rendered as versions
27
+ def word_edition?
28
+ number.positive? &&
29
+ %w[monograph component_part serial_part]
30
+ .include?(item_kind)
31
+ end
32
+
33
+ def raw
34
+ (@model.edition&.number || @model.edition&.content).to_s
35
+ end
36
+
20
37
  def number
21
- raw = @model.edition&.number || @model.edition&.content
22
38
  Integer(raw)
23
39
  rescue ArgumentError, TypeError
24
40
  0
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ module Elements
7
+ # Extent of the item (ISO 690 clause 7.11): page localities.
8
+ class Extent < Element
9
+ def present?
10
+ !render.empty?
11
+ end
12
+
13
+ def render
14
+ groups = [numeration, page_range].reject { |g| g.empty? }
15
+ groups.join(", ")
16
+ end
17
+
18
+ private
19
+
20
+ def numeration
21
+ %w[volume issue].filter_map do |type|
22
+ loc = localities.find { |l| l.type == type }
23
+ next if loc.nil? || loc.reference_from.to_s.empty?
24
+
25
+ "#{@i18n.label(type)} #{loc.reference_from}"
26
+ end.join(" ")
27
+ end
28
+
29
+ def page_range
30
+ loc = localities.find { |l| l.type == "page" } or return ""
31
+ from = loc.reference_from.to_s
32
+ to = loc.reference_to.to_s
33
+ if to.empty? || to == from
34
+ "#{@i18n.label('page')} #{from}"
35
+ else
36
+ "#{@i18n.label('pages')} " \
37
+ "#{from}#{@i18n.label('date_range')}#{to}"
38
+ end
39
+ end
40
+
41
+ def localities
42
+ Array(@model.extent).flat_map do |e|
43
+ Array(e.locality) +
44
+ Array(e.locality_stack).flat_map { |s| Array(s.locality) }
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -12,13 +12,28 @@ module Relaton
12
12
  end
13
13
 
14
14
  def render
15
- "#{@i18n.label('available_from')} #{uri}"
15
+ text = if (form = @style.templates.uri) && !form.empty?
16
+ ::Relaton::Render::Iso690::Template.new(form).evaluate(
17
+ "uri" => Template::Field[!uri.empty?, uri],
18
+ )
19
+ else
20
+ uri
21
+ end
22
+
23
+ "#{@i18n.label('available_from')} #{text}".strip
16
24
  end
17
25
 
18
26
  private
19
27
 
20
28
  def uri
21
- Array(@model.accesslocation).reject(&:empty?).first.to_s
29
+ from_accesslocation =
30
+ Array(@model.accesslocation).map(&:to_s).reject(&:empty?)
31
+ unless from_accesslocation.empty?
32
+ return from_accesslocation.first.to_s
33
+ end
34
+
35
+ Array(@model.source).map { |u| u.content.to_s }
36
+ .reject(&:empty?).first.to_s
22
37
  end
23
38
  end
24
39
  end
@@ -12,13 +12,18 @@ module Relaton
12
12
  end
13
13
 
14
14
  def render
15
- "[#{medium}]"
15
+ medium
16
16
  end
17
17
 
18
18
  private
19
19
 
20
20
  def medium
21
- localized(@model.medium)
21
+ genre = @model.medium&.genre.to_s
22
+ return genre.sub(/^\w/) { |c| c.upcase } unless genre.empty?
23
+
24
+ key = "medium_#{@model.type}"
25
+ label = @i18n.label(key)
26
+ label == key ? "" : label
22
27
  end
23
28
  end
24
29
  end
@@ -20,8 +20,16 @@ module Relaton
20
20
  private
21
21
 
22
22
  def places
23
- Array(@model.place).map { |p| p.city.to_s }
24
- .reject { |c| c.strip.empty? }
23
+ Array(@model.place).map do |p|
24
+ structured = [p.city.to_s, p.region.map(&:content),
25
+ p.country.map(&:content)]
26
+ .flatten.reject { |c| c.strip.empty? }
27
+ if structured.empty? && !p.formatted_place.to_s.empty?
28
+ next p.formatted_place.to_s
29
+ end
30
+
31
+ structured.join(", ")
32
+ end.reject(&:empty?)
25
33
  end
26
34
 
27
35
  def publishers
@@ -21,7 +21,8 @@ module Relaton
21
21
 
22
22
  def publication_date
23
23
  Array(@model.date).find(&:published) ||
24
- Array(@model.date).find { |d| d.type.nil? }
24
+ Array(@model.date).find { |d| d.type.nil? } ||
25
+ Array(@model.date).find { |d| d.type == "created" }
25
26
  end
26
27
 
27
28
  def publication_date_value
@@ -11,6 +11,15 @@ module Relaton
11
11
  end
12
12
 
13
13
  def render
14
+ if (form = series_form) && !form.empty?
15
+ return ::Relaton::Render::Iso690::Template.new(form).evaluate(
16
+ "seriestitle" => Template::Field[!series_title.empty?,
17
+ series_title],
18
+ "seriesnumber" => Template::Field[!number.empty?, number],
19
+ "seriesrun" => Template::Field[!run.empty?, run],
20
+ )
21
+ end
22
+
14
23
  base = "#{@style.templates.title_open}#{series_title}" \
15
24
  "#{@style.templates.title_close}"
16
25
  if number.empty?
@@ -33,6 +42,18 @@ module Relaton
33
42
  def number
34
43
  series&.number.to_s
35
44
  end
45
+
46
+ def run
47
+ series&.run.to_s
48
+ end
49
+
50
+ # The style's series form for this item's resource kind, falling
51
+ # back to the style-wide form
52
+ def series_form
53
+ variant = @style.per_type
54
+ .find { |t| t.type == Kinds.kind_for(@model.type) }
55
+ variant&.series || @style.templates.series
56
+ end
36
57
  end
37
58
  end
38
59
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ module Elements
7
+ # Physical size (ISO 690 clause 7.5, area): the model's typed
8
+ # values render as "N unit" through the language pack; untyped
9
+ # values pass through as written.
10
+ class Size < Element
11
+ def present?
12
+ !values.empty?
13
+ end
14
+
15
+ def render
16
+ values.map { |v| render_value(v) }.join(", ")
17
+ end
18
+
19
+ private
20
+
21
+ def values
22
+ Array(@model.size&.value)
23
+ end
24
+
25
+ def render_value(value)
26
+ content = value.is_a?(String) ? value : value.content.to_s
27
+ return content if content.empty? || value.is_a?(String)
28
+
29
+ type = value.is_a?(Hash) ? value[:type] : value.type
30
+ unit = type && @i18n.label("size_#{type}")
31
+ unit.nil? || unit == "size_#{type}" ? content : "#{content} #{unit}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ module Elements
7
+ # Issuing body or status of a standard (ISO 690 clause 8.9): the
8
+ # publisher or authorizer organization, falling back to the
9
+ # capitalized status stage.
10
+ class Stddoc < Element
11
+ def present?
12
+ !body.empty?
13
+ end
14
+
15
+ def render
16
+ body
17
+ end
18
+
19
+ private
20
+
21
+ def body
22
+ orgs = %w[publisher authorizer].flat_map { |role|
23
+ contributors(role).map { |c| org_plain(c) }
24
+ }.reject(&:empty?)
25
+ return orgs.join("; ") unless orgs.empty?
26
+
27
+ stage = @model.status&.stage&.content.to_s
28
+ stage.empty? ? "" : stage.sub(/^\w/) { |c| c.upcase }
29
+ end
30
+
31
+ # Issuing bodies render in mixed case, unlike creator organs
32
+ def org_plain(contributor)
33
+ Array(contributor.organization&.name)
34
+ .map { |n| localized(n) }.join(", ")
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -12,12 +12,27 @@ module Relaton
12
12
  end
13
13
 
14
14
  def render
15
- "#{@style.templates.title_open}#{main_title}" \
15
+ text = main_title
16
+ return text if analytic?
17
+ if (form = @style.title_form_for(Kinds.kind_for(@model.type)))
18
+ text = ::Relaton::Render::Iso690::Template.new(form).evaluate(
19
+ "title" => Template::Field[!text.empty?, text],
20
+ )
21
+ end
22
+
23
+ "#{@style.templates.title_open}#{text}" \
16
24
  "#{@style.templates.title_close}"
17
25
  end
18
26
 
19
27
  private
20
28
 
29
+ # ISO 690: analytic titles are not emphasised; the host or
30
+ # serial carries the emphasis
31
+ def analytic?
32
+ %w[component_part serial_part]
33
+ .include?(Kinds.kind_for(@model.type))
34
+ end
35
+
21
36
  def main_title
22
37
  t = Array(@model.title).find { |x| (x.type || "main") == "main" } ||
23
38
  Array(@model.title).first
@@ -16,6 +16,10 @@ module Relaton
16
16
  autoload :Location, "relaton/render/iso690/elements/location"
17
17
  autoload :Numeration, "relaton/render/iso690/elements/numeration"
18
18
  autoload :ComponentPart, "relaton/render/iso690/elements/component_part"
19
+ autoload :Size, "relaton/render/iso690/elements/size"
20
+ autoload :Extent, "relaton/render/iso690/elements/extent"
21
+ autoload :Access, "relaton/render/iso690/elements/access"
22
+ autoload :Stddoc, "relaton/render/iso690/elements/stddoc"
19
23
 
20
24
  CLASSES = {
21
25
  creator: Creator,
@@ -29,6 +33,10 @@ module Relaton
29
33
  location: Location,
30
34
  numeration: Numeration,
31
35
  component_part: ComponentPart,
36
+ size: Size,
37
+ extent: Extent,
38
+ access: Access,
39
+ stddoc: Stddoc,
32
40
  }.freeze
33
41
 
34
42
  def self.build(name, model, style:, i18n:)
@@ -10,7 +10,8 @@ module Relaton
10
10
  class Fields
11
11
  ELEMENT_SLOTS = %i[
12
12
  creator title edition medium series production
13
- date numeration component_part identifier location
13
+ date numeration component_part identifier location size
14
+ extent access stddoc
14
15
  ].freeze
15
16
 
16
17
  def initialize(model, style:, i18n:, disambiguator: nil)
@@ -1,6 +1,28 @@
1
1
  lang: en
2
2
  labels:
3
3
  edition: "ed"
4
+ size_volume: "vol."
5
+ size_page: "pp."
6
+ page: "p."
7
+ pages: "pp."
8
+ volume: "vol."
9
+ issue: "no."
10
+ version: "Version"
11
+ medium_website: "[website]"
12
+ viewed: "viewed"
13
+ month_1: "January"
14
+ month_2: "February"
15
+ month_3: "March"
16
+ month_4: "April"
17
+ month_5: "May"
18
+ month_6: "June"
19
+ month_7: "July"
20
+ month_8: "August"
21
+ month_9: "September"
22
+ month_10: "October"
23
+ month_11: "November"
24
+ month_12: "December"
25
+
4
26
  series_no: "no."
5
27
  report_no: "Report no."
6
28
  available_from: "Available from:"
@@ -26,8 +26,12 @@ module Relaton
26
26
  "inbook" => "component_part",
27
27
  "incollection" => "component_part",
28
28
  "inproceedings" => "component_part",
29
- "website" => "online",
30
- "webresource" => "online",
29
+ "article" => "serial_part",
30
+ "software" => "online",
31
+ "unpublished" => "online",
32
+ "dataset" => "online",
33
+ "website" => "webdoc",
34
+ "webresource" => "webdoc",
31
35
  "webpage" => "online",
32
36
  "online" => "online",
33
37
  }.freeze
@@ -8,8 +8,6 @@ module Relaton
8
8
  # the template evaluator to the field resolver. No XML, no
9
9
  # intermediate hashes.
10
10
  class Renderer
11
- class Unrenderable < StandardError; end
12
-
13
11
  DEFAULT_STYLE = "author-date"
14
12
 
15
13
  class << self
@@ -33,14 +31,23 @@ module Relaton
33
31
  end
34
32
 
35
33
  def render(model)
36
- out = Template.new(@style.template_for(Kinds.kind_for(model.type))).evaluate(
34
+ kind = Kinds.kind_for(model.type)
35
+ template = @style.template_for(kind, home: home_docid?(model))
36
+ out = Template.new(template).evaluate(
37
37
  Fields.new(model, style: @style, i18n: @i18n).to_h,
38
38
  )
39
- raise Unrenderable, "no renderable elements" if out.strip.empty?
39
+ raise ::Relaton::Render::Unrenderable,
40
+ "no renderable elements" if out.strip.empty?
40
41
 
41
42
  out
42
43
  end
43
44
 
45
+ def home_docid?(model)
46
+ type = @style.scheme.home_docid_type
47
+ !type.nil? && !type.empty? &&
48
+ Array(model.docidentifier).any? { |d| d.type == type }
49
+ end
50
+
44
51
  # In-text form: SURNAME, year + disambiguator (name-and-date)
45
52
  def citation(model, disambiguator: nil)
46
53
  Template.new(@style.templates.citation).evaluate(
@@ -14,6 +14,9 @@ module Relaton
14
14
  attribute :in_str, :string
15
15
  attribute :at, :string
16
16
  attribute :available_at, :string
17
+ # Arbitrary label overrides (edition words, join punctuation,
18
+ # size units): merged over the language pack verbatim.
19
+ attribute :labels, :hash, default: -> { {} }
17
20
 
18
21
  key_value do
19
22
  map "and", to: :conj
@@ -23,6 +26,7 @@ module Relaton
23
26
  map "in", to: :in_str
24
27
  map "at", to: :at
25
28
  map "availableAt", to: :available_at
29
+ map "labels", to: :labels
26
30
  end
27
31
 
28
32
  LABEL_FOR = {
@@ -32,10 +36,11 @@ module Relaton
32
36
  }.freeze
33
37
 
34
38
  def label_map
35
- LABEL_FOR.filter_map do |attr, label|
39
+ base = LABEL_FOR.filter_map do |attr, label|
36
40
  value = public_send(attr)
37
41
  [label, value] unless value.nil?
38
42
  end.to_h
43
+ base.merge(labels || {})
39
44
  end
40
45
  end
41
46
  end
@@ -9,10 +9,20 @@ module Relaton
9
9
  class NameForm < Lutaml::Model::Serializable
10
10
  attribute :initials, :boolean, default: -> { false }
11
11
  attribute :given_name_first, :boolean, default: -> { false }
12
+ # Where an all-editor creator list's role marker sits relative
13
+ # to the name's terminating period: "glue" renders
14
+ # "NAME (eds.)." (the name-date default), "afterPeriod"
15
+ # renders "NAME. (eds.)" (ISO 690 clause-flavour).
16
+ attribute :role_placement, :string, default: "glue"
17
+ # A completename is rendered, not decomposed; styles that
18
+ # upcase inverted names may declare the completename treatment
19
+ attribute :completename_upcase, :boolean, default: -> { false }
12
20
 
13
21
  key_value do
14
22
  map "initials", to: :initials
15
23
  map "givenNameFirst", to: :given_name_first
24
+ map "rolePlacement", to: :role_placement
25
+ map "completenameUpcase", to: :completename_upcase
16
26
  end
17
27
  end
18
28
  end
@@ -10,11 +10,13 @@ module Relaton
10
10
  attribute :system, :string, default: "name-date"
11
11
  attribute :name_form, NameForm, default: -> { NameForm.new }
12
12
  attribute :locale, Locale, default: -> { Locale.new }
13
+ attribute :home_docid_type, :string
13
14
 
14
15
  key_value do
15
16
  map "system", to: :system
16
17
  map "nameForm", to: :name_form
17
18
  map "locale", to: :locale
19
+ map "homeDocidType", to: :home_docid_type
18
20
  end
19
21
  end
20
22
  end
@@ -13,6 +13,8 @@ module Relaton
13
13
  attribute :citation, :string, default: "{{surname}}, {{date}}"
14
14
  attribute :reference, :string
15
15
  attribute :name, :string, default: "{{surname}}, {{givenNames}}"
16
+ attribute :series, :string, default: ""
17
+ attribute :uri, :string, default: ""
16
18
 
17
19
  key_value do
18
20
  map "titleOpen", to: :title_open
@@ -20,6 +22,8 @@ module Relaton
20
22
  map "citation", to: :citation
21
23
  map "reference", to: :reference
22
24
  map "name", to: :name
25
+ map "series", to: :series
26
+ map "uri", to: :uri
23
27
  end
24
28
  end
25
29
  end
@@ -9,10 +9,16 @@ module Relaton
9
9
  class TypeTemplate < Lutaml::Model::Serializable
10
10
  attribute :type, :string
11
11
  attribute :template, :string
12
+ attribute :series, :string
13
+ attribute :title, :string
14
+ attribute :home, :boolean
12
15
 
13
16
  key_value do
14
17
  map "type", to: :type
15
18
  map "template", to: :template
19
+ map "series", to: :series
20
+ map "title", to: :title
21
+ map "home", to: :home
16
22
  end
17
23
  end
18
24
  end
@@ -41,10 +41,20 @@ module Relaton
41
41
  end
42
42
 
43
43
  # Per-type template selection is a data lookup; unmatched types
44
- # fall back to the general reference template.
45
- def template_for(type)
46
- per_type.find { |t| t.type == type.to_s }&.template ||
47
- templates.reference
44
+ # fall back to the general reference template. When a kind has
45
+ # home-flagged variants, the split is resolved by whether the item
46
+ # carries the scheme's home document identifier.
47
+ def template_for(type, home: nil)
48
+ candidates = per_type.select { |t| t.type == type.to_s }
49
+ if candidates.size > 1 && !home.nil?
50
+ candidates = candidates.select { |t| (t.home || false) == home }
51
+ end
52
+ candidates.first&.template || templates.reference
53
+ end
54
+
55
+ def title_form_for(type)
56
+ per_type.lazy.select { |t| t.type == type.to_s }
57
+ .find { |t| t.title && !t.title.empty? }&.title
48
58
  end
49
59
 
50
60
  # First-creator name form: the style's declared name template with
@@ -1,5 +1,5 @@
1
1
  module Relaton
2
2
  module Render
3
- VERSION = "3.0.0.pre.alpha.3".freeze
3
+ VERSION = "3.0.0.pre.alpha.4".freeze
4
4
  end
5
5
  end
@@ -5,6 +5,10 @@ require "relaton/render/iso690"
5
5
 
6
6
  module Relaton
7
7
  module Render
8
+ # A bibliographic item whose style template resolves to nothing: the
9
+ # single-item API raises, the batch API skips
10
+ class Unrenderable < StandardError; end
11
+
8
12
  # Deprecated 1.x engine leaves, restored so flavor gems that still
9
13
  # subclass them load against this line. Superseded by the Iso690
10
14
  # engine; removed when the flavors have migrated to CitationStyle
metadata CHANGED
@@ -1,7 +1,7 @@
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.3
4
+ version: 3.0.0.pre.alpha.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -120,9 +120,11 @@ files:
120
120
  - lib/relaton/render/iso690.rb
121
121
  - lib/relaton/render/iso690/element.rb
122
122
  - lib/relaton/render/iso690/elements.rb
123
+ - lib/relaton/render/iso690/elements/access.rb
123
124
  - lib/relaton/render/iso690/elements/component_part.rb
124
125
  - lib/relaton/render/iso690/elements/creator.rb
125
126
  - lib/relaton/render/iso690/elements/edition.rb
127
+ - lib/relaton/render/iso690/elements/extent.rb
126
128
  - lib/relaton/render/iso690/elements/identifier.rb
127
129
  - lib/relaton/render/iso690/elements/location.rb
128
130
  - lib/relaton/render/iso690/elements/medium.rb
@@ -130,6 +132,8 @@ files:
130
132
  - lib/relaton/render/iso690/elements/production.rb
131
133
  - lib/relaton/render/iso690/elements/pub_date.rb
132
134
  - lib/relaton/render/iso690/elements/series.rb
135
+ - lib/relaton/render/iso690/elements/size.rb
136
+ - lib/relaton/render/iso690/elements/stddoc.rb
133
137
  - lib/relaton/render/iso690/elements/title.rb
134
138
  - lib/relaton/render/iso690/fields.rb
135
139
  - lib/relaton/render/iso690/i18n.rb