relaton-render 1.2.2 → 1.4.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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +6 -1
  3. data/.github/workflows/release.yml +1 -1
  4. data/.rubocop.yml +2 -2
  5. data/Gemfile +15 -2
  6. data/lib/isodoc-yaml/i18n-ar.yaml +8 -2
  7. data/lib/isodoc-yaml/i18n-de.yaml +8 -2
  8. data/lib/isodoc-yaml/i18n-en.yaml +8 -2
  9. data/lib/isodoc-yaml/i18n-es.yaml +8 -2
  10. data/lib/isodoc-yaml/i18n-fr.yaml +8 -2
  11. data/lib/isodoc-yaml/i18n-ja.yaml +13 -7
  12. data/lib/isodoc-yaml/i18n-ru.yaml +8 -2
  13. data/lib/isodoc-yaml/i18n-zh-Hans.yaml +8 -2
  14. data/lib/isodoc-yaml/i18n-zh-Hant.yaml +8 -2
  15. data/lib/relaton/render/iso690/element.rb +43 -0
  16. data/lib/relaton/render/iso690/elements/component_part.rb +50 -0
  17. data/lib/relaton/render/iso690/elements/creator.rb +129 -0
  18. data/lib/relaton/render/iso690/elements/edition.rb +47 -0
  19. data/lib/relaton/render/iso690/elements/identifier.rb +32 -0
  20. data/lib/relaton/render/iso690/elements/location.rb +27 -0
  21. data/lib/relaton/render/iso690/elements/medium.rb +27 -0
  22. data/lib/relaton/render/iso690/elements/numeration.rb +26 -0
  23. data/lib/relaton/render/iso690/elements/production.rb +36 -0
  24. data/lib/relaton/render/iso690/elements/pub_date.rb +51 -0
  25. data/lib/relaton/render/iso690/elements/series.rb +40 -0
  26. data/lib/relaton/render/iso690/elements/title.rb +30 -0
  27. data/lib/relaton/render/iso690/elements.rb +40 -0
  28. data/lib/relaton/render/iso690/fields.rb +53 -0
  29. data/lib/relaton/render/iso690/i18n/en.yml +13 -0
  30. data/lib/relaton/render/iso690/i18n/fr.yml +14 -0
  31. data/lib/relaton/render/iso690/i18n.rb +53 -0
  32. data/lib/relaton/render/iso690/identifier_kinds.rb +34 -0
  33. data/lib/relaton/render/iso690/index_renderer.rb +57 -0
  34. data/lib/relaton/render/iso690/kinds.rb +43 -0
  35. data/lib/relaton/render/iso690/renderer.rb +52 -0
  36. data/lib/relaton/render/iso690/style/locale.rb +44 -0
  37. data/lib/relaton/render/iso690/style/name_form.rb +21 -0
  38. data/lib/relaton/render/iso690/style/scheme.rb +23 -0
  39. data/lib/relaton/render/iso690/style/sort_rule.rb +25 -0
  40. data/lib/relaton/render/iso690/style/template_map.rb +28 -0
  41. data/lib/relaton/render/iso690/style/type_template.rb +21 -0
  42. data/lib/relaton/render/iso690/style.rb +76 -0
  43. data/lib/relaton/render/iso690/styles/author-date.yml +28 -0
  44. data/lib/relaton/render/iso690/template.rb +72 -0
  45. data/lib/relaton/render/iso690.rb +22 -0
  46. data/lib/relaton/render/version.rb +1 -1
  47. data/lib/relaton-render.rb +3 -7
  48. data/relaton-render.gemspec +2 -19
  49. metadata +35 -249
  50. data/lib/relaton/render/fields/date.rb +0 -48
  51. data/lib/relaton/render/fields/fields.rb +0 -266
  52. data/lib/relaton/render/general/citations.rb +0 -178
  53. data/lib/relaton/render/general/config.yml +0 -129
  54. data/lib/relaton/render/general/render.rb +0 -316
  55. data/lib/relaton/render/general/render_classes.rb +0 -132
  56. data/lib/relaton/render/general/uri.rb +0 -57
  57. data/lib/relaton/render/i18n/i18n.rb +0 -40
  58. data/lib/relaton/render/parse/parse.rb +0 -152
  59. data/lib/relaton/render/parse/parse_contributors.rb +0 -184
  60. data/lib/relaton/render/parse/parse_extract.rb +0 -200
  61. data/lib/relaton/render/parse/parse_id.rb +0 -128
  62. data/lib/relaton/render/template/liquid.rb +0 -48
  63. data/lib/relaton/render/template/subclasses.rb +0 -123
  64. data/lib/relaton/render/template/template.rb +0 -228
