relaton-render 0.1.0 → 0.3.3

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.
@@ -0,0 +1,129 @@
1
+ module Relaton
2
+ module Render
3
+ class Parse
4
+ def extract_orgname(org)
5
+ org.name&.first&.content
6
+ end
7
+
8
+ def extract_personname(person)
9
+ surname = person.name.surname || person.name.completename
10
+ given, middle, initials = given_and_middle_name(person)
11
+ { surname: surname.content,
12
+ given: given,
13
+ middle: middle,
14
+ initials: initials }
15
+ end
16
+
17
+ def given_and_middle_name(person)
18
+ forenames = person.name.forename.map(&:content)
19
+ initials = person.name.initial.map(&:content)
20
+ .map { |x| x.sub(/\.$/, "") }
21
+ forenames.empty? and initials.empty? and return [nil, nil, nil]
22
+ initials.empty? and initials = forenames.map { |x| x[0] }
23
+ [forenames.first, forenames[1..-1], initials]
24
+ end
25
+
26
+ def extractname(contributor)
27
+ org = contributor.entity if contributor.entity
28
+ .is_a?(RelatonBib::Organization)
29
+ person = contributor.entity if contributor.entity
30
+ .is_a?(RelatonBib::Person)
31
+ return { nonpersonal: extract_orgname(org) } if org
32
+ return extract_personname(person) if person
33
+
34
+ nil
35
+ end
36
+
37
+ def contributor_role(contributors)
38
+ return nil unless contributors.length.positive?
39
+
40
+ desc = contributors[0].role.first.description.join("\n")
41
+ type = contributors[0].role.first.type
42
+ desc.empty? ? type : desc
43
+ end
44
+
45
+ def creatornames(doc)
46
+ cr = creatornames1(doc)
47
+ cr.empty? and return [nil, nil]
48
+ [cr.map { |x| extractname(x) }, contributor_role(cr)]
49
+ end
50
+
51
+ def creatornames_roles_allowed
52
+ %w(author performer adapter translator editor publisher distributor)
53
+ end
54
+
55
+ def creatornames1(doc)
56
+ cr = []
57
+ return [] if doc.nil?
58
+
59
+ creatornames_roles_allowed.each do |r|
60
+ add = pick_contributor(doc, r)
61
+ next if add.nil?
62
+
63
+ cr = add and break
64
+ end
65
+ cr.nil? and cr = doc.contributor
66
+ cr
67
+ end
68
+
69
+ def datepick(date)
70
+ return nil if date.nil?
71
+
72
+ on = date.on
73
+ from = date.from
74
+ to = date.to
75
+ return { on: on } if on
76
+ return { from: from, to: to } if from
77
+
78
+ nil
79
+ end
80
+
81
+ def date1(date)
82
+ %w(published issued circulated).each do |t|
83
+ ret = date.detect { |x| x.type == t } and
84
+ return ret
85
+ end
86
+ date.first
87
+ end
88
+
89
+ def date(doc, host)
90
+ ret = date1(doc.date)
91
+ host and ret ||= date1(host.date)
92
+ datepick(ret)
93
+ end
94
+
95
+ def date_updated(doc, host)
96
+ ret = doc.date.detect { |x| x.type == "updated" }
97
+ host and ret ||= host.date.detect { |x| x.type == "updated" }
98
+ datepick(ret)
99
+ end
100
+
101
+ def date_accessed(doc, host)
102
+ ret = doc.date.detect { |x| x.type == "accessed" }
103
+ host and ret ||= host.date.detect { |x| x.type == "accessed" }
104
+ datepick(ret)
105
+ end
106
+
107
+ def publisher(doc, host)
108
+ x = pick_contributor(doc, "publisher")
109
+ host and x ||= pick_contributor(host, "publisher")
110
+ x.nil? and return nil
111
+ x.map { |c| extractname(c) }
112
+ end
113
+
114
+ def publisher_abbrev(doc, host)
115
+ x = pick_contributor(doc, "publisher")
116
+ host and x ||= pick_contributor(host, "publisher")
117
+ x.nil? and return nil
118
+ x.map { |c| c.entity.abbreviation&.content }
119
+ end
120
+
121
+ def distributor(doc, host)
122
+ x = pick_contributor(doc, "distributor")
123
+ host and x ||= pick_contributor(host, "distributor")
124
+ x.nil? and return nil
125
+ x.map { |c| extractname(c) }
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,176 @@
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
+ return nil if doc.nil? || doc.title.empty?
11
+
12
+ t = doc.title.select { |x| x.title.language&.include? @lang }
13
+ t.empty? and t = doc.title
14
+ t1 = t.select { |x| x.type == "main" }
15
+ t1.empty? and t1 = t
16
+ t1.first&.title&.content
17
+ end
18
+
19
+ def medium(doc, host)
20
+ x = doc.medium || host&.medium or return nil
21
+
22
+ %w(content genre form carrier size scale).each_with_object({}) do |i, m|
23
+ m[i] = x.send i
24
+ end.compact
25
+ end
26
+
27
+ def size(doc)
28
+ x = doc.size or return nil
29
+ x.size.each_with_object({}) do |v, m|
30
+ m[v.type] ||= []
31
+ m[v.type] << v.value
32
+ end
33
+ end
34
+
35
+ def edition(doc, host)
36
+ doc.edition || host&.edition
37
+ end
38
+
39
+ def place(doc, host)
40
+ x = doc.place
41
+ x.empty? && host and x = host.place
42
+ x.empty? and return x
43
+ x.map(&:name)
44
+ end
45
+
46
+ def series(doc)
47
+ doc.series.detect { |s| s.type == "main" } ||
48
+ doc.series.detect { |s| s.type.nil? } ||
49
+ doc.series.first
50
+ end
51
+
52
+ def series_title(series, _doc)
53
+ return nil if series.nil?
54
+
55
+ series.title.respond_to?(:titles) and
56
+ return series.title.titles.first.title.content
57
+ series.title&.title&.content || series.formattedref&.content
58
+ end
59
+
60
+ def series_formatted(series, _doc)
61
+ series.formattedref&.content
62
+ end
63
+
64
+ def series_abbr(series, _doc)
65
+ series.abbreviation&.content
66
+ end
67
+
68
+ def series_num(series, _doc)
69
+ series.number
70
+ end
71
+
72
+ def series_partnumber(series, _doc)
73
+ series.partnumber
74
+ end
75
+
76
+ def series_run(series, _doc)
77
+ series.run
78
+ end
79
+
80
+ def standardidentifier(doc)
81
+ doc.docidentifier.each_with_object([]) do |id, ret|
82
+ ret << id.id unless standardidentifier_exclude.include? id.type
83
+ end
84
+ end
85
+
86
+ def standardidentifier_exclude
87
+ %w(metanorma metanorma-ordinal)
88
+ end
89
+
90
+ def uri(doc)
91
+ uri = nil
92
+ %w(doi uri src).each do |t|
93
+ uri = doc.link.detect { |u| u.type == t } and break
94
+ end
95
+ uri ||= doc.link.first
96
+ return nil unless uri
97
+
98
+ uri.content.to_s
99
+ end
100
+
101
+ def access_location(doc, host)
102
+ x = doc.accesslocation || host&.accesslocation or
103
+ return nil
104
+ x.first
105
+ end
106
+
107
+ def included(type)
108
+ ["article", "inbook", "incollection", "inproceedings"].include? type
109
+ end
110
+
111
+ def type(doc)
112
+ type = doc.type and return type
113
+ doc.relation.any? { |r| r.type == "includedIn" } and return "inbook"
114
+ "book"
115
+ end
116
+
117
+ def extent1(localities)
118
+ localities.each_with_object({}) do |l, ret|
119
+ ret[(l.type || "page").to_sym] = {
120
+ from: localized_string_or_text(l.reference_from),
121
+ to: localized_string_or_text(l.reference_to),
122
+ }
123
+ end
124
+ end
125
+
126
+ def extent(doc)
127
+ doc.extent.each_with_object([]) do |e, acc|
128
+ case e
129
+ when RelatonBib::LocalityStack
130
+ acc << extent1(e.locality)
131
+ when RelatonBib::Locality
132
+ acc << extent1(Array(e))
133
+ end
134
+ end
135
+ end
136
+
137
+ def draft(doc, host)
138
+ dr = doc.status&.stage&.value || host&.status&.stage&.value
139
+
140
+ { iteration: iter_ordinal(doc) || iter_ordinal(host), status: dr }
141
+ end
142
+
143
+ def iter_ordinal(doc)
144
+ return nil unless iter = doc&.status&.iteration
145
+
146
+ iter
147
+ end
148
+
149
+ def status(doc)
150
+ doc.status&.stage&.value
151
+ end
152
+
153
+ private
154
+
155
+ def blank?(text)
156
+ text.nil? || text.empty?
157
+ end
158
+
159
+ def pick_contributor(doc, role)
160
+ ret = doc.contributor.select do |c|
161
+ c.role.any? { |r| r.type == role }
162
+ end
163
+ ret.empty? ? nil : ret
164
+ end
165
+
166
+ def localized_string_or_text(str)
167
+ case str
168
+ when RelatonBib::LocalizedString
169
+ str.content
170
+ when String
171
+ str
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,15 @@
1
+ module Relaton
2
+ module Render
3
+ module Template
4
+ module CapitalizeFirst
5
+ def capitalize_first(words)
6
+ return nil if words.nil?
7
+
8
+ ret = words.split(/[ _]/)
9
+ ret.first.capitalize! if ret.size.positive?
10
+ ret.join("_")
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,184 @@
1
+ require_relative "../utils/utils"
2
+ require_relative "liquid"
3
+
4
+ module Relaton
5
+ module Render
6
+ module Template
7
+ class General
8
+ def initialize(opt = {})
9
+ @htmlentities = HTMLEntities.new
10
+ customise_liquid
11
+ parse_options(opt)
12
+ end
13
+
14
+ def parse_options(opt)
15
+ opt = Utils::sym_keys(opt)
16
+ @i18n = opt[:i18n]
17
+ @template_raw = opt[:template].dup
18
+ @template =
19
+ case opt[:template]
20
+ when Hash
21
+ opt[:template].transform_values { |x| template_process(x) }
22
+ when Array then opt[:template].map { |x| template_process(x) }
23
+ else { default: template_process(opt[:template]) }
24
+ end
25
+ end
26
+
27
+ def customise_liquid
28
+ ::Liquid::Template
29
+ .register_filter(::Relaton::Render::Template::CapitalizeFirst)
30
+ end
31
+
32
+ # denote start and end of field,
33
+ # so that we can detect empty fields in postprocessing
34
+ FIELD_DELIM = "\u0018".freeze
35
+
36
+ # escape < >
37
+ LT_DELIM = "\u0019".freeze
38
+ GT_DELIM = "\u001a".freeze
39
+
40
+ # use tab internally for non-spacing delimiter
41
+ NON_SPACING_DELIM = "\t".freeze
42
+
43
+ def template_process(template)
44
+ t = template.gsub(/\{\{/, "#{FIELD_DELIM}{{")
45
+ .gsub(/\}\}/, "}}#{FIELD_DELIM}")
46
+ .gsub(/\t/, " ")
47
+ t1 = t.split(/(\{\{.+?\}\})/).map do |n|
48
+ n.include?("{{") ? n : n.gsub(/(?<!\\)\|/, "\t")
49
+ end.join
50
+ ::Liquid::Template.parse(t1)
51
+ end
52
+
53
+ def render(hash)
54
+ t = template_select(hash) or return nil
55
+
56
+ template_clean(t.render(liquid_hash(hash.merge("labels" => @i18n.get))))
57
+ end
58
+
59
+ def template_select(_hash)
60
+ @template[:default]
61
+ end
62
+
63
+ def template_clean(str)
64
+ str = str.gsub(/&#x3c;/i, LT_DELIM).gsub(/&#x3e;/i, GT_DELIM)
65
+ str = template_clean1(@htmlentities.decode(str))
66
+ /[[:alnum:]]/.match?(str) or return nil
67
+ str.strip.gsub(/#{LT_DELIM}/o, "&#x3c;")
68
+ .gsub(/#{GT_DELIM}/o, "&#x3e;")
69
+ .gsub(/&(?!#\S+?;)/, "&#x26;")
70
+ end
71
+
72
+ # use tab internally for non-spacing delimiter
73
+ def template_clean1(str)
74
+ str.gsub(/\S*#{FIELD_DELIM}#{FIELD_DELIM}\S*/o, "")
75
+ .gsub(/#{FIELD_DELIM}/o, "")
76
+ .gsub(/([,:;]\s*)+([,:;](\s|$))/, "\\2")
77
+ .gsub(/([,.:;]\s*)+([.](\s|$))/, "\\2")
78
+ .gsub(/([,:;]\s*)+(,(\s|$))/, "\\2")
79
+ .gsub(/(:\s+)(&\s)/, "\\2")
80
+ .gsub(/\s+([,.:;)])/, "\\1")
81
+ .sub(/^\s*[,.:;]\s*/, "")
82
+ .sub(/[,:;]\s*$/, "")
83
+ .gsub(/_/, " ")
84
+ .gsub(/#{NON_SPACING_DELIM}/o, "").gsub(/\s+/, " ")
85
+ end
86
+
87
+ # need non-breaking spaces in fields: "Updated:_nil" ---
88
+ # we want the "Updated:" deleted,
89
+ # even if it's multiple words, as in French Mise_à_jour.
90
+ def liquid_hash(hash)
91
+ case hash
92
+ when Hash
93
+ hash.map { |k, v| [k.to_s, liquid_hash(v)] }.to_h
94
+ when Array
95
+ hash.map { |v| liquid_hash(v) }
96
+ when String
97
+ hash.empty? ? nil : hash.gsub(/ /, "_")
98
+ else hash
99
+ end
100
+ end
101
+ end
102
+
103
+ class Series < General
104
+ end
105
+
106
+ class Extent < General
107
+ def template_select(hash)
108
+ @template[hash[:type].to_sym]
109
+ end
110
+ end
111
+
112
+ class Size < General
113
+ def template_select(hash)
114
+ @template[hash[:type].to_sym]
115
+ end
116
+ end
117
+
118
+ class Name < General
119
+ def initialize(opt = {})
120
+ @etal_count = opt[:template]["etal_count"]
121
+ opt[:template].delete("etal_count")
122
+ super
123
+ end
124
+
125
+ def template_select(names)
126
+ return nil if names.nil? || names.empty?
127
+
128
+ case names[:surname].size
129
+ when 1 then @template[:one]
130
+ when 2 then @template[:two]
131
+ when 3 then @template[:more]
132
+ else template_select_etal(names)
133
+ end
134
+ end
135
+
136
+ def template_select_etal(names)
137
+ if @etal_count && names[:surname].size >= @etal_count
138
+ @template[:etal]
139
+ else expand_nametemplate(@template_raw[:more], names[:surname].size)
140
+ end
141
+ end
142
+
143
+ # assumes that template contains, consecutively and not interleaved,
144
+ # ...[0], ...[1], ...[2]
145
+ def expand_nametemplate(template, size)
146
+ t = nametemplate_split(template)
147
+ mid = (1..size - 2).each_with_object([]) do |i, m|
148
+ m << t[1].gsub(/\[1\]/, "[#{i}]")
149
+ end
150
+ template_process(t[0] + mid.join + t[2].gsub(/\[2\]/,
151
+ "[#{size - 1}]"))
152
+ end
153
+
154
+ def nametemplate_split(template)
155
+ curr = 0
156
+ prec = ""
157
+ t = template.split(/(\{[{%].+?[}%]\})/)
158
+ .each_with_object(["", "", ""]) do |n, m|
159
+ m, curr, prec = nametemplate_split1(n, m, curr, prec)
160
+
161
+ m
162
+ end
163
+ t[-1] += prec
164
+ t
165
+ end
166
+
167
+ def nametemplate_split1(elem, acc, curr, prec)
168
+ if match = /\{[{%].+?\[(\d)\]/.match(elem)
169
+ curr += 1 if match[1].to_i > curr
170
+ acc[curr] += prec
171
+ prec = ""
172
+ acc[curr] += elem
173
+ elsif /\{%\s*endif/.match?(elem)
174
+ acc[curr] += prec
175
+ prec = ""
176
+ acc[curr] += elem
177
+ else prec += elem
178
+ end
179
+ [acc, curr, prec]
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
File without changes
@@ -1,5 +1,5 @@
1
1
  module Relaton
2
2
  module Render
3
- VERSION = "0.1.0".freeze
3
+ VERSION = "0.3.3".freeze
4
4
  end
5
5
  end
@@ -1,6 +1,7 @@
1
- require "relaton/version"
2
- require "relaton/render"
3
- require "parse/parse"
4
- require "template/template"
1
+ require "relaton/render/version"
2
+ require "relaton/render/general/render"
3
+ require "relaton/render/fields/fields"
4
+ require "relaton/render/parse/parse"
5
+ require "relaton/render/utils/utils"
5
6
  require "isodoc/i18n"
6
7
 
@@ -1,6 +1,6 @@
1
1
  lib = File.expand_path("lib", __dir__)
2
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require "relaton/version"
3
+ require "relaton/render/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "relaton-render"
@@ -31,5 +31,8 @@ Gem::Specification.new do |spec|
31
31
  spec.add_dependency "isodoc-i18n"
32
32
  spec.add_dependency "liquid", "~> 4"
33
33
  spec.add_dependency "nokogiri"
34
+ spec.add_dependency "relaton-bib", ">= 1.11.0"
34
35
  spec.add_dependency "twitter_cldr"
36
+ spec.add_dependency "tzinfo-data" # we need this for windows only
37
+ #spec.metadata["rubygems_mfa_required"] = "true"
35
38
  end
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: 0.1.0
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-01 00:00:00.000000000 Z
11
+ date: 2022-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: relaton-bib
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 1.11.0
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 1.11.0
139
153
  - !ruby/object:Gem::Dependency
140
154
  name: twitter_cldr
141
155
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +164,20 @@ dependencies:
150
164
  - - ">="
151
165
  - !ruby/object:Gem::Version
152
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: tzinfo-data
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
153
181
  description: Rendering of ISO 690 XML
154
182
  email:
155
183
  - open.source@ribose.com
@@ -175,17 +203,19 @@ files:
175
203
  - lib/isodoc-yaml/i18n-ru.yaml
176
204
  - lib/isodoc-yaml/i18n-zh-Hans.yaml
177
205
  - lib/isodoc/i18n.rb
178
- - lib/parse/parse.rb
179
- - lib/parse/parse_contributors.rb
180
- - lib/parse/parse_extract.rb
181
206
  - lib/relaton-render.rb
182
- - lib/relaton/config.yml
183
- - lib/relaton/render.rb
184
- - lib/relaton/render_classes.rb
185
- - lib/relaton/render_fields.rb
186
- - lib/relaton/version.rb
187
- - lib/template/template.rb
188
- - lib/utils/utils.rb
207
+ - lib/relaton/render/fields/date.rb
208
+ - lib/relaton/render/fields/fields.rb
209
+ - lib/relaton/render/general/config.yml
210
+ - lib/relaton/render/general/render.rb
211
+ - lib/relaton/render/general/render_classes.rb
212
+ - lib/relaton/render/parse/parse.rb
213
+ - lib/relaton/render/parse/parse_contributors.rb
214
+ - lib/relaton/render/parse/parse_extract.rb
215
+ - lib/relaton/render/template/liquid.rb
216
+ - lib/relaton/render/template/template.rb
217
+ - lib/relaton/render/utils/utils.rb
218
+ - lib/relaton/render/version.rb
189
219
  - relaton-render.gemspec
190
220
  homepage: https://github.com/relaton/relaton-render
191
221
  licenses:
data/lib/parse/parse.rb DELETED
@@ -1,54 +0,0 @@
1
- require "nokogiri"
2
- require "twitter_cldr"
3
- require_relative "parse_contributors"
4
- require_relative "parse_extract"
5
-
6
- class Iso690Parse
7
- def initialize; end
8
-
9
- def extract(doc)
10
- host = doc.at("./relation[@type = 'includedIn']/bibitem")
11
- simple_xml2hash(doc).merge(simple_or_host_xml2hash(doc, host))
12
- .merge(host_xml2hash(host))
13
- .merge(series_xml2hash(doc, host))
14
- end
15
-
16
- def simple_xml2hash(doc)
17
- creators, role = creatornames(doc)
18
- { type: type(doc), title: title(doc), extent_raw: extent(doc),
19
- standardidentifier: standardidentifier(doc), uri: uri(doc),
20
- status: status(doc), creators: creators, role_raw: role }
21
- end
22
-
23
- def simple_or_host_xml2hash(doc, host)
24
- { edition_raw: edition(doc, host), medium: medium(doc, host),
25
- place: place(doc, host), publisher: publisher(doc, host),
26
- distributor: distributor(doc, host),
27
- access_location: access_location(doc, host),
28
- date: date(doc, host), date_updated: date_updated(doc, host),
29
- date_accessed: date_accessed(doc, host) }
30
- end
31
-
32
- def host_xml2hash(host)
33
- creators, role = creatornames(host)
34
- { host_creators: creators, host_role_raw: role, host_title: title(host) }
35
- end
36
-
37
- def series_xml2hash(doc, host)
38
- series = doc.at("./series[@type = 'main']") ||
39
- doc.at("./series[not(@type)]") || doc.at("./series")
40
- host and series ||=
41
- host.at("./series[@type = 'main']") ||
42
- host.at("./series[not(@type)]") || host.at("./series")
43
-
44
- series_xml2hash1(series)
45
- end
46
-
47
- def series_xml2hash1(series)
48
- return {} unless series
49
-
50
- { series_title: series_title(series), series_abbr: series_abbr(series),
51
- series_run: series_run(series), series_num: series_num(series),
52
- series_partnumber: series_partnumber(series) }
53
- end
54
- end