relaton-render 0.4.2 → 3.0.0.pre.alpha.26

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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +6 -1
  3. data/.github/workflows/release.yml +24 -0
  4. data/.gitignore +14 -0
  5. data/.rubocop.yml +2 -2
  6. data/Gemfile +17 -1
  7. data/README.adoc +13 -258
  8. data/docs/citation-style-pack.adoc +370 -0
  9. data/lib/relaton/render/general.rb +305 -0
  10. data/lib/relaton/render/iso690/element.rb +153 -0
  11. data/lib/relaton/render/iso690/elements/access.rb +44 -0
  12. data/lib/relaton/render/iso690/elements/authorizer.rb +30 -0
  13. data/lib/relaton/render/iso690/elements/citeid.rb +39 -0
  14. data/lib/relaton/render/iso690/elements/component_part.rb +99 -0
  15. data/lib/relaton/render/iso690/elements/creator.rb +258 -0
  16. data/lib/relaton/render/iso690/elements/edition.rb +92 -0
  17. data/lib/relaton/render/iso690/elements/extent.rb +71 -0
  18. data/lib/relaton/render/iso690/elements/identifier.rb +46 -0
  19. data/lib/relaton/render/iso690/elements/location.rb +73 -0
  20. data/lib/relaton/render/iso690/elements/medium.rb +32 -0
  21. data/lib/relaton/render/iso690/elements/numeration.rb +26 -0
  22. data/lib/relaton/render/iso690/elements/production.rb +92 -0
  23. data/lib/relaton/render/iso690/elements/pub_date.rb +56 -0
  24. data/lib/relaton/render/iso690/elements/series.rb +66 -0
  25. data/lib/relaton/render/iso690/elements/size.rb +41 -0
  26. data/lib/relaton/render/iso690/elements/status.rb +30 -0
  27. data/lib/relaton/render/iso690/elements/stddoc.rb +40 -0
  28. data/lib/relaton/render/iso690/elements/title.rb +56 -0
  29. data/lib/relaton/render/iso690/elements/updated.rb +38 -0
  30. data/lib/relaton/render/iso690/elements.rb +67 -0
  31. data/lib/relaton/render/iso690/fields.rb +142 -0
  32. data/lib/relaton/render/iso690/i18n/en.yml +47 -0
  33. data/lib/relaton/render/iso690/i18n/fr.yml +14 -0
  34. data/lib/relaton/render/iso690/i18n/ja.yml +38 -0
  35. data/lib/relaton/render/iso690/i18n.rb +83 -0
  36. data/lib/relaton/render/iso690/identifier_kinds.rb +29 -0
  37. data/lib/relaton/render/iso690/index_renderer.rb +57 -0
  38. data/lib/relaton/render/iso690/kinds.rb +53 -0
  39. data/lib/relaton/render/iso690/renderer.rb +148 -0
  40. data/lib/relaton/render/iso690/rules.rb +36 -0
  41. data/lib/relaton/render/iso690/style/locale.rb +53 -0
  42. data/lib/relaton/render/iso690/style/name_form.rb +87 -0
  43. data/lib/relaton/render/iso690/style/scheme.rb +33 -0
  44. data/lib/relaton/render/iso690/style/sort_rule.rb +25 -0
  45. data/lib/relaton/render/iso690/style/template_map.rb +40 -0
  46. data/lib/relaton/render/iso690/style/type_template.rb +37 -0
  47. data/lib/relaton/render/iso690/style.rb +222 -0
  48. data/lib/relaton/render/iso690/styles/apa.yml +45 -0
  49. data/lib/relaton/render/iso690/styles/author-date.yml +31 -0
  50. data/lib/relaton/render/iso690/styles/chicago.yml +48 -0
  51. data/lib/relaton/render/iso690/styles/ieee-sa.yml +45 -0
  52. data/lib/relaton/render/iso690/styles/lncs.yml +54 -0
  53. data/lib/relaton/render/iso690/template.rb +248 -0
  54. data/lib/relaton/render/iso690.rb +27 -0
  55. data/lib/relaton/render/version.rb +1 -1
  56. data/lib/relaton-render.rb +11 -5
  57. data/relaton-render.gemspec +6 -13
  58. metadata +61 -116
  59. data/lib/isodoc/i18n.rb +0 -33
  60. data/lib/isodoc-yaml/i18n-ar.yaml +0 -44
  61. data/lib/isodoc-yaml/i18n-de.yaml +0 -43
  62. data/lib/isodoc-yaml/i18n-en.yaml +0 -40
  63. data/lib/isodoc-yaml/i18n-es.yaml +0 -47
  64. data/lib/isodoc-yaml/i18n-fr.yaml +0 -48
  65. data/lib/isodoc-yaml/i18n-ru.yaml +0 -51
  66. data/lib/isodoc-yaml/i18n-zh-Hans.yaml +0 -40
  67. data/lib/relaton/render/fields/date.rb +0 -45
  68. data/lib/relaton/render/fields/fields.rb +0 -245
  69. data/lib/relaton/render/general/citations.rb +0 -96
  70. data/lib/relaton/render/general/config.yml +0 -79
  71. data/lib/relaton/render/general/render.rb +0 -185
  72. data/lib/relaton/render/general/render_classes.rb +0 -129
  73. data/lib/relaton/render/parse/parse.rb +0 -64
  74. data/lib/relaton/render/parse/parse_contributors.rb +0 -132
  75. data/lib/relaton/render/parse/parse_extract.rb +0 -180
  76. data/lib/relaton/render/template/liquid.rb +0 -15
  77. data/lib/relaton/render/template/template.rb +0 -188
  78. data/lib/relaton/render/utils/utils.rb +0 -27
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ module Elements
7
+ # Edition and version (ISO 690 clause 7.6). The model numbers the
8
+ # edition through Edition#number (falling back to its content).
9
+ class Edition < Element
10
+ def present?
11
+ !raw.to_s.empty?
12
+ end
13
+
14
+ def render
15
+ return "#{@i18n.label('version')} #{raw}" unless word_edition?
16
+
17
+ # The worded forms first ("First edition", "第N版" via the
18
+ # ordinal template), the cardinal last
19
+ ordinal = locale_template("edition_ordinal", number)
20
+ return ordinal if ordinal
21
+
22
+ word = @i18n.label("edition_#{number}")
23
+ return word unless word == "edition_#{number}"
24
+
25
+ cardinal_form || "#{ordinalize(number)} #{@i18n.label('edition')}"
26
+ end
27
+
28
+ # The plain cardinal form ("edition 7")
29
+ def cardinal_form
30
+ locale_template("edition_cardinal", number) ||
31
+ locale_template("version_cardinal", number)
32
+ end
33
+
34
+ private
35
+
36
+ # A locale template carrying {{ var1 }} (optionally through
37
+ # the ordinal_word filter) populated with the edition number;
38
+ # a bare label (no placeholder) is not one
39
+ def locale_template(key, value)
40
+ raw = @i18n.label(key)
41
+ return nil if raw == key || raw !~ /\{{/
42
+
43
+ if raw.include?("ordinal_word")
44
+ word = @i18n.label("ordinal_word_#{value}")
45
+ word = ordinalize(value) if word == "ordinal_word_#{value}"
46
+ raw.gsub(/\{\{\s*var1[^}]*\}\}/, word)
47
+ else
48
+ raw.gsub("{{ var1 }}", value.to_s)
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ # Worded editions are document editions; online resources
55
+ # carry numbered builds rendered as versions
56
+ def word_edition?
57
+ number.positive? &&
58
+ %w[monograph component_part serial_part]
59
+ .include?(item_kind)
60
+ end
61
+
62
+ def raw
63
+ (@model.edition&.number || @model.edition&.content).to_s
64
+ end
65
+
66
+ def number
67
+ Integer(raw)
68
+ rescue ArgumentError, TypeError
69
+ 0
70
+ end
71
+
72
+ def ordinalize(num)
73
+ "#{num}#{ordinal_suffix(num)}"
74
+ end
75
+
76
+ def ordinal_suffix(num)
77
+ case num % 100
78
+ when 11, 12, 13 then "th"
79
+ else
80
+ case num % 10
81
+ when 1 then "st"
82
+ when 2 then "nd"
83
+ when 3 then "rd"
84
+ else "th"
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,71 @@
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(group_sep)
16
+ end
17
+
18
+ private
19
+
20
+ # An extent label is a template over its value: "vol. %" in
21
+ # the Latin pack, "巻%" / "%頁" in the CJK packs
22
+ def unit(type, value)
23
+ label = @i18n.label(type)
24
+ return "#{label} #{value}" unless label.include?("%")
25
+
26
+ label.sub("%", value)
27
+ end
28
+
29
+ def cjk?(label)
30
+ label.match?(/\p{Han}|\p{Hiragana}|\p{Katakana}/)
31
+ end
32
+
33
+ def group_sep
34
+ sep = @i18n.label("extent_group_separator")
35
+ return sep unless sep == "extent_group_separator"
36
+
37
+ cjk?(@i18n.label("page")) ? "、 " : ", "
38
+ end
39
+
40
+ def numeration
41
+ %w[volume issue].filter_map do |type|
42
+ loc = localities.find { |l| l.type == type }
43
+ next if loc.nil? || loc.reference_from.to_s.empty?
44
+
45
+ unit(type, loc.reference_from)
46
+ end.join(" ")
47
+ end
48
+
49
+ def page_range
50
+ loc = localities.find { |l| l.type == "page" } or return ""
51
+ from = loc.reference_from.to_s
52
+ to = loc.reference_to.to_s
53
+ if to.empty? || to == from
54
+ unit("page", from)
55
+ else
56
+ range = "#{from}#{@i18n.label('date_range')}#{to}"
57
+ unit("pages", range)
58
+ end
59
+ end
60
+
61
+ def localities
62
+ Array(@model.extent).flat_map do |e|
63
+ Array(e.locality) +
64
+ Array(e.locality_stack).flat_map { |s| Array(s.locality) }
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ module Elements
7
+ # Standard and persistent identifiers, rendered by kind
8
+ class Identifier < Element
9
+ INTERNAL_TYPES = %w[metanorma metanorma-ordinal metanorma-objid]
10
+ .freeze
11
+
12
+ def present?
13
+ !identifiers.empty?
14
+ end
15
+
16
+
17
+ def render
18
+ return "" if identifiers.empty?
19
+
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)
30
+ end
31
+
32
+ private
33
+
34
+ # Scoped identifiers (anchor, biblio-tag) are pipeline
35
+ # artefacts, not cited identifiers
36
+ def identifiers
37
+ Array(@model.docidentifier).reject do |d|
38
+ INTERNAL_TYPES.include?(d.type) ||
39
+ !d.scope.to_s.empty? || d.content.to_s.strip.empty?
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ module Elements
7
+ # Availability and location (ISO 690 clause 7.12): access location
8
+ # (URI) of the resource
9
+ class Location < Element
10
+ def present?
11
+ !uri.empty?
12
+ end
13
+
14
+ def render
15
+ text = uri
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
+
23
+ text = if form && !form.empty?
24
+ ::Relaton::Render::Iso690::Template.new(form).evaluate(
25
+ "uri" => Template::Field[!text.empty?, text],
26
+ )
27
+ else
28
+ text
29
+ end
30
+
31
+ label = @short ? "" : "#{@i18n.label('available_from')} "
32
+ "#{label}#{text}".strip
33
+ end
34
+
35
+ private
36
+
37
+ # The short cite renders the bare uri (1.x short templates),
38
+ # the reference the declared form
39
+ def uri_form
40
+ if @short
41
+ short_form = @style.templates.uri_short
42
+ return :bare if short_form.empty?
43
+
44
+ return short_form
45
+ end
46
+
47
+ @style.templates.uri
48
+ end
49
+
50
+ def uri
51
+ from_accesslocation =
52
+ Array(@model.accesslocation).map(&:to_s).reject(&:empty?)
53
+ unless from_accesslocation.empty?
54
+ @from_accesslocation = true
55
+ return from_accesslocation.first.to_s
56
+ end
57
+
58
+ uris = Array(@model.source).reject do |u|
59
+ u.content.to_s.strip.empty?
60
+ end
61
+ # the citation uri outranks the rest (1.x uri extraction)
62
+ preferred = uris.find { |u| u.type == "citation" } ||
63
+ uris.find { |u| u.type == "attachment" } ||
64
+ uris.find { |u| u.type == "src" } || uris.first
65
+ # the uri lands inside a markup attribute: escape it
66
+ require "cgi"
67
+ CGI.escapeHTML(preferred&.content.to_s)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ module Elements
7
+ # Medium designation (ISO 690 clause 7.5), bracketed per the
8
+ # standard's worked examples, e.g. "[online]"
9
+ class Medium < Element
10
+ def present?
11
+ !medium.empty?
12
+ end
13
+
14
+ def render
15
+ medium
16
+ end
17
+
18
+ private
19
+
20
+ def 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
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ module Elements
7
+ # Numeration within a series (ISO 690 clause 7.9): report numbers
8
+ class Numeration < Element
9
+ def present?
10
+ !docnumber.to_s.empty?
11
+ end
12
+
13
+ def render
14
+ "#{@i18n.label('report_no')} #{docnumber}"
15
+ end
16
+
17
+ private
18
+
19
+ def docnumber
20
+ @model.docnumber
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ module Elements
7
+ # Production information: place and publisher (ISO 690 clause 7.8).
8
+ # Place comes from the model's typed city; publishers from
9
+ # organization contributors.
10
+ class Production < Element
11
+ def present?
12
+ !places.empty? || !publishers.empty? || no_place_declared?
13
+ end
14
+
15
+ def render
16
+ if publisher_first?
17
+ return no_place_production_publisher_first if places.empty? &&
18
+ no_place_declared?
19
+
20
+ return [publishers.join("; "), places.map { |p| cjk_place(p) }]
21
+ .reject(&:empty?).join(production_sep)
22
+ end
23
+
24
+ return no_place_production if places.empty? && no_place_declared?
25
+
26
+ [places.map { |p| cjk_place(p) }, publishers.join("; ")]
27
+ .reject(&:empty?).join(production_sep)
28
+ end
29
+
30
+ # A CJK locale's place names carry the CJK-Latin separator
31
+ # ("Cambridge、UK" over "Cambridge, UK")
32
+ def cjk_place(place)
33
+ sep = @i18n.punct_fetch("cjk-latin-separator", "")
34
+ sep.empty? ? place : place.gsub(", ", sep)
35
+ end
36
+
37
+ def publisher_first?
38
+ @i18n.punct_fetch("production_order", "") == "publisher_first"
39
+ end
40
+
41
+ def production_sep
42
+ @i18n.punct_fetch("production_sep", ": ")
43
+ end
44
+
45
+ def no_place_production_publisher_first
46
+ label = @i18n.label("no_place")
47
+ return "" if label.empty? || label == "no_place"
48
+
49
+ pubs = publishers.join("; ")
50
+ pubs.empty? ? label : "#{pubs}, #{label}"
51
+ end
52
+
53
+ # The no-place placeholder keeps the production separator even
54
+ # when no publisher follows ("n.p.: n.d.")
55
+ def no_place_production
56
+ label = @i18n.label("no_place")
57
+ return "" if label.empty? || label == "no_place"
58
+
59
+ pubs = publishers.join("; ")
60
+ pubs.empty? ? "#{label}#{production_sep}".rstrip :
61
+ "#{label}#{production_sep}#{pubs}"
62
+ end
63
+
64
+ def no_place_declared?
65
+ @style.type_template_for(item_kind)&.no_place == true
66
+ end
67
+
68
+ private
69
+
70
+ def places
71
+ Array(@model.place).map do |p|
72
+ structured = [p.city.to_s, p.region.map(&:content),
73
+ p.country.map(&:content)]
74
+ .flatten.reject { |c| c.strip.empty? }
75
+ if structured.empty? && !p.formatted_place.to_s.empty?
76
+ next p.formatted_place.to_s
77
+ end
78
+
79
+ structured.join(", ")
80
+ end.reject(&:empty?)
81
+ end
82
+
83
+ def publishers
84
+ contributors("publisher").map do |c|
85
+ Array(c.organization&.name).map { |n| localized(n) }.join(", ")
86
+ end.reject(&:empty?)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ module Elements
7
+ # Date of publication (ISO 690 clause 7.7). The relaton model
8
+ # provides typed date values (StringDate::Value#to_date); years
9
+ # come from the model, never from string manipulation. A from/to
10
+ # pair renders as a range (closed or open).
11
+ class PubDate < Element
12
+ def present?
13
+ !render.nil?
14
+ end
15
+
16
+ def render
17
+ range || year&.to_s
18
+ end
19
+
20
+ private
21
+
22
+ def publication_date
23
+ Array(@model.date).find(&:published) ||
24
+ Array(@model.date).find { |d| d.type.nil? } ||
25
+ Array(@model.date).find { |d| d.type == "created" } ||
26
+ Array(@model.date).find { |d| d.type == "issued" } ||
27
+ Array(@model.date).find { |d| d.type == "circulated" }
28
+ end
29
+
30
+ def publication_date_value
31
+ d = publication_date or return nil
32
+ d.from || d.at || d.to
33
+ end
34
+
35
+ def range
36
+ d = publication_date or return nil
37
+ dash = @i18n.label("date_range")
38
+ if d.from && d.to
39
+ fy = d.from.to_date.year
40
+ ty = d.to.to_date.year
41
+ fy == ty ? fy.to_s : "#{fy}#{dash}#{ty}"
42
+ elsif d.from
43
+ "#{d.from.to_date.year}#{dash}"
44
+ end
45
+ end
46
+
47
+ def year
48
+ publication_date_value.to_date.year
49
+ rescue ArgumentError, TypeError, NoMethodError
50
+ nil
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ module Elements
7
+ # Series title and number (ISO 690 clause 7.10)
8
+ class Series < Element
9
+ def present?
10
+ !series_title.empty?
11
+ end
12
+
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
+
23
+ base = "#{@style.templates.title_open}#{series_title}" \
24
+ "#{@style.templates.title_close}"
25
+ if number.empty?
26
+ base
27
+ else
28
+ # an empty series_no label joins on the space alone (the
29
+ # CJK form: "Series 472")
30
+ label = @i18n.label("series_no")
31
+ label.empty? ? "#{base} #{number}" :
32
+ "#{base}, #{label} #{number}"
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def series
39
+ Array(@model.series).first
40
+ end
41
+
42
+ def series_title
43
+ titles_in_lang(series&.title).map { |t| localized(t) }
44
+ .join(" ").strip
45
+ end
46
+
47
+ def number
48
+ series&.number.to_s
49
+ end
50
+
51
+ def run
52
+ series&.run.to_s
53
+ end
54
+
55
+ # The style's series form for this item's resource kind, falling
56
+ # back to the style-wide form
57
+ def series_form
58
+ variant = @style.per_type
59
+ .find { |t| t.type == @style.kind_for(@model.type) }
60
+ variant&.series || @style.templates.series
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,41 @@
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
+ return content if unit.nil? || unit == "size_#{type}"
32
+
33
+ # The value/unit glue is locale-declared (CJK cites tight,
34
+ # "巻1" over "vol. 1")
35
+ "#{unit}#{@i18n.punct_fetch('size-join', ' ')}#{content}"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ module Elements
7
+ # The item's status stage, capitalized, parenthesized beside
8
+ # the identifier ("OGC 05-020r27 (Draft)"); published statuses
9
+ # are not cited
10
+ class Status < Element
11
+ def present?
12
+ !stage.empty? &&
13
+ !%w[published retired].include?(stage.downcase)
14
+ end
15
+
16
+ def render
17
+ " (#{stage})"
18
+ end
19
+
20
+ private
21
+
22
+ def stage
23
+ raw = @model.status&.stage&.content.to_s
24
+ raw.empty? ? "" : raw.sub(/^\w/) { |c| c.upcase }
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ 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