relaton-render 3.0.0.pre.alpha.10 → 3.0.0.pre.alpha.12
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/lib/relaton/render/general.rb +11 -3
- data/lib/relaton/render/iso690/element.rb +2 -2
- data/lib/relaton/render/iso690/elements/creator.rb +16 -7
- data/lib/relaton/render/iso690/elements/identifier.rb +11 -2
- data/lib/relaton/render/iso690/elements/location.rb +10 -2
- data/lib/relaton/render/iso690/elements/pub_date.rb +7 -8
- data/lib/relaton/render/iso690/elements/updated.rb +38 -0
- data/lib/relaton/render/iso690/elements.rb +15 -3
- data/lib/relaton/render/iso690/fields.rb +59 -9
- data/lib/relaton/render/iso690/i18n/en.yml +1 -0
- data/lib/relaton/render/iso690/identifier_kinds.rb +1 -10
- data/lib/relaton/render/iso690/kinds.rb +6 -0
- data/lib/relaton/render/iso690/renderer.rb +11 -7
- data/lib/relaton/render/iso690/style/scheme.rb +2 -0
- data/lib/relaton/render/iso690/style/template_map.rb +4 -2
- data/lib/relaton/render/iso690/style.rb +10 -0
- data/lib/relaton/render/iso690/template.rb +104 -46
- data/lib/relaton/render/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6940d659b738ee1c15dc46b7ee7be2bfb3f3bf322fd6cb8ae5fe70e19a2ab7ce
|
|
4
|
+
data.tar.gz: 060007b9cd392e9bf4b84cac3961aa8f0881849e74195c4ca6eb3f5f6dcf64f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a989f5e702e6e27afbad1fab8dc59c7bd092355eab5dbfb1cedf0b99c2373e71c99c3ed418e033f6638f00addcf68d983006213206a4200b7ef6e45c9907682
|
|
7
|
+
data.tar.gz: 56ac7f18bd13de8324a8ac118d68c7c9d98b6b3eb49b455a32cbca194802097b2e5123e725427ff84d16fd8fe1bd4fe952de7e90c610bf27e39cd94d105a76c2
|
|
@@ -63,7 +63,7 @@ module Relaton
|
|
|
63
63
|
end
|
|
64
64
|
m[item.id] = {
|
|
65
65
|
id: item.id, ord: i,
|
|
66
|
-
formattedref: terminate_reference(ref),
|
|
66
|
+
formattedref: terminate_reference(ref, item),
|
|
67
67
|
citation: citation_renderings(item, ref),
|
|
68
68
|
}
|
|
69
69
|
end
|
|
@@ -82,7 +82,9 @@ module Relaton
|
|
|
82
82
|
# render_all feeds the bibliography list: a reference not ending
|
|
83
83
|
# in the biblio terminator takes one (the 1.x render1 behaviour;
|
|
84
84
|
# single-item render stays verbatim)
|
|
85
|
-
|
|
85
|
+
# The bibliography terminator; flavors override to suppress it
|
|
86
|
+
# (1.x use_terminator?), receiving the item it terminates
|
|
87
|
+
def terminate_reference(ref, _item = nil)
|
|
86
88
|
return ref if ref.empty? || ref.rstrip.end_with?(".")
|
|
87
89
|
|
|
88
90
|
"#{ref}."
|
|
@@ -147,6 +149,12 @@ module Relaton
|
|
|
147
149
|
EXCLUDED_ID_TYPES = %w(METANORMA METANORMA-ORDINAL AUTHOR-DATE TITLE
|
|
148
150
|
URN ISO-REFERENCE ISSN ISBN DOI).freeze
|
|
149
151
|
|
|
152
|
+
# Flavors extend the authoritative-identifier exclusions by
|
|
153
|
+
# overriding this reader on their facade subclass
|
|
154
|
+
def excluded_id_types
|
|
155
|
+
EXCLUDED_ID_TYPES
|
|
156
|
+
end
|
|
157
|
+
|
|
150
158
|
def facade_docids(doc)
|
|
151
159
|
node = doc
|
|
152
160
|
unless node.respond_to?(:xpath)
|
|
@@ -165,7 +173,7 @@ module Relaton
|
|
|
165
173
|
->(_x) { true },
|
|
166
174
|
].each do |p|
|
|
167
175
|
out = ids.select do |x|
|
|
168
|
-
p.call(x) && !
|
|
176
|
+
p.call(x) && !excluded_id_types.include?(facade_id_type(x))
|
|
169
177
|
end
|
|
170
178
|
out.empty? or break
|
|
171
179
|
end
|
|
@@ -62,7 +62,7 @@ module Relaton
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def item_kind
|
|
65
|
-
@kind ||
|
|
65
|
+
@kind || @style.kind_for(@model.type)
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def person_completename(person)
|
|
@@ -77,7 +77,7 @@ module Relaton
|
|
|
77
77
|
parts = Array(person.name&.forename).map do |f|
|
|
78
78
|
content = localized(f)
|
|
79
79
|
next content unless content.empty?
|
|
80
|
-
next
|
|
80
|
+
next initial(f.initial) if f.initial.to_s.length == 1
|
|
81
81
|
|
|
82
82
|
""
|
|
83
83
|
end.reject(&:empty?)
|
|
@@ -93,14 +93,23 @@ module Relaton
|
|
|
93
93
|
end
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
-
# The declared creator fallback
|
|
97
|
-
#
|
|
96
|
+
# The declared creator fallback for items with no personal
|
|
97
|
+
# creators: the publisher's abbreviation or full name
|
|
98
98
|
def fallback_name
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
orgs = contributors("publisher")
|
|
100
|
+
name = case name_form.creator_fallback
|
|
101
|
+
when "publisher_abbrev"
|
|
102
|
+
orgs.map do |c|
|
|
103
|
+
localized(c.organization&.abbreviation)
|
|
104
|
+
end
|
|
105
|
+
when "publisher_name"
|
|
106
|
+
orgs.map do |c|
|
|
107
|
+
localized(Array(c.organization&.name).first)
|
|
108
|
+
end
|
|
109
|
+
else
|
|
110
|
+
[]
|
|
111
|
+
end
|
|
112
|
+
name.reject(&:empty?).first.to_s
|
|
104
113
|
end
|
|
105
114
|
|
|
106
115
|
def names
|
|
@@ -15,9 +15,18 @@ module Relaton
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def render
|
|
18
|
-
|
|
18
|
+
return "" if identifiers.empty?
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
ids = @style.templates.identifier_mode == "first" ?
|
|
21
|
+
[identifiers.first] : identifiers
|
|
22
|
+
ids.map { |d| render_id(d) }
|
|
23
|
+
.join(@i18n.punct_fetch("identifier_join", ". "))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# A flavor's identifier kinds subclass this element and
|
|
27
|
+
# override here
|
|
28
|
+
def render_id(docidentifier)
|
|
29
|
+
IdentifierKinds.render(docidentifier)
|
|
21
30
|
end
|
|
22
31
|
|
|
23
32
|
private
|
|
@@ -12,13 +12,20 @@ module Relaton
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def render
|
|
15
|
+
text = uri
|
|
15
16
|
form = uri_form
|
|
17
|
+
# an access location cites its own label ("At: Library"); a
|
|
18
|
+
# uri carries the availability label
|
|
19
|
+
if @from_accesslocation
|
|
20
|
+
return "#{@i18n.label('at_url')} #{text}".strip
|
|
21
|
+
end
|
|
22
|
+
|
|
16
23
|
text = if form && !form.empty?
|
|
17
24
|
::Relaton::Render::Iso690::Template.new(form).evaluate(
|
|
18
|
-
"uri" => Template::Field[!
|
|
25
|
+
"uri" => Template::Field[!text.empty?, text],
|
|
19
26
|
)
|
|
20
27
|
else
|
|
21
|
-
|
|
28
|
+
text
|
|
22
29
|
end
|
|
23
30
|
|
|
24
31
|
label = @short ? "" : "#{@i18n.label('available_from')} "
|
|
@@ -44,6 +51,7 @@ module Relaton
|
|
|
44
51
|
from_accesslocation =
|
|
45
52
|
Array(@model.accesslocation).map(&:to_s).reject(&:empty?)
|
|
46
53
|
unless from_accesslocation.empty?
|
|
54
|
+
@from_accesslocation = true
|
|
47
55
|
return from_accesslocation.first.to_s
|
|
48
56
|
end
|
|
49
57
|
|
|
@@ -14,12 +14,7 @@ module Relaton
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def render
|
|
17
|
-
|
|
18
|
-
return text if (form = @style.templates.date_form).empty?
|
|
19
|
-
|
|
20
|
-
::Relaton::Render::Iso690::Template.new(form).evaluate(
|
|
21
|
-
"date" => Template::Field[!text.to_s.empty?, text.to_s],
|
|
22
|
-
)
|
|
17
|
+
range || year&.to_s
|
|
23
18
|
end
|
|
24
19
|
|
|
25
20
|
private
|
|
@@ -27,7 +22,9 @@ module Relaton
|
|
|
27
22
|
def publication_date
|
|
28
23
|
Array(@model.date).find(&:published) ||
|
|
29
24
|
Array(@model.date).find { |d| d.type.nil? } ||
|
|
30
|
-
Array(@model.date).find { |d| d.type == "created" }
|
|
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" }
|
|
31
28
|
end
|
|
32
29
|
|
|
33
30
|
def publication_date_value
|
|
@@ -39,7 +36,9 @@ module Relaton
|
|
|
39
36
|
d = publication_date or return nil
|
|
40
37
|
dash = @i18n.label("date_range")
|
|
41
38
|
if d.from && d.to
|
|
42
|
-
|
|
39
|
+
fy = d.from.to_date.year
|
|
40
|
+
ty = d.to.to_date.year
|
|
41
|
+
fy == ty ? fy.to_s : "#{fy}#{dash}#{ty}"
|
|
43
42
|
elsif d.from
|
|
44
43
|
"#{d.from.to_date.year}#{dash}"
|
|
45
44
|
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
|
|
@@ -22,6 +22,7 @@ module Relaton
|
|
|
22
22
|
autoload :Stddoc, "relaton/render/iso690/elements/stddoc"
|
|
23
23
|
autoload :Status, "relaton/render/iso690/elements/status"
|
|
24
24
|
autoload :Citeid, "relaton/render/iso690/elements/citeid"
|
|
25
|
+
autoload :Updated, "relaton/render/iso690/elements/updated"
|
|
25
26
|
|
|
26
27
|
CLASSES = {
|
|
27
28
|
creator: Creator,
|
|
@@ -41,11 +42,22 @@ module Relaton
|
|
|
41
42
|
stddoc: Stddoc,
|
|
42
43
|
status: Status,
|
|
43
44
|
citeid: Citeid,
|
|
45
|
+
updated: Updated,
|
|
44
46
|
}.freeze
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
class << self
|
|
49
|
+
# Flavors extend the vocabulary through the renderer's
|
|
50
|
+
# element map: a flavor slot resolves before the built-ins,
|
|
51
|
+
# scoped to that renderer alone (never process-wide)
|
|
52
|
+
def resolve(name, elements = {})
|
|
53
|
+
elements[name.to_sym] || CLASSES.fetch(name, nil)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.build(name, model, style:, i18n:, short: false,
|
|
58
|
+
elements: {})
|
|
59
|
+
klass = resolve(name, elements) or return nil
|
|
60
|
+
klass.new(model, style: style, i18n: i18n, short: short)
|
|
49
61
|
end
|
|
50
62
|
end
|
|
51
63
|
end
|
|
@@ -11,49 +11,99 @@ module Relaton
|
|
|
11
11
|
ELEMENT_SLOTS = %i[
|
|
12
12
|
creator title edition medium series production
|
|
13
13
|
date numeration component_part identifier location size
|
|
14
|
-
extent access stddoc status citeid
|
|
14
|
+
extent access stddoc status citeid updated
|
|
15
15
|
].freeze
|
|
16
16
|
|
|
17
17
|
def initialize(model, style:, i18n:, disambiguator: nil,
|
|
18
|
-
short: false)
|
|
18
|
+
short: false, elements: {})
|
|
19
19
|
@model = model
|
|
20
20
|
@style = style
|
|
21
21
|
@i18n = i18n
|
|
22
22
|
@disambiguator = disambiguator.to_s
|
|
23
23
|
@short = short
|
|
24
|
+
@elements = elements
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
def to_h
|
|
27
28
|
table = {}
|
|
28
|
-
|
|
29
|
+
slots.each do |slot|
|
|
29
30
|
key = slot.to_s.delete("_").downcase
|
|
30
31
|
table[key] = element_field(slot)
|
|
31
32
|
end
|
|
33
|
+
table["dategroup"] = dategroup_field
|
|
34
|
+
unless table["dategroup"].present?
|
|
35
|
+
table["dategroup"] = fallback_field(:dategroup)
|
|
36
|
+
end
|
|
32
37
|
table.merge(name_fields)
|
|
33
38
|
end
|
|
34
39
|
|
|
40
|
+
def fallback_field(slot)
|
|
41
|
+
fallback = @style.fallback_for(item_kind, slot)
|
|
42
|
+
fallback ? Template::Field[true, fallback] :
|
|
43
|
+
Template::Field[false, ""]
|
|
44
|
+
end
|
|
45
|
+
|
|
35
46
|
private
|
|
36
47
|
|
|
48
|
+
# The date with the updated date nested ("(2018 (updated
|
|
49
|
+
# November 2018))"); the bare date slot stays untouched
|
|
50
|
+
def dategroup_field
|
|
51
|
+
date = raw_field(:date)
|
|
52
|
+
updated = raw_field(:updated)
|
|
53
|
+
return Template::Field[false, ""] if !date.present? &&
|
|
54
|
+
!updated.present?
|
|
55
|
+
|
|
56
|
+
inner = updated.present? ? updated.text : date.text
|
|
57
|
+
inner = "#{date.text}#{updated.text}" if date.present? &&
|
|
58
|
+
updated.present?
|
|
59
|
+
Template::Field[true, "(#{inner})"]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def slots
|
|
65
|
+
ELEMENT_SLOTS | @elements.keys
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# The element's own render, before any absent-slot fallback:
|
|
69
|
+
# the creator-date group cites real dates only
|
|
70
|
+
def raw_field(slot)
|
|
71
|
+
element_field = build_field(slot)
|
|
72
|
+
return element_field if element_field.present?
|
|
73
|
+
|
|
74
|
+
Template::Field[false, ""]
|
|
75
|
+
end
|
|
76
|
+
|
|
37
77
|
def element_field(slot)
|
|
78
|
+
field = build_field(slot)
|
|
79
|
+
return field if field.present?
|
|
80
|
+
|
|
81
|
+
fallback = @style.fallback_for(item_kind, slot)
|
|
82
|
+
return Template::Field[true, fallback] if fallback
|
|
83
|
+
|
|
84
|
+
Template::Field[false, ""]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def build_field(slot)
|
|
38
88
|
element = Elements.build(slot, @model, style: @style,
|
|
39
|
-
i18n: @i18n, short: @short
|
|
89
|
+
i18n: @i18n, short: @short,
|
|
90
|
+
elements: @elements) or
|
|
91
|
+
return Template::Field[false, ""]
|
|
40
92
|
text = element.render.to_s
|
|
41
93
|
if element.present? && !text.empty?
|
|
42
94
|
return Template::Field[true, text]
|
|
43
95
|
end
|
|
44
96
|
|
|
45
|
-
fallback = @style.fallback_for(item_kind, slot)
|
|
46
|
-
return Template::Field[true, fallback] if fallback
|
|
47
|
-
|
|
48
97
|
Template::Field[false, ""]
|
|
49
98
|
end
|
|
50
99
|
|
|
51
100
|
def item_kind
|
|
52
|
-
|
|
101
|
+
@style.kind_for(@model.type)
|
|
53
102
|
end
|
|
54
103
|
|
|
55
104
|
def name_fields
|
|
56
|
-
creator = Elements.build(:creator, @model, style: @style,
|
|
105
|
+
creator = Elements.build(:creator, @model, style: @style,
|
|
106
|
+
i18n: @i18n, elements: @elements)
|
|
57
107
|
{
|
|
58
108
|
"surname" => Template::Field[creator.present?, creator.in_text],
|
|
59
109
|
"givennames" => Template::Field[!creator.principal_given.empty?,
|
|
@@ -16,19 +16,10 @@ module Relaton
|
|
|
16
16
|
"MRN" => ->(content) { content },
|
|
17
17
|
}.freeze
|
|
18
18
|
|
|
19
|
-
# Flavor-registered kinds, consulted before the built-ins; the
|
|
20
|
-
# registry is process-wide by design
|
|
21
|
-
OVERRIDES = {}
|
|
22
|
-
|
|
23
19
|
class << self
|
|
24
|
-
def register(type, kind)
|
|
25
|
-
OVERRIDES[type] = kind
|
|
26
|
-
kind
|
|
27
|
-
end
|
|
28
|
-
|
|
29
20
|
def render(docidentifier)
|
|
30
21
|
content = docidentifier.content.to_s
|
|
31
|
-
kind =
|
|
22
|
+
kind = KINDS[docidentifier.type]
|
|
32
23
|
kind ? kind.call(content) : content
|
|
33
24
|
end
|
|
34
25
|
end
|
|
@@ -23,7 +23,8 @@ module Relaton
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def initialize(style: DEFAULT_STYLE, lang: "en", script: "Latn",
|
|
26
|
-
labels: {})
|
|
26
|
+
labels: {}, elements: {})
|
|
27
|
+
@elements = elements
|
|
27
28
|
@style = Style.load(style)
|
|
28
29
|
@i18n = I18n.load(lang)
|
|
29
30
|
@i18n.overlay_hash!(labels) unless labels.empty?
|
|
@@ -34,10 +35,11 @@ module Relaton
|
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
def render(model)
|
|
37
|
-
kind =
|
|
38
|
+
kind = @style.kind_for(model.type)
|
|
38
39
|
template = @style.template_for(kind, home: home_docid?(model))
|
|
39
40
|
out = Template.new(template).evaluate(
|
|
40
|
-
Fields.new(model, style: @style, i18n: @i18n
|
|
41
|
+
Fields.new(model, style: @style, i18n: @i18n,
|
|
42
|
+
elements: @elements).to_h,
|
|
41
43
|
)
|
|
42
44
|
raise ::Relaton::Render::Unrenderable,
|
|
43
45
|
"no renderable elements" if out.strip.empty?
|
|
@@ -55,16 +57,17 @@ module Relaton
|
|
|
55
57
|
# A creator list alone (flavors' document-history name forms)
|
|
56
58
|
def render_creators(model)
|
|
57
59
|
Elements.build(:creator, model, style: @style, i18n: @i18n,
|
|
58
|
-
short: true).render
|
|
60
|
+
short: true, elements: @elements).render
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
# The short cite: the reference with the first-biblio marker
|
|
62
64
|
def render_short(model, delim)
|
|
63
|
-
kind =
|
|
65
|
+
kind = @style.kind_for(model.type)
|
|
64
66
|
template = @style.type_template_for(kind)&.short ||
|
|
65
67
|
@style.template_for(kind, home: home_docid?(model))
|
|
66
68
|
Template.new(template).evaluate_short(
|
|
67
|
-
Fields.new(model, style: @style, i18n: @i18n, short: true
|
|
69
|
+
Fields.new(model, style: @style, i18n: @i18n, short: true,
|
|
70
|
+
elements: @elements).to_h,
|
|
68
71
|
delim,
|
|
69
72
|
)
|
|
70
73
|
rescue ::Relaton::Render::Unrenderable
|
|
@@ -75,7 +78,8 @@ module Relaton
|
|
|
75
78
|
def citation(model, disambiguator: nil)
|
|
76
79
|
Template.new(@style.templates.citation).evaluate(
|
|
77
80
|
Fields.new(model, style: @style, i18n: @i18n,
|
|
78
|
-
disambiguator: disambiguator
|
|
81
|
+
disambiguator: disambiguator,
|
|
82
|
+
elements: @elements).to_h,
|
|
79
83
|
)
|
|
80
84
|
end
|
|
81
85
|
end
|
|
@@ -11,12 +11,14 @@ module Relaton
|
|
|
11
11
|
attribute :name_form, NameForm, default: -> { NameForm.new }
|
|
12
12
|
attribute :locale, Locale, default: -> { Locale.new }
|
|
13
13
|
attribute :home_docid_type, :string, collection: true
|
|
14
|
+
attribute :default_kind, :string, default: ""
|
|
14
15
|
|
|
15
16
|
key_value do
|
|
16
17
|
map "system", to: :system
|
|
17
18
|
map "nameForm", to: :name_form
|
|
18
19
|
map "locale", to: :locale
|
|
19
20
|
map "homeDocidType", to: :home_docid_type
|
|
21
|
+
map "defaultKind", to: :default_kind
|
|
20
22
|
end
|
|
21
23
|
end
|
|
22
24
|
end
|
|
@@ -17,7 +17,9 @@ module Relaton
|
|
|
17
17
|
attribute :uri, :string, default: ""
|
|
18
18
|
attribute :creators, :string, default: ""
|
|
19
19
|
attribute :uri_short, :string, default: ""
|
|
20
|
-
|
|
20
|
+
# "first": cite only the first identifier (1.x
|
|
21
|
+
# authoritative_identifier|first styles)
|
|
22
|
+
attribute :identifier_mode, :string, default: "all"
|
|
21
23
|
|
|
22
24
|
key_value do
|
|
23
25
|
map "titleOpen", to: :title_open
|
|
@@ -29,7 +31,7 @@ module Relaton
|
|
|
29
31
|
map "uri", to: :uri
|
|
30
32
|
map "creators", to: :creators
|
|
31
33
|
map "uriShort", to: :uri_short
|
|
32
|
-
map "
|
|
34
|
+
map "identifierMode", to: :identifier_mode
|
|
33
35
|
end
|
|
34
36
|
end
|
|
35
37
|
end
|
|
@@ -40,6 +40,16 @@ module Relaton
|
|
|
40
40
|
[SortRule.new(attribute: "creator"), SortRule.new(attribute: "date")]
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
# The ISO 690 kind of an item: the vocabulary's mapping, or
|
|
44
|
+
# the style's declared kind for types the vocabulary does not
|
|
45
|
+
# map (a flavor whose untyped items are not reports)
|
|
46
|
+
def kind_for(type)
|
|
47
|
+
return Kinds.kind_for(type) if Kinds.mapped?(type)
|
|
48
|
+
|
|
49
|
+
declared = scheme.default_kind.to_s
|
|
50
|
+
declared.empty? ? Kinds.kind_for(type) : declared
|
|
51
|
+
end
|
|
52
|
+
|
|
43
53
|
# Per-type template selection is a data lookup; unmatched types
|
|
44
54
|
# fall back to the general reference template. When a kind has
|
|
45
55
|
# home-flagged variants, the split is resolved by whether the item
|
|
@@ -17,7 +17,7 @@ module Relaton
|
|
|
17
17
|
Field = Struct.new(:present?, :text)
|
|
18
18
|
|
|
19
19
|
def initialize(source)
|
|
20
|
-
@
|
|
20
|
+
@chunks = []
|
|
21
21
|
@terminator = ""
|
|
22
22
|
scan(source.to_s)
|
|
23
23
|
end
|
|
@@ -27,41 +27,56 @@ module Relaton
|
|
|
27
27
|
# references (eref2linkshort) split there (the 1.x
|
|
28
28
|
# fmt-first-biblio-delim)
|
|
29
29
|
def evaluate_short(fields, delim)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
next unless field.present?
|
|
30
|
+
present = present_indices(fields)
|
|
31
|
+
return "" if present.empty?
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
33
|
+
out = +""
|
|
34
|
+
first_seen = false
|
|
35
|
+
@chunks.each_with_index do |(kind, value), i|
|
|
36
|
+
text =
|
|
37
|
+
case kind
|
|
38
|
+
when :slot
|
|
39
|
+
field = fields[normalise(value)]
|
|
40
|
+
next unless field&.present?
|
|
41
|
+
|
|
42
|
+
first_seen ? field.text : "#{field.text}#{delim} "
|
|
43
|
+
else
|
|
44
|
+
# the split marker precedes the component's trailing
|
|
45
|
+
# punctuation (1.x ret[0] += delim, then join), with
|
|
46
|
+
# the join's separating space
|
|
47
|
+
next unless bridged?(i, present)
|
|
48
|
+
|
|
49
|
+
value
|
|
50
|
+
end
|
|
51
|
+
first_seen = true if @chunks[i].first == :slot
|
|
52
|
+
out << text unless text.nil?
|
|
49
53
|
end
|
|
50
|
-
terminate(
|
|
54
|
+
terminate(out).rstrip
|
|
51
55
|
end
|
|
52
56
|
|
|
57
|
+
# 1.x segment-join semantics: present elements joined with the
|
|
58
|
+
# template's separators — a literal renders only when it
|
|
59
|
+
# separates (a run of absent elements between) two present
|
|
60
|
+
# elements; leading and trailing literals drop with the
|
|
61
|
+
# elements they would have attached to
|
|
53
62
|
def evaluate(fields)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
next unless field.present?
|
|
63
|
+
present = present_indices(fields)
|
|
64
|
+
return "" if present.empty?
|
|
57
65
|
|
|
58
|
-
|
|
66
|
+
out = +"".dup
|
|
67
|
+
@chunks.each_with_index do |(kind, value), i|
|
|
68
|
+
if kind == :slot
|
|
69
|
+
field = fields[normalise(value)]
|
|
70
|
+
out << field.text if field&.present?
|
|
71
|
+
elsif bridged?(i, present) ||
|
|
72
|
+
(value.include?("<") && present.any? { |j| j < i })
|
|
73
|
+
# markup attaches to the element it closes
|
|
74
|
+
out << value
|
|
75
|
+
elsif close_pending_paren?(out, value)
|
|
76
|
+
out << value
|
|
77
|
+
end
|
|
59
78
|
end
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
terminate(rendered.each_with_index.map do |(head, text, tail), i|
|
|
63
|
-
i == rendered.size - 1 ? "#{head}#{text}" : "#{head}#{text}#{tail}"
|
|
64
|
-
end.join).rstrip
|
|
79
|
+
terminate(out).rstrip
|
|
65
80
|
end
|
|
66
81
|
|
|
67
82
|
private
|
|
@@ -69,31 +84,74 @@ module Relaton
|
|
|
69
84
|
def scan(source)
|
|
70
85
|
rest = source
|
|
71
86
|
while (slot = SLOT.match(rest))
|
|
72
|
-
|
|
73
|
-
|
|
87
|
+
@chunks << [:text, rest[0...slot.begin(0)]] if
|
|
88
|
+
slot.begin(0).positive?
|
|
89
|
+
@chunks << [:slot, slot[1]]
|
|
74
90
|
rest = rest[slot.end(0)..]
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
end
|
|
92
|
+
@terminator = rest
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def present_indices(fields)
|
|
96
|
+
@chunks.each_index.select do |i|
|
|
97
|
+
kind, value = @chunks[i]
|
|
98
|
+
next false unless kind == :slot
|
|
99
|
+
|
|
100
|
+
field = fields[normalise(value)]
|
|
101
|
+
!field.nil? && field.present?
|
|
84
102
|
end
|
|
85
103
|
end
|
|
86
104
|
|
|
105
|
+
# A literal bridges two present elements when at least one
|
|
106
|
+
# present element stands on each side of it; a leading literal
|
|
107
|
+
# (no slot precedes it) attaches to the first slot, emitting
|
|
108
|
+
# only when that slot is present
|
|
109
|
+
def bridged?(index, present)
|
|
110
|
+
first_slot = @chunks.index { |(kind, _)| kind == :slot }
|
|
111
|
+
return present.include?(first_slot) if index < first_slot
|
|
112
|
+
|
|
113
|
+
present.any? { |i| i < index } && present.any? { |i| i > index }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# A template's paired literal ("({{production}})") closes even
|
|
117
|
+
# when nothing follows the present element it wraps
|
|
118
|
+
def close_pending_paren?(out, value)
|
|
119
|
+
value.start_with?(")") && out.count("(") > out.count(")")
|
|
120
|
+
end
|
|
121
|
+
|
|
87
122
|
def terminate(body)
|
|
88
|
-
# 1.x punctuation cleanup:
|
|
89
|
-
#
|
|
90
|
-
#
|
|
123
|
+
# 1.x punctuation cleanup: a sentence period before a spaced
|
|
124
|
+
# comma yields to the comma (", 2013" over ". , 2013"; an
|
|
125
|
+
# initials period before a comma ("P.,") stands, being
|
|
126
|
+
# unspaced), and stacked separators around absent elements
|
|
127
|
+
# collapse into one (". : ." into ". "); an empty pair left
|
|
128
|
+
# by an absent element drops
|
|
129
|
+
body = body.gsub(/:\s*\.\s*/, ": ")
|
|
130
|
+
body = body.gsub(/\.\s+,/, ",")
|
|
131
|
+
body = body.gsub(/(?:[.,:;]\s+)+[.,:;]/, ". ")
|
|
132
|
+
body = body.gsub(/\.\s*\./, ". ")
|
|
91
133
|
body = body.gsub(/:\s*\.\s*/, ": ")
|
|
134
|
+
body = body.gsub(/\(\s*\)/, "")
|
|
92
135
|
body = body.gsub(/ +/, " ").gsub(" ,", ",")
|
|
93
|
-
|
|
94
|
-
return body if
|
|
136
|
+
terminator = unbalanced_close(body, @terminator)
|
|
137
|
+
return body if terminator.strip.empty?
|
|
138
|
+
if terminator !~ /[<\w]/ && body.rstrip.end_with?(terminator.strip)
|
|
139
|
+
return body
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
"#{body}#{terminator}"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# The terminator's closing parens stand only when the body
|
|
146
|
+
# opened them (their "(" died with an absent element)
|
|
147
|
+
def unbalanced_close(body, terminator)
|
|
148
|
+
return terminator unless terminator.start_with?(")")
|
|
149
|
+
|
|
150
|
+
open = body.count("(") - body.count(")")
|
|
151
|
+
return terminator.sub(/\A\)+/) { |closes| closes[0, open] } if
|
|
152
|
+
open.positive?
|
|
95
153
|
|
|
96
|
-
|
|
154
|
+
terminator.sub(/\A\)+/, "")
|
|
97
155
|
end
|
|
98
156
|
|
|
99
157
|
def normalise(slot)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: relaton-render
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.0.pre.alpha.
|
|
4
|
+
version: 3.0.0.pre.alpha.12
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: htmlentities
|
|
@@ -137,6 +137,7 @@ files:
|
|
|
137
137
|
- lib/relaton/render/iso690/elements/status.rb
|
|
138
138
|
- lib/relaton/render/iso690/elements/stddoc.rb
|
|
139
139
|
- lib/relaton/render/iso690/elements/title.rb
|
|
140
|
+
- lib/relaton/render/iso690/elements/updated.rb
|
|
140
141
|
- lib/relaton/render/iso690/fields.rb
|
|
141
142
|
- lib/relaton/render/iso690/i18n.rb
|
|
142
143
|
- lib/relaton/render/iso690/i18n/en.yml
|