relaton-render 0.10.2 → 1.0.0

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.
@@ -3,22 +3,28 @@ module Relaton
3
3
  module Template
4
4
  module CustomFilters
5
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("_")
6
+ words.nil? and return nil
7
+ # Split while preserving delimiters (spaces/underscores) and extracting XML tags
8
+ ret = words.split(/(<[^>]+>|[ _])/).reject(&:empty?)
9
+ # Find and capitalize the first element that is not a delimiter or XML tag
10
+ ret.each do |element|
11
+ element.match?(/^[ _]$/) || element.match?(/^<[^>]+>$/) and next
12
+ element.capitalize!
13
+ break
14
+ end
15
+ # Join with empty string since delimiters are preserved
16
+ ret.join.sub(/^[ _]+/, "").sub(/[ _]+$/, "")
11
17
  end
12
18
 
13
19
  def selective_upcase(text)
14
- return nil if text.nil?
15
-
16
- ret = text.split(/(\+\+\+[^+]+?\+\+\+)/)
17
- ret.map do |n|
20
+ text.nil? and return nil
21
+ # Split to extract both +++...+++ sections and XML tags
22
+ text.split(/(\+\+\+[^+]+?\+\+\+|<[^<>]+>)/).map do |n|
18
23
  if m = /^\+\+\+(.+)\+\+\+$/.match(n)
19
- m[1]
20
- else
21
- n.upcase
24
+ m[1] # Keep content inside +++ unchanged
25
+ elsif n.match?(/^<[^>]+>$/)
26
+ n # Keep XML tags unchanged
27
+ else n.upcase # Upcase everything else
22
28
  end
23
29
  end.join
24
30
  end
