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.
- checksums.yaml +4 -4
- data/.github/workflows/rake.yml +6 -1
- data/.github/workflows/release.yml +24 -0
- data/.gitignore +14 -0
- data/.rubocop.yml +2 -2
- data/Gemfile +17 -1
- data/README.adoc +13 -258
- data/docs/citation-style-pack.adoc +370 -0
- data/lib/relaton/render/general.rb +305 -0
- data/lib/relaton/render/iso690/element.rb +153 -0
- data/lib/relaton/render/iso690/elements/access.rb +44 -0
- data/lib/relaton/render/iso690/elements/authorizer.rb +30 -0
- data/lib/relaton/render/iso690/elements/citeid.rb +39 -0
- data/lib/relaton/render/iso690/elements/component_part.rb +99 -0
- data/lib/relaton/render/iso690/elements/creator.rb +258 -0
- data/lib/relaton/render/iso690/elements/edition.rb +92 -0
- data/lib/relaton/render/iso690/elements/extent.rb +71 -0
- data/lib/relaton/render/iso690/elements/identifier.rb +46 -0
- data/lib/relaton/render/iso690/elements/location.rb +73 -0
- data/lib/relaton/render/iso690/elements/medium.rb +32 -0
- data/lib/relaton/render/iso690/elements/numeration.rb +26 -0
- data/lib/relaton/render/iso690/elements/production.rb +92 -0
- data/lib/relaton/render/iso690/elements/pub_date.rb +56 -0
- data/lib/relaton/render/iso690/elements/series.rb +66 -0
- data/lib/relaton/render/iso690/elements/size.rb +41 -0
- data/lib/relaton/render/iso690/elements/status.rb +30 -0
- data/lib/relaton/render/iso690/elements/stddoc.rb +40 -0
- data/lib/relaton/render/iso690/elements/title.rb +56 -0
- data/lib/relaton/render/iso690/elements/updated.rb +38 -0
- data/lib/relaton/render/iso690/elements.rb +67 -0
- data/lib/relaton/render/iso690/fields.rb +142 -0
- data/lib/relaton/render/iso690/i18n/en.yml +47 -0
- data/lib/relaton/render/iso690/i18n/fr.yml +14 -0
- data/lib/relaton/render/iso690/i18n/ja.yml +38 -0
- data/lib/relaton/render/iso690/i18n.rb +83 -0
- data/lib/relaton/render/iso690/identifier_kinds.rb +29 -0
- data/lib/relaton/render/iso690/index_renderer.rb +57 -0
- data/lib/relaton/render/iso690/kinds.rb +53 -0
- data/lib/relaton/render/iso690/renderer.rb +148 -0
- data/lib/relaton/render/iso690/rules.rb +36 -0
- data/lib/relaton/render/iso690/style/locale.rb +53 -0
- data/lib/relaton/render/iso690/style/name_form.rb +87 -0
- data/lib/relaton/render/iso690/style/scheme.rb +33 -0
- data/lib/relaton/render/iso690/style/sort_rule.rb +25 -0
- data/lib/relaton/render/iso690/style/template_map.rb +40 -0
- data/lib/relaton/render/iso690/style/type_template.rb +37 -0
- data/lib/relaton/render/iso690/style.rb +222 -0
- data/lib/relaton/render/iso690/styles/apa.yml +45 -0
- data/lib/relaton/render/iso690/styles/author-date.yml +31 -0
- data/lib/relaton/render/iso690/styles/chicago.yml +48 -0
- data/lib/relaton/render/iso690/styles/ieee-sa.yml +45 -0
- data/lib/relaton/render/iso690/styles/lncs.yml +54 -0
- data/lib/relaton/render/iso690/template.rb +248 -0
- data/lib/relaton/render/iso690.rb +27 -0
- data/lib/relaton/render/version.rb +1 -1
- data/lib/relaton-render.rb +11 -5
- data/relaton-render.gemspec +6 -13
- metadata +61 -116
- data/lib/isodoc/i18n.rb +0 -33
- data/lib/isodoc-yaml/i18n-ar.yaml +0 -44
- data/lib/isodoc-yaml/i18n-de.yaml +0 -43
- data/lib/isodoc-yaml/i18n-en.yaml +0 -40
- data/lib/isodoc-yaml/i18n-es.yaml +0 -47
- data/lib/isodoc-yaml/i18n-fr.yaml +0 -48
- data/lib/isodoc-yaml/i18n-ru.yaml +0 -51
- data/lib/isodoc-yaml/i18n-zh-Hans.yaml +0 -40
- data/lib/relaton/render/fields/date.rb +0 -45
- data/lib/relaton/render/fields/fields.rb +0 -245
- data/lib/relaton/render/general/citations.rb +0 -96
- data/lib/relaton/render/general/config.yml +0 -79
- data/lib/relaton/render/general/render.rb +0 -185
- data/lib/relaton/render/general/render_classes.rb +0 -129
- data/lib/relaton/render/parse/parse.rb +0 -64
- data/lib/relaton/render/parse/parse_contributors.rb +0 -132
- data/lib/relaton/render/parse/parse_extract.rb +0 -180
- data/lib/relaton/render/template/liquid.rb +0 -15
- data/lib/relaton/render/template/template.rb +0 -188
- 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,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
|