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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f944c1a5f1a26e1ec5c4d1cceae37ea7420257034c87b31eaa6ea0c8e46269e6
4
- data.tar.gz: 5bcd504fbf58c890f778d4f492d70bbabe9b8a974837c838752fbf367129c67c
3
+ metadata.gz: f301bc95c707486b2546c988eb50acef801327df7ec89b53f319045fba834e25
4
+ data.tar.gz: 0c16ff123fddd608086af6d9a4b3577bfe0f828df4304be9e0a031e4afff86e2
5
5
  SHA512:
6
- metadata.gz: 0032e8464f6d1731e4aef47a69dfca0e646df0d6c52d85dfa16b72194357f5a5b01f513ffe65498b08e5b6ae4f3e83ef8f6819c6e87e6d1b5a361787a1ce7495
7
- data.tar.gz: ab3af5ef2028428f712fca4727fe126cf56cccfb33fac0d523190536b237616d0207d2363aa2f422fc48e2c3fa78e4a9922ade6f7ad6df6c5738e1be8dc02098
6
+ metadata.gz: 251ca7594d0e52edd0a42f6bfc840f6fed7a383a9e0f46cc34a20daa720e3c0c455b184169f4448d73aee6cd737d2dd6431a7ff5e05f37b3f3be4eca0584feaa
7
+ data.tar.gz: 15a48d602d48b469197df9472d0fd7c3304507f50edb5bfdddb4299f6eadafe6d0a1ceb9bf65b706d4f111b379be980e94b12a6c089e2f836f8f8da6aa16251a
@@ -0,0 +1,48 @@
1
+ module Relaton
2
+ module Render
3
+ class Date
4
+ def initialize(date, options)
5
+ @date = date
6
+ @r = options[:renderer]
7
+ @type = options[:type]
8
+ @bibitem = options[:bibitem]
9
+ end
10
+
11
+ def render
12
+ return @date if @date.nil? || /^\d+$/.match?(@date)
13
+
14
+ render1(granularity)
15
+ end
16
+
17
+ # @r.date is using document language setting, not citation-specific
18
+ def render1(format)
19
+ datef = dateparse(format, @r.lang.to_sym)
20
+ case @r.date[format]
21
+ when "to_full_s", "to_long_s", "to_medium_s", "to_short_s"
22
+ datef.send @r.date[format]
23
+ else
24
+ datef.to_additional_s(@r.date[format])
25
+ end
26
+ end
27
+
28
+ def granularity
29
+ case @date
30
+ when /^\d+-\d+$/ then "month_year"
31
+ when /^\d+-\d+-\d+$/ then "day_month_year"
32
+ else "date_time"
33
+ end
34
+ end
35
+
36
+ def dateparse(format, lang)
37
+ case format
38
+ when "date_time"
39
+ DateTime.parse(@date).localize(lang, timezone: "Zulu")
40
+ when "day_month_year"
41
+ DateTime.parse(@date).localize(lang, timezone: "Zulu").to_date
42
+ when "month_year"
43
+ DateTime.parse("#{@date}-01").localize(lang, timezone: "Zulu").to_date
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,266 @@
1
+ require_relative "date"
2
+
3
+ module Relaton
4
+ module Render
5
+ class Fields
6
+ def initialize(options)
7
+ @r = options[:renderer]
8
+ end
9
+
10
+ # the @i18n.select choices here need to rely on
11
+ # fields already present, they can't rely on fields they are yet to build
12
+ def compound_fields_format(hash)
13
+ name_fields_format(hash)
14
+ role_fields_format(hash)
15
+ date_fields_format(hash)
16
+ edition_fields_format(hash)
17
+ place_fields_format(hash)
18
+ series_fields_format(hash)
19
+ misc_fields_format(hash)
20
+ end
21
+
22
+ def comma(hash)
23
+ "#{@r.i18n.select(hash).get.dig('punct', 'comma') || ','} "
24
+ end
25
+
26
+ def place_fields_format(hash)
27
+ hash[:place_raw]&.map! do |p|
28
+ # TODO use enum-comma?
29
+ if p.is_a?(Array)
30
+ p.join(comma(hash))
31
+ else p
32
+ end
33
+ end
34
+ hash[:place] =
35
+ nameformat(hash[:place_raw].map { |x| { nonpersonal: x } }, hash)
36
+ end
37
+
38
+ def name_fields_format(hash)
39
+ [%i(creatornames creators), %i(host_creatornames host_creators),
40
+ %i(publisher publisher_raw), %i(distributor distributor_raw),
41
+ %i(authorizer authorizer_raw)]
42
+ .each do |k|
43
+ hash[k[0]] = nameformat(hash[k[1]], hash)
44
+ end
45
+ hash[:publisher_abbrev] = hash[:publisher_abbrev_raw]&.join(comma(hash))
46
+ hash[:authorcite] = authorciteformat(hash[:creators], hash)
47
+ end
48
+
49
+ def role_fields_format(hash)
50
+ hash[:role] = role_inflect(hash[:creators], hash[:role_raw], hash)
51
+ hash[:host_role] =
52
+ role_inflect(hash[:host_creators], hash[:host_role_raw], hash)
53
+ end
54
+
55
+ def edition_fields_format(hash)
56
+ hash[:edition] =
57
+ editionformat(hash[:edition_raw], hash[:edition_num], hash)
58
+ hash[:draft] = draftformat(hash[:draft_raw], hash)
59
+ end
60
+
61
+ def misc_fields_format(hash)
62
+ hash[:medium] = mediumformat(hash[:medium_raw], hash)
63
+ hash[:extent] = extentformat(hash[:extent_raw], hash)
64
+ hash[:size] = sizeformat(hash[:size_raw], hash)
65
+ hash[:uri] = uriformat(hash[:uri_raw])
66
+ hash[:status] = statusformat(hash[:status_raw], hash)
67
+ hash[:other_identifier] =
68
+ otheridentifierformat(hash[:other_identifier_raw], hash)
69
+ hash
70
+ end
71
+
72
+ def otheridentifierformat(otherids, _hash)
73
+ otherids&.map do |i|
74
+ # ret = "#{i[0]}: <esc>#{i[1]}</esc>"
75
+ # @r.i18n.select(hash).l10n(ret) # no i18n!
76
+ "#{i[0]}: #{i[1]}"
77
+ end
78
+ end
79
+
80
+ def statusformat(status, hash)
81
+ status.nil? and return
82
+ @r.i18n.select(hash).get.dig("stage", status) || status
83
+ end
84
+
85
+ # date => disambiguated_date, the disambiguation happens in citations/
86
+ def date_fields_format(hash)
87
+ [%i(date date), %i(disambiguated_date date),
88
+ %i(date_updated date_updated),
89
+ %i(date_accessed date_accessed)].each do |k|
90
+ hash[k[0]] = dateformat(hash[k[1]], hash, k)
91
+ k[1] == :date &&
92
+ !%w(standard webresource website).include?(hash[:type]) and
93
+ hash[k[0]] ||= @r.i18n.select(nil).get["no_date"]
94
+ end
95
+ end
96
+
97
+ def mediumformat(medium, hash)
98
+ medium.nil? and return nil
99
+ ret = %w(content genre form carrier size
100
+ scale).each_with_object([]) do |i, m|
101
+ m << "<esc>#{medium[i]}</esc>" if medium[i]
102
+ m
103
+ end.compact
104
+ i = @r.i18n.select(hash)
105
+ i.l10n(ret.join(comma(hash)))
106
+ end
107
+
108
+ def series_fields_format(hash)
109
+ parts =
110
+ %i(series_title series_abbr series_num series_partnumber
111
+ series_run series_formatted series_dates series_place series_org)
112
+ series_out = parts.each_with_object({}) do |i, m|
113
+ m[i] = hash[i]
114
+ end
115
+ t = hash[:type] == "article" ? @r.journaltemplate : @r.seriestemplate
116
+ hash[:series] = t.render(series_out, hash)
117
+ end
118
+
119
+ def nameformat(names, hash)
120
+ names.nil? and return names
121
+ parts = %i(surname initials given middle nonpersonal)
122
+ names_out = names.each_with_object({}) do |n, m|
123
+ parts.each do |i|
124
+ m[i] ||= []
125
+ m[i] << n[i]
126
+ end
127
+ end
128
+ @r.nametemplate.render(names_out, hash)
129
+ end
130
+
131
+ def authorciteformat(names, hash)
132
+ names.nil? || @r.authorcitetemplate.nil? and return names
133
+ parts = %i(surname initials given middle nonpersonal)
134
+ names_out = names.each_with_object({}) do |n, m|
135
+ parts.each do |i|
136
+ m[i] ||= []
137
+ m[i] << n[i]
138
+ end
139
+ end
140
+ @r.authorcitetemplate.render(names_out, hash)
141
+ end
142
+
143
+ def role_inflect(contribs, role, hash)
144
+ role.nil? || contribs.empty? ||
145
+ %w(author publisher distributor
146
+ authorizer).include?(role) and return nil
147
+ number = contribs.size > 1 ? "pl" : "sg"
148
+ x = @r.i18n.select(hash).get[role]
149
+ x.is_a?(Hash) or return role
150
+ x[number] || role
151
+ end
152
+
153
+ def editionformat(edn, num, hash)
154
+ num || edn && !edn.empty? or return
155
+ edn_num = edn&.gsub(/<\/?esc>/, "")
156
+ num || /^\d+$/.match?(edn_num) and
157
+ return @r.i18n.select(hash)
158
+ .populate("edition_ordinal", { "var1" => num || edn_num.to_i })
159
+ @r.i18n.select(hash).populate("edition_cardinal", { "var1" => edn })
160
+ end
161
+
162
+ def draftformat(num, hash)
163
+ num.nil? ||
164
+ (num.is_a?(Hash) && num[:status].nil? &&
165
+ num[:iteration].nil?) and return nil
166
+ @r.i18n.select(hash).draft.sub("%", num)
167
+ end
168
+
169
+ def extentformat(extent, hash)
170
+ extent.map do |stack|
171
+ stack.map do |e|
172
+ e1 = e.transform_values { |v| v.is_a?(Hash) ? range(v) : v }
173
+ ret = e.each_with_object({}) do |(k, v), m|
174
+ extentformat1(k, v, m, e1, hash)
175
+ m
176
+ end
177
+ @r.extenttemplate.render(hash.merge(ret), hash)
178
+ end.join(" ")
179
+ end.join("; ")
180
+ end
181
+
182
+ def extentformat1(key, val, hash, norm_hash, context)
183
+ if %i(volume issue page).include?(key)
184
+ hash["#{key}_raw".to_sym] = norm_hash[key]
185
+ hash[key] =
186
+ pagevolformat(norm_hash[key], val, key.to_s, false, context)
187
+ end
188
+ end
189
+
190
+ def range(hash)
191
+ hash[:on] and return hash[:on]
192
+ hash.has_key?(:from) && hash[:from].nil? and return nil
193
+ !hash[:from] and return hash
194
+ hash[:to] && hash[:to] != hash[:from] and
195
+ return "#{hash[:from]}&#x2013;#{hash[:to]}"
196
+ hash[:from]
197
+ end
198
+
199
+ def sizeformat(size, hash)
200
+ return nil unless size
201
+
202
+ ret = size.transform_values do |v|
203
+ @r.i18n.select(hash).l10n(v.join(" + "))
204
+ end
205
+ .each_with_object({}) do |(k, v), m|
206
+ sizeformat1(k, v, m, hash)
207
+ m
208
+ end
209
+ @r.sizetemplate.render(ret.merge(type: hash[:type]), hash)
210
+ end
211
+
212
+ def sizeformat1(key, val, hash, context)
213
+ case key
214
+ when "volume", "issue", "page"
215
+ hash["#{key}_raw".to_sym] = val
216
+ hash[key.to_sym] = pagevolformat(val, nil, key, true, context)
217
+ when "data" then hash[:data] = val
218
+ when "duration" then hash[:duration] = val
219
+ end
220
+ end
221
+
222
+ def pagevolformat(value, value_raw, type, is_size, hash)
223
+ value.nil? and return nil
224
+ num = "pl"
225
+ if is_size
226
+ value == "1" and num = "sg"
227
+ else
228
+ value_raw[:to] or num = "sg"
229
+ end
230
+ i = @r.i18n.select(hash)
231
+ i.l10n(i.get[is_size ? "size" : "extent"][type][num]
232
+ .sub("%", value))
233
+ end
234
+
235
+ def date_range(hash)
236
+ hash[:from] && !hash[:to] and return "#{hash[:from]}&#x2013;"
237
+ range(hash)
238
+ end
239
+
240
+ def dateformat(date, hash, type)
241
+ date.nil? and return nil
242
+ date.is_a?(String) and return date
243
+ %i(from to on).each do |k|
244
+ date[k] = @r.dateklass
245
+ .new(date[k], renderer: @r, bibitem: hash, type: type).render
246
+ end
247
+ date_range(date)
248
+ end
249
+
250
+ def uriformat(uri)
251
+ uri.nil? || uri.empty? and return nil
252
+ # do not process uri contents in l10n
253
+ "<fmt-link target='#{uri}'><esc>#{uri}</esc></fmt-link>"
254
+ end
255
+
256
+ private
257
+
258
+ def tw_cldr_lang
259
+ if @r.lang != "zh" then @r.lang.to_sym
260
+ elsif @r.script == "Hant" then :"zh-tw"
261
+ else :"zh-cn"
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
@@ -95,6 +95,15 @@ module Relaton
95
95
  Relaton::Bib::Item