@@ -1,316 +0,0 @@
1
- require_relative "render_classes"
2
- require_relative "citations"
3
- require_relative "uri"
4
- require "yaml"
5
- require "liquid"
6
- require "date"
7
- require "relaton/bib"
8
- require "metanorma-utils"
9
- require_relative "../template/template"
10
- require_relative "../../../isodoc/i18n"
11
-
12
- module Relaton
13
- module Render
14
- class General
15
- Hash.include ::Metanorma::Utils::Hash
16
- Array.include ::Metanorma::Utils::Array
17
-
18
- attr_reader :template, :journaltemplate, :seriestemplate, :nametemplate,
19
- :authorcitetemplate, :extenttemplate, :sizetemplate,
20
- :citetemplate, :citeshorttemplate, :lang, :script, :i18n,
21
- :edition, :date, :fieldsklass, :dateklass, :config
22
-
23
- def initialize(opt = {})
24
- options = init_options(opt)
25
- @type = self.class.name.downcase.split("::").last
26
- klass_initialize(options)
27
- root_initalize(options)
28
- render_initialize(options)
29
- @parse ||= options["parse"]
30
- init_misc
31
- end
32
-
33
- def init_options(opt)
34
- opt = opt.stringify_all_keys
35
- @override_file ||= opt["config"]
36
- @config = config_override(read_config)
37
- @config.merge(opt)
38
- end
39
-
40
- def init_misc
41
- @semaphore = Mutex.new
42
- @urlcache = {}
43
- @url_warned = {}
44
- end
45
-
46
- def read_config
47
- YAML.load_file(File.join(File.dirname(__FILE__), "config.yml"))
48
- end
49
-
50
- def config_override(ret)
51
- new = {}
52
- @override_file and new = YAML.load_file(@override_file)
53
- ret.deep_merge(new)
54
- end
55
-
56
- def klass_initialize(options)
57
- @nametemplateklass = Relaton::Render::Template::Name
58
- @authorcitetemplateklass = Relaton::Render::Template::AuthorCite
59
- @citetemplateklass = Relaton::Render::Template::Cite
60
- @citeshorttemplateklass = Relaton::Render::Template::Cite
61
- @seriestemplateklass = Relaton::Render::Template::Series
62
- @extenttemplateklass = Relaton::Render::Template::Extent
63
- @sizetemplateklass = Relaton::Render::Template::Size
64
- @generaltemplateklass = Relaton::Render::Template::General
65
- @fieldsklass = Relaton::Render::Fields
66
- @dateklass = Relaton::Render::Date
67
- @parseklass = Relaton::Render::Parse
68
- @i18nklass = options["i18nklass"] || Relaton::Render::I18n
69
- @citeklass = Relaton::Render::Citations
70
- end
71
-
72
- def root_initalize(opt)
73
- i18n_initialize(opt)
74
- @parse = @parseklass.new(lang: @lang, script: @script, i18n: @i18n)
75
- @nametemplate = @nametemplateklass
76
- .new(template: opt["nametemplate"], i18n: @i18n)
77
- @citetemplate, @citeshorttemplate = citerenderers(opt)
78
- @authorcitetemplate = @authorcitetemplateklass
79
- &.new(template: opt["authorcitetemplate"], i18n: @i18n)
80
- @seriestemplate = @seriestemplateklass
81
- .new(template: opt["seriestemplate"], i18n: @i18n)
82
- @journaltemplate = @seriestemplateklass
83
- .new(template: opt["journaltemplate"], i18n: @i18n)
84
- @extenttemplate = extentrenderers(opt)
85
- @sizetemplate = sizerenderers(opt)
86
- end
87
-
88
- def i18n_initialize(opt)
89
- @lang = opt["language"]
90
- @script = opt["script"]
91
- @locale = opt["locale"]
92
- default_i18n = opt["i18n"] ||
93
- i18n_klass(language: @lang, script: @script, locale: @locale,
94
- i18nhash: opt["i18nhash"])
95
- @i18n = @i18nklass.new(opt.merge({ "i18n" => default_i18n }))
96
- i18n_default_strs(opt)
97
- end
98
-
99
- # avoid using these, they are document-set not citation-specific
100
- def i18n_default_strs(opt)
101
- @edition = opt["edition"] || @i18n.select(nil).edition
102
- @date = opt["date"] || @i18n.select(nil).get["date_formats"] ||
103
- { "month_year" => "yMMMM", "day_month_year" => "to_long_s",
104
- "date_time" => "to_long_s" }
105
- end
106
-
107
- def render_initialize(opt)
108
- case opt["template"]
109
- when String
110
- @template = @generaltemplateklass
111
- .new(template: opt["template"], i18n: @i18n)
112
- when Hash
113
- @render = renderers(opt)
114
- end
115
- end
116
-
117
- def renderers(opt)
118
- template_hash_fill(opt["template"]).each_with_object({}) do |(k, v), m|
119
- @type == "general" || @type == k or next
120
- m[k] = self.class.subclass(k)
121
- .new(template: v, parse: @parse, i18n_multi: @i18n.config,
122
- language: @lang, script: @script, i18nklass: @i18nklass)
123
- end
124
- end
125
-
126
- def extentrenderers(opt)
127
- @extenttemplateklass
128
- .new(template: template_hash_fill(opt["extenttemplate"]), i18n: @i18n)
129
- end
130
-
131
- def sizerenderers(opt)
132
- @sizetemplateklass
133
- .new(template: template_hash_fill(opt["sizetemplate"]), i18n: @i18n)
134
- end
135
-
136
- def citerenderers(opt)
137
- s = opt["citetemplate"]["short"]
138
- r = opt["citetemplate"].reject { |k, _| k == "short" }
139
- short = @citetemplateklass
140
- &.new(template: template_hash_fill(s), i18n: @i18n)
141
- ret = @citetemplateklass
142
- &.new(template: r, i18n: @i18n)
143
- [ret, short]
144
- end
145
-
146
- def default_template
147
- "{{creatornames}}. {{title}}. {{disambiguated_date}}."
148
- end
149
-
150
- def i18n_klass(language: "en", script: "Latn", locale: nil, i18nhash: nil)
151
- ::IsoDoc::RelatonRenderI18n.new(language, script, locale: locale,
152
- i18nhash: i18nhash)
153
- end
154
-
155
- def render(bib, embedded: false, terminator: true)
156
- bib = xml2relaton(bib)
157
- f = bib.formattedref and
158
- return embedded ? f.content : fmtref(f.content)
159
- ret = render1(bib, terminator) or return nil
160
- embedded and return ret
161
- fmtref(ret)
162
- end
163
-
164
- def xml2relaton(bib)
165
- bib.is_a?(Relaton::Bib::ItemData) and return bib
166
- xml = xml_string2noko(bib)
167
- bib = xml.to_xml(encoding: "UTF-8", indent: 0,
168
- save_with: Nokogiri::XML::Node::SaveOptions::AS_XML)
169
- .gsub(/ xmlns="[^"]*"/, "")
170
- Relaton::Bib::Bibitem.from_xml(bib)
171
- end
172
-
173
- def xml_string2noko(bib)
174
- case bib
175
- when String
176
- xml = Nokogiri::XML(bib)
177
- xml.errors.empty? or return bib
178
- xml.root
179
- when Nokogiri::XML::Element
180
- bib
181
- when Nokogiri::XML::Document
182
- bib.root
183
- end
184
- end
185
-
186
- def fmtref(doc)
187
- "<formattedref>#{doc}</formattedref>"
188
- end
189
-
190
- def renderer(type)
191
- ret = @template || @render[type]&.template or
192
- raise "No renderer defined for #{type}"
193
- @type == "general" || @type == type or
194
- raise "No renderer defined for #{type}"
195
- ret
196
- end
197
-
198
- # <esc> in field can get capitalised in filters
199
- def esc_cleanup(text)
200
- text.gsub(/<esc>/i, "<esc>").gsub(/<\/esc>/i, "</esc>")
201
- end
202
-
203
- def render1(doc, terminator)
204
- obj, str = render_single_bibitem(doc)
205
- out = [str] + also_pub_as(doc)
206
- ret1 = out.join(@i18n.select(obj).get["punct"]["biblio-field-delimiter"] || ". ")
207
- ret = @i18n.select(obj).l10n(esc_cleanup(ret1)
208
- .gsub(".</esc>.", ".</esc>").gsub(".. ", ". "))
209
- final = @i18n.select(obj).get["punct"]["biblio-terminator"] || "."
210
- terminator && !ret.end_with?(final) && !ret.empty? and ret += final
211
- ret
212
- end
213
-
214
- def also_pub_as(doc)
215
- r = Array(doc.relation).select { |x| x.type == "hasRepresentation" }
216
- r.map do |x|
217
- _, out = render_single_bibitem(x.bibitem)
218
- @i18n.select(nil).also_pub_as + out
219
- end
220
- end
221
-
222
- def render_single_bibitem(doc)
223
- data_liquid, r = parse(doc)
224
- out = liquid(data_liquid, r)
225
- [data_liquid, out]
226
- end
227
-
228
- def liquid(data_liquid, renderer)
229
- valid_parse(renderer.render(data_liquid, data_liquid))
230
- end
231
-
232
- def parse(doc)
233
- doc = xml2relaton(doc)
234
- r = renderer(doc.type || "misc")
235
- data = @parse.extract(doc)
236
- enhance_data(data, r.template_raw)
237
- data_liquid = @fieldsklass.new(renderer: self)
238
- .compound_fields_format(data)
239
- [data_liquid, r]
240
- end
241
-
242
- def valid_parse(ret)
243
- @i18n.select(nil).get["no_date"] == ret and return nil
244
- ret&.gsub(/\+\+\+(.+?)\+\+\+/, '\1')
245
- end
246
-
247
- # expect array of Relaton objects, in sorted order
248
- # enhance_data is skipped here, and is done in batch inside Citations
249
- def render_all(bib, type: "author-date")
250
- bib = sanitise_citations_input(bib) or return
251
- @citeklass.new(type: type, renderer: citation_renderers,
252
- i18n: @i18n, lang: @lang, script: @script)
253
- .render(citations1(bib))
254
- end
255
-
256
- def citation_renderers
257
- { default: self }
258
- end
259
-
260
- def sanitise_citations_input(bib)
261
- bib.is_a?(Array) and return bib
262
- bib.is_a?(String) and return sanitise_citations_input_string(bib)
263
- end
264
-
265
- def sanitise_citations_input_string(bib)
266
- p = Nokogiri::XML(bib) or return
267
- (p.errors.empty? && p.root.at("./bibitem")) or return nil
268
- p.root.xpath("./bibitem").each_with_object([]) do |b, m|
269
- m << Relaton::Bib::Bibitem.from_xml(b.to_xml)
270
- end
271
- end
272
-
273
- def citations1(bib)
274
- bib.each_with_object([]).with_index do |(b, m), i|
275
- data_liquid = @fieldsklass.new(renderer: self)
276
- .compound_fields_format(@parse.extract(b))
277
- m << { author: data_liquid[:authorcite], date: data_liquid[:date],
278
- ord: i, id: b.id, data: data_liquid, type: b.type }
279
- end
280
- end
281
-
282
- # add to liquid data based on template
283
- def enhance_data(_data, template)
284
- template.is_a?(String) or return
285
- # add_date_accessed(data, template)
286
- end
287
-
288
- # functionality removed: date needs to be given explicitly
289
- def add_date_accessed(data, template)
290
- (/\{\{\s*date_accessed\s*\}\}/.match?(template) &&
291
- /\{\{\s*uri\s*\}\}/.match?(template) &&
292
- data[:uri_raw] && !data[:date_accessed]) or return
293
- if url_exist?(data[:uri_raw])
294
- data[:date_accessed] = { on: ::Date.today.to_s }
295
- else url_warn(data[:uri_raw])
296
- end
297
- end
298
-
299
- def url_warn(uri)
300
- @url_warned[uri] and return
301
- warn "BIBLIOGRAPHY WARNING: cannot access #{uri}"
302
- @url_warned[uri] = true
303
- end
304
-
305
- private
306
-
307
- def template_hash_fill(templates)
308
- BIBTYPE.each_with_object({}) do |type, m|
309
- template = templates[type] || templates["misc"] || default_template
310
- BIBTYPE.include?(template) and template = templates[template]
311
- m[type] = template
312
- end
313
- end
314
- end
315
- end
316
- end
@@ -1,132 +0,0 @@
1
- module Relaton
2
- module Render
3
- BIBTYPE =
4
- %w(article book booklet manual proceedings presentation thesis techreport
5
- standard unpublished map electronic_resource audiovisual film video
6
- broadcast software graphic_work music patent inbook incollection
7
- inproceedings journal website webresource dataset archival social_media
8
- alert message conversation misc internal).freeze
9
-
10
- class General
11
- singleton_class.send(:attr_reader, :descendants)
12
- @descendants = {}
13
-
14
- def self.inherited(subclass) # rubocop:disable Lint/MissingSuper
15
- type = subclass.name.downcase.sub(/relaton::render::/, "")
16
- General.descendants[type] = subclass
17
- end
18
-
19
- def self.subclass(type)
20
- # Always look up from the root General class, not from subclasses
21
- base = self
22
- base = base.superclass while base.superclass != Object && base.superclass.name =~ /Relaton::Render/
23
- base.instance_variable_get(:@descendants)&.[](type)
24
- end
25
- end
26
-
27
- class Book < General
28
- end
29
-
30
- class Journal < General
31
- end
32
-
33
- class Booklet < General
34
- end
35
-
36
- class Manual < General
37
- end
38
-
39
- class Proceedings < General
40
- end
41
-
42
- class Presentation < General
43
- end
44
-
45
- class Thesis < General
46
- end
47
-
48
- class Techreport < General
49
- end
50
-
51
- class Standard < General
52
- end
53
-
54
- class Unpublished < General
55
- end
56
-
57
- class Map < General
58
- end
59
-
60
- class Electronic_resource < General
61
- end
62
-
63
- class Software < Electronic_resource
64
- end
65
-
66
- class Webresource < Electronic_resource
67
- end
68
-
69
- class Website < Webresource
70
- end
71
-
72
- class Dataset < Electronic_resource
73
- end
74
-
75
- class Social_media < Electronic_resource
76
- end
77
-
78
- class Alert < Social_media
79
- end
80
-
81
- class Message < Social_media
82
- end
83
-
84
- class Audiovisual < General
85
- end
86
-
87
- class Film < Audiovisual
88
- end
89
-
90
- class Video < Audiovisual
91
- end
92
-
93
- class Broadcast < Audiovisual
94
- end
95
-
96
- class Graphic_work < General
97
- end
98
-
99
- class Music < General
100
- end
101
-
102
- class Patent < General
103
- end
104
-
105
- class Hosted < General
106
- end
107
-
108
- class Article < Hosted
109
- end
110
-
111
- class InBook < Hosted
112
- end
113
-
114
- class InCollection < Hosted
115
- end
116
-
117
- class InProceedings < Hosted
118
- end
119
-
120
- class Archival < General
121
- end
122
-
123
- class Conversation < General
124
- end
125
-
126
- class Misc < General
127
- end
128
-
129
- class Internal < General
130
- end
131
- end
132
- end
@@ -1,57 +0,0 @@
1
- require "concurrent"
2
- require "net/http"
3
- require "uri"
4
-
5
- module Relaton
6
- module Render
7
- class General
8
- def url_exist?(url_string)
9
- return true # temporarily disabling validation of URIs
10
- url = URI.parse(url_string)
11
- url.host or return true # allow file URLs
12
- res = access_url(url) or return false
13
- res.is_a?(Net::HTTPRedirection) and return url_exist?(res["location"])
14
- res.code[0] != "4"
15
- rescue Errno::ENOENT, SocketError
16
- false # false if can't find the server
17
- end
18
-
19
- def access_url(url)
20
- path = url.path or return false
21
- path.empty? and path = "/"
22
- url_head(url, path)
23
- rescue StandardError => e
24
- warn e
25
- false
26
- end
27
-
28
- def url_head(url, path)
29
- ret = nil
30
- @semaphore.synchronize { ret = @urlcache[url.to_s] }
31
- ret and return ret
32
- ret = Net::HTTP.start(url.host, url.port,
33
- read_timeout: 2, open_timeout: 2,
34
- use_ssl: url.scheme == "https") do |http|
35
- http.request_head(path)
36
- end
37
- @semaphore.synchronize { @urlcache[url.to_s] = ret }
38
- ret
39
- end
40
-
41
- def urls_exist_concurrent(urls)
42
- responses = Concurrent::Array.new
43
- thread_pool = Concurrent::FixedThreadPool.new(5)
44
- urls.each do |u|
45
- thread_pool.post { responses << url_exist_async?(u) }
46
- end
47
- thread_pool.shutdown
48
- thread_pool.wait_for_termination
49
- responses.each_with_object({}) { |n, m| m[n[:url]] = n[:status] }
50
- end
51
-
52
- def url_exist_async?(url_string)
53
- { url: url_string, status: url_exist?(url_string) }
54
- end
55
- end
56
- end
57
- end
@@ -1,40 +0,0 @@
1
- module Relaton
2
- module Render
3
- class I18n
4
- # @i18n is a hash of IsoDoc::I18n objects, selected from according to
5
- # attributes of the parsed Relaton object
6
- def initialize(opt)
7
- if opt["i18n_multi"]
8
- @i18n = opt["i18n_multi"]
9
- elsif opt["i18n"]
10
- @i18n = { "" => opt["i18n"] }
11
- else
12
- raise error "Bad configuration, Relaton::Render::I18n"
13
- end
14
- end
15
-
16
- # obj is the biblio hash, extracted by Relaton::Render::Fields
17
- # There must always be a default selection, for obj = nil,
18
- # being the document language setting. If select is applied on obj,
19
- # the i18n selected is appropriate to the citation specifically,
20
- # and potentially to the field specificaly
21
- def select(obj)
22
- if obj.nil? then select_default
23
- else select_obj(obj)
24
- end
25
- end
26
-
27
- def select_default
28
- @i18n[""]
29
- end
30
-
31
- def select_obj(_obj)
32
- @i18n[""]
33
- end
34
-
35
- def config
36
- @i18n
37
- end
38
- end
39
- end
40
- end
@@ -1,152 +0,0 @@
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 (&lt;tag&gt;) 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 (&lt;em&gt;foo&lt;/em&gt;); 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("&lt;", "<").gsub("&gt;", ">")
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