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.
- checksums.yaml +4 -4
- data/bin/relaton +20 -0
- data/lib/relaton/bib/model/docidentifier.rb +17 -2
- data/lib/relaton/bibcollection.rb +146 -0
- data/lib/relaton/bibdata.rb +92 -0
- data/lib/relaton/bipm/data_outcomes_parser.rb +6 -1
- data/lib/relaton/cli/base_convertor.rb +134 -0
- data/lib/relaton/cli/command.rb +390 -0
- data/lib/relaton/cli/data_fetcher.rb +20 -0
- data/lib/relaton/cli/frontend/dist/404.html +18 -0
- data/lib/relaton/cli/frontend/dist/app.iife.js +1 -0
- data/lib/relaton/cli/frontend/dist/style.css +3 -0
- data/lib/relaton/cli/frontend/index.html +14 -0
- data/lib/relaton/cli/frontend/package-lock.json +2945 -0
- data/lib/relaton/cli/frontend/package.json +29 -0
- data/lib/relaton/cli/frontend/public/404.html +18 -0
- data/lib/relaton/cli/frontend/src/App.test.ts +501 -0
- data/lib/relaton/cli/frontend/src/App.vue +506 -0
- data/lib/relaton/cli/frontend/src/app.ts +90 -0
- data/lib/relaton/cli/frontend/src/components/DocumentDetail.test.ts +142 -0
- data/lib/relaton/cli/frontend/src/components/DocumentDetail.vue +228 -0
- data/lib/relaton/cli/frontend/src/components/DocumentRow.vue +88 -0
- data/lib/relaton/cli/frontend/src/components/Icon.test.ts +44 -0
- data/lib/relaton/cli/frontend/src/components/Icon.vue +55 -0
- data/lib/relaton/cli/frontend/src/components/Pagination.vue +26 -0
- data/lib/relaton/cli/frontend/src/dev/main.ts +100 -0
- data/lib/relaton/cli/frontend/src/dev/sample-data.json +71 -0
- data/lib/relaton/cli/frontend/src/lib/filter.test.ts +88 -0
- data/lib/relaton/cli/frontend/src/lib/filter.ts +95 -0
- data/lib/relaton/cli/frontend/src/lib/hydrate.test.ts +102 -0
- data/lib/relaton/cli/frontend/src/lib/hydrate.ts +55 -0
- data/lib/relaton/cli/frontend/src/lib/shards.test.ts +319 -0
- data/lib/relaton/cli/frontend/src/lib/shards.ts +183 -0
- data/lib/relaton/cli/frontend/src/lib/types.ts +148 -0
- data/lib/relaton/cli/frontend/src/lib/url.test.ts +151 -0
- data/lib/relaton/cli/frontend/src/lib/url.ts +111 -0
- data/lib/relaton/cli/frontend/src/shims-vue.d.ts +5 -0
- data/lib/relaton/cli/frontend/src/styles/index.css +27 -0
- data/lib/relaton/cli/frontend/src/test/fetch.ts +58 -0
- data/lib/relaton/cli/frontend/tsconfig.json +20 -0
- data/lib/relaton/cli/frontend/vite.config.ts +42 -0
- data/lib/relaton/cli/frontend/vitest.config.ts +11 -0
- data/lib/relaton/cli/frontend_assets.rb +58 -0
- data/lib/relaton/cli/full_text_search.rb +98 -0
- data/lib/relaton/cli/i18n.yaml +1 -0
- data/lib/relaton/cli/index_item_normalizer.rb +380 -0
- data/lib/relaton/cli/index_site_generator.rb +1015 -0
- data/lib/relaton/cli/relaton_file.rb +290 -0
- data/lib/relaton/cli/subcommand_collection.rb +263 -0
- data/lib/relaton/cli/subcommand_db.rb +81 -0
- data/lib/relaton/cli/templates/_document.liquid +111 -0
- data/lib/relaton/cli/templates/_index.liquid +61 -0
- data/lib/relaton/cli/templates/index/page.liquid +20 -0
- data/lib/relaton/cli/templates/index-style.css +1017 -0
- data/lib/relaton/cli/util.rb +8 -0
- data/lib/relaton/cli/version.rb +11 -0
- data/lib/relaton/cli/xml_convertor.rb +47 -0
- data/lib/relaton/cli/xml_to_html_renderer.rb +117 -0
- data/lib/relaton/cli/yaml_convertor.rb +102 -0
- data/lib/relaton/cli.rb +113 -0
- data/lib/relaton/doi/CLAUDE.md +1 -1
- data/lib/relaton/element_finder.rb +29 -0
- data/lib/relaton/gb/CLAUDE.md +1 -1
- data/lib/relaton/ietf/CLAUDE.md +1 -1
- data/lib/relaton/iho/CLAUDE.md +3 -3
- data/lib/relaton/iho/bibliography.rb +41 -27
- data/lib/relaton/iho/processor.rb +3 -2
- data/lib/relaton/nist/CLAUDE.md +11 -1
- data/lib/relaton/nist/README.adoc +7 -1
- data/lib/relaton/nist/hit_collection.rb +25 -1
- data/lib/relaton/nist/mods_parser.rb +32 -8
- data/lib/relaton/nist/pubs_export.rb +69 -11
- data/lib/relaton/ogc/CLAUDE.md +1 -1
- data/lib/relaton/omg/CLAUDE.md +1 -1
- data/lib/relaton/plateau/CLAUDE.md +1 -1
- data/lib/relaton/render/general.rb +319 -0
- data/lib/relaton/render/iso690/csl_import.rb +129 -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 +45 -0
- data/lib/relaton/render/iso690/elements/citeid.rb +39 -0
- data/lib/relaton/render/iso690/elements/component_part.rb +97 -0
- data/lib/relaton/render/iso690/elements/creator.rb +267 -0
- data/lib/relaton/render/iso690/elements/edition.rb +97 -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 +111 -0
- data/lib/relaton/render/iso690/elements/pub_date.rb +63 -0
- data/lib/relaton/render/iso690/elements/series.rb +66 -0
- data/lib/relaton/render/iso690/elements/size.rb +45 -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 +48 -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 +150 -0
- data/lib/relaton/render/iso690/rules/bipm.rb +186 -0
- data/lib/relaton/render/iso690/rules/jis.rb +103 -0
- data/lib/relaton/render/iso690/rules/nist.rb +384 -0
- data/lib/relaton/render/iso690/rules.rb +227 -0
- data/lib/relaton/render/iso690/style/locale.rb +59 -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 +45 -0
- data/lib/relaton/render/iso690/style.rb +227 -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 +49 -0
- data/lib/relaton/render/iso690/styles/ieee-sa.yml +45 -0
- data/lib/relaton/render/iso690/styles/jis-en.yml +68 -0
- data/lib/relaton/render/iso690/styles/jis-ja.yml +67 -0
- data/lib/relaton/render/iso690/styles/lncs.yml +54 -0
- data/lib/relaton/render/iso690/styles/mla.yml +56 -0
- data/lib/relaton/render/iso690/template.rb +248 -0
- data/lib/relaton/render/iso690.rb +28 -0
- data/lib/relaton/render/version.rb +5 -0
- data/lib/relaton/un/CLAUDE.md +1 -1
- data/lib/relaton/version.rb +1 -1
- data/lib/relaton-cli.rb +17 -0
- data/lib/relaton-render.rb +13 -0
- 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
|