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,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
@@ -0,0 +1,47 @@
1
+ lang: en
2
+ labels:
3
+ edition: "ed"
4
+ ordinal_word_1: "First"
5
+ ordinal_word_2: "Second"
6
+ ordinal_word_3: "Third"
7
+ ordinal_word_4: "Fourth"
8
+ ordinal_word_5: "Fifth"
9
+ ordinal_word_6: "Sixth"
10
+ ordinal_word_7: "Seventh"
11
+ ordinal_word_8: "Eighth"
12
+ ordinal_word_9: "Ninth"
13
+ ordinal_word_10: "Tenth"
14
+ size_volume: "vol."
15
+ size_page: "pp."
16
+ page: "p. %"
17
+ pages: "pp. %"
18
+ volume: "vol. %"
19
+ issue: "no. %"
20
+ no_place: "n.p."
21
+ version: "Version"
22
+ medium_website: "[website]"
23
+ viewed: "viewed"
24
+ month_1: "January"
25
+ month_2: "February"
26
+ month_3: "March"
27
+ month_4: "April"
28
+ month_5: "May"
29
+ month_6: "June"
30
+ month_7: "July"
31
+ month_8: "August"
32
+ month_9: "September"
33
+ month_10: "October"
34
+ month_11: "November"
35
+ month_12: "December"
36
+
37
+ series_no: "no."
38
+ report_no: "Report no."
39
+ available_from: "Available from:"
40
+ at_url: "At:"
41
+ in: "In:"
42
+ and: "and"
43
+ oxford_comma: ","
44
+ ed: "ed."
45
+ eds: "eds."
46
+ date_range: "–"
47
+ punct: {}
@@ -0,0 +1,14 @@
1
+ lang: fr
2
+ labels:
3
+ edition: "éd"
4
+ series_no: "n°"
5
+ report_no: "Rapport no."
6
+ available_from: "Disponible sur :"
7
+ in: "Dans :"
8
+ and: "et"
9
+ oxford_comma: ""
10
+ ed: "éd."
11
+ eds: "éd."
12
+ date_range: "–"
13
+ punct:
14
+ production_sep: " : "
@@ -0,0 +1,38 @@
1
+ lang: ja
2
+ labels:
3
+ edition: "版"
4
+ size_volume: "巻"
5
+ size_page: "頁"
6
+ page: "%頁"
7
+ pages: "%頁"
8
+ volume: "巻%"
9
+ issue: "%号"
10
+ version: "版"
11
+ viewed: "閲覧"
12
+ series_no: "番号"
13
+ report_no: "報告番号"
14
+ available_from: "入手先:"
15
+ at_url: "URL:"
16
+ in: "収録:"
17
+ ed: "編"
18
+ eds: "編"
19
+ updated: "更新"
20
+ no_place: "出版地不明"
21
+ edition_cardinal: "第{{ var1 }}版"
22
+ edition_ordinal: "第{{ var1 }}版"
23
+ version_cardinal: "第{{ var1 }}版"
24
+ in_series: "シリーズ"
25
+ list_separator: "、 "
26
+ and: ""
27
+ oxford_comma: ""
28
+ date_range: "〜"
29
+ punct:
30
+ size-join: ""
31
+ marker-space: ""
32
+ production_sep: ": "
33
+ cjk-latin-separator: "、 "
34
+ period: "。"
35
+ comma: "、"
36
+ colon: ":"
37
+ open-paren: "("
38
+ close-paren: ")"
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Relaton
6
+ module Render
7
+ module Iso690
8
+ # Language-specific labels and typographic punctuation, declared per
9
+ # language in i18n/<lang>.yml. Adding a language adds a YAML
10
+ # declaration — no code. A style instance's localized strings overlay
11
+ # the language pack; language-varying intra-element punctuation
12
+ # (e.g. French " : " production separator) is declared under
13
+ # `punct:`.
14
+ class I18n < Lutaml::Model::Serializable
15
+ attribute :lang, :string, default: "en"
16
+ attribute :labels, :hash, default: {}
17
+ attribute :punct, :hash, default: {}
18
+
19
+ key_value do
20
+ map "lang", to: :lang
21
+ map "labels", to: :labels
22
+ map "punct", to: :punct
23
+ end
24
+
25
+ def label(key)
26
+ labels.fetch(key.to_s, key.to_s)
27
+ end
28
+
29
+ def punct_fetch(key, default)
30
+ punct.fetch(key, default)
31
+ end
32
+
33
+ # Style-instance localized strings win over the language pack.
34
+ def overlay!(locale)
35
+ labels.merge!(locale.label_map)
36
+ punct.merge!(locale.punct || {})
37
+ self
38
+ end
39
+
40
+ # Runtime label overrides (a caller's i18n hash): the pack's label
41
+ # names win as the key mapping; unknown keys are ignored.
42
+ def overlay_hash!(hash)
43
+ known = %w[
44
+ edition series_no report_no available_from in and oxford_comma
45
+ ed eds date_range others no_date no_author at
46
+ edition_ordinal edition_cardinal version_cardinal
47
+ ]
48
+ hash.each do |key, value|
49
+ labels[key.to_s] = value if known.include?(key.to_s) && !value.nil?
50
+ end
51
+ self
52
+ end
53
+
54
+ class << self
55
+ def load(lang = "en")
56
+ code = lang.to_s.empty? ? "en" : lang
57
+ path = File.join(__dir__, "i18n", "#{code}.yml")
58
+ unless File.file?(path)
59
+ # 1.x carried declarations for every locale isodoc uses; the
60
+ # v2 packs are per-style data. Unknown languages fall back to
61
+ # the English pack rather than killing the whole conversion.
62
+ fallback_warn(code)
63
+ path = File.join(__dir__, "i18n", "en.yml")
64
+ raise ArgumentError, "no i18n declarations for language #{code}" unless File.file?(path)
65
+ # The pack supplies the labels; the requested language still
66
+ # governs localization (bilingual titles select by it)
67
+ return from_yaml(File.read(path)).tap { |i| i.lang = code }
68
+ end
69
+
70
+ from_yaml(File.read(path))
71
+ end
72
+
73
+ def fallback_warn(code)
74
+ (@fallback_warned ||= {})[code] and return
75
+ @fallback_warned[code] = true
76
+ warn "relaton-render: no i18n declarations for language " \
77
+ "#{code}; falling back to en"
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ # Standard and persistent identifiers (ISO 690 clause 7.11): each
7
+ # identifier kind carries its own rendering method. Kinds are keyed by
8
+ # the docidentifier's type attribute — never guessed from content.
9
+ # Flavors register additional kinds (e.g. Pubid::Iala's MRN).
10
+ module IdentifierKinds
11
+ KINDS = {
12
+ "DOI" => ->(content) { "https://doi.org/#{content}" },
13
+ "ISBN" => ->(content) { "ISBN #{content}" },
14
+ "ISSN" => ->(content) { "ISSN #{content}" },
15
+ "URN" => ->(content) { content },
16
+ "MRN" => ->(content) { content },
17
+ }.freeze
18
+
19
+ class << self
20
+ def render(docidentifier)
21
+ content = docidentifier.content.to_s
22
+ kind = KINDS[docidentifier.type]
23
+ kind ? kind.call(content) : content
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ # A bibliographic index: the ordered reference list, laid out per the
7
+ # BibliographicStyle instance. Sorting follows the style's declared
8
+ # sortKey rules (creator, then date); numbering is layout data.
9
+ class IndexRenderer
10
+ def initialize(style: "author-date", lang: "en", script: "Latn")
11
+ @style = Style.load(style)
12
+ @i18n = I18n.load(lang).overlay!(@style.scheme.locale)
13
+ @renderer = Renderer.new(style: style, lang: lang, script: script)
14
+ end
15
+
16
+ # The reference strings, sorted per the style's sortKey rules.
17
+ def references(models)
18
+ sort_rules = @style.sort_keys
19
+ fields = models.to_h { |m| [m, field_table(m)] }
20
+ models.sort do |a, b|
21
+ fa = fields[a]
22
+ fb = fields[b]
23
+ sort_rules.each do |rule|
24
+ cmp = key_of(fa, rule) <=> key_of(fb, rule)
25
+ next if cmp.zero?
26
+
27
+ break(rule.descending? ? -cmp : cmp)
28
+ end || 0
29
+ end.map { |model| @renderer.render(model) }
30
+ end
31
+
32
+ # The laid-out index: numbered entries per the layout rules
33
+ # (arabic by default; "none" renders bare references).
34
+ def render(models, numbering: "arabic")
35
+ refs = references(models)
36
+ return refs if numbering == "none"
37
+
38
+ refs.each_with_index.map { |ref, i| "#{i + 1}. #{ref}" }
39
+ end
40
+
41
+ private
42
+
43
+ def field_table(model)
44
+ Fields.new(model, style: @style, i18n: @i18n).to_h
45
+ end
46
+
47
+ def key_of(fields, rule)
48
+ field = fields[rule.attribute.to_s.delete("_").downcase]
49
+ return "" if field.nil?
50
+
51
+ text = field.respond_to?(:text) ? field.text : field.to_s
52
+ text.downcase
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ # The vocabulary mapping from a bibliographic item's wire type value
7
+ # to its ISO 690 clause 8 resource kind (TemplateType). This table is
8
+ # the engine's charter — style instances select perType templates on
9
+ # kinds and never enumerate type synonyms. Unmapped values fall to
10
+ # the report kind (clause 8.11), the runner's fallback.
11
+ module Kinds
12
+ TO_KIND = {
13
+ "book" => "monograph",
14
+ "booklet" => "monograph",
15
+ "manual" => "monograph",
16
+ "proceedings" => "monograph",
17
+ "thesis" => "monograph",
18
+ "standard" => "report",
19
+ "techreport" => "report",
20
+ "report" => "report",
21
+ "patent" => "patent",
22
+ "journal" => "continuing",
23
+ "serial" => "continuing",
24
+ "article-journal" => "continuing",
25
+ "article-magazine" => "continuing",
26
+ "inbook" => "component_part",
27
+ "incollection" => "component_part",
28
+ "inproceedings" => "component_part",
29
+ "article" => "serial_part",
30
+ "software" => "online",
31
+ "unpublished" => "online",
32
+ "dataset" => "online",
33
+ "website" => "webdoc",
34
+ "webresource" => "webdoc",
35
+ "webpage" => "online",
36
+ "online" => "online",
37
+ }.freeze
38
+
39
+ DEFAULT_KIND = "report"
40
+
41
+ class << self
42
+ def mapped?(type)
43
+ TO_KIND.key?(type.to_s)
44
+ end
45
+ end
46
+
47
+ def self.kind_for(type)
48
+ TO_KIND.fetch(type.to_s, DEFAULT_KIND)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end