require "date"
require "htmlentities"
module Metanorma
module Standoc
module Cleanup
def para_cleanup(xmldoc)
["//p[not(ancestor::bibdata)]", "//ol[not(ancestor::bibdata)]",
"//ul[not(ancestor::bibdata)]", "//quote[not(ancestor::bibdata)]",
"//dl[not(ancestor::bibdata)]",
"//note[not(ancestor::bibitem or " \
"ancestor::table or ancestor::bibdata)]"].each do |w|
inject_id(xmldoc, w)
end
end
def inject_id(xmldoc, path)
xmldoc.xpath(path).each do |x|
x["id"] ||= Metanorma::Utils::anchor_or_uuid
end
end
# include where definition list inside stem block
def formula_cleanup(formula)
formula_cleanup_where1(formula)
formula_cleanup_where2(formula)
end
def formula_cleanup_where1(formula)
q = "//formula/following-sibling::*[1][self::dl]"
formula.xpath(q).each do |s|
s["key"] == "true" and s.previous_element << s.remove
end
end
def formula_cleanup_where2(formula)
q = "//formula/following-sibling::*[1][self::p]"
formula.xpath(q).each do |s|
if s.text =~ /^\s*where[^a-z]*$/i && s&.next_element&.name == "dl"
s.next_element["key"] = "true"
s.previous_element << s.next_element.remove
s.remove
end
end
end
def figure_dl_cleanup1(xmldoc)
q = "//figure/following-sibling::*[self::dl]"
q1 = "//figure/figure/following-sibling::*[self::dl]"
(xmldoc.xpath(q) - xmldoc.xpath(q1)).each do |s|
s["key"] == "true" and s.previous_element << s.remove
end
end
# include key definition list inside figure
def figure_dl_cleanup2(xmldoc)
q = "//figure/following-sibling::*[self::p]"
xmldoc.xpath(q).each do |s|
if s.text =~ /^\s*key[^a-z]*$/i && s&.next_element&.name == "dl"
s.next_element["key"] = "true"
s.previous_element << s.next_element.remove
s.remove
end
end
end
# examples containing only figures become subfigures of figures
def subfigure_cleanup(xmldoc)
xmldoc.xpath("//example[figure]").each do |e|
next unless e.elements.reject do |m|
%w(name figure index note).include?(m.name) ||
(m.name == "dl" && m["key"] == "true")
end.empty?
e.name = "figure"
end
end
def single_subfigure_cleanup(xmldoc)
xmldoc.xpath("//figure[figure]").each do |e|
s = e.xpath("./figure")
return unless s.size == 1
s[0].replace(s[0].children)
end
end
def figure_cleanup(xmldoc)
figure_footnote_cleanup(xmldoc)
subfigure_cleanup(xmldoc)
figure_dl_cleanup1(xmldoc)
figure_dl_cleanup2(xmldoc)
single_subfigure_cleanup(xmldoc)
end
ELEMS_ALLOW_NOTES = %w[p formula ul ol dl figure].freeze
# if a note is at the end of a section, it is left alone
# if a note is followed by a non-note block,
# it is moved inside its preceding block if it is not delimited
# (so there was no way of making that block include the note)
def note_cleanup(xmldoc)
q = "//note[following-sibling::*[not(local-name() = 'note')]]"
xmldoc.xpath(q).each do |n|
next if n["keep-separate"] == "true" || !n.ancestors("table").empty?
prev = n.previous_element || next
n.parent = prev if ELEMS_ALLOW_NOTES.include? prev.name
end
xmldoc.xpath("//note[@keep-separate] | " \
"//termnote[@keep-separate]").each do |n|
n.delete("keep-separate")
end
end
def link_callouts_to_annotations(callouts, annotations)
callouts.each_with_index do |c, i|
c["target"] = "_#{UUIDTools::UUID.random_create}"
annotations[i]["id"] = c["target"]
end
end
def align_callouts_to_annotations(xmldoc)
xmldoc.xpath("//sourcecode").each do |x|
callouts = x.elements.select { |e| e.name == "callout" }
annotations = x.elements.select { |e| e.name == "annotation" }
callouts.size == annotations.size and
link_callouts_to_annotations(callouts, annotations)
end
end
def merge_annotations_into_sourcecode(xmldoc)
xmldoc.xpath("//sourcecode").each do |x|
while x&.next_element&.name == "annotation"
x.next_element.parent = x
end
end
end
def callout_cleanup(xmldoc)
merge_annotations_into_sourcecode(xmldoc)
align_callouts_to_annotations(xmldoc)
end
def sourcecode_cleanup(xmldoc)
xmldoc.xpath("//sourcecode").each do |x|
x.traverse do |n|
next unless n.text?
next unless /#{Regexp.escape(@sourcecode_markup_start)}/
.match?(n.text)
n.replace(sourcecode_markup(n))
end
end
end
def safe_noko(text, doc)
Nokogiri::XML::Text.new(text, doc).to_xml(
encoding: "US-ASCII",
save_with: Nokogiri::XML::Node::SaveOptions::NO_DECLARATION,
)
end
def sourcecode_markup(node)
node.text.split(/(#{Regexp.escape(@sourcecode_markup_start)}|
#{Regexp.escape(@sourcecode_markup_end)})/x)
.each_slice(4).map.with_object([]) do |a, acc|
acc << safe_noko(a[0], node.document)
next unless a.size == 4
acc << Asciidoctor.convert(
a[2], doctype: :inline, backend: (self&.backend&.to_sym || :standoc)
)
end.join
end
def form_cleanup(xmldoc)
xmldoc.xpath("//select").each do |s|
while s&.next_element&.name == "option"
s << s.next_element
end
end
end
def block_index_cleanup(xmldoc)
xmldoc.xpath("//quote | //td | //th | //formula | //li | //dt | " \
"//dd | //example | //note | //figure | //sourcecode | " \
"//admonition | //termnote | //termexample | //form | " \
"//requirement | //recommendation | //permission | " \
"//imagemap | //svgmap").each do |b|
b.xpath("./p[indexterm]").each do |p|
indexterm_para?(p) or next
p.replace(p.children)
end
end
end
def indexterm_para?(para)
p = para.dup
p.xpath("./index").each(&:remove)
p.text.strip.empty?
end
def include_indexterm?(elem)
return false if elem.nil?
!%w(image literal sourcecode).include?(elem.name)
end
def para_index_cleanup(xmldoc)
xmldoc.xpath("//p[index]").select { |p| indexterm_para?(p) }
.each do |p|
para_index_cleanup1(p, p.previous_element, p.next_element)
end
end
def para_index_cleanup1(para, prev, foll)
if include_indexterm?(prev)
prev << para.remove.children
elsif include_indexterm?(foll) && !foll.children.empty?
foll.children.first.previous = para.remove.children
end
end
end
end
end