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,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relaton
|
|
4
|
+
module Render
|
|
5
|
+
module Iso690
|
|
6
|
+
# Public API: relaton model in, citation string out. The style
|
|
7
|
+
# instance supplies every rendering decision; this class only wires
|
|
8
|
+
# the template evaluator to the field resolver. No XML, no
|
|
9
|
+
# intermediate hashes.
|
|
10
|
+
class Renderer
|
|
11
|
+
DEFAULT_STYLE = "author-date"
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def render(model, style: DEFAULT_STYLE, lang: "en", script: "Latn")
|
|
15
|
+
new(style: style, lang: lang, script: script).render(model)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def citation(model, disambiguator: nil, style: DEFAULT_STYLE,
|
|
19
|
+
lang: "en", script: "Latn")
|
|
20
|
+
new(style: style, lang: lang, script: script)
|
|
21
|
+
.citation(model, disambiguator: disambiguator)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize(style: DEFAULT_STYLE, lang: "en", script: "Latn",
|
|
26
|
+
labels: {}, elements: {})
|
|
27
|
+
@style = Style.load(style)
|
|
28
|
+
@elements = Rules.resolve(@style.rules, elements)
|
|
29
|
+
@i18n = I18n.load(lang)
|
|
30
|
+
@i18n.overlay_hash!(labels) unless labels.empty?
|
|
31
|
+
# The style instance is the citation-style authority: its
|
|
32
|
+
# declared strings win over the caller's generic i18n hash
|
|
33
|
+
@i18n.overlay!(@style.scheme.locale)
|
|
34
|
+
@script = script
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def render(model, disambiguator: nil)
|
|
38
|
+
kind = @style.kind_for(model.type)
|
|
39
|
+
template = @style.template_for(kind, home: home_docid?(model))
|
|
40
|
+
out = Template.new(template).evaluate(
|
|
41
|
+
Fields.new(model, style: @style, i18n: @i18n,
|
|
42
|
+
disambiguator: disambiguator,
|
|
43
|
+
elements: @elements).to_h,
|
|
44
|
+
)
|
|
45
|
+
raise ::Relaton::Render::Unrenderable,
|
|
46
|
+
"no renderable elements" if out.strip.empty?
|
|
47
|
+
|
|
48
|
+
out
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def home_docid?(model)
|
|
52
|
+
types = Array(@style.scheme.home_docid_type)
|
|
53
|
+
return false if types.empty?
|
|
54
|
+
|
|
55
|
+
Array(model.docidentifier).any? { |d| types.include?(d.type) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# A creator list alone (flavors' document-history name forms)
|
|
59
|
+
def render_creators(model)
|
|
60
|
+
Elements.build(:creator, model, style: @style, i18n: @i18n,
|
|
61
|
+
short: true, elements: @elements).render
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The short cite: the reference with the first-biblio marker
|
|
65
|
+
def render_short(model, delim)
|
|
66
|
+
kind = @style.kind_for(model.type)
|
|
67
|
+
template = @style.short_template_for(kind,
|
|
68
|
+
home: home_docid?(model)) ||
|
|
69
|
+
@style.template_for(kind, home: home_docid?(model))
|
|
70
|
+
Template.new(template).evaluate_short(
|
|
71
|
+
Fields.new(model, style: @style, i18n: @i18n, short: true,
|
|
72
|
+
elements: @elements).to_h,
|
|
73
|
+
delim,
|
|
74
|
+
)
|
|
75
|
+
rescue ::Relaton::Render::Unrenderable
|
|
76
|
+
""
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# The in-text creator with its et-al truncation: the
|
|
80
|
+
# author-date tag's author part
|
|
81
|
+
def in_text_author(model)
|
|
82
|
+
Elements.build(:creator, model, style: @style, i18n: @i18n,
|
|
83
|
+
elements: @elements).in_text
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# The author-date in-text capsule ("Aluffi <em>et al.</em>
|
|
87
|
+
# 2022a"): the in-text creator and the disambiguated date
|
|
88
|
+
def author_date_citation(model, disambiguator: nil)
|
|
89
|
+
f = Fields.new(model, style: @style, i18n: @i18n,
|
|
90
|
+
disambiguator: disambiguator,
|
|
91
|
+
elements: @elements).to_h
|
|
92
|
+
[in_text_author(model), f["disambiguateddate"].text]
|
|
93
|
+
.reject(&:empty?).join(" ")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# The bracketed author-date in-text capsule ("Aluffi (2022)"):
|
|
97
|
+
# the author and the PLAIN date (un-disambiguated, the 1.x
|
|
98
|
+
# citetemplate's {{ date }}) in the locale's parentheses
|
|
99
|
+
def bracketed_date_citation(model, **)
|
|
100
|
+
f = Fields.new(model, style: @style, i18n: @i18n,
|
|
101
|
+
elements: @elements).to_h
|
|
102
|
+
date = f["date"].text
|
|
103
|
+
return "" if date.empty?
|
|
104
|
+
|
|
105
|
+
open = @i18n.punct_fetch("open-paren", "(")
|
|
106
|
+
close = @i18n.punct_fetch("close-paren", ")")
|
|
107
|
+
[in_text_author(model), "#{open}#{date}#{close}"]
|
|
108
|
+
.reject(&:empty?).join(" ")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# The disambiguated date alone (the citation renderings' date)
|
|
112
|
+
def disambiguated_date(model, disambiguator: nil)
|
|
113
|
+
Fields.new(model, style: @style, i18n: @i18n,
|
|
114
|
+
disambiguator: disambiguator,
|
|
115
|
+
elements: @elements).to_h["disambiguateddate"].text
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def short_from_reference?
|
|
119
|
+
@style.scheme.short_from_reference
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# The title citation ("{{ title }}" in the 1.x citetemplate):
|
|
123
|
+
# the title element's plain form, no style markers
|
|
124
|
+
def title_citation(model)
|
|
125
|
+
Elements.build(:title, model, style: @style, i18n: @i18n,
|
|
126
|
+
elements: @elements).plain
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# The in-text author key (the principal creator's surname, as
|
|
130
|
+
# the citation renders it): the batch disambiguation groups by
|
|
131
|
+
# it
|
|
132
|
+
def author_key(model)
|
|
133
|
+
Fields.new(model, style: @style, i18n: @i18n,
|
|
134
|
+
elements: @elements).to_h["surname"].text
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# In-text form: SURNAME, year + disambiguator (name-and-date)
|
|
138
|
+
def citation(model, disambiguator: nil)
|
|
139
|
+
Template.new(@style.templates.citation).evaluate(
|
|
140
|
+
Fields.new(model, style: @style, i18n: @i18n,
|
|
141
|
+
disambiguator: disambiguator,
|
|
142
|
+
elements: @elements).to_h,
|
|
143
|
+
)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relaton
|
|
4
|
+
module Render
|
|
5
|
+
module Iso690
|
|
6
|
+
# The named-rule registry: presentation-of-models implementations
|
|
7
|
+
# the engine ships once, addressable from pack data as
|
|
8
|
+
# rules: { slot: rule_name }. Flavors migrate their ELEMENTS maps
|
|
9
|
+
# onto registered rules; nothing references a flavor class.
|
|
10
|
+
module Rules
|
|
11
|
+
REGISTRY = {}
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def register(name, klass)
|
|
15
|
+
REGISTRY[name.to_s] = klass
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# A pack's rule selections become the renderer's element map;
|
|
19
|
+
# caller-supplied elements win over the pack's selections
|
|
20
|
+
def resolve(rules, elements = {})
|
|
21
|
+
Array(rules).to_h { |slot, name| [slot.to_sym, fetch(name)] }
|
|
22
|
+
.merge(elements || {})
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def fetch(name)
|
|
28
|
+
REGISTRY.fetch(name.to_s) do
|
|
29
|
+
raise ArgumentError, "unknown citation style rule #{name}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relaton
|
|
4
|
+
module Render
|
|
5
|
+
module Iso690
|
|
6
|
+
class Style
|
|
7
|
+
# LocalizedStrings: per-style overrides of the language pack's
|
|
8
|
+
# connective strings, keyed as in the CitationStyle model.
|
|
9
|
+
class Locale < Lutaml::Model::Serializable
|
|
10
|
+
attribute :conj, :string
|
|
11
|
+
attribute :others, :string
|
|
12
|
+
attribute :no_date, :string
|
|
13
|
+
attribute :no_author, :string
|
|
14
|
+
attribute :in_str, :string
|
|
15
|
+
attribute :at, :string
|
|
16
|
+
attribute :available_at, :string
|
|
17
|
+
# Arbitrary label overrides (edition words, join punctuation,
|
|
18
|
+
# size units): merged over the language pack verbatim.
|
|
19
|
+
attribute :labels, :hash, default: -> { {} }
|
|
20
|
+
# Typographic punctuation overrides (production order and
|
|
21
|
+
# separator), declared as in the language packs
|
|
22
|
+
attribute :punct, :hash, default: -> { {} }
|
|
23
|
+
|
|
24
|
+
key_value do
|
|
25
|
+
map "and", to: :conj
|
|
26
|
+
map "others", to: :others
|
|
27
|
+
map "noDate", to: :no_date
|
|
28
|
+
map "noAuthor", to: :no_author
|
|
29
|
+
map "in", to: :in_str
|
|
30
|
+
map "at", to: :at
|
|
31
|
+
map "availableAt", to: :available_at
|
|
32
|
+
map "labels", to: :labels
|
|
33
|
+
map "punct", to: :punct
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
LABEL_FOR = {
|
|
37
|
+
"conj" => "and", "others" => "others", "no_date" => "no_date",
|
|
38
|
+
"no_author" => "no_author", "in_str" => "in", "at" => "at",
|
|
39
|
+
"available_at" => "available_from",
|
|
40
|
+
}.freeze
|
|
41
|
+
|
|
42
|
+
def label_map
|
|
43
|
+
base = LABEL_FOR.filter_map do |attr, label|
|
|
44
|
+
value = public_send(attr)
|
|
45
|
+
[label, value] unless value.nil?
|
|
46
|
+
end.to_h
|
|
47
|
+
base.merge(labels || {})
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relaton
|
|
4
|
+
module Render
|
|
5
|
+
module Iso690
|
|
6
|
+
class Style
|
|
7
|
+
# How personal names are formed, as declared by the style's
|
|
8
|
+
# citation scheme (relaton-models NameFormRules).
|
|
9
|
+
class NameForm < Lutaml::Model::Serializable
|
|
10
|
+
attribute :initials, :boolean, default: -> { false }
|
|
11
|
+
attribute :given_name_first, :boolean, default: -> { false }
|
|
12
|
+
# Where an all-editor creator list's role marker sits relative
|
|
13
|
+
# to the name's terminating period: "glue" renders
|
|
14
|
+
# "NAME (eds.)." (the name-date default), "afterPeriod"
|
|
15
|
+
# renders "NAME. (eds.)" (ISO 690 clause-flavour).
|
|
16
|
+
attribute :role_placement, :string, default: "glue"
|
|
17
|
+
# A completename is rendered, not decomposed; styles that
|
|
18
|
+
# upcase inverted names may declare the completename treatment
|
|
19
|
+
attribute :completename_upcase, :boolean, default: -> { false }
|
|
20
|
+
# Every personal name takes the inverted form (surname first),
|
|
21
|
+
# not just the principal creator
|
|
22
|
+
attribute :inverted_all, :boolean, default: -> { false }
|
|
23
|
+
# "serial": the oxford comma appears at every gap of the
|
|
24
|
+
# creator list, including between two names
|
|
25
|
+
attribute :list_style, :string, default: ""
|
|
26
|
+
# Organization names take the inverted form's casing; small-cap
|
|
27
|
+
# styles keep the declared case
|
|
28
|
+
attribute :org_upcase, :boolean, default: -> { true }
|
|
29
|
+
# When no personal creator exists, the creator slot falls back
|
|
30
|
+
# to the publisher's abbreviation ("ISO: ISO 712, ...")
|
|
31
|
+
attribute :creator_fallback, :string
|
|
32
|
+
# A lone editor still carries the role marker ("(ed.)")
|
|
33
|
+
attribute :lone_editor_marked, :boolean, default: -> { false }
|
|
34
|
+
# Initials without their trailing periods ("Nixon RM")
|
|
35
|
+
attribute :initials_period, :boolean, default: -> { true }
|
|
36
|
+
# The inverted form's surname casing: the name-date convention
|
|
37
|
+
# upcases; small-cap styles keep the declared case
|
|
38
|
+
attribute :surname_upcase, :boolean, default: -> { true }
|
|
39
|
+
# Initials are a list; this separates them ("J. K." vs "J.K.")
|
|
40
|
+
attribute :initials_separator, :string, default: " "
|
|
41
|
+
# The first forename cites in full, the rest as initials
|
|
42
|
+
# ("Milena S."; document-history name forms)
|
|
43
|
+
attribute :subsequent_initials, :boolean, default: -> { false }
|
|
44
|
+
# The direct-order surname casing after the first creator:
|
|
45
|
+
# the name-date convention upcases
|
|
46
|
+
attribute :subsequent_surname_upcase, :boolean,
|
|
47
|
+
default: -> { true }
|
|
48
|
+
# At this many creators the creator list truncates to the
|
|
49
|
+
# first etalDisplay names with "et al." (0 disables)
|
|
50
|
+
attribute :etal_count, :integer, default: 0
|
|
51
|
+
attribute :etal_display, :integer, default: 0
|
|
52
|
+
# The in-text cite truncates separately from the creator
|
|
53
|
+
# list: at this many creators it shows the first
|
|
54
|
+
# inTextEtalDisplay names with "et al." (0 disables; IEEE
|
|
55
|
+
# cites 3+ as "Aluffi et al." while listing 6 in the
|
|
56
|
+
# bibliography)
|
|
57
|
+
attribute :in_text_etal_count, :integer, default: 0
|
|
58
|
+
attribute :in_text_etal_display, :integer, default: 1
|
|
59
|
+
# Editor lists carry the role marker ("(Eds.)")
|
|
60
|
+
attribute :editors_marked, :boolean, default: -> { true }
|
|
61
|
+
|
|
62
|
+
key_value do
|
|
63
|
+
map "initials", to: :initials
|
|
64
|
+
map "givenNameFirst", to: :given_name_first
|
|
65
|
+
map "rolePlacement", to: :role_placement
|
|
66
|
+
map "completenameUpcase", to: :completename_upcase
|
|
67
|
+
map "invertedAll", to: :inverted_all
|
|
68
|
+
map "listStyle", to: :list_style
|
|
69
|
+
map "orgUpcase", to: :org_upcase
|
|
70
|
+
map "creatorFallback", to: :creator_fallback
|
|
71
|
+
map "loneEditorMarked", to: :lone_editor_marked
|
|
72
|
+
map "initialsPeriod", to: :initials_period
|
|
73
|
+
map "surnameUpcase", to: :surname_upcase
|
|
74
|
+
map "initialsSeparator", to: :initials_separator
|
|
75
|
+
map "subsequentInitials", to: :subsequent_initials
|
|
76
|
+
map "subsequentSurnameUpcase", to: :subsequent_surname_upcase
|
|
77
|
+
map "etalCount", to: :etal_count
|
|
78
|
+
map "etalDisplay", to: :etal_display
|
|
79
|
+
map "inTextEtalCount", to: :in_text_etal_count
|
|
80
|
+
map "inTextEtalDisplay", to: :in_text_etal_display
|
|
81
|
+
map "editorsMarked", to: :editors_marked
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relaton
|
|
4
|
+
module Render
|
|
5
|
+
module Iso690
|
|
6
|
+
class Style
|
|
7
|
+
# CitationScheme: the citation system, its name form and its
|
|
8
|
+
# localized strings (relaton-models CitationScheme).
|
|
9
|
+
class Scheme < Lutaml::Model::Serializable
|
|
10
|
+
attribute :system, :string, default: "name-date"
|
|
11
|
+
attribute :name_form, NameForm, default: -> { NameForm.new }
|
|
12
|
+
attribute :locale, Locale, default: -> { Locale.new }
|
|
13
|
+
attribute :home_docid_type, :string, collection: true
|
|
14
|
+
attribute :default_kind, :string, default: ""
|
|
15
|
+
|
|
16
|
+
# The renderings' short cite takes the reference form with the
|
|
17
|
+
# first-biblio marker (the 1.x citetemplate short), not the
|
|
18
|
+
# citation template
|
|
19
|
+
attribute :short_from_reference, :boolean, default: -> { false }
|
|
20
|
+
|
|
21
|
+
key_value do
|
|
22
|
+
map "system", to: :system
|
|
23
|
+
map "nameForm", to: :name_form
|
|
24
|
+
map "locale", to: :locale
|
|
25
|
+
map "homeDocidType", to: :home_docid_type
|
|
26
|
+
map "defaultKind", to: :default_kind
|
|
27
|
+
map "shortFromReference", to: :short_from_reference
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relaton
|
|
4
|
+
module Render
|
|
5
|
+
module Iso690
|
|
6
|
+
class Style
|
|
7
|
+
# SortRule: one ordering key of a bibliographic index sort
|
|
8
|
+
# (relaton-models SortRule).
|
|
9
|
+
class SortRule < Lutaml::Model::Serializable
|
|
10
|
+
attribute :attribute, :string
|
|
11
|
+
attribute :order, :string
|
|
12
|
+
|
|
13
|
+
key_value do
|
|
14
|
+
map "attribute", to: :attribute
|
|
15
|
+
map "order", to: :order
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def descending?
|
|
19
|
+
order == "descending"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relaton
|
|
4
|
+
module Render
|
|
5
|
+
module Iso690
|
|
6
|
+
class Style
|
|
7
|
+
# TemplateMap: citation and reference templates as data, plus the
|
|
8
|
+
# title emphasis markers and the first-creator name form. Slots
|
|
9
|
+
# name ISO 690 clause 7 data elements or name-form fields.
|
|
10
|
+
class TemplateMap < Lutaml::Model::Serializable
|
|
11
|
+
attribute :title_open, :string, default: "_"
|
|
12
|
+
attribute :title_close, :string, default: "_"
|
|
13
|
+
attribute :citation, :string, default: "{{surname}}, {{date}}"
|
|
14
|
+
attribute :reference, :string
|
|
15
|
+
attribute :name, :string, default: "{{surname}}, {{givenNames}}"
|
|
16
|
+
attribute :series, :string, default: ""
|
|
17
|
+
attribute :uri, :string, default: ""
|
|
18
|
+
attribute :creators, :string, default: ""
|
|
19
|
+
attribute :uri_short, :string, default: ""
|
|
20
|
+
# "first": cite only the first identifier (1.x
|
|
21
|
+
# authoritative_identifier|first styles)
|
|
22
|
+
attribute :identifier_mode, :string, default: "all"
|
|
23
|
+
|
|
24
|
+
key_value do
|
|
25
|
+
map "titleOpen", to: :title_open
|
|
26
|
+
map "titleClose", to: :title_close
|
|
27
|
+
map "citation", to: :citation
|
|
28
|
+
map "reference", to: :reference
|
|
29
|
+
map "name", to: :name
|
|
30
|
+
map "series", to: :series
|
|
31
|
+
map "uri", to: :uri
|
|
32
|
+
map "creators", to: :creators
|
|
33
|
+
map "uriShort", to: :uri_short
|
|
34
|
+
map "identifierMode", to: :identifier_mode
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relaton
|
|
4
|
+
module Render
|
|
5
|
+
module Iso690
|
|
6
|
+
class Style
|
|
7
|
+
# TypeTemplate: the template variant for one bibliographic item
|
|
8
|
+
# type, selected by data lookup (relaton-models TypeTemplate).
|
|
9
|
+
class TypeTemplate < Lutaml::Model::Serializable
|
|
10
|
+
attribute :type, :string
|
|
11
|
+
attribute :template, :string
|
|
12
|
+
attribute :series, :string
|
|
13
|
+
attribute :title, :string
|
|
14
|
+
attribute :home, :boolean
|
|
15
|
+
attribute :fallbacks, :hash, default: -> { {} }
|
|
16
|
+
# The short-cite template for this kind; absent falls back to
|
|
17
|
+
# the reference template in short mode
|
|
18
|
+
attribute :short, :string
|
|
19
|
+
# Render the style's no_place label when the publisher has no
|
|
20
|
+
# place (the book-family templates of some flavors)
|
|
21
|
+
attribute :no_place, :boolean
|
|
22
|
+
|
|
23
|
+
key_value do
|
|
24
|
+
map "type", to: :type
|
|
25
|
+
map "template", to: :template
|
|
26
|
+
map "series", to: :series
|
|
27
|
+
map "title", to: :title
|
|
28
|
+
map "home", to: :home
|
|
29
|
+
map "fallbacks", to: :fallbacks
|
|
30
|
+
map "short", to: :short
|
|
31
|
+
map "noPlace", to: :no_place
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Relaton
|
|
6
|
+
module Render
|
|
7
|
+
module Iso690
|
|
8
|
+
# A citation style as an instance of the relaton-models CitationStyle
|
|
9
|
+
# model: the citation scheme (name form, localized strings), the
|
|
10
|
+
# citation and reference templates, and per-type template variants.
|
|
11
|
+
# All rendering knowledge is data; the engine holds no styles, no
|
|
12
|
+
# vocabularies, no localized strings.
|
|
13
|
+
class Style < Lutaml::Model::Serializable
|
|
14
|
+
autoload :NameForm, "relaton/render/iso690/style/name_form"
|
|
15
|
+
autoload :Locale, "relaton/render/iso690/style/locale"
|
|
16
|
+
autoload :Scheme, "relaton/render/iso690/style/scheme"
|
|
17
|
+
autoload :TemplateMap, "relaton/render/iso690/style/template_map"
|
|
18
|
+
autoload :SortRule, "relaton/render/iso690/style/sort_rule"
|
|
19
|
+
autoload :TypeTemplate, "relaton/render/iso690/style/type_template"
|
|
20
|
+
|
|
21
|
+
attribute :name, :string
|
|
22
|
+
# The citation-style family the pack belongs to (iso690 | lncs |
|
|
23
|
+
# chicago | ieee-sa | apa): selects the presentation-of-models
|
|
24
|
+
# conventions the templates address
|
|
25
|
+
attribute :family, :string, default: "iso690"
|
|
26
|
+
# A named pack this one deltas over; resolved at load with the
|
|
27
|
+
# documented merge semantics (#merge_delta)
|
|
28
|
+
attribute :extends, :string
|
|
29
|
+
# Engine capabilities the pack addresses; a resolver that lacks
|
|
30
|
+
# one fails loudly instead of mis-rendering
|
|
31
|
+
attribute :requires, :string, collection: true, default: []
|
|
32
|
+
# Pack-declared taxonomy: wire type -> kind (aliasing or
|
|
33
|
+
# fallthrough), consulted before the engine's Kinds table
|
|
34
|
+
attribute :types, :hash, default: -> { {} }
|
|
35
|
+
# Presentation-of-models selections: slot -> named engine rule
|
|
36
|
+
attribute :rules, :hash, default: -> { {} }
|
|
37
|
+
attribute :scheme, Scheme, default: -> { Scheme.new }
|
|
38
|
+
attribute :templates, TemplateMap, default: -> { TemplateMap.new }
|
|
39
|
+
attribute :per_type, TypeTemplate, collection: true, default: []
|
|
40
|
+
attribute :sort_key, SortRule, collection: true, default: []
|
|
41
|
+
|
|
42
|
+
key_value do
|
|
43
|
+
map "name", to: :name
|
|
44
|
+
map "family", to: :family
|
|
45
|
+
map "extends", to: :extends
|
|
46
|
+
map "requires", to: :requires
|
|
47
|
+
map "types", to: :types
|
|
48
|
+
map "rules", to: :rules
|
|
49
|
+
map "scheme", to: :scheme
|
|
50
|
+
map "templates", to: :templates
|
|
51
|
+
map "perType", to: :per_type
|
|
52
|
+
map "sortKey", to: :sort_key
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# The index sort rules; creator then date when the style declares
|
|
56
|
+
# none (the name-and-date default).
|
|
57
|
+
def sort_keys
|
|
58
|
+
return sort_key unless sort_key.empty?
|
|
59
|
+
|
|
60
|
+
[SortRule.new(attribute: "creator"), SortRule.new(attribute: "date")]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The ISO 690 kind of an item: the vocabulary's mapping, or
|
|
64
|
+
# the style's declared kind for types the vocabulary does not
|
|
65
|
+
# map (a flavor whose untyped items are not reports)
|
|
66
|
+
def kind_for(type)
|
|
67
|
+
declared_type = types[type.to_s]
|
|
68
|
+
unless declared_type.to_s.empty?
|
|
69
|
+
return Kinds.mapped?(declared_type) ? Kinds.kind_for(declared_type) :
|
|
70
|
+
declared_type
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
return Kinds.kind_for(type) if Kinds.mapped?(type)
|
|
74
|
+
|
|
75
|
+
declared = scheme.default_kind.to_s
|
|
76
|
+
declared.empty? ? Kinds.kind_for(type) : declared
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Per-type template selection is a data lookup; unmatched types
|
|
80
|
+
# fall back to the general reference template. When a kind has
|
|
81
|
+
# home-flagged variants, the split is resolved by whether the item
|
|
82
|
+
# carries the scheme's home document identifier.
|
|
83
|
+
def template_for(type, home: nil)
|
|
84
|
+
candidates = per_type.select { |t| t.type == type.to_s }
|
|
85
|
+
if candidates.size > 1 && !home.nil?
|
|
86
|
+
candidates = candidates.select { |t| (t.home || false) == home }
|
|
87
|
+
end
|
|
88
|
+
candidates.first&.template || templates.reference
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def type_template_for(type)
|
|
92
|
+
per_type.find { |t| t.type == type.to_s }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# The home split applies to the short cite as it does to the
|
|
96
|
+
# reference template (a home standard's short cite is not an
|
|
97
|
+
# external standard's)
|
|
98
|
+
def short_template_for(type, home: nil)
|
|
99
|
+
candidates = per_type.select { |t| t.type == type.to_s }
|
|
100
|
+
if candidates.size > 1 && !home.nil?
|
|
101
|
+
candidates = candidates.select { |t| (t.home || false) == home }
|
|
102
|
+
end
|
|
103
|
+
candidates.filter_map(&:short).first
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# The absent-slot replacement text declared for a type, when any
|
|
107
|
+
def fallback_for(type, slot)
|
|
108
|
+
type_template_for(type)&.fallbacks&.[](slot.to_s)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def title_form_for(type)
|
|
112
|
+
per_type.lazy.select { |t| t.type == type.to_s }
|
|
113
|
+
.find { |t| t.title && !t.title.empty? }&.title
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# First-creator name form: the style's declared name template with
|
|
117
|
+
# the surname upcased, per the name-and-date convention.
|
|
118
|
+
def render_name(surname:, given:)
|
|
119
|
+
shown = scheme.name_form.surname_upcase ? surname.upcase : surname
|
|
120
|
+
Template.new(templates.name).evaluate(
|
|
121
|
+
"surname" => Template::Field[!shown.empty?, shown],
|
|
122
|
+
"givennames" => Template::Field[!given.empty?, given],
|
|
123
|
+
)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# The documented delta merge: perType entries merge per type
|
|
127
|
+
# key (a delta re-declaring `article` never drops the base's
|
|
128
|
+
# `monograph`); hash sections merge per key; scalars, templates
|
|
129
|
+
# and rule selections replace; sortKey and requires union.
|
|
130
|
+
# Deterministic and engine-portable.
|
|
131
|
+
def merge_delta(base)
|
|
132
|
+
merged_per_type = base.per_type.dup
|
|
133
|
+
per_type.each do |delta|
|
|
134
|
+
index = merged_per_type.find_index do |b|
|
|
135
|
+
b.type == delta.type && (b.home || false) == (delta.home || false)
|
|
136
|
+
end
|
|
137
|
+
index ? merged_per_type[index] = delta : merged_per_type << delta
|
|
138
|
+
end
|
|
139
|
+
self.per_type = merged_per_type
|
|
140
|
+
self.types = base.types.merge(types)
|
|
141
|
+
self.rules = base.rules.merge(rules)
|
|
142
|
+
merge_scheme(base)
|
|
143
|
+
merge_templates(base)
|
|
144
|
+
self.sort_key = (base.sort_key + sort_key).uniq
|
|
145
|
+
self.requires = (base.requires + requires).uniq
|
|
146
|
+
self
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
151
|
+
def merge_scheme(base)
|
|
152
|
+
fresh = NameForm.new
|
|
153
|
+
NameForm.attributes.each_key do |attr|
|
|
154
|
+
val = scheme.name_form.send(attr)
|
|
155
|
+
next unless val == fresh.send(attr)
|
|
156
|
+
|
|
157
|
+
base_val = base.scheme.name_form.send(attr)
|
|
158
|
+
scheme.name_form.send("#{attr}=", base_val) if
|
|
159
|
+
base_val != fresh.send(attr)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
scheme.system = base.scheme.system if scheme.system.to_s.empty?
|
|
163
|
+
scheme.short_from_reference = base.scheme.short_from_reference unless
|
|
164
|
+
scheme.short_from_reference
|
|
165
|
+
scheme.home_docid_type = base.scheme.home_docid_type if
|
|
166
|
+
Array(scheme.home_docid_type).empty?
|
|
167
|
+
scheme.default_kind = base.scheme.default_kind if
|
|
168
|
+
scheme.default_kind.to_s.empty?
|
|
169
|
+
|
|
170
|
+
locale = scheme.locale
|
|
171
|
+
base_locale = base.scheme.locale
|
|
172
|
+
locale.conj ||= base_locale.conj
|
|
173
|
+
locale.others ||= base_locale.others
|
|
174
|
+
locale.no_date ||= base_locale.no_date
|
|
175
|
+
locale.no_author ||= base_locale.no_author
|
|
176
|
+
locale.in_str ||= base_locale.in_str
|
|
177
|
+
locale.at ||= base_locale.at
|
|
178
|
+
locale.available_at ||= base_locale.available_at
|
|
179
|
+
locale.labels.merge!(base_locale.label_map) { |_k, _b, d| d }
|
|
180
|
+
locale.punct = (base_locale.punct || {}).merge(locale.punct || {})
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def merge_templates(base)
|
|
184
|
+
fresh = TemplateMap.new
|
|
185
|
+
TemplateMap.attributes.each_key do |attr|
|
|
186
|
+
val = templates.send(attr)
|
|
187
|
+
base_val = base.templates.send(attr)
|
|
188
|
+
next unless val == fresh.send(attr) && base_val != fresh.send(attr)
|
|
189
|
+
|
|
190
|
+
templates.send("#{attr}=", base_val)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
class << self
|
|
195
|
+
def load(name_or_path, seen = [])
|
|
196
|
+
path = style_path(name_or_path) or
|
|
197
|
+
raise ArgumentError, "unknown style #{name_or_path}"
|
|
198
|
+
style = from_yaml(File.read(path))
|
|
199
|
+
return style if style.extends.to_s.empty?
|
|
200
|
+
|
|
201
|
+
style.extends.split(/\s+/).each do |base_name|
|
|
202
|
+
raise ArgumentError,
|
|
203
|
+
"circular extends #{base_name}" if seen.include?(base_name)
|
|
204
|
+
|
|
205
|
+
style.merge_delta(load(base_name, seen + [base_name]))
|
|
206
|
+
end
|
|
207
|
+
style
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
private
|
|
211
|
+
|
|
212
|
+
def style_path(name)
|
|
213
|
+
file = File.join(__dir__, "styles", "#{name}.yml")
|
|
214
|
+
return file if File.file?(file)
|
|
215
|
+
|
|
216
|
+
name if name.is_a?(String) && File.file?(name)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|