@@ -0,0 +1,117 @@
1
+ module Relaton
2
+ module Render
3
+ module Template
4
+ class Series < General
5
+ end
6
+
7
+ class Extent < General
8
+ def template_select(hash)
9
+ @template[hash[:type].to_sym]
10
+ end
11
+ end
12
+
13
+ class Size < General
14
+ def template_select(hash)
15
+ @template[hash[:type].to_sym]
16
+ end
17
+ end
18
+
19
+ class Name < General
20
+ def initialize(opt = {})
21
+ @etal_count = opt[:template]["etal_count"]
22
+ @etal_display = opt[:template]["etal_display"] || @etal_count
23
+ opt[:template].delete("etal_count")
24
+ opt[:template].delete("etal_display")
25
+ super
26
+ end
27
+
28
+ def template_select(names)
29
+ return nil if names.nil? || names.empty?
30
+
31
+ case names[:surname].size
32
+ when 1 then @template[:one]
33
+ when 2 then @template[:two]
34
+ when 3 then @template[:more]
35
+ else template_select_etal(names)
36
+ end
37
+ end
38
+
39
+ def template_select_etal(names)
40
+ if @etal_count && names[:surname].size > @etal_count
41
+ expand_nametemplate(@template_raw[:etal], @etal_display)
42
+ else
43
+ expand_nametemplate(@template_raw[:more], names[:surname].size)
44
+ end
45
+ end
46
+
47
+ # assumes that template contains, consecutively and not interleaved,
48
+ # ...[0], ...[1], ...[2]
49
+ def expand_nametemplate(template, size)
50
+ t = nametemplate_split(template)
51
+
52
+ mid = (1..size - 2).each_with_object([]) do |i, m|
53
+ m << t[1].gsub("[1]", "[#{i}]")
54
+ end
55
+ t[1] = mid.join
56
+ t[2].gsub!(/\[\d+\]/, "[#{size - 1}]")
57
+ template_process(combine_nametemplate(t, size))
58
+ end
59
+
60
+ def combine_nametemplate(parts, size)
61
+ case size
62
+ when 1 then parts[0] + parts[3]
63
+ when 2 then parts[0] + parts[2] + parts[3]
64
+ else parts.join
65
+ end
66
+ end
67
+
68
+ def nametemplate_split(template)
69
+ curr = 0
70
+ prec = ""
71
+ t = template.split(/(\{[{%][^{]+?[}%]\})/)
72
+ .each_with_object([""]) do |n, m|
73
+ m, curr, prec = nametemplate_split1(n, m, curr, prec)
74
+
75
+ m
76
+ end
77
+ [t[0], t[1], t[-1], prec]
78
+ end
79
+
80
+ def nametemplate_split1(elem, acc, curr, prec)
81
+ if match = /\{[{%].+?\[(\d)\]/.match(elem)
82
+ if match[1].to_i > curr
83
+ curr += 1
84
+ acc[curr] ||= ""
85
+ end
86
+ acc[curr] += prec
87
+ prec = ""
88
+ acc[curr] += elem
89
+ elsif /\{%\s*endif/.match?(elem)
90
+ acc[curr] += prec
91
+ prec = ""
92
+ acc[curr] += elem
93
+ else prec += elem
94
+ end
95
+ [acc, curr, prec]
96
+ end
97
+ end
98
+
99
+ class AuthorCite < Name
100
+ end
101
+
102
+ class Cite < General
103
+ def template_select(hash)
104
+ if hash[:citestyle].to_sym == :short
105
+ @template[hash[:type].to_sym]
106
+ else
107
+ @template[hash[:citestyle].to_sym]
108
+ end
109
+ end
110
+
111
+ def citation_styles
112
+ @template.keys
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -57,10 +57,12 @@ module Relaton
57
57
  env
58
58
  end
59
59
 
60
- # denote start and end of field,
61
- # so that we can detect empty fields in postprocessing
62
- # FIELD_DELIM = "\u0018".freeze
63
- FIELD_DELIM = "%%".freeze
60
+ # denote start and end of variable,
61
+ # so that we can detect empty variables in postprocessing
62
+ VARIABLE_DELIM = "%%".freeze
63
+
64
+ # denote citation components which get delimited by period conventionally
65
+ COMPONENT_DELIM = Regexp.quote("$$$").freeze
64
66
 
65
67
  # escape < >
66
68
  LT_DELIM = "\u0019".freeze
@@ -72,8 +74,9 @@ module Relaton
72
74
  def punct_field?(name)
73
75
  name or return false
74
76
  name = name.tr("'", '"')
75
- %w(labels["qq-open"] labels["qq-close"] labels["q-open"]
76
- labels["q-close"]).include?(name)
77
+ %w(labels["punct"]["open-title"] labels["punct"]["close-title"]
78
+ labels["punct"]["open-secondary-title"]
79
+ labels["punct"]["close-secondary-title"]).include?(name)
77
80
  end
78
81
 
79
82
  def template_process(template)
@@ -92,19 +95,25 @@ module Relaton
92
95
  def add_field_delim_to_template(template)
93
96
  t = template.split(/(\{\{|\}\})/).each_slice(4).map do |a|
94
97
  unless !a[2] || punct_field?(a[2]&.strip)
95
- a[1] = "#{FIELD_DELIM}{{"
96
- a[3] = "}}#{FIELD_DELIM}"
98
+ a[1] = "#{VARIABLE_DELIM}{{"
99
+ a[3] = "}}#{VARIABLE_DELIM}"
97
100
  end
98
101
  a.join
99
102
  end.join.tr("\t", " ")
100
- t.gsub(/\}\}#{FIELD_DELIM}\|/o, "}}#{FIELD_DELIM}#{NON_SPACING_DELIM}")
101
- .gsub(/\|#{FIELD_DELIM}\{\{/o, "#{NON_SPACING_DELIM}#{FIELD_DELIM}{{")
103
+ t.gsub(/\}\}#{VARIABLE_DELIM}\|/o, "}}#{VARIABLE_DELIM}#{NON_SPACING_DELIM}")
104
+ .gsub(/\|#{VARIABLE_DELIM}\{\{/o, "#{NON_SPACING_DELIM}#{VARIABLE_DELIM}{{")
102
105
  end
103
106
 
104
- def render(hash)
105
- t = template_select(hash) or return nil
106
-
107
- template_clean(t.render(liquid_hash(hash.merge("labels" => @i18n.get))))
107
+ # hash is what to render, which can be entire bib entry,
108
+ # or a subset of it like names
109
+ # context is information for selecting i18n, which is entire bib entry,
110
+ # potentially enhanced
111
+ def render(hash, context)
112
+ t = template_select(hash) or return nil # TODO select on context?
113
+ i = @i18n.select(context).get
114
+ ret = template_clean(t.render(liquid_hash(hash.merge("labels" => i))))
115
+ template_components(ret,
116
+ i.dig("punct", "biblio-field-delimiter") || ". ")
108
117
  end
109
118
 
110
119
  def template_select(_hash)
@@ -120,27 +129,70 @@ module Relaton
120
129
  .gsub(/&(?!#\S+?;)/, "&#x26;")
121
130
  end
122
131
 
123
- # use tab internally for non-spacing delimiter
124
132
  def template_clean1(str)
125
- str.gsub(/\S*#{FIELD_DELIM}#{FIELD_DELIM}\S*/o, "")
126
- .gsub(/#{FIELD_DELIM}/o, "")
127
- .gsub(/([,:;]\s*)+<\/esc>([,:;])(\s|_|$)/, "\\2</esc>\\3")
133
+ str = strip_empty_variables(str)
134
+ str.gsub(/([,:;]\s*)+<\/esc>([,:;])(\s|_|$)/, "\\2</esc>\\3")
128
135
  .gsub(/([,:;]\s*)+([,:;](\s|_|$))/, "\\2")
129
- .gsub(/([,.:;]\s*)+<\/esc>([.])(\s|_|$)/, "\\2</esc>\\3")
130
- .gsub(/([,.:;]\s*)+([.](\s|_|$))/, "\\2")
136
+ .gsub(/([,.:;]\s*)+<\/esc>([.])(\s|_|$)/, "\\2</esc>\\3") # move outside
137
+ .gsub(/([,.:;]\s*)+([.](\s|_|$))/, "\\2") # move outside
131
138
  .gsub(/([,:;]\s*)+<\/esc>(,)(\s|_|$)/, "\\2</esc>\\3")
132
139
  .gsub(/([,:;]\s*)+(,(\s|_|$))/, "\\2")
140
+ .gsub(/([,:;]\s*)+(#{COMPONENT_DELIM})/o, "\\2")
133
141
  .gsub(/(:\s+)(&\s)/, "\\2")
134
- .gsub(/\s+([,.:;)])/, "\\1")
135
- .sub(/^\s*[,.:;]\s*/, "")
142
+ .gsub(/\s+([,.:;)])/, "\\1") # trim around $$$
143
+ .sub(/^\s*[,.:;]\s*/, "") # no init $$$
136
144
  .sub(/[,:;]\s*$/, "")
137
145
  .gsub(/(?<!\\)_/, " ")
138
146
  .gsub("\\_", "_")
139
- .gsub(/#{NON_SPACING_DELIM}/o, "")
140
- .gsub(/\s+/, " ")
147
+ .gsub(/(?<!#{COMPONENT_DELIM})#{NON_SPACING_DELIM}(?!#{COMPONENT_DELIM})/o, "") # preserve NON_SPACING_DELIM near $$$
148
+ .gsub(/[\n\r ]+/, " ")
141
149
  .gsub(/<(\/)?esc>/i, "<\\1esc>")
142
150
  end
143
151
 
152
+ # get rid of all empty variables, and any text around them,
153
+ # including component delimiters:
154
+ # [{{}}]$$$ => ""
155
+ # [{{}}] $$$ => " $$$"
156
+ def strip_empty_variables(str)
157
+ str.gsub(/\S*#{VARIABLE_DELIM}#{VARIABLE_DELIM}\S*/o, "")
158
+ .gsub(/#{VARIABLE_DELIM}/o, "")
159
+ end
160
+
161
+ # delim = punct.biblio-field-terminator must not be i18n'ised:
162
+ # .</esc>. deletes first .
163
+ # .</esc>。does not delete first .
164
+ # So we do not want to pass delim in as .,
165
+ # and then have it i18n to 。after we are done parsing
166
+ #
167
+ # Do not strip any delimiters from final field in string
168
+ #
169
+ # if delim = ". " , then: ({{ series }}$$$|) => (series1.)
170
+ def template_components(str, delim)
171
+ str or return str
172
+ delimrstrip, delimre, delimrstripre = template_components_prep(delim)
173
+ ret = str.gsub(NON_SPACING_DELIM, "|").split(/#{COMPONENT_DELIM}/o)
174
+ .map(&:strip).reject(&:empty?)
175
+ ret = ret[0...-1].map do |s|
176
+ s.sub(/#{delimre}$/, "").sub(%r[#{delimre}(</[^>]+>)$], "\\1")
177
+ end + [ret.last]
178
+ delim != delimrstrip and # "." in field followed by ". " in delim
179
+ ret = remove_double_period(ret, delimrstripre)
180
+ ret.join(delim).gsub(/#{delim}\|/, delimrstrip)
181
+ end
182
+
183
+ def remove_double_period(ret, delimrstripre)
184
+ ret[0...-1].map do |s|
185
+ s.sub(/#{delimrstripre}$/, "")
186
+ .sub(%r[#{delimrstripre}(</[^>]+>)$], "\\1")
187
+ end + [ret.last]
188
+ end
189
+
190
+ def template_components_prep(delim)
191
+ [delim.rstrip, Regexp.quote(delim),
192
+ # if delim is esc'd, ignore the escs in the preceding span
193
+ Regexp.quote(delim.rstrip.gsub(%r{</?esc>}, ""))]
194
+ end
195
+
144
196
  # need non-breaking spaces in fields: "Updated:_nil" ---
145
197
  # we want the "Updated:" deleted,
146
198
  # even if it's multiple words, as in French Mise_à_jour.
@@ -156,104 +208,8 @@ module Relaton
156
208
  end
157
209
  end
158
210
  end
159
-
160
- class Series < General
161
- end
162
-
163
- class Extent < General
164
- def template_select(hash)
165
- @template[hash[:type].to_sym]
166
- end
167
- end
168
-
169
- class Size < General
170
- def template_select(hash)
171
- @template[hash[:type].to_sym]
172
- end
173
- end
174
-
175
- class Name < General
176
- def initialize(opt = {})
177
- @etal_count = opt[:template]["etal_count"]
178
- @etal_display = opt[:template]["etal_display"] || @etal_count
179
- opt[:template].delete("etal_count")
180
- opt[:template].delete("etal_display")
181
- super
182
- end
183
-
184
- def template_select(names)
185
- return nil if names.nil? || names.empty?
186
-
187
- case names[:surname].size
188
- when 1 then @template[:one]
189
- when 2 then @template[:two]
190
- when 3 then @template[:more]
191
- else template_select_etal(names)
192
- end
193
- end
194
-
195
- def template_select_etal(names)
196
- if @etal_count && names[:surname].size > @etal_count
197
- expand_nametemplate(@template_raw[:etal], @etal_display)
198
- else
199
- expand_nametemplate(@template_raw[:more], names[:surname].size)
200
- end
201
- end
202
-
203
- # assumes that template contains, consecutively and not interleaved,
204
- # ...[0], ...[1], ...[2]
205
- def expand_nametemplate(template, size)
206
- t = nametemplate_split(template)
207
-
208
- mid = (1..size - 2).each_with_object([]) do |i, m|
209
- m << t[1].gsub("[1]", "[#{i}]")
210
- end
211
- t[1] = mid.join
212
- t[2].gsub!(/\[\d+\]/, "[#{size - 1}]")
213
- template_process(combine_nametemplate(t, size))
214
- end
215
-
216
- def combine_nametemplate(parts, size)
217
- case size
218
- when 1 then parts[0] + parts[3]
219
- when 2 then parts[0] + parts[2] + parts[3]
220
- else parts.join
221
- end
222
- end
223
-
224
- def nametemplate_split(template)
225
- curr = 0
226
- prec = ""
227
- t = template.split(/(\{[{%].+?[}%]\})/)
228
- .each_with_object([""]) do |n, m|
229
- m, curr, prec = nametemplate_split1(n, m, curr, prec)
230
-
231
- m
232
- end
233
- [t[0], t[1], t[-1], prec]
234
- end
235
-
236
- def nametemplate_split1(elem, acc, curr, prec)
237
- if match = /\{[{%].+?\[(\d)\]/.match(elem)
238
- if match[1].to_i > curr
239
- curr += 1
240
- acc[curr] ||= ""
241
- end
242
- acc[curr] += prec
243
- prec = ""
244
- acc[curr] += elem
245
- elsif /\{%\s*endif/.match?(elem)
246
- acc[curr] += prec
247
- prec = ""
248
- acc[curr] += elem
249
- else prec += elem
250
- end
251
- [acc, curr, prec]
252
- end
253
- end
254
-
255
- class AuthorCite < Name
256
- end
257
211
  end
258
212
  end
259
213
  end
214
+
215
+ require_relative "subclasses"
@@ -1,5 +1,5 @@
1
1
  module Relaton
2
2
  module Render
3
- VERSION = "0.10.2".freeze
3
+ VERSION = "1.0.0".freeze
4
4
  end
5
5
  end
@@ -3,6 +3,7 @@ require "relaton/render/general/render"
3
3
  require "relaton/render/fields/fields"
4
4
  require "relaton/render/parse/parse"
5
5
  require "relaton/render/utils/utils"
6
+ require "relaton/render/i18n/i18n"
6
7
  require "isodoc/i18n"
7
8
  require "base64" # Liquid
8
9
  require "bigdecimal" # Liquid
@@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency "rake", ">= 12.3.3"
29
29
  spec.add_development_dependency "rspec", "~> 3.0"
30
30
  spec.add_development_dependency "simplecov"
31
+ spec.add_development_dependency "openssl"
31
32
 
32
33
  spec.add_dependency "isodoc-i18n", "~> 1.4.0"
33
34
  spec.add_dependency "liquid", "~> 5"
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.10.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-10-13 00:00:00.000000000 Z
11
+ date: 2025-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: openssl
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: isodoc-i18n
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -257,11 +271,13 @@ files:
257
271
  - lib/relaton/render/general/render.rb
258
272
  - lib/relaton/render/general/render_classes.rb
259
273
  - lib/relaton/render/general/uri.rb
274
+ - lib/relaton/render/i18n/i18n.rb
260
275
  - lib/relaton/render/parse/parse.rb
261
276
  - lib/relaton/render/parse/parse_contributors.rb
262
277
  - lib/relaton/render/parse/parse_extract.rb
263
278
  - lib/relaton/render/parse/parse_id.rb
264
279
  - lib/relaton/render/template/liquid.rb
280
+ - lib/relaton/render/template/subclasses.rb
265
281
  - lib/relaton/render/template/template.rb
266
282
  - lib/relaton/render/utils/utils.rb
267
283
  - lib/relaton/render/version.rb