96
96
  end
97
97
 
98
+ # 1.x Parse#authoritative_identifier semantics: the
99
+ # primary/language cascade, the excluded types (URN, DOI, ISBN...)
100
+ # dropped, and scoped ids (biblio-tag duplicates, IEEE trademark)
101
+ # demoted within their type group. isodoc hands the live bibitem
102
+ # node with the document's default namespace still attached, so the
103
+ # element queries must be namespace-agnostic.
104
+ EXCLUDED_ID_TYPES = %w(METANORMA METANORMA-ORDINAL AUTHOR-DATE TITLE
105
+ URN ISO-REFERENCE ISSN ISBN DOI).freeze
106
+
98
107
  def facade_docids(doc)
99
108
  node = doc
100
109
  unless node.respond_to?(:xpath)
@@ -103,7 +112,37 @@ module Relaton
103
112
  end
104
113
  return [] unless node
105
114
 
106
- node.xpath("./docidentifier").map(&:text)
115
+ ids = node.xpath("./*[local-name() = 'docidentifier']")
116
+ ids = facade_scope_filter(ids)
117
+ out = nil
118
+ [
119
+ ->(x) { x["language"] == @lang && x["primary"] },
120
+ ->(x) { x["primary"] },
121
+ ->(x) { x["language"] == @lang },
122
+ ->(_x) { true },
123
+ ].each do |p|
124
+ out = ids.select do |x|
125
+ p.call(x) && !EXCLUDED_ID_TYPES.include?(facade_id_type(x))
126
+ end
127
+ out.empty? or break
128
+ end
129
+ out.map { |x| x.text.strip }
130
+ end
131
+
132
+ def facade_id_type(id)
133
+ t = id["type"] or return nil
134
+ m = /\A(ISBN|ISSN)\..*/i.match(t) or return t.upcase
135
+ m[1].upcase
136
+ end
137
+
138
+ def facade_scope_filter(ids)
139
+ ids.detect { |i| i["scope"] } or return ids
140
+ ids.group_by { |i| i["type"] }.flat_map do |type, group|
141
+ grouped = group.group_by { |i| i["scope"] }
142
+ if type == "IEEE" then grouped["trademark"] || grouped[nil] || []
143
+ else grouped[nil] || []
144
+ end
145
+ end
107
146
  end
