relaton-render 1.3.0 → 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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +15 -2
  3. data/lib/relaton/render/iso690/element.rb +43 -0
  4. data/lib/relaton/render/iso690/elements/component_part.rb +50 -0
  5. data/lib/relaton/render/iso690/elements/creator.rb +129 -0
  6. data/lib/relaton/render/iso690/elements/edition.rb +47 -0
  7. data/lib/relaton/render/iso690/elements/identifier.rb +32 -0
  8. data/lib/relaton/render/iso690/elements/location.rb +27 -0
  9. data/lib/relaton/render/iso690/elements/medium.rb +27 -0
  10. data/lib/relaton/render/iso690/elements/numeration.rb +26 -0
  11. data/lib/relaton/render/iso690/elements/production.rb +36 -0
  12. data/lib/relaton/render/iso690/elements/pub_date.rb +51 -0
  13. data/lib/relaton/render/iso690/elements/series.rb +40 -0
  14. data/lib/relaton/render/iso690/elements/title.rb +30 -0
  15. data/lib/relaton/render/iso690/elements.rb +40 -0
  16. data/lib/relaton/render/iso690/fields.rb +53 -0
  17. data/lib/relaton/render/iso690/i18n/en.yml +13 -0
  18. data/lib/relaton/render/iso690/i18n/fr.yml +14 -0
  19. data/lib/relaton/render/iso690/i18n.rb +53 -0
  20. data/lib/relaton/render/iso690/identifier_kinds.rb +34 -0
  21. data/lib/relaton/render/iso690/index_renderer.rb +57 -0
  22. data/lib/relaton/render/iso690/kinds.rb +43 -0
  23. data/lib/relaton/render/iso690/renderer.rb +52 -0
  24. data/lib/relaton/render/iso690/style/locale.rb +44 -0
  25. data/lib/relaton/render/iso690/style/name_form.rb +21 -0
  26. data/lib/relaton/render/iso690/style/scheme.rb +23 -0
  27. data/lib/relaton/render/iso690/style/sort_rule.rb +25 -0
  28. data/lib/relaton/render/iso690/style/template_map.rb +28 -0
  29. data/lib/relaton/render/iso690/style/type_template.rb +21 -0
  30. data/lib/relaton/render/iso690/style.rb +76 -0
  31. data/lib/relaton/render/iso690/styles/author-date.yml +28 -0
  32. data/lib/relaton/render/iso690/template.rb +72 -0
  33. data/lib/relaton/render/iso690.rb +22 -0
  34. data/lib/relaton/render/version.rb +1 -1
  35. data/lib/relaton-render.rb +3 -7
  36. data/relaton-render.gemspec +2 -19
  37. metadata +35 -249
  38. data/lib/relaton/render/fields/date.rb +0 -48
  39. data/lib/relaton/render/fields/fields.rb +0 -266
  40. data/lib/relaton/render/general/citations.rb +0 -178
  41. data/lib/relaton/render/general/config.yml +0 -129
  42. data/lib/relaton/render/general/render.rb +0 -316
  43. data/lib/relaton/render/general/render_classes.rb +0 -132
  44. data/lib/relaton/render/general/uri.rb +0 -57
  45. data/lib/relaton/render/i18n/i18n.rb +0 -40
  46. data/lib/relaton/render/parse/parse.rb +0 -152
  47. data/lib/relaton/render/parse/parse_contributors.rb +0 -184
  48. data/lib/relaton/render/parse/parse_extract.rb +0 -200
  49. data/lib/relaton/render/parse/parse_id.rb +0 -128
  50. data/lib/relaton/render/template/liquid.rb +0 -48
  51. data/lib/relaton/render/template/subclasses.rb +0 -123
  52. data/lib/relaton/render/template/template.rb +0 -228
@@ -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
@@ -1,184 +0,0 @@
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