relaton-render 1.4.0.pre.alpha.3 → 3.0.0.pre.alpha.1
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/fields/date.rb +48 -0
- data/lib/relaton/render/fields/fields.rb +266 -0
- data/lib/relaton/render/general.rb +40 -1
- data/lib/relaton/render/legacy/citations.rb +178 -0
- data/lib/relaton/render/parse/parse.rb +152 -0
- data/lib/relaton/render/parse/parse_contributors.rb +184 -0
- data/lib/relaton/render/parse/parse_extract.rb +200 -0
- data/lib/relaton/render/parse/parse_id.rb +128 -0
- data/lib/relaton/render/template/hash_utils.rb +64 -0
- data/lib/relaton/render/template/liquid.rb +48 -0
- data/lib/relaton/render/template/subclasses.rb +123 -0
- data/lib/relaton/render/template/template.rb +230 -0
- data/lib/relaton/render/version.rb +1 -1
- data/lib/relaton-render.rb +14 -0
- data/relaton-render.gemspec +5 -0
- metadata +83 -12
- data/lib/isodoc/i18n.rb +0 -34
- data/lib/isodoc-yaml/i18n-ar.yaml +0 -62
- data/lib/isodoc-yaml/i18n-de.yaml +0 -61
- data/lib/isodoc-yaml/i18n-en.yaml +0 -57
- data/lib/isodoc-yaml/i18n-es.yaml +0 -65
- data/lib/isodoc-yaml/i18n-fr.yaml +0 -66
- data/lib/isodoc-yaml/i18n-ja.yaml +0 -58
- data/lib/isodoc-yaml/i18n-ru.yaml +0 -69
- data/lib/isodoc-yaml/i18n-zh-Hans.yaml +0 -59
- data/lib/isodoc-yaml/i18n-zh-Hant.yaml +0 -58
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
require "nokogiri"
|
|
2
|
+
require "twitter_cldr"
|
|
3
|
+
require_relative "parse_contributors"
|
|
4
|
+
require_relative "parse_extract"
|
|
5
|
+
require_relative "parse_id"
|
|
6
|
+
|
|
7
|
+
module Relaton
|
|
8
|
+
module Render
|
|
9
|
+
class Parse
|
|
10
|
+
def initialize(options)
|
|
11
|
+
@lang = options[:lang] || "en"
|
|
12
|
+
@script = options[:script] || "Latn"
|
|
13
|
+
@i18n = options[:i18n]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def extract(doc)
|
|
17
|
+
host = host(doc)
|
|
18
|
+
ret = simple_xml2hash(doc).merge(simple_or_host_xml2hash(doc, host))
|
|
19
|
+
.merge(host_xml2hash(host))
|
|
20
|
+
.merge(series_xml2hash(doc, host))
|
|
21
|
+
ret
|
|
22
|
+
#i18n(ret)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def simple_xml2hash(doc)
|
|
26
|
+
creators, role = creatornames(doc)
|
|
27
|
+
{ type: type(doc), title: title(doc), extent_raw: extent(doc),
|
|
28
|
+
size_raw: size(doc), uri_raw: uri(doc), doi: doi(doc),
|
|
29
|
+
authoritative_identifier: authoritative_identifier(doc),
|
|
30
|
+
other_identifier_raw: other_identifier(doc), biblio_tag: biblio_tag(doc),
|
|
31
|
+
status_raw: status(doc), creators: creators, role_raw: role,
|
|
32
|
+
language: language(doc), script: script(doc), locale: locale(doc) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def simple_or_host_xml2hash(doc, host)
|
|
36
|
+
{ edition_raw: edition(doc, host), edition_num: edition_num(doc, host),
|
|
37
|
+
medium_raw: medium(doc, host), draft_raw: draft(doc, host),
|
|
38
|
+
place_raw: place(doc, host), publisher_raw: publisher(doc, host),
|
|
39
|
+
publisher_abbrev_raw: publisher_abbrev(doc, host),
|
|
40
|
+
authorizer_raw: authorizer(doc, host),
|
|
41
|
+
distributor_raw: distributor(doc, host),
|
|
42
|
+
access_location: access_location(doc, host),
|
|
43
|
+
date: date(doc, host), date_updated: date_updated(doc, host),
|
|
44
|
+
date_accessed: date_accessed(doc, host) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def host_xml2hash(host)
|
|
48
|
+
creators, role = creatornames(host)
|
|
49
|
+
{ host_creators: creators, host_role_raw: role,
|
|
50
|
+
host_title: title(host) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def series_xml2hash(doc, host)
|
|
54
|
+
series = series(doc)
|
|
55
|
+
host and series ||= series(host)
|
|
56
|
+
series_xml2hash1(series, doc)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def series_xml2hash1(series, doc)
|
|
60
|
+
series or return {}
|
|
61
|
+
ret = { series_formatted: series_formatted(series, doc),
|
|
62
|
+
series_title: series_title(series, doc),
|
|
63
|
+
series_abbr: series_abbr(series, doc),
|
|
64
|
+
series_run: series_run(series, doc),
|
|
65
|
+
series_num: series_num(series, doc),
|
|
66
|
+
series_partnumber: series_partnumber(series, doc),
|
|
67
|
+
series_place: series_place(series, doc),
|
|
68
|
+
series_org: series_org(series, doc),
|
|
69
|
+
series_dates: series_dates(series, doc) }
|
|
70
|
+
ret.each do |k, v|
|
|
71
|
+
%i(series_num series_dates).include?(k) and next
|
|
72
|
+
ret[k] = esc(v)
|
|
73
|
+
end
|
|
74
|
+
ret
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
#KILL
|
|
78
|
+
def other_identifier_i18n(hash)
|
|
79
|
+
#ret << "#{type}: #{esc id.id}"
|
|
80
|
+
hash[:other_identifier]&.map! do |i|
|
|
81
|
+
i.include?("<esc>") ? @i18n.select(hash).l10n(i) : i
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
ALLOWED_INLINE_TAGS = %w[
|
|
86
|
+
em eref strong stem sub sup tt underline keyword ruby
|
|
87
|
+
strike smallcap xref br link hr pagebreak bookmark
|
|
88
|
+
image index index-xref concept add del span erefstack
|
|
89
|
+
date fn
|
|
90
|
+
].freeze
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def blank?(text)
|
|
95
|
+
text.nil? || text.empty?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def esc(text)
|
|
99
|
+
blank?(text) and return text
|
|
100
|
+
"<esc>#{text}</esc>"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Strip any tag not in ALLOWED_INLINE_TAGS, in both literal (<tag>) and
|
|
104
|
+
# entity-encoded (<tag>) forms. Used to defend Liquid template
|
|
105
|
+
# output against embedded structural markup (most commonly <title>) in
|
|
106
|
+
# bibliographic text fields, where relaton-bib may surface either form
|
|
107
|
+
# depending on lutaml-model serialisation behaviour.
|
|
108
|
+
#
|
|
109
|
+
# Authors embed inline markup either literally (<em>foo</em>) or
|
|
110
|
+
# entity-encoded (<em>foo</em>); both are intended as
|
|
111
|
+
# markup, and downstream template rendering decodes entities anyway
|
|
112
|
+
# (see HTMLEntities.decode in template.rb), so we normalise the
|
|
113
|
+
# encoded form to literal up front and let a single Nokogiri-fragment
|
|
114
|
+
# walk handle both. Elements whose name is in the allow-list are
|
|
115
|
+
# preserved verbatim with their entire subtree, so wrappers that
|
|
116
|
+
# legitimately carry block content (notably <fn><p>…</p></fn>)
|
|
117
|
+
# round-trip intact rather than being shredded one tag at a time.
|
|
118
|
+
# Disallowed elements are unwrapped (tag dropped, children kept),
|
|
119
|
+
# recursing first so allowed wrappers nested inside disallowed
|
|
120
|
+
# wrappers still survive. Nokogiri re-escapes any genuine `<` left in
|
|
121
|
+
# text positions on serialisation, so plain prose like "5 < 10" is
|
|
122
|
+
# preserved correctly.
|
|
123
|
+
def sanitise_inline_markup(str)
|
|
124
|
+
blank?(str) and return str
|
|
125
|
+
normalised = str.gsub("<", "<").gsub(">", ">")
|
|
126
|
+
frag = Nokogiri::XML.fragment(normalised)
|
|
127
|
+
unwrap_disallowed(frag)
|
|
128
|
+
save_opts = Nokogiri::XML::Node::SaveOptions::AS_XML |
|
|
129
|
+
Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
|
|
130
|
+
frag.to_xml(encoding: "UTF-8", save_with: save_opts)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def unwrap_disallowed(node)
|
|
134
|
+
node.children.to_a.each do |child|
|
|
135
|
+
next unless child.element?
|
|
136
|
+
next if ALLOWED_INLINE_TAGS.include?(child.name)
|
|
137
|
+
|
|
138
|
+
unwrap_disallowed(child)
|
|
139
|
+
child.replace(child.children)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def wrap_in_esc(obj)
|
|
144
|
+
case obj
|
|
145
|
+
when String then esc(obj)
|
|
146
|
+
when Array then obj.map { |e| wrap_in_esc(e) }
|
|
147
|
+
else obj
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
module Relaton
|
|
2
|
+
module Render
|
|
3
|
+
class Parse
|
|
4
|
+
def content(node)
|
|
5
|
+
node.nil? and return node
|
|
6
|
+
node.is_a?(String) and
|
|
7
|
+
return sanitise_inline_markup(node.strip)
|
|
8
|
+
.gsub(/>\n\s*</, "><").gsub(/\n\s*/, " ")
|
|
9
|
+
node.content.is_a?(Array) and return node.content.map { |x| content(x) }
|
|
10
|
+
ret = sanitise_inline_markup(node.content.strip)
|
|
11
|
+
# safeguard against indented XML
|
|
12
|
+
ret.gsub(/>\n\s*</, "><").gsub(/\n\s*/, " ")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def extract_orgname(org)
|
|
16
|
+
content(org.name&.first)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def extract_personname(person)
|
|
20
|
+
surname = person.name.surname || person.name.completename
|
|
21
|
+
given, middle, initials = given_and_middle_name(person)
|
|
22
|
+
{ surname: wrap_in_esc(content(surname)),
|
|
23
|
+
given: given,
|
|
24
|
+
middle: middle,
|
|
25
|
+
initials: initials }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def given_and_middle_name(person)
|
|
29
|
+
forenames = forenames_parse(person)
|
|
30
|
+
initials = extract_initials(person)
|
|
31
|
+
forenames.empty? and initials.empty? and return [nil, nil, nil]
|
|
32
|
+
initials.empty? and initials = initials_from_forenames(forenames)
|
|
33
|
+
[forenames.first, forenames[1..-1],
|
|
34
|
+
wrap_in_esc(Array(initials))]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def extract_initials(person)
|
|
38
|
+
initials = content(person.name.formatted_initials)&.sub(/(.)\.?$/, "\\1.")
|
|
39
|
+
&.split /(?<=\.) /
|
|
40
|
+
initials ||= person.name.forename.map(&:initial)
|
|
41
|
+
.compact.map { |x| x.sub(/(.)\.?$/, "\\1.") }
|
|
42
|
+
initials
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def forenames_parse(person)
|
|
46
|
+
person.name.forename.map do |x|
|
|
47
|
+
x.content.nil? || x.content.empty? ? esc("#{x.initial}.") : content(x)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# de S. => one initial, M.-J. => one initial
|
|
52
|
+
def initials_parse(person)
|
|
53
|
+
i = content(person.name.formatted_initials) or
|
|
54
|
+
return person.name.forename.map(&:initial)
|
|
55
|
+
.compact.map { |x| x.sub(/(.)\.?$/, "\\1.") }
|
|
56
|
+
|
|
57
|
+
i.sub(/(.)\.?$/, "\\1.")
|
|
58
|
+
.scan(/.+?\.(?=(?:$|\s|\p{Alpha}))/).map(&:strip)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def initials_from_forenames(forenames)
|
|
62
|
+
forenames.map(&:split).flatten.map { |x| "#{x[0]}." }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def extractname(contributor)
|
|
66
|
+
org = contributor.organization
|
|
67
|
+
person = contributor.person
|
|
68
|
+
return { nonpersonal: extract_orgname(org) } if org
|
|
69
|
+
return extract_personname(person) if person
|
|
70
|
+
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def contributor_role(contributors)
|
|
75
|
+
contributors.length.positive? or return nil
|
|
76
|
+
role = contributors[0].role.first
|
|
77
|
+
desc = Array(role&.description).map(&:content).join("\n")
|
|
78
|
+
type = role&.type
|
|
79
|
+
desc.empty? ? type : desc
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def creatornames(doc)
|
|
83
|
+
cr = creatornames1(doc)
|
|
84
|
+
cr.empty? and return [nil, nil]
|
|
85
|
+
[cr.map { |x| extractname(x) }, contributor_role(cr)]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def creatornames_roles_allowed
|
|
89
|
+
%w(author performer adapter translator editor distributor authorizer)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def creatornames1(doc)
|
|
93
|
+
cr = []
|
|
94
|
+
doc.nil? and return []
|
|
95
|
+
creatornames_roles_allowed.each do |r|
|
|
96
|
+
add = pick_contributor(doc, r)
|
|
97
|
+
add.nil? and next
|
|
98
|
+
cr = add and break
|
|
99
|
+
end
|
|
100
|
+
cr.nil? and cr = Array(doc.contributor)
|
|
101
|
+
cr
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def datepick(date)
|
|
105
|
+
date.nil? and return nil
|
|
106
|
+
at = date.at
|
|
107
|
+
from = date.from
|
|
108
|
+
to = date.to
|
|
109
|
+
at and return { on: at.to_s }
|
|
110
|
+
from and return { from: from.to_s, to: to&.to_s }
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def date1(date)
|
|
115
|
+
%w(published issued circulated).each do |t|
|
|
116
|
+
ret = date.detect { |x| x.type == t } and
|
|
117
|
+
return ret
|
|
118
|
+
end
|
|
119
|
+
date.reject { |x| x.type == "accessed" }.first
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# year-only
|
|
123
|
+
def date(doc, host)
|
|
124
|
+
ret = date1(Array(doc.date))
|
|
125
|
+
host and ret ||= date1(Array(host.date))
|
|
126
|
+
datepick(ret)&.transform_values do |v|
|
|
127
|
+
v&.sub(/-.*$/, "")
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def date_updated(doc, host)
|
|
132
|
+
ret = Array(doc.date).detect { |x| x.type == "updated" }
|
|
133
|
+
host and ret ||= Array(host.date).detect { |x| x.type == "updated" }
|
|
134
|
+
datepick(ret)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def date_accessed(doc, host)
|
|
138
|
+
ret = Array(doc.date).detect { |x| x.type == "accessed" }
|
|
139
|
+
host and ret ||= Array(host.date).detect { |x| x.type == "accessed" }
|
|
140
|
+
datepick(ret)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def publisher(doc, host)
|
|
144
|
+
x = pick_contributor(doc, "publisher")
|
|
145
|
+
host and x ||= pick_contributor(host, "publisher")
|
|
146
|
+
x.nil? and return nil
|
|
147
|
+
x.map { |c| extractname(c) }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def publisher_abbrev(doc, host)
|
|
151
|
+
x = pick_contributor(doc, "publisher")
|
|
152
|
+
host and x ||= pick_contributor(host, "publisher")
|
|
153
|
+
x.nil? and return nil
|
|
154
|
+
x.map do |c|
|
|
155
|
+
content(c.organization.abbreviation) ||
|
|
156
|
+
content(c.organization.name.first)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def distributor(doc, host)
|
|
161
|
+
x = pick_contributor(doc, "distributor")
|
|
162
|
+
host and x ||= pick_contributor(host, "distributor")
|
|
163
|
+
x.nil? and return nil
|
|
164
|
+
x.map { |c| extractname(c) }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def authorizer(doc, host)
|
|
168
|
+
x = pick_contributor(doc, "authorizer") ||
|
|
169
|
+
pick_contributor(doc, "publisher")
|
|
170
|
+
host and x ||= pick_contributor(host, "authorizer") ||
|
|
171
|
+
pick_contributor(host, "publisher")
|
|
172
|
+
x.nil? and return nil
|
|
173
|
+
x.map { |c| extractname(c) }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def pick_contributor(doc, role)
|
|
177
|
+
ret = Array(doc.contributor).select do |c|
|
|
178
|
+
Array(c.role).any? { |r| r.type == role }
|
|
179
|
+
end
|
|
180
|
+
ret.empty? ? nil : ret
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
module Relaton
|
|
2
|
+
module Render
|
|
3
|
+
class Parse
|
|
4
|
+
def host(doc)
|
|
5
|
+
doc.relation&.detect { |r| r.type == "includedIn" }&.bibitem
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# TODO : first is naive choice
|
|
9
|
+
def title(doc)
|
|
10
|
+
doc.nil? || doc.title.nil? || doc.title.empty? and return nil
|
|
11
|
+
t = Array(doc.title).select { |x| x.language == @lang }
|
|
12
|
+
t.empty? and t = Array(doc.title)
|
|
13
|
+
t1 = t.select { |x| x.type == "main" }
|
|
14
|
+
t1.empty? and t1 = t
|
|
15
|
+
t1.first or return
|
|
16
|
+
esc(content(t1.first))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def medium(doc, host)
|
|
20
|
+
x = doc.medium || host&.medium or return nil
|
|
21
|
+
%w(content genre form carrier size scale).each_with_object({}) do |i, m|
|
|
22
|
+
m[i] = x.send i
|
|
23
|
+
end.compact
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def size(doc)
|
|
27
|
+
x = doc.size or return nil
|
|
28
|
+
x.value.each_with_object({}) do |v, m|
|
|
29
|
+
m[v.type] ||= []
|
|
30
|
+
m[v.type] << v.content
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def edition(doc, host)
|
|
35
|
+
ret = content(doc.edition || host&.edition)
|
|
36
|
+
ret &&= esc(ret)
|
|
37
|
+
ret
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def edition_num(doc, host)
|
|
41
|
+
doc.edition&.number || host&.edition&.number
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def place(doc, host)
|
|
45
|
+
x = Array(doc.place)
|
|
46
|
+
x.empty? && host and x = Array(host.place)
|
|
47
|
+
x.empty? and return x
|
|
48
|
+
x.map { |p| place1(p) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def place1(place)
|
|
52
|
+
c = place.city
|
|
53
|
+
r = place.region
|
|
54
|
+
n = place.country
|
|
55
|
+
c.nil? && r.empty? && n.empty? and return place.formatted_place
|
|
56
|
+
[c, *r.map(&:content), *n.map(&:content)].compact.join(", ")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def series(doc)
|
|
60
|
+
doc.series&.detect { |s| s.type == "main" } ||
|
|
61
|
+
doc.series&.detect { |s| s.type.nil? } ||
|
|
62
|
+
doc.series&.first
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def series_title(series, _doc)
|
|
66
|
+
series.nil? and return nil
|
|
67
|
+
t = Array(series.title).select { |x| x.language == @lang }
|
|
68
|
+
t.empty? and t = Array(series.title)
|
|
69
|
+
t1 = t.select { |x| x.type == "main" }
|
|
70
|
+
t1.empty? and t1 = t
|
|
71
|
+
t1.first.nil? and return nil
|
|
72
|
+
content(t1.first)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def series_formatted(series, _doc)
|
|
76
|
+
content(series.formattedref)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def series_abbr(series, _doc)
|
|
80
|
+
content(series.abbreviation)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def series_num(series, _doc)
|
|
84
|
+
series.number&.strip
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def series_partnumber(series, _doc)
|
|
88
|
+
series.partnumber
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def series_run(series, _doc)
|
|
92
|
+
series.run
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def series_org(series, _doc)
|
|
96
|
+
series.organization
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def series_place(series, _doc)
|
|
100
|
+
p = series.place or return nil
|
|
101
|
+
place1(p)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def series_dates(series, _doc)
|
|
105
|
+
f = series.from
|
|
106
|
+
t = series.to
|
|
107
|
+
f || t or return nil
|
|
108
|
+
"#{f}–#{t}"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def access_location(doc, host)
|
|
112
|
+
x = doc.accesslocation || host&.accesslocation or
|
|
113
|
+
return nil
|
|
114
|
+
Array(x).first
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def included(type)
|
|
118
|
+
["article", "inbook", "incollection", "inproceedings"].include? type
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def type(doc)
|
|
122
|
+
type = doc.type and return type
|
|
123
|
+
doc.relation&.any? { |r| r.type == "includedIn" } and return "inbook"
|
|
124
|
+
"book"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def language(doc)
|
|
128
|
+
doc.language&.first || @lang
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def script(doc)
|
|
132
|
+
doc.script&.first || @script
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def locale(doc)
|
|
136
|
+
# TODO not yet implemented in relaton-bib
|
|
137
|
+
# doc.locale&.first
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def extent1(localities)
|
|
141
|
+
localities.each_with_object({}) do |l, ret|
|
|
142
|
+
ret[(l.type || "page").to_sym] = {
|
|
143
|
+
from: localized_string_or_text(l.reference_from),
|
|
144
|
+
to: localized_string_or_text(l.reference_to),
|
|
145
|
+
}
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def localized_string_or_text(str)
|
|
150
|
+
case str
|
|
151
|
+
when Relaton::Bib::LocalizedString then content(str)
|
|
152
|
+
when String then str
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def extent(doc)
|
|
157
|
+
Array(doc.extent).each_with_object([]) do |e, acc|
|
|
158
|
+
case e
|
|
159
|
+
when Relaton::Bib::Extent, Relaton::Bib::LocalityStack
|
|
160
|
+
if e.locality.any?
|
|
161
|
+
a = e.locality.each_with_object([]) do |e1, m|
|
|
162
|
+
if e1.is_a?(Relaton::Bib::LocalityStack)
|
|
163
|
+
m << extent1(e1.locality)
|
|
164
|
+
else
|
|
165
|
+
m.empty? and m << {}
|
|
166
|
+
m[-1].merge!(extent1(Array(e1)))
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
acc << a
|
|
170
|
+
else
|
|
171
|
+
Array(e.locality_stack).each do |stack|
|
|
172
|
+
a = stack.locality.each_with_object([{}]) do |e1, m|
|
|
173
|
+
m[-1].merge!(extent1(Array(e1)))
|
|
174
|
+
end
|
|
175
|
+
acc << a
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
when Relaton::Bib::Locality
|
|
179
|
+
acc << extent1(Array(e))
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def draft(doc, host)
|
|
185
|
+
{ iteration: iter_ordinal(doc) || iter_ordinal(host),
|
|
186
|
+
status: status(doc) || status(host) }
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def iter_ordinal(doc)
|
|
190
|
+
iter = doc&.status&.iteration or return nil
|
|
191
|
+
iter
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def status(doc)
|
|
195
|
+
v = doc&.status&.stage&.content
|
|
196
|
+
#@i18n.get.dig("stage", v) || v
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
module Relaton
|
|
2
|
+
module Render
|
|
3
|
+
class Parse
|
|
4
|
+
# filter applied across full list of auth_id
|
|
5
|
+
def auth_id_filter(ids = [])
|
|
6
|
+
id_scope_filter(ids)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def id_scope_filter(ids)
|
|
10
|
+
ids.detect(&:scope) or return ids
|
|
11
|
+
id_sort_by_scope(ids).each_with_object([]) do |(k, v), m|
|
|
12
|
+
case k
|
|
13
|
+
when "IEEE"
|
|
14
|
+
f = id_extract_by_scope(v, "trademark")
|
|
15
|
+
f.empty? and f = id_extract_by_scope(v, nil)
|
|
16
|
+
m << f
|
|
17
|
+
else m << id_extract_by_scope(v, nil)
|
|
18
|
+
end
|
|
19
|
+
end.flatten
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def id_sort_by_scope(ids)
|
|
23
|
+
ids.each_with_object({}) do |i, m|
|
|
24
|
+
m[i.type] ||= {}
|
|
25
|
+
m[i.type][i.scope] ||= []
|
|
26
|
+
m[i.type][i.scope] << i
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def id_extract_by_scope(idents, key)
|
|
31
|
+
if idents.key?(key)
|
|
32
|
+
idents[key]
|
|
33
|
+
else []
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# list of successive filters on individual auth_id instances
|
|
38
|
+
def auth_id_allow
|
|
39
|
+
[->(x) { x.language == @lang && x.primary },
|
|
40
|
+
->(x) { x.primary },
|
|
41
|
+
->(x) { x.language == @lang },
|
|
42
|
+
->(_x) { true }]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def authoritative_identifier(doc)
|
|
46
|
+
out = []
|
|
47
|
+
[auth_id_filter(Array(doc.docidentifier)), Array(doc.docidentifier)].each do |a|
|
|
48
|
+
out = authoritative_identifier_select(a)
|
|
49
|
+
out.empty? or break
|
|
50
|
+
end
|
|
51
|
+
# prevent l10n of identifier contents
|
|
52
|
+
out.map(&:content).map { |i| esc(i.strip) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def authoritative_identifier_select(idents)
|
|
56
|
+
out = []
|
|
57
|
+
auth_id_allow.each do |p|
|
|
58
|
+
out = idents.select do |x|
|
|
59
|
+
p.call(x) &&
|
|
60
|
+
!authoritative_identifier_exclude.include?(id_type_norm(x))
|
|
61
|
+
end
|
|
62
|
+
out.empty? or break
|
|
63
|
+
end
|
|
64
|
+
out
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def authoritative_identifier_exclude
|
|
68
|
+
%w(METANORMA METANORMA-ORDINAL AUTHOR-DATE TITLE URN ISO-REFERENCE) +
|
|
69
|
+
other_identifier_include
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def other_identifier(doc)
|
|
73
|
+
Array(doc.docidentifier).each_with_object([]) do |id, ret|
|
|
74
|
+
type = id_type_norm(id)
|
|
75
|
+
other_identifier_include.include? type or next
|
|
76
|
+
ret << [type, id.content]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def other_identifier_include
|
|
81
|
+
%w(ISSN ISBN DOI)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def doi(doc)
|
|
85
|
+
out = Array(doc.docidentifier).each_with_object([]) do |id, ret|
|
|
86
|
+
type = id.type&.sub(/^(DOI)\..*$/i, "\\1") or next
|
|
87
|
+
type.casecmp("doi").zero? or next
|
|
88
|
+
ret << id.content
|
|
89
|
+
end
|
|
90
|
+
out.empty? ? nil : out.map { |i| esc(i.strip) }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def id_type_norm(id)
|
|
94
|
+
id.type or return nil
|
|
95
|
+
m = /^(ISBN|ISSN)\./i.match(id.type) or return id.type.upcase
|
|
96
|
+
m[1].upcase
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def biblio_tag(doc)
|
|
100
|
+
ret = Array(doc.docidentifier).detect do |id|
|
|
101
|
+
id.scope&.downcase == "biblio-tag"
|
|
102
|
+
end
|
|
103
|
+
ret&.content
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def uri(doc)
|
|
107
|
+
uri = nil
|
|
108
|
+
%w(citation uri src).each do |t|
|
|
109
|
+
uri = uri_type_select(doc, t) and break
|
|
110
|
+
end
|
|
111
|
+
uri ||= Array(doc.source).detect do |u|
|
|
112
|
+
u.language == @lang && !u.type&.casecmp("doi")&.zero?
|
|
113
|
+
end
|
|
114
|
+
uri ||= Array(doc.source).detect { |u| !u.type&.casecmp("doi")&.zero? }
|
|
115
|
+
uri or return nil
|
|
116
|
+
uri.content.to_s.strip
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def uri_type_select(doc, type)
|
|
120
|
+
uri = Array(doc.source).detect do |u|
|
|
121
|
+
u.type&.downcase == type && u.language == @lang
|
|
122
|
+
end and return uri
|
|
123
|
+
uri = Array(doc.source).detect { |u| u.type&.downcase == type } and return uri
|
|
124
|
+
nil
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|