108
147
 
109
148
  def warn_general_config
@@ -0,0 +1,178 @@
1
+ module Relaton
2
+ module Render
3
+ class Citations
4
+ def initialize(opt = {})
5
+ # invoking i18n hash, but we preference the specific renderer's i18n
6
+ @i18n = opt[:i18n]
7
+ @lang = opt[:lang] # document lang and script
8
+ @script = opt[:script]
9
+ # hash of renderers, to be applied to specific citation by selector
10
+ @renderer = opt[:renderer]
11
+ end
12
+
13
+ def renderer(_cite)
14
+ @renderer[:default]
15
+ end
16
+
17
+ # takes array of { id, type, author, date, ord, data }
18
+ def render(ret)
19
+ cites = citations(ret)
20
+ enhance_data(cites)
21
+ cites.each_key do |k|
22
+ cites[k] = render1(cites[k])
23
+ end
24
+ cites
25
+ end
26
+
27
+ def enhance_data(cites)
28
+ ret = extract_uris_for_lookup(cites)
29
+ ret.empty? and return
30
+ # functionality removed: date needs to be given explicitly
31
+ # @renderer.urls_exist_concurrent(ret.keys).each do |k, v|
32
+ # ret[k].each { |u| add_date_accessed(cites[u], k, v) }
33
+ # end
34
+ end
35
+
36
+ def extract_uris_for_lookup(cites)
37
+ cites.each_with_object({}) do |(k, v), m|
38
+ u = extract_uri_for_lookup(v) or next
39
+ m[u] ||= []
40
+ m[u] << k
41
+ end
42
+ end
43
+
44
+ def extract_uri_for_lookup(cite)
45
+ t = renderer(cite).renderer(cite[:type] || "misc").template_raw
46
+ c = cite[:data]
47
+ t.is_a?(String) or return
48
+ (/\{\{\s*date_accessed\s*\}\}/.match?(t) &&
49
+ /\{\{\s*uri\s*\}\}/.match?(t) &&
50
+ c[:uri_raw] && !c[:date_accessed]) or return
51
+ c[:uri_raw]
52
+ end
53
+
54
+ def add_date_accessed(cite, uri, status)
55
+ r = renderer(cite)
56
+ if status
57
+ cite[:data][:date_accessed] = { on: ::Date.today.to_s }
58
+ cite[:data] = r.fieldsklass.new(renderer: r)
59
+ .compound_fields_format(cite[:data])
60
+ else
61
+ r.url_warn(uri)
62
+ end
63
+ end
64
+
65
+ def render1(cit)
66
+ ref, ref1, r = render1_prep(cit)
67
+ i = @i18n.select(cit[:data])
68
+ cit[:formattedref] =
69
+ r.valid_parse(i.l10n(ref1))
70
+ cit[:citation][:full] = r.valid_parse(i.l10n(ref))
71
+ %i(type data renderer).each { |x| cit.delete(x) }
72
+ cit
73
+ end
74
+
75
+ def use_terminator?(ref, final, _cit)
76
+ !ref || ref.empty? and return false
77
+ !ref.end_with?(final)
78
+ end
79
+
80
+ def render1_prep(cit)
81
+ r = renderer(cit)
82
+ ref = r.renderer(cit[:type] || "misc").render(cit[:data], cit[:data])
83
+ final = @i18n.select(cit[:data]).get
84
+ .dig("punct", "biblio-terminator") || "."
85
+ ref1 = ref
86
+ use_terminator?(ref, final, cit) and ref1 += final
87
+ [ref, ref1, r]
88
+ end
89
+
90
+ # TODO: configure how multiple ids are joined, from template?
91
+ def citations(ret)
92
+ ret = disambig_author_date_citations(ret)
93
+ ret.each_value do |b|
94
+ i = @i18n.select(b[:data])
95
+ b[:citation][:default] =
96
+ i.l10n(b[:data][:authoritative_identifier]&.first || "")
97
+ d = b[:data].merge(citestyle: "short")
98
+ b[:citation][:short] = i.l10n(renderer(b).citeshorttemplate.render(d, d))
99
+ citations_iterate_cite_styles(b, i)
100
+ end
101
+ ret
102
+ end
103
+
104
+ def citations_iterate_cite_styles(bib, i18n)
105
+ r = renderer(bib)
106
+ r.citetemplate.citation_styles.each do |style|
107
+ bib[:citation][style] =
108
+ i18n.l10n(r.citetemplate
109
+ .render(bib.merge(citestyle: style)
110
+ .merge(bib[:data]), bib[:data]))
111
+ end
112
+ end
113
+
114
+ # takes array of { id, type, author, date, ord, data }
115
+ def disambig_author_date_citations(ret)
116
+ author_date_to_hash(suffix_date(sort_ord(author_date_breakdown(ret))))
117
+ end
118
+
119
+ def author_date_breakdown(ret)
120
+ ret.each_with_object({}) do |b, m|
121
+ m[b[:author]] ||= {}
122
+ m[b[:author]][b[:date]] ||= []
123
+ m[b[:author]][b[:date]] << b
124
+ end
125
+ end
126
+
127
+ def sort_ord(ret)
128
+ ret.each do |author, v|
129
+ v.each_key do |date|
130
+ ret[author][date].sort_by! { |a| a[:ord] }
131
+ end
132
+ end
133
+ end
134
+
135
+ def suffix_date(ret)
136
+ ret.each do |k, v|
137
+ v.each do |k1, v1|
138
+ s = v1.reject { |b| b[:date].nil? }.size
139
+ s.zero? || k.nil? and next
140
+ suffix_date1(ret, k, k1, s)
141
+ copy_date_fields(ret, k, k1)
142
+ end
143
+ end
144
+ ret
145
+ end
146
+
147
+ def suffix_date1(ret, key1, key2, size)
148
+ size > 1 or return
149
+ ret[key1][key2].each_with_index do |b, i|
150
+ b[:date].nil? and next
151
+ b[:disambiguated_date] = b[:date] + ("a".ord + i).chr.to_s
152
+ end
153
+ end
154
+
155
+ def copy_date_fields(ret, key1, key2)
156
+ ret[key1][key2].each do |b|
157
+ b[:date].nil? and next
158
+ b[:data][:date] = b[:date]
159
+ b[:disambiguated_date] ||= b[:date]
160
+ b[:data][:disambiguated_date] = b[:disambiguated_date]
161
+ end
162
+ end
163
+
164
+ def author_date_to_hash(ret)
165
+ ret.each_with_object({}) do |(_k, v), m|
166
+ v.each_value do |v1|
167
+ v1.each do |b|
168
+ m[b[:id]] = { author: @i18n.select(b[:data]).l10n(b[:author]),
169
+ date: b[:disambiguated_date] || b[:date],
170
+ citation: {},
171
+ data: b[:data], type: b[:type] }
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end