relaton 3.0.7 → 3.0.8

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 (134) hide show
  1. checksums.yaml +4 -4
  2. data/bin/relaton +20 -0
  3. data/lib/relaton/bib/model/docidentifier.rb +17 -2
  4. data/lib/relaton/bibcollection.rb +146 -0
  5. data/lib/relaton/bibdata.rb +92 -0
  6. data/lib/relaton/bipm/data_outcomes_parser.rb +6 -1
  7. data/lib/relaton/cli/base_convertor.rb +134 -0
  8. data/lib/relaton/cli/command.rb +390 -0
  9. data/lib/relaton/cli/data_fetcher.rb +20 -0
  10. data/lib/relaton/cli/frontend/dist/404.html +18 -0
  11. data/lib/relaton/cli/frontend/dist/app.iife.js +1 -0
  12. data/lib/relaton/cli/frontend/dist/style.css +3 -0
  13. data/lib/relaton/cli/frontend/index.html +14 -0
  14. data/lib/relaton/cli/frontend/package-lock.json +2945 -0
  15. data/lib/relaton/cli/frontend/package.json +29 -0
  16. data/lib/relaton/cli/frontend/public/404.html +18 -0
  17. data/lib/relaton/cli/frontend/src/App.test.ts +501 -0
  18. data/lib/relaton/cli/frontend/src/App.vue +506 -0
  19. data/lib/relaton/cli/frontend/src/app.ts +90 -0
  20. data/lib/relaton/cli/frontend/src/components/DocumentDetail.test.ts +142 -0
  21. data/lib/relaton/cli/frontend/src/components/DocumentDetail.vue +228 -0
  22. data/lib/relaton/cli/frontend/src/components/DocumentRow.vue +88 -0
  23. data/lib/relaton/cli/frontend/src/components/Icon.test.ts +44 -0
  24. data/lib/relaton/cli/frontend/src/components/Icon.vue +55 -0
  25. data/lib/relaton/cli/frontend/src/components/Pagination.vue +26 -0
  26. data/lib/relaton/cli/frontend/src/dev/main.ts +100 -0
  27. data/lib/relaton/cli/frontend/src/dev/sample-data.json +71 -0
  28. data/lib/relaton/cli/frontend/src/lib/filter.test.ts +88 -0
  29. data/lib/relaton/cli/frontend/src/lib/filter.ts +95 -0
  30. data/lib/relaton/cli/frontend/src/lib/hydrate.test.ts +102 -0
  31. data/lib/relaton/cli/frontend/src/lib/hydrate.ts +55 -0
  32. data/lib/relaton/cli/frontend/src/lib/shards.test.ts +319 -0
  33. data/lib/relaton/cli/frontend/src/lib/shards.ts +183 -0
  34. data/lib/relaton/cli/frontend/src/lib/types.ts +148 -0
  35. data/lib/relaton/cli/frontend/src/lib/url.test.ts +151 -0
  36. data/lib/relaton/cli/frontend/src/lib/url.ts +111 -0
  37. data/lib/relaton/cli/frontend/src/shims-vue.d.ts +5 -0
  38. data/lib/relaton/cli/frontend/src/styles/index.css +27 -0
  39. data/lib/relaton/cli/frontend/src/test/fetch.ts +58 -0
  40. data/lib/relaton/cli/frontend/tsconfig.json +20 -0
  41. data/lib/relaton/cli/frontend/vite.config.ts +42 -0
  42. data/lib/relaton/cli/frontend/vitest.config.ts +11 -0
  43. data/lib/relaton/cli/frontend_assets.rb +58 -0
  44. data/lib/relaton/cli/full_text_search.rb +98 -0
  45. data/lib/relaton/cli/i18n.yaml +1 -0
  46. data/lib/relaton/cli/index_item_normalizer.rb +380 -0
  47. data/lib/relaton/cli/index_site_generator.rb +1015 -0
  48. data/lib/relaton/cli/relaton_file.rb +290 -0
  49. data/lib/relaton/cli/subcommand_collection.rb +263 -0
  50. data/lib/relaton/cli/subcommand_db.rb +81 -0
  51. data/lib/relaton/cli/templates/_document.liquid +111 -0
  52. data/lib/relaton/cli/templates/_index.liquid +61 -0
  53. data/lib/relaton/cli/templates/index/page.liquid +20 -0
  54. data/lib/relaton/cli/templates/index-style.css +1017 -0
  55. data/lib/relaton/cli/util.rb +8 -0
  56. data/lib/relaton/cli/version.rb +11 -0
  57. data/lib/relaton/cli/xml_convertor.rb +47 -0
  58. data/lib/relaton/cli/xml_to_html_renderer.rb +117 -0
  59. data/lib/relaton/cli/yaml_convertor.rb +102 -0
  60. data/lib/relaton/cli.rb +113 -0
  61. data/lib/relaton/doi/CLAUDE.md +1 -1
  62. data/lib/relaton/element_finder.rb +29 -0
  63. data/lib/relaton/gb/CLAUDE.md +1 -1
  64. data/lib/relaton/ietf/CLAUDE.md +1 -1
  65. data/lib/relaton/iho/CLAUDE.md +3 -3
  66. data/lib/relaton/iho/bibliography.rb +41 -27
  67. data/lib/relaton/iho/processor.rb +3 -2
  68. data/lib/relaton/nist/CLAUDE.md +11 -1
  69. data/lib/relaton/nist/README.adoc +7 -1
  70. data/lib/relaton/nist/hit_collection.rb +25 -1
  71. data/lib/relaton/nist/mods_parser.rb +32 -8
  72. data/lib/relaton/nist/pubs_export.rb +69 -11
  73. data/lib/relaton/ogc/CLAUDE.md +1 -1
  74. data/lib/relaton/omg/CLAUDE.md +1 -1
  75. data/lib/relaton/plateau/CLAUDE.md +1 -1
  76. data/lib/relaton/render/general.rb +319 -0
  77. data/lib/relaton/render/iso690/csl_import.rb +129 -0
  78. data/lib/relaton/render/iso690/element.rb +153 -0
  79. data/lib/relaton/render/iso690/elements/access.rb +44 -0
  80. data/lib/relaton/render/iso690/elements/authorizer.rb +45 -0
  81. data/lib/relaton/render/iso690/elements/citeid.rb +39 -0
  82. data/lib/relaton/render/iso690/elements/component_part.rb +97 -0
  83. data/lib/relaton/render/iso690/elements/creator.rb +267 -0
  84. data/lib/relaton/render/iso690/elements/edition.rb +97 -0
  85. data/lib/relaton/render/iso690/elements/extent.rb +71 -0
  86. data/lib/relaton/render/iso690/elements/identifier.rb +46 -0
  87. data/lib/relaton/render/iso690/elements/location.rb +73 -0
  88. data/lib/relaton/render/iso690/elements/medium.rb +32 -0
  89. data/lib/relaton/render/iso690/elements/numeration.rb +26 -0
  90. data/lib/relaton/render/iso690/elements/production.rb +111 -0
  91. data/lib/relaton/render/iso690/elements/pub_date.rb +63 -0
  92. data/lib/relaton/render/iso690/elements/series.rb +66 -0
  93. data/lib/relaton/render/iso690/elements/size.rb +45 -0
  94. data/lib/relaton/render/iso690/elements/status.rb +30 -0
  95. data/lib/relaton/render/iso690/elements/stddoc.rb +40 -0
  96. data/lib/relaton/render/iso690/elements/title.rb +56 -0
  97. data/lib/relaton/render/iso690/elements/updated.rb +38 -0
  98. data/lib/relaton/render/iso690/elements.rb +67 -0
  99. data/lib/relaton/render/iso690/fields.rb +142 -0
  100. data/lib/relaton/render/iso690/i18n/en.yml +48 -0
  101. data/lib/relaton/render/iso690/i18n/fr.yml +14 -0
  102. data/lib/relaton/render/iso690/i18n/ja.yml +38 -0
  103. data/lib/relaton/render/iso690/i18n.rb +83 -0
  104. data/lib/relaton/render/iso690/identifier_kinds.rb +29 -0
  105. data/lib/relaton/render/iso690/index_renderer.rb +57 -0
  106. data/lib/relaton/render/iso690/kinds.rb +53 -0
  107. data/lib/relaton/render/iso690/renderer.rb +150 -0
  108. data/lib/relaton/render/iso690/rules/bipm.rb +186 -0
  109. data/lib/relaton/render/iso690/rules/jis.rb +103 -0
  110. data/lib/relaton/render/iso690/rules/nist.rb +384 -0
  111. data/lib/relaton/render/iso690/rules.rb +227 -0
  112. data/lib/relaton/render/iso690/style/locale.rb +59 -0
  113. data/lib/relaton/render/iso690/style/name_form.rb +87 -0
  114. data/lib/relaton/render/iso690/style/scheme.rb +33 -0
  115. data/lib/relaton/render/iso690/style/sort_rule.rb +25 -0
  116. data/lib/relaton/render/iso690/style/template_map.rb +40 -0
  117. data/lib/relaton/render/iso690/style/type_template.rb +45 -0
  118. data/lib/relaton/render/iso690/style.rb +227 -0
  119. data/lib/relaton/render/iso690/styles/apa.yml +45 -0
  120. data/lib/relaton/render/iso690/styles/author-date.yml +31 -0
  121. data/lib/relaton/render/iso690/styles/chicago.yml +49 -0
  122. data/lib/relaton/render/iso690/styles/ieee-sa.yml +45 -0
  123. data/lib/relaton/render/iso690/styles/jis-en.yml +68 -0
  124. data/lib/relaton/render/iso690/styles/jis-ja.yml +67 -0
  125. data/lib/relaton/render/iso690/styles/lncs.yml +54 -0
  126. data/lib/relaton/render/iso690/styles/mla.yml +56 -0
  127. data/lib/relaton/render/iso690/template.rb +248 -0
  128. data/lib/relaton/render/iso690.rb +28 -0
  129. data/lib/relaton/render/version.rb +5 -0
  130. data/lib/relaton/un/CLAUDE.md +1 -1
  131. data/lib/relaton/version.rb +1 -1
  132. data/lib/relaton-cli.rb +17 -0
  133. data/lib/relaton-render.rb +13 -0
  134. metadata +192 -21
@@ -0,0 +1,111 @@
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 (form = production_form) && !form.empty?
17
+ return Template.new(form).evaluate(
18
+ "production" => Template::Field[true, render_production],
19
+ )
20
+ end
21
+
22
+ render_production
23
+ end
24
+
25
+ def render_production
26
+ if publisher_first?
27
+ return no_place_production_publisher_first if places.empty? &&
28
+ no_place_declared?
29
+
30
+ return [publishers.join("; "), places.map { |p| cjk_place(p) }]
31
+ .reject(&:empty?).join(production_sep)
32
+ end
33
+
34
+ return no_place_production if places.empty? && no_place_declared?
35
+
36
+ [places.map { |p| cjk_place(p) }, publishers.join("; ")]
37
+ .reject(&:empty?).join(production_sep)
38
+ end
39
+
40
+ # A CJK locale's place names carry the CJK-Latin separator
41
+ # ("Cambridge、UK" over "Cambridge, UK")
42
+ def cjk_place(place)
43
+ sep = @i18n.punct_fetch("cjk-latin-separator", "")
44
+ sep.empty? ? place : place.gsub(", ", sep)
45
+ end
46
+
47
+ # The kind's declared form for this slot: the first entry of
48
+ # the kind carrying one (a home variant may shadow the type)
49
+ def production_form
50
+ kind = @style.kind_for(@model.type)
51
+ @style.per_type.find do |t|
52
+ t.type == kind && !t.production.to_s.empty?
53
+ end&.production
54
+ end
55
+
56
+ def publisher_first?
57
+ @i18n.punct_fetch("production_order", "") == "publisher_first"
58
+ end
59
+
60
+ def production_sep
61
+ @i18n.punct_fetch("production_sep", ": ")
62
+ end
63
+
64
+ def no_place_production_publisher_first
65
+ label = @i18n.label("no_place")
66
+ return "" if label.empty? || label == "no_place"
67
+
68
+ pubs = publishers.join("; ")
69
+ pubs.empty? ? label : "#{pubs}, #{label}"
70
+ end
71
+
72
+ # The no-place placeholder keeps the production separator even
73
+ # when no publisher follows ("n.p.: n.d.")
74
+ def no_place_production
75
+ label = @i18n.label("no_place")
76
+ return "" if label.empty? || label == "no_place"
77
+
78
+ pubs = publishers.join("; ")
79
+ pubs.empty? ? "#{label}#{production_sep}".rstrip :
80
+ "#{label}#{production_sep}#{pubs}"
81
+ end
82
+
83
+ def no_place_declared?
84
+ @style.type_template_for(item_kind)&.no_place == true
85
+ end
86
+
87
+ private
88
+
89
+ def places
90
+ Array(@model.place).map do |p|
91
+ structured = [p.city.to_s, p.region.map(&:content),
92
+ p.country.map(&:content)]
93
+ .flatten.reject { |c| c.strip.empty? }
94
+ if structured.empty? && !p.formatted_place.to_s.empty?
95
+ next p.formatted_place.to_s
96
+ end
97
+
98
+ structured.join(", ")
99
+ end.reject(&:empty?)
100
+ end
101
+
102
+ def publishers
103
+ contributors("publisher").map do |c|
104
+ Array(c.organization&.name).map { |n| localized(n) }.join(", ")
105
+ end.reject(&:empty?)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,63 @@
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
+ return (fy == ty ? fy.to_s : "#{fy}#{dash}#{ty}")
42
+ end
43
+
44
+ # A from-only date is an open run ("1925–") for a
45
+ # continuing resource; a monograph's publication date
46
+ # cites the year bare ("2022") — the trailing dash would
47
+ # leak into the terminator otherwise
48
+ if d.from && %w[continuing serial_part online webdoc]
49
+ .include?(item_kind)
50
+ "#{d.from.to_date.year}#{dash}"
51
+ end
52
+ end
53
+
54
+ def year
55
+ publication_date_value.to_date.year
56
+ rescue ArgumentError, TypeError, NoMethodError
57
+ nil
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ 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,45 @@
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
+ # A %-template places the value at its % (the extent
34
+ # precedent: "% vol." cites "1 vol."); the value/unit glue
35
+ # is otherwise locale-declared (CJK cites tight, "巻1"
36
+ # over "vol. 1")
37
+ return unit.gsub("%", content) if unit.include?("%")
38
+
39
+ "#{unit}#{@i18n.punct_fetch('size-join', ' ')}#{content}"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ 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
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ module Elements
7
+ # Title of the resource (ISO 690 clause 7.3), emphasised by the
8
+ # style's declared title markers
9
+ class Title < Element
10
+ def present?
11
+ !main_title.empty?
12
+ end
13
+
14
+ # The citation capsule's plain title (the 1.x citetemplate's
15
+ # "{{ title }}"): the same resolution, no style markers
16
+ def plain
17
+ main_title
18
+ end
19
+
20
+ def render
21
+ text = main_title
22
+ return text if analytic? && @short
23
+ if !@short &&
24
+ (form = @style.title_form_for(@style.kind_for(@model.type)))
25
+ # the kind's own form carries the whole emphasis
26
+ return ::Relaton::Render::Iso690::Template.new(form)
27
+ .evaluate("title" => Template::Field[!text.empty?, text])
28
+ end
29
+
30
+ "#{@style.templates.title_open}#{text}" \
31
+ "#{@style.templates.title_close}"
32
+ end
33
+
34
+ private
35
+
36
+ # ISO 690: analytic titles are not emphasised; the host or
37
+ # serial carries the emphasis
38
+ def analytic?
39
+ %w[component_part serial_part]
40
+ .include?(@style.kind_for(@model.type))
41
+ end
42
+
43
+ # A typed main title wins; an untyped title is main by
44
+ # default, but yields to a typed one
45
+ def main_title
46
+ titles = Array(@model.title)
47
+ mains = titles.select { |x| x.type == "main" }
48
+ mains = titles.select { |x| x.type.nil? } if mains.empty?
49
+ mains = titles if mains.empty?
50
+ localized(titles_in_lang(mains).first)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ 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
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ # Element registry (ISO 690 clause 7 vocabulary)
7
+ module Elements
8
+ autoload :Creator, "relaton/render/iso690/elements/creator"
9
+ autoload :Title, "relaton/render/iso690/elements/title"
10
+ autoload :Edition, "relaton/render/iso690/elements/edition"
11
+ autoload :Medium, "relaton/render/iso690/elements/medium"
12
+ autoload :Series, "relaton/render/iso690/elements/series"
13
+ autoload :Production, "relaton/render/iso690/elements/production"
14
+ autoload :PubDate, "relaton/render/iso690/elements/pub_date"
15
+ autoload :Identifier, "relaton/render/iso690/elements/identifier"
16
+ autoload :Location, "relaton/render/iso690/elements/location"
17
+ autoload :Numeration, "relaton/render/iso690/elements/numeration"
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"
23
+ autoload :Status, "relaton/render/iso690/elements/status"
24
+ autoload :Authorizer, "relaton/render/iso690/elements/authorizer"
25
+ autoload :Citeid, "relaton/render/iso690/elements/citeid"
26
+ autoload :Updated, "relaton/render/iso690/elements/updated"
27
+
28
+ CLASSES = {
29
+ creator: Creator,
30
+ title: Title,
31
+ edition: Edition,
32
+ medium: Medium,
33
+ series: Series,
34
+ production: Production,
35
+ date: PubDate,
36
+ identifier: Identifier,
37
+ location: Location,
38
+ numeration: Numeration,
39
+ component_part: ComponentPart,
40
+ size: Size,
41
+ extent: Extent,
42
+ access: Access,
43
+ stddoc: Stddoc,
44
+ status: Status,
45
+ authorizer: Authorizer,
46
+ citeid: Citeid,
47
+ updated: Updated,
48
+ }.freeze
49
+
50
+ class << self
51
+ # Flavors extend the vocabulary through the renderer's
52
+ # element map: a flavor slot resolves before the built-ins,
53
+ # scoped to that renderer alone (never process-wide)
54
+ def resolve(name, elements = {})
55
+ elements[name.to_sym] || CLASSES.fetch(name, nil)
56
+ end
57
+ end
58
+
59
+ def self.build(name, model, style:, i18n:, short: false,
60
+ elements: {})
61
+ klass = resolve(name, elements) or return nil
62
+ klass.new(model, style: style, i18n: i18n, short: short)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ # Field resolver: builds the template evaluator's field table from a
7
+ # relaton model instance. Slots resolve to ISO 690 clause 7 data
8
+ # elements, to name-form fields of the principal creator, or to the
9
+ # disambiguator supplied by the citation caller.
10
+ class Fields
11
+ ELEMENT_SLOTS = %i[
12
+ creator title edition medium series production
13
+ date numeration component_part identifier location size
14
+ extent access stddoc status citeid authorizer updated
15
+ ].freeze
16
+
17
+ def initialize(model, style:, i18n:, disambiguator: nil,
18
+ short: false, elements: {})
19
+ @model = model
20
+ @style = style
21
+ @i18n = i18n
22
+ @disambiguator = disambiguator.to_s
23
+ @short = short
24
+ @elements = elements
25
+ end
26
+
27
+ def to_h
28
+ table = {}
29
+ slots.each do |slot|
30
+ key = slot.to_s.delete("_").downcase
31
+ table[key] = element_field(slot)
32
+ end
33
+ table["role"] = role_field
34
+ table["dategroup"] = dategroup_field
35
+ table["disambiguateddate"] = disambiguated_date_field(table["date"])
36
+ unless table["dategroup"].present?
37
+ table["dategroup"] = fallback_field(:dategroup)
38
+ end
39
+ table.merge(name_fields)
40
+ end
41
+
42
+ # The creators' editor-role marker ("(eds.)", "(編)"), as a
43
+ # template slot: a creator list that is not all editors cites no
44
+ # role
45
+ def role_field
46
+ creator = Elements.build(:creator, @model, style: @style,
47
+ i18n: @i18n, elements: @elements)
48
+ return Template::Field[false, ""] unless
49
+ creator&.present? && creator.respond_to?(:role_marker)
50
+
51
+ marker = creator.role_marker
52
+ marker.empty? ? Template::Field[false, ""] :
53
+ Template::Field[true, marker]
54
+ end
55
+
56
+ # The date carrying the batch disambiguation suffix ("2022a"):
57
+ # identity when the caller passes none
58
+ def disambiguated_date_field(date)
59
+ return date if @disambiguator.empty? || !date.present?
60
+
61
+ Template::Field[true, "#{date.text}#{@disambiguator}"]
62
+ end
63
+
64
+ def fallback_field(slot)
65
+ fallback = @style.fallback_for(item_kind, slot)
66
+ fallback ? Template::Field[true, fallback] :
67
+ Template::Field[false, ""]
68
+ end
69
+
70
+ private
71
+
72
+ # The date with the updated date nested ("(2018 (updated
73
+ # November 2018))"); the bare date slot stays untouched
74
+ def dategroup_field
75
+ date = raw_field(:date)
76
+ updated = raw_field(:updated)
77
+ return Template::Field[false, ""] if !date.present? &&
78
+ !updated.present?
79
+
80
+ inner = updated.present? ? updated.text : date.text
81
+ inner = "#{date.text}#{updated.text}" if date.present? &&
82
+ updated.present?
83
+ Template::Field[true, "(#{inner})"]
84
+ end
85
+
86
+ private
87
+
88
+ def slots
89
+ ELEMENT_SLOTS | @elements.keys
90
+ end
91
+
92
+ # The element's own render, before any absent-slot fallback:
93
+ # the creator-date group cites real dates only
94
+ def raw_field(slot)
95
+ element_field = build_field(slot)
96
+ return element_field if element_field.present?
97
+
98
+ Template::Field[false, ""]
99
+ end
100
+
101
+ def element_field(slot)
102
+ field = build_field(slot)
103
+ return field if field.present?
104
+
105
+ fallback = @style.fallback_for(item_kind, slot)
106
+ return Template::Field[true, fallback] if fallback
107
+
108
+ Template::Field[false, ""]
109
+ end
110
+
111
+ def build_field(slot)
112
+ element = Elements.build(slot, @model, style: @style,
113
+ i18n: @i18n, short: @short,
114
+ elements: @elements) or
115
+ return Template::Field[false, ""]
116
+ text = element.render.to_s
117
+ if element.present? && !text.empty?
118
+ return Template::Field[true, text]
119
+ end
120
+
121
+ Template::Field[false, ""]
122
+ end
123
+
124
+ def item_kind
125
+ @style.kind_for(@model.type)
126
+ end
127
+
128
+ def name_fields
129
+ creator = Elements.build(:creator, @model, style: @style,
130
+ i18n: @i18n, elements: @elements)
131
+ {
132
+ "surname" => Template::Field[creator.present?, creator.in_text],
133
+ "givennames" => Template::Field[!creator.principal_given.empty?,
134
+ creator.principal_given],
135
+ "disambiguator" => Template::Field[!@disambiguator.empty?,
136
+ @disambiguator],
137
+ }
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end