relaton-render 3.0.0.pre.alpha.24 → 3.0.0.pre.alpha.25

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.
@@ -1,123 +0,0 @@
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
- mid = (1..size - 2).each_with_object([]) do |i, m|
52
- m << t[1].gsub("[1]", "[#{i}]")
53
- end
54
- t[1] = mid.join
55
- t[2].gsub!(/\[\d+\]/, "[#{size - 1}]")
56
- template_process(combine_nametemplate(t, size))
57
- end
58
-
59
- def combine_nametemplate(parts, size)
60
- case size
61
- when 1 then parts[0] + parts[3]
62
- when 2 then parts[0] + parts[2] + parts[3]
63
- else parts.join
64
- end
65
- end
66
-
67
- def nametemplate_split(template)
68
- curr = 0
69
- prec = ""
70
- @in_name = 0
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
- # we are in a name component
83
- # if an if is started, track the if nesting...
84
- @in_name += 1 if /\{%\s*if/.match?(elem)
85
- if match[1].to_i > curr
86
- curr += 1
87
- acc[curr] ||= ""
88
- end
89
- acc[curr] += prec
90
- prec = ""
91
- acc[curr] += elem
92
- elsif /\{%\s*endif/.match?(elem) && @in_name.positive?
93
- # stick the endif to the currently if-nested name component,
94
- # if we are in one (@in_name.positive?)
95
- @in_name -= 1
96
- acc[curr] += prec
97
- prec = ""
98
- acc[curr] += elem
99
- else prec += elem
100
- end
101
- [acc, curr, prec]
102
- end
103
- end
104
-
105
- class AuthorCite < Name
106
- end
107
-
108
- class Cite < General
109
- def template_select(hash)
110
- if hash[:citestyle].to_sym == :short
111
- @template[hash[:type].to_sym]
112
- else
113
- @template[hash[:citestyle].to_sym]
114
- end
115
- end
116
-
117
- def citation_styles
118
- @template.keys
119
- end
120
- end
121
- end
122
- end
123
- end
@@ -1,230 +0,0 @@
1
- # require_relative "../utils/utils"
2
- require "liquid"
3
- require_relative "liquid"
4
- require_relative "hash_utils"
5
- require "singleton"
6
- require "htmlentities"
7
-
8
- module Relaton
9
- module Render
10
- module Template
11
- class CacheManager
12
- include Singleton
13
-
14
- attr_accessor :mutex
15
-
16
- Hash.include ::Relaton::Render::Legacy::HashCompat
17
- Array.include ::Relaton::Render::Legacy::ArrayCompat
18
-
19
- def initialize
20
- @cache = {}
21
- @mutex = Mutex.new
22
- end
23
-
24
- def store(key, value)
25
- @cache[key] = value
26
- end
27
-
28
- def retrieve(key)
29
- @cache[key]
30
- end
31
-
32
- def clear
33
- @cache.clear
34
- end
35
- end
36
-
37
- class General
38
- attr_reader :template_raw
39
-
40
- def initialize(opt = {})
41
- @htmlentities = HTMLEntities.new
42
- @templatecache = CacheManager.instance
43
- @liquid_env = create_liquid_environment
44
- parse_options(opt)
45
- end
46
-
47
- def parse_options(opt)
48
- # opt = Utils::sym_keys(opt)
49
- opt = opt.symbolize_all_keys
50
- @i18n = opt[:i18n]
51
- @template_raw = opt[:template].dup
52
- @template =
53
- case opt[:template]
54
- when Hash
55
- opt[:template].transform_values { |x| template_process(x) }
56
- when Array then opt[:template].map { |x| template_process(x) }
57
- else { default: template_process(opt[:template]) }
58
- end
59
- end
60
-
61
- def create_liquid_environment
62
- env = ::Liquid::Environment.new
63
- env.register_filter(::Relaton::Render::Template::CustomFilters)
64
- env
65
- end
66
-
67
- # denote start and end of variable,
68
- # so that we can detect empty variables in postprocessing
69
- VARIABLE_DELIM = "%%".freeze
70
-
71
- # denote citation components which get delimited by period conventionally
72
- COMPONENT_DELIM = Regexp.quote("$$$").freeze
73
-
74
- # escape < >
75
- LT_DELIM = "\u0019".freeze
76
- GT_DELIM = "\u001a".freeze
77
-
78
- # use tab internally for non-spacing delimiter
79
- NON_SPACING_DELIM = "\t".freeze
80
-
81
- def punct_field?(name)
82
- name or return false
83
- name = name.tr("'", '"')
84
- %w(labels["punct"]["open-title"] labels["punct"]["close-title"]
85
- labels["punct"]["open-secondary-title"]
86
- labels["punct"]["close-secondary-title"]).include?(name)
87
- end
88
-
89
- def template_process(template)
90
- template.is_a?(String) or return template
91
- t = nil
92
- @templatecache.mutex.synchronize do
93
- unless t = @templatecache.retrieve(template)
94
- t = ::Liquid::Template
95
- .parse(add_field_delim_to_template(template), environment: @liquid_env)
96
- @templatecache.store(template, t)
97
- end
98
- end
99
- t
100
- end
101
-
102
- def add_field_delim_to_template(template)
103
- t = template.split(/(\{\{|\}\})/).each_slice(4).map do |a|
104
- unless !a[2] || punct_field?(a[2]&.strip)
105
- a[1] = "#{VARIABLE_DELIM}{{"
106
- a[3] = "}}#{VARIABLE_DELIM}"
107
- end
108
- a.join
109
- end.join.tr("\t", " ")
110
- t.gsub(/\}\}#{VARIABLE_DELIM}\|/o, "}}#{VARIABLE_DELIM}#{NON_SPACING_DELIM}")
111
- .gsub(/\|#{VARIABLE_DELIM}\{\{/o, "#{NON_SPACING_DELIM}#{VARIABLE_DELIM}{{")
112
- end
113
-
114
- # hash is what to render, which can be entire bib entry,
115
- # or a subset of it like names
116
- # context is information for selecting i18n, which is entire bib entry,
117
- # potentially enhanced
118
- def render(hash, context)
119
- t = template_select(hash) or return nil # TODO select on context?
120
- i = @i18n.select(context)
121
- i18nsettings = { "labels" => i.get, "lang" => i.lang,
122
- "script" => i.script, "locale" => i.locale }
123
- ret = template_clean(t.render(liquid_hash(hash.merge(i18nsettings))))
124
- bib_delim = i.get.dig("punct", "biblio-field-delimiter") || ". "
125
- template_components(ret, bib_delim, context)
126
- end
127
-
128
- def template_select(_hash)
129
- @template[:default]
130
- end
131
-
132
- def template_clean(str)
133
- str = str.gsub(/&#x3c;/i, LT_DELIM).gsub(/&#x3e;/i, GT_DELIM)
134
- str = template_clean1(@htmlentities.decode(str))
135
- /[[:alnum:]]/.match?(str) or return nil
136
- str.strip.gsub(/#{LT_DELIM}/o, "&#x3c;")
137
- .gsub(/#{GT_DELIM}/o, "&#x3e;")
138
- .gsub(/&(?!#\S+?;)/, "&#x26;")
139
- end
140
-
141
- def template_clean1(str)
142
- str = strip_empty_variables(str)
143
- str.gsub(/([,:;]\s*)+<\/esc>([,:;])(\s|_|$)/, "\\2</esc>\\3")
144
- .gsub(/([,:;]\s*)+([,:;](\s|_|$))/, "\\2")
145
- .gsub(/([,.:;]\s*)+<\/esc>([.])(\s|_|$)/, "\\2</esc>\\3") # move outside
146
- .gsub(/([,.:;]\s*)+([.](\s|_|$))/, "\\2") # move outside
147
- .gsub(/([,:;]\s*)+<\/esc>(,)(\s|_|$)/, "\\2</esc>\\3")
148
- .gsub(/([,:;]\s*)+(,(\s|_|$))/, "\\2")
149
- .gsub(/([,:;]\s*)+(#{COMPONENT_DELIM})/o, "\\2")
150
- .gsub(/(:\s+)(&\s)/, "\\2")
151
- .gsub(/\s+([,.:;)])/, "\\1") # trim around $$$
152
- .sub(/^\s*[,.:;]\s*/, "") # no init $$$
153
- .sub(/[,:;]\s*$/, "")
154
- .gsub(/(?<!\\)_/, " ")
155
- .gsub("\\_", "_")
156
- .gsub(/(?<!#{COMPONENT_DELIM})#{NON_SPACING_DELIM}(?!#{COMPONENT_DELIM})/o, "") # preserve NON_SPACING_DELIM near $$$
157
- .gsub(/[\n\r ]+/, " ")
158
- .gsub(/<(\/)?esc>/i, "<\\1esc>")
159
- end
160
-
161
- # get rid of all empty variables, and any text around them,
162
- # including component delimiters:
163
- # [{{}}]$$$ => ""
164
- # [{{}}] $$$ => " $$$"
165
- def strip_empty_variables(str)
166
- str.gsub(/\S*#{VARIABLE_DELIM}#{VARIABLE_DELIM}\S*/o, "")
167
- .gsub(/#{VARIABLE_DELIM}/o, "")
168
- end
169
-
170
- # delim = punct.biblio-field-terminator must not be i18n'ised:
171
- # .</esc>. deletes first .
172
- # .</esc>。does not delete first .
173
- # So we do not want to pass delim in as .,
174
- # and then have it i18n to 。after we are done parsing
175
- #
176
- # Do not strip any delimiters from final field in string
177
- #
178
- # if delim = ". " , then: ({{ series }}$$$|) => (series1.)
179
- def template_components(str, delim, context)
180
- str or return str
181
- delimrstrip, delimre, delimrstripre = template_components_prep(delim)
182
- ret = template_components_split(str, delimre)
183
- delim != delimrstrip and # "." in field followed by ". " in delim
184
- ret = remove_double_period(ret, delimrstripre)
185
- context[:citestyle] == "short" and
186
- ret[0] += "<span class='fmt-first-biblio-delim'/>"
187
- ret.join(delim).gsub(/#{delim}\|/, delimrstrip)
188
- end
189
-
190
- def template_components_split(str, delimre)
191
- ret = str.gsub(NON_SPACING_DELIM, "|").split(/#{COMPONENT_DELIM}/o)
192
- .map(&:strip).reject(&:empty?)
193
- ret[0...-1].map do |s|
194
- s.sub(/#{delimre}$/, "").sub(%r[#{delimre}(</[^>]+>)$], "\\1")
195
- end + [ret.last]
196
- end
197
-
198
- def remove_double_period(ret, delimrstripre)
199
- ret[0...-1].map do |s|
200
- s.sub(/#{delimrstripre}$/, "")
201
- .sub(%r[#{delimrstripre}(</[^>]+>)$], "\\1")
202
- end + [ret.last]
203
- end
204
-
205
- def template_components_prep(delim)
206
- [delim.rstrip, Regexp.quote(delim),
207
- # if delim is esc'd, ignore the escs in the preceding span
208
- Regexp.quote(delim.rstrip.gsub(%r{</?esc>}, ""))]
209
- end
210
-
211
- # need non-breaking spaces in fields: "Updated:_nil" ---
212
- # we want the "Updated:" deleted,
213
- # even if it's multiple words, as in French Mise_à_jour.
214
- def liquid_hash(hash)
215
- case hash
216
- when Hash
217
- hash.map { |k, v| [k.to_s, liquid_hash(v)] }.to_h
218
- when Array
219
- hash.map { |v| liquid_hash(v) }
220
- when String
221
- hash.empty? ? nil : hash.gsub("_", "\\_").tr(" ", "_")
222
- else hash
223
- end
224
- end
225
- end
226
- end
227
- end
228
- end
229
-
230
- require_relative "subclasses"