require 'kramdown-rfc/erb'
module KramdownRFC
extend Kramdown::Utils::Html
def self.escattr(str)
escape_html(str.to_s, :attribute)
end
AUTHOR_ATTRIBUTES = %w{
initials surname fullname
asciiInitials asciiSurname asciiFullname
role
}
def self.ref_to_xml(k, v)
vps = KramdownRFC::ParameterSet.new(v)
erb = ERB.trim_new <<-REFERB, '-'
<reference anchor="<%= escattr(k) %>" <%=
vps.attrstf("target", "quoteTitle=quote-title=quotetitle=qt") %>>
<front>
<%= vps.ele("title") -%>
<% vps.arr("author", true, true) do |au|
aups = authorps_from_hash(au)
-%>
<author <%=aups.attrs(*AUTHOR_ATTRIBUTES)%>>
<%= aups.ele("organization=org", aups.attr("abbrev=orgabbrev"), "") %>
</author>
<% aups.warn_if_leftovers -%>
<% end -%>
<date <%= dateattrs(vps[:date]) %>/>
</front>
<% vps.arr("seriesinfo", false) do |k, v| -%>
<seriesInfo name="<%=escattr(k)%>" value="<%=escattr(v)%>"/>
<% end -%>
<% vps.arr("format", false) do |k, v| -%>
<format type="<%=escattr(k)%>" target="<%=escattr(v)%>"/>
<% end -%>
<%= vps.ele("annotation=ann", nil, nil, true) -%>
<%= vps.ele("refcontent=rc", nil, nil, true) -%>
</reference>
REFERB
ret = erb.result(binding)
vps.warn_if_leftovers
ret
end
def self.treat_multi_attribute_member(ps, an)
value = ps.rest[an]
if Hash === value
value.each do |k, v|
ps.rest[if k == ':'
an
else
Kramdown::Element.attrmangle(k + an) ||
Kramdown::Element.attrmangle(k) ||
k
end] = v
end
end
end
def self.initializify(s) # XXX Jean-Pierre
w = '\p{Lu}\p{Lo}'
if s =~ /\A[-.#{w}]+[.]/u
$&
elsif s =~ /\A([#{w}])[^-]*/u
ret = "#$1."
while (s = $') && s =~ /\A(-[\p{L}])[^-]*/u
ret << "#$1."
end
ret
else
warn "*** Can't initializify #{s}"
s
end
end
def self.looks_like_initial(s)
s =~ /\A[\p{Lu}\p{Lo}]([-.][\p{Lu}\p{Lo}]?)*\z/u
end
def self.initials_from_parts_and_surname(aups, parts, s)
ssz = s.size
nonsurname = parts[0...-ssz]
if (ns = parts[-ssz..-1]) != s
warn "*** inconsistent surnames #{ns} and #{s}"
end
nonsurname.map{|x| initializify(x)}.join(" ")
end
def self.handle_ins(aups, ins_k, initials_k, surname_k)
if ins = aups[ins_k]
parts = ins.split('.').map(&:strip) # split on dots first
if parts == []
warn "*** an empty '#{ins_k}:' value is not useful, try leaving it out"
return
end
# Coalesce H.-P.
i = 1; while i < parts.size
if parts[i][0] == "-"
parts[i-1..i] = [parts[i-1] + "." + parts[i]]
else
i += 1
end
end
# Multiple surnames in ins?
parts[-1..-1] = parts[-1].split
s = if surname = aups.rest[surname_k]
surname.split
else parts.reverse.take_while{|x| !looks_like_initial(x)}.reverse
end
aups.rest[initials_k] = initials_from_parts_and_surname(aups, parts, s)
aups.rest[surname_k] = s.join(" ")
end
end
def self.handle_name(aups, fn_k, initials_k, surname_k)
if name = aups.rest[fn_k]
names = name.split(/ *\| */, 2) # boundary for given/last name
if names == []
warn "*** an empty '#{fn_k}:' value is not useful, try leaving it out"
return
end
if names[1]
aups.rest[fn_k] = name = names.join(" ") # remove boundary
if surname = aups.rest[surname_k]
if surname != names[1]
warn "*** inconsistent embedded surname #{names[1]} and surname #{surname}"
end
end
aups.rest[surname_k] = names[1]
end
parts = name.split
if parts == []
warn "*** a blank '#{fn_k}:' value is not useful, try leaving it out"
return
end
surname = aups.rest[surname_k] || parts[-1]
s = surname.split
aups.rest[initials_k] ||= initials_from_parts_and_surname(aups, parts, s)
aups.rest[surname_k] = s.join(" ")
end
end
def self.authorps_from_hash(au)
aups = KramdownRFC::ParameterSet.new(au)
if n = aups[:name]
warn "** both name #{n} and fullname #{fn} are set on one author" if fn = aups.rest["fullname"]
aups.rest["fullname"] = n
usename = true
end
["fullname", "ins", "initials", "surname"].each do |an|
treat_multi_attribute_member(aups, an)
end
handle_ins(aups, :ins, "initials", "surname")
handle_ins(aups, :asciiIns, "asciiInitials", "asciiSurname")
# hack ("heuristic for") initials and surname from name
# -- only works for people with exactly one last name and uncomplicated first names
# -- add surname for people with more than one last name
if usename
handle_name(aups, "fullname", "initials", "surname")
handle_name(aups, "asciiFullname", "asciiInitials", "asciiSurname")
end
aups
end
# The below anticipates the "postalLine" changes.
# If a postalLine is used (abbreviated "postal" in YAML),
# non-postalLine elements are appended as further postalLines.
# This prepares for how "country" is expected to be handled
# specially with the next schema update.
# So an address is now best keyboarded as:
# postal:
# - Foo Street
# - 28359 Bar
# country: Germany
PERSON_ERB = <<~ERB
<<%= element_name%> <%=aups.attrs(*AUTHOR_ATTRIBUTES)%>>
<%= aups.ele("organization=org", aups.attrs("abbrev=orgabbrev",
*[$options.v3 && "ascii=orgascii"]), "") %>
<address>
<% postal_elements = %w{extaddr pobox street cityarea city region code sortingcode country postal}.select{|gi| aups.has(gi)}
if postal_elements != [] -%>
<postal>
<% if pl = postal_elements.delete("postal") -%>
<%= aups.ele("postalLine=postal") %>
<% postal_elements.each do |gi| -%>
<%= aups.ele("postalLine=" << gi) %>
<% end -%>
<% else -%>
<% postal_elements.each do |gi| -%>
<%= aups.ele(gi) %>
<% end -%>
<% end -%>
</postal>
<% end -%>
<% %w{phone facsimile email uri}.select{|gi| aups.has(gi)}.each do |gi| -%>
<%= aups.ele(gi) %>
<% end -%>
</address>
</<%= element_name%>>
ERB
def self.person_element_from_aups(element_name, aups)
erb = ERB.trim_new(PERSON_ERB, '-')
erb.result(binding)
end
def self.dateattrs(date)
begin
case date
when /\A\d\d\d\d\z/
%{year="#{date}"}
when Integer
%{year="#{"%04d" % date}"}
when String
Date.parse("#{date}-01").strftime(%{year="%Y" month="%B"})
when Date
date.strftime(%{year="%Y" month="%B" day="%d"})
when Array # this allows to explicitly give a string
%{year="#{date.join(" ")}"}
when nil
%{year="n.d."}
end
rescue ArgumentError
warn "*** Invalid date: #{date} -- use 2012, 2012-07, or 2012-07-28"
end
end
end