require_relative "../utils/utils" require_relative "liquid" module Relaton module Render module Template class General def initialize(opt = {}) @htmlentities = HTMLEntities.new customise_liquid parse_options(opt) end def parse_options(opt) opt = Utils::sym_keys(opt) @i18n = opt[:i18n] @template_raw = opt[:template].dup @template = case opt[:template] when Hash opt[:template].transform_values { |x| template_process(x) } when Array then opt[:template].map { |x| template_process(x) } else { default: template_process(opt[:template]) } end end def customise_liquid ::Liquid::Template .register_filter(::Relaton::Render::Template::CapitalizeFirst) end # denote start and end of field, # so that we can detect empty fields in postprocessing # FIELD_DELIM = "\u0018".freeze FIELD_DELIM = "%%".freeze # escape < > LT_DELIM = "\u0019".freeze GT_DELIM = "\u001a".freeze # use tab internally for non-spacing delimiter NON_SPACING_DELIM = "\t".freeze def punct_field?(name) name or return false name = name.gsub(/'/, '"') %w(labels["qq-open"] labels["qq-close"] labels["q-open"] labels["q-close"]).include?(name) end def template_process(template) template.is_a?(String) or return template ::Liquid::Template.parse(add_field_delim_to_template(template)) end def add_field_delim_to_template(template) t = template.split(/(\{\{|\}\})/).each_slice(4).map do |a| unless !a[2] || punct_field?(a[2]&.strip) a[1] = "#{FIELD_DELIM}{{" a[3] = "}}#{FIELD_DELIM}" end a.join end.join.gsub(/\t/, " ") t.gsub(/\}\}#{FIELD_DELIM}\|/o, "}}#{FIELD_DELIM}\t") .gsub(/\|#{FIELD_DELIM}\{\{/o, "\t#{FIELD_DELIM}{{") end def render(hash) t = template_select(hash) or return nil template_clean(t.render(liquid_hash(hash.merge("labels" => @i18n.get)))) end def template_select(_hash) @template[:default] end def template_clean(str) str = str.gsub(/</i, LT_DELIM).gsub(/>/i, GT_DELIM) str = template_clean1(@htmlentities.decode(str)) /[[:alnum:]]/.match?(str) or return nil str.strip.gsub(/#{LT_DELIM}/o, "<") .gsub(/#{GT_DELIM}/o, ">") .gsub(/&(?!#\S+?;)/, "&") end # use tab internally for non-spacing delimiter def template_clean1(str) str.gsub(/\S*#{FIELD_DELIM}#{FIELD_DELIM}\S*/o, "") .gsub(/#{FIELD_DELIM}/o, "") .gsub(/([,:;]\s*)+([,:;](\s|_|$))/, "\\2") .gsub(/([,.:;]\s*)+([.](\s|_|$))/, "\\2") .gsub(/([,:;]\s*)+(,(\s|_|$))/, "\\2") .gsub(/(:\s+)(&\s)/, "\\2") .gsub(/\s+([,.:;)])/, "\\1") .sub(/^\s*[,.:;]\s*/, "") .sub(/[,:;]\s*$/, "") .gsub(/(?= @etal_count expand_nametemplate(@template_raw[:etal], @etal_display) else expand_nametemplate(@template_raw[:more], names[:surname].size) end end # assumes that template contains, consecutively and not interleaved, # ...[0], ...[1], ...[2] def expand_nametemplate(template, size) t = nametemplate_split(template) mid = (1..size - 2).each_with_object([]) do |i, m| m << t[1].gsub(/\[1\]/, "[#{i}]") end t[1] = mid.join t[2].gsub!(/\[\d+\]/, "[#{size - 1}]") template_process(combine_nametemplate(t, size)) end def combine_nametemplate(parts, size) case size when 1 then parts[0] + parts[3] when 2 then parts[0] + parts[2] + parts[3] else parts.join end end def nametemplate_split(template) curr = 0 prec = "" t = template.split(/(\{[{%].+?[}%]\})/) .each_with_object([""]) do |n, m| m, curr, prec = nametemplate_split1(n, m, curr, prec) m end [t[0], t[1], t[-1], prec] end def nametemplate_split1(elem, acc, curr, prec) if match = /\{[{%].+?\[(\d)\]/.match(elem) if match[1].to_i > curr curr += 1 acc[curr] ||= "" end acc[curr] += prec prec = "" acc[curr] += elem elsif /\{%\s*endif/.match?(elem) acc[curr] += prec prec = "" acc[curr] += elem else prec += elem end [acc, curr, prec] end end class AuthorCite < Name end end end end