relaton-render 0.1.0 → 0.3.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.
@@ -0,0 +1,188 @@
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
+ t.first&.title&.content
15
+ end
16
+
17
+ def medium(doc, host)
18
+ x = doc.medium || host&.medium or return nil
19
+
20
+ %w(content genre form carrier size scale).each_with_object({}) do |i, m|
21
+ m[i] = x.send i
22
+ end.compact
23
+ end
24
+
25
+ def size(doc)
26
+ x = doc.size or return nil
27
+ x.size.each_with_object({}) do |v, m|
28
+ m[v.type] ||= []
29
+ m[v.type] << v.value
30
+ end
31
+ end
32
+
33
+ def edition(doc, host)
34
+ doc.edition || host&.edition
35
+ end
36
+
37
+ def place(doc, host)
38
+ x = doc.place
39
+ x.empty? && host and x = host.place
40
+ x.empty? and return x
41
+ x.map(&:name)
42
+ end
43
+
44
+ def publisher(doc, host)
45
+ x = pick_contributor(doc, "publisher")
46
+ host and x ||= pick_contributor(host, "publisher")
47
+ x.nil? and return nil
48
+ x.map { |c| extractname(c) }
49
+ end
50
+
51
+ def distributor(doc, host)
52
+ x = pick_contributor(doc, "distributor")
53
+ host and x ||= pick_contributor(host, "distributor")
54
+ x.nil? and return nil
55
+ x.map { |c| extractname(c) }
56
+ end
57
+
58
+ def series(doc)
59
+ doc.series.detect { |s| s.type == "main" } ||
60
+ doc.series.detect { |s| s.type.nil? } ||
61
+ doc.series.first
62
+ end
63
+
64
+ def series_title(series, _doc)
65
+ return nil if series.nil?
66
+
67
+ series.title.respond_to?(:titles) and
68
+ return series.title.titles.first.title.content
69
+ series.title&.title&.content || series.formattedref&.content
70
+ end
71
+
72
+ def series_formatted(series, _doc)
73
+ series.formattedref&.content
74
+ end
75
+
76
+ def series_abbr(series, _doc)
77
+ series.abbreviation&.content
78
+ end
79
+
80
+ def series_num(series, _doc)
81
+ series.number
82
+ end
83
+
84
+ def series_partnumber(series, _doc)
85
+ series.partnumber
86
+ end
87
+
88
+ def series_run(series, _doc)
89
+ series.run
90
+ end
91
+
92
+ def standardidentifier(doc)
93
+ doc.docidentifier.each_with_object([]) do |id, ret|
94
+ ret << id.id unless standardidentifier_exclude.include? id.type
95
+ end
96
+ end
97
+
98
+ def standardidentifier_exclude
99
+ %w(metanorma metanorma-ordinal)
100
+ end
101
+
102
+ def uri(doc)
103
+ uri = nil
104
+ %i(doi uri src).each do |t|
105
+ uri = doc.link.detect { |u| u.type == t } and break
106
+ end
107
+ uri ||= doc.link.first
108
+ return nil unless uri
109
+
110
+ uri.content.to_s
111
+ end
112
+
113
+ def access_location(doc, host)
114
+ x = doc.accesslocation || host&.accesslocation or
115
+ return nil
116
+ x.first
117
+ end
118
+
119
+ def included(type)
120
+ ["article", "inbook", "incollection", "inproceedings"].include? type
121
+ end
122
+
123
+ def type(doc)
124
+ type = doc.type and return type
125
+ doc.relation.any? { |r| r.type == "includedIn" } and return "inbook"
126
+ "book"
127
+ end
128
+
129
+ def extent1(localities)
130
+ localities.each_with_object({}) do |l, ret|
131
+ ret[(l.type || "page").to_sym] = {
132
+ from: localized_string_or_text(l.reference_from),
133
+ to: localized_string_or_text(l.reference_to),
134
+ }
135
+ end
136
+ end
137
+
138
+ def extent(doc)
139
+ doc.extent.each_with_object([]) do |e, acc|
140
+ case e
141
+ when RelatonBib::LocalityStack
142
+ acc << extent1(e.locality)
143
+ when RelatonBib::Locality
144
+ acc << extent1(Array(e))
145
+ end
146
+ end
147
+ end
148
+
149
+ def draft(doc, host)
150
+ dr = doc.status&.stage&.value || host&.status&.stage&.value
151
+
152
+ { iteration: iter_ordinal(doc) || iter_ordinal(host), status: dr }
153
+ end
154
+
155
+ def iter_ordinal(doc)
156
+ return nil unless iter = doc&.status&.iteration
157
+
158
+ iter
159
+ end
160
+
161
+ def status(doc)
162
+ doc.status&.stage&.value
163
+ end
164
+
165
+ private
166
+
167
+ def blank?(text)
168
+ text.nil? || text.empty?
169
+ end
170
+
171
+ def pick_contributor(doc, role)
172
+ ret = doc.contributor.select do |c|
173
+ c.role.any? { |r| r.type == role }
174
+ end
175
+ ret.empty? ? nil : ret
176
+ end
177
+
178
+ def localized_string_or_text(str)
179
+ case str
180
+ when RelatonBib::LocalizedString
181
+ str.content
182
+ when String
183
+ str
184
+ end
185
+ end
186
+ end
187
+ end
188
+ 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,183 @@
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
+ .gsub(/_/, " ")
83
+ .gsub(/#{NON_SPACING_DELIM}/o, "").gsub(/\s+/, " ")
84
+ end
85
+
86
+ # need non-breaking spaces in fields: "Updated:_nil" ---
87
+ # we want the "Updated:" deleted,
88
+ # even if it's multiple words, as in French Mise_à_jour.
89
+ def liquid_hash(hash)
90
+ case hash
91
+ when Hash
92
+ hash.map { |k, v| [k.to_s, liquid_hash(v)] }.to_h
93
+ when Array
94
+ hash.map { |v| liquid_hash(v) }
95
+ when String
96
+ hash.empty? ? nil : hash.gsub(/ /, "_")
97
+ else hash
98
+ end
99
+ end
100
+ end
101
+
102
+ class Series < General
103
+ end
104
+
105
+ class Extent < General
106
+ def template_select(hash)
107
+ @template[hash[:type].to_sym]
108
+ end
109
+ end
110
+
111
+ class Size < General
112
+ def template_select(hash)
113
+ @template[hash[:type].to_sym]
114
+ end
115
+ end
116
+
117
+ class Name < General
118
+ def initialize(opt = {})
119
+ @etal_count = opt[:template]["etal_count"]
120
+ opt[:template].delete("etal_count")
121
+ super
122
+ end
123
+
124
+ def template_select(names)
125
+ return nil if names.nil? || names.empty?
126
+
127
+ case names[:surname].size
128
+ when 1 then @template[:one]
129
+ when 2 then @template[:two]
130
+ when 3 then @template[:more]
131
+ else template_select_etal(names)
132
+ end
133
+ end
134
+
135
+ def template_select_etal(names)
136
+ if @etal_count && names.size >= @etal_count
137
+ @template[:etal]
138
+ else expand_nametemplate(@template_raw[:more], names.size)
139
+ end
140
+ end
141
+
142
+ # assumes that template contains, consecutively and not interleaved,
143
+ # ...[0], ...[1], ...[2]
144
+ def expand_nametemplate(template, size)
145
+ t = nametemplate_split(template)
146
+ mid = (1..size - 2).each_with_object([]) do |i, m|
147
+ m << t[1].gsub(/\[1\]/, "[#{i}]")
148
+ end
149
+ template_process(t[0] + mid.join + t[2].gsub(/\[2\]/,
150
+ "[#{size - 1}]"))
151
+ end
152
+
153
+ def nametemplate_split(template)
154
+ curr = 0
155
+ prec = ""
156
+ t = template.split(/(\{[{%].+?[}%]\})/)
157
+ .each_with_object(["", "", ""]) do |n, m|
158
+ m, curr, prec = nametemplate_split1(n, m, curr, prec)
159
+
160
+ m
161
+ end
162
+ t[-1] += prec
163
+ t
164
+ end
165
+
166
+ def nametemplate_split1(elem, acc, curr, prec)
167
+ if match = /\{[{%].+?\[(\d)\]/.match(elem)
168
+ curr += 1 if match[1].to_i > curr
169
+ acc[curr] += prec
170
+ prec = ""
171
+ acc[curr] += elem
172
+ elsif /\{%\s*endif/.match?(elem)
173
+ acc[curr] += prec
174
+ prec = ""
175
+ acc[curr] += elem
176
+ else prec += elem
177
+ end
178
+ [acc, curr, prec]
179
+ end
180
+ end
181
+ end
182
+ end
183
+ 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.1".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.1
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-20 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
@@ -1,96 +0,0 @@
1
- class Iso690Parse
2
- def extract_orgname(org)
3
- name = org.at("./name")
4
- name&.text
5
- end
6
-
7
- def extract_personname(person)
8
- surname = person.at("./name/surname") || person.at("./name/completename")
9
- given, middle, initials = given_and_middle_name(person)
10
- { surname: surname&.text,
11
- given: given,
12
- middle: middle,
13
- initials: initials }
14
- end
15
-
16
- def given_and_middle_name(person)
17
- forenames = person.xpath("./name/forename")&.map(&:text)
18
- initials = person.xpath("./name/initial")&.map(&:text)
19
- forenames.empty? and initials.empty? and return [nil, nil, nil]
20
- forenames.empty? and forenames = initials.dup
21
- initials.empty? and initials = forenames.map { |x| x[0] }
22
- [forenames.first, forenames[1..-1], initials]
23
- end
24
-
25
- def extractname(contributor)
26
- org = contributor.at("./organization")
27
- person = contributor.at("./person")
28
- return { surname: extract_orgname(org) } if org
29
- return extract_personname(person) if person
30
-
31
- nil
32
- end
33
-
34
- def contributor_role(contributors)
35
- return nil unless contributors.length.positive?
36
-
37
- desc = contributors[0].at("role/description")&.text
38
- type = contributors[0].at("role/@type")&.text
39
- return nil if %w(author publisher).include?(type) && desc.nil?
40
-
41
- type
42
- end
43
-
44
- def creatornames(doc)
45
- cr = creatornames1(doc)
46
- cr.empty? and return [nil, nil]
47
- [cr.map { |x| extractname(x) }, contributor_role(cr)]
48
- end
49
-
50
- def creatornames1(doc)
51
- cr = []
52
- return cr if doc.nil?
53
-
54
- %w(author performer adapter translator editor publisher distributor)
55
- .each do |r|
56
- add = doc.xpath("./contributor[role/@type = '#{r}']")
57
- next if add.empty?
58
-
59
- cr = add and break
60
- end
61
- cr.empty? and cr = doc.xpath("./contributor")
62
- cr
63
- end
64
-
65
- def date1(date)
66
- on = date.at("./on")
67
- from = date.at("./from")
68
- to = date.at("./to")
69
- return { on: on.text } if on
70
- return { from: from.text, to: to&.text } if from
71
-
72
- nil
73
- end
74
-
75
- def date(doc, host)
76
- x = doc.at("./date[@type = 'issued']") ||
77
- doc.at("./date[@type = 'circulated']") ||
78
- doc.at("./date") ||
79
- host&.at("./date[@type = 'issued']") ||
80
- host&.at("./date[@type = 'circulated']") ||
81
- host&.at("./date") or return nil
82
- date1(x)
83
- end
84
-
85
- def date_updated(doc, host)
86
- x = doc.at("./date[@type = 'updated']") ||
87
- host&.at("./date[@type = 'updated']") or return nil
88
- date1(x)
89
- end
90
-
91
- def date_accessed(doc, host)
92
- x = doc.at("./date[@type = 'accessed']") ||
93
- host&.at("./date[@type = 'accessed']") or return nil
94
- date1(x)
95
- end
96
- end