relaton-render 0.1.0 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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