Module: IsoDoc::HtmlFunction::Html

Included in:
IsoDoc::HeadlessHtmlConvert, IsoDoc::HtmlConvert, PdfConvert
Defined in:
lib/isodoc/html_function/html.rb,
lib/isodoc/html_function/postprocess.rb,
lib/isodoc/html_function/postprocess_cover.rb,
lib/isodoc/html_function/postprocess_footnotes.rb

Constant Summary collapse

MATHJAX_ADDR =
"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js".freeze
MATHJAX =
<<~"MATHJAX".freeze
  <script type="text/x-mathjax-config">
    MathJax.Hub.Config({
      "HTML-CSS": { preferredFont: "STIX" },
      asciimath2jax: { delimiters: [['OPEN', 'CLOSE']] }
   });
  </script>
  <script src="#{MATHJAX_ADDR}?config=MML_HTMLorMML-full" async="async"></script>
MATHJAX

Instance Method Summary collapse

Instance Method Details

#authority_cleanup(docxml) ⇒ Object



58
59
60
61
62
63
# File 'lib/isodoc/html_function/postprocess_cover.rb', line 58

def authority_cleanup(docxml)
  %w(copyright license legal feedback).each do |t|
    authority_cleanup1(docxml, t)
  end
  coverpage_note_cleanup(docxml)
end

#authority_cleanup1(docxml, klass) ⇒ Object



49
50
51
52
53
54
55
56
# File 'lib/isodoc/html_function/postprocess_cover.rb', line 49

def authority_cleanup1(docxml, klass)
  dest = docxml.at("//div[@id = 'boilerplate-#{klass}-destination']")
  auth = docxml.at("//div[@id = 'boilerplate-#{klass}' or "\
                   "@class = 'boilerplate-#{klass}']")
  auth&.xpath(".//h1[not(text())] | .//h2[not(text())]")&.each(&:remove)
  auth&.xpath(".//h1 | .//h2")&.each { |h| h["class"] = "IntroTitle" }
  dest and auth and dest.replace(auth.remove)
end

#convert1(docxml, filename, dir) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
# File 'lib/isodoc/html_function/html.rb', line 7

def convert1(docxml, filename, dir)
  bibitem_lookup(docxml)
  noko do |xml|
    xml.html **{ lang: @lang.to_s } do |html|
      info docxml, nil
      populate_css
      html.head { |head| define_head head, filename, dir }
      make_body(html, docxml)
    end
  end.join("\n")
end

#coverpage_note_cleanup(docxml) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/isodoc/html_function/postprocess_cover.rb', line 65

def coverpage_note_cleanup(docxml)
  if dest = docxml.at("//div[@id = 'coverpage-note-destination']")
    auth = docxml.xpath("//*[@coverpage]")
    if auth.empty? then dest.remove
    else
      auth.each do |x|
        dest << x.remove
      end
    end
  end
  docxml.xpath("//*[@coverpage]").each { |x| x.delete("coverpage") }
end

#datauri(img) ⇒ Object



74
75
76
# File 'lib/isodoc/html_function/postprocess.rb', line 74

def datauri(img)
  img["src"] = Metanorma::Utils::datauri(img["src"], @localdir)
end


36
37
38
39
40
41
42
43
44
45
46
# File 'lib/isodoc/html_function/postprocess_footnotes.rb', line 36

def footnote_backlinks(docxml)
  seen = {}
  docxml.xpath('//a[@class = "FootnoteRef"]').each_with_index do |x, i|
    (seen[x["href"]] and next) or seen[x["href"]] = true
    fn = docxml.at(%<//*[@id = '#{x['href'].sub(/^#/, '')}']>) || next
    footnote_backlinks1(x, fn)
    x["id"] ||= "fnref:#{i + 1}"
    fn.add_child "<a href='##{x['id']}'>&#x21A9;</a>"
  end
  docxml
end

#footnote_backlinks1(x, fn) ⇒ Object



26
27
28
29
30
31
32
33
34
# File 'lib/isodoc/html_function/postprocess_footnotes.rb', line 26

def footnote_backlinks1(x, fn)
  xdup = x.dup
  xdup.remove["id"]
  if fn.elements.empty?
    fn.children.first.previous = xdup
  else
    fn.elements.first.children.first.previous = xdup
  end
end

#footnote_format(docxml) ⇒ Object



48
49
50
51
52
53
54
55
56
57
# File 'lib/isodoc/html_function/postprocess_footnotes.rb', line 48

def footnote_format(docxml)
  docxml.xpath("//a[@class = 'FootnoteRef']/sup").each do |x|
    footnote_reference_format(x)
  end
  docxml.xpath("//a[@class = 'TableFootnoteRef'] | "\
               "//span[@class = 'TableFootnoteRef']").each do |x|
    table_footnote_reference_format(x)
  end
  docxml
end

#googlefontsObject



52
53
54
55
56
57
# File 'lib/isodoc/html_function/html.rb', line 52

def googlefonts
  <<~HEAD.freeze
    <link href="https://fonts.googleapis.com/css?family=Overpass:300,300i,600,900" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css?family=Lato:400,400i,700,900" rel="stylesheet">
  HEAD
end

#html5(doc) ⇒ Object



22
23
24
25
# File 'lib/isodoc/html_function/postprocess.rb', line 22

def html5(doc)
  doc.sub(%r{<!DOCTYPE html [^>]+>}, "<!DOCTYPE html>")
    .sub(%r{<\?xml[^>]+>}, "")
end

#html_buttonObject



78
79
80
81
82
83
# File 'lib/isodoc/html_function/html.rb', line 78

def html_button
  return "" if @bare

  '<button onclick="topFunction()" id="myBtn" '\
  'title="Go to top">Top</button>'.freeze
end

#html_cleanup(html) ⇒ Object



27
28
29
30
31
# File 'lib/isodoc/html_function/postprocess.rb', line 27

def html_cleanup(html)
  html = term_header(html_footnote_filter(html_preface(htmlstyle(html))))
  html = footnote_format(footnote_backlinks(html_toc(html)))
  mathml(html_list_clean(remove_placeholder_paras(html)))
end

#html_cover(docxml) ⇒ Object



78
79
80
81
82
83
84
# File 'lib/isodoc/html_function/postprocess_cover.rb', line 78

def html_cover(docxml)
  doc = to_xhtml_fragment(File.read(@htmlcoverpage, encoding: "UTF-8"))
  d = docxml.at('//div[@class="title-section"]')
  d.children.first.add_previous_sibling(
    populate_template(doc.to_xml(encoding: "US-ASCII"), :html),
  )
end

#html_footnote_filter(docxml) ⇒ Object



16
17
18
19
20
21
22
23
24
# File 'lib/isodoc/html_function/postprocess_footnotes.rb', line 16

def html_footnote_filter(docxml)
  seen = {}
  i = 1
  docxml.xpath('//a[@class = "FootnoteRef"]').each do |x|
    fn = docxml.at(%<//*[@id = '#{x['href'].sub(/^#/, '')}']>) || next
    i, seen = update_footnote_filter(fn, x, i, seen)
  end
  docxml
end

#html_headObject



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/isodoc/html_function/html.rb', line 59

def html_head
  <<~HEAD.freeze
    <title>#{@meta&.get&.dig(:doctitle)}</title>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

    <!--TOC script import-->
    <script type="text/javascript"  src="https://cdn.rawgit.com/jgallen23/toc/0.3.2/dist/toc.min.js"></script>
    <script type="text/javascript">#{toclevel}</script>

    <!--Google fonts-->
    <link rel="preconnect" href="https://fonts.gstatic.com">#{' '}
    #{googlefonts}
    <!--Font awesome import for the link icon-->
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.8/css/solid.css" integrity="sha384-v2Tw72dyUXeU3y4aM2Y0tBJQkGfplr39mxZqlTBDUZAb9BGoC40+rdFCG0m10lXk" crossorigin="anonymous">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.8/css/fontawesome.css" integrity="sha384-q3jl8XQu1OpdLgGFvNRnPdj5VIlCvgsDQTQB6owSOHWlAurxul7f+JpUOVdAiJ5P" crossorigin="anonymous">
    <style class="anchorjs"></style>
  HEAD
end

#html_intro(docxml) ⇒ Object



86
87
88
89
90
91
92
# File 'lib/isodoc/html_function/postprocess_cover.rb', line 86

def html_intro(docxml)
  doc = to_xhtml_fragment(File.read(@htmlintropage, encoding: "UTF-8"))
  d = docxml.at('//div[@class="prefatory-section"]')
  d.children.first.add_previous_sibling(
    populate_template(doc.to_xml(encoding: "US-ASCII"), :html),
  )
end

#html_list_clean(html) ⇒ Object



40
41
42
43
44
45
46
47
# File 'lib/isodoc/html_function/postprocess.rb', line 40

def html_list_clean(html)
  html.xpath("//ol/div | //ul/div").each do |div|
    li = div&.xpath("./preceding-sibling::li")&.last ||
      div.at("./following-sibling::li")
    div.parent = li
  end
  html
end

#html_main(docxml) ⇒ Object



85
86
87
88
89
90
# File 'lib/isodoc/html_function/html.rb', line 85

def html_main(docxml)
  docxml.at("//head").add_child(html_head)
  d = docxml.at('//div[@class="main-section"]')
  d.name = "main"
  d.children.empty? or d.children.first.previous = html_button
end

#html_preface(docxml) ⇒ Object



39
40
41
42
43
44
45
46
47
# File 'lib/isodoc/html_function/postprocess_cover.rb', line 39

def html_preface(docxml)
  html_cover(docxml) if @htmlcoverpage && !@bare
  html_intro(docxml) if @htmlintropage && !@bare
  docxml.at("//body") << mathjax(@openmathdelim, @closemathdelim)
  docxml.at("//body") << sourcecode_highlighter
  html_main(docxml)
  authority_cleanup(docxml)
  docxml
end

#html_toc(docxml) ⇒ Object

needs to be same output as toclevel



115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/isodoc/html_function/postprocess_cover.rb', line 115

def html_toc(docxml)
  idx = docxml.at("//div[@id = 'toc']") or return docxml
  toc = "<ul>"
  path = toclevel_classes.map do |l|
    "//main//#{l}#{toc_exclude_class}"
  end
  docxml.xpath(path.join(" | ")).each_with_index do |h, tocidx|
    h["id"] ||= "toc#{tocidx}"
    toc += html_toc_entry(h.name, h)
  end
  idx.children = "#{toc}</ul>"
  docxml
end

#html_toc_entry(level, header) ⇒ Object



94
95
96
97
98
99
# File 'lib/isodoc/html_function/postprocess_cover.rb', line 94

def html_toc_entry(level, header)
  content = header.at("./following-sibling::p"\
                      "[@class = 'variant-title-toc']") || header
  %(<li class="#{level}"><a href="##{header['id']}">\
#{header_strip(content)}</a></li>)
end

#htmlstyle(docxml) ⇒ Object



28
29
30
31
32
33
34
35
36
37
# File 'lib/isodoc/html_function/postprocess_cover.rb', line 28

def htmlstyle(docxml)
  return docxml unless @htmlstylesheet

  head = docxml.at("//*[local-name() = 'head']")
  head << htmlstylesheet(@htmlstylesheet)
  s = htmlstylesheet(@htmlstylesheet_override) and head << s
  @bare and
    head << "<style>body {margin-left: 2em; margin-right: 2em;}</style>"
  docxml
end

#htmlstylesheet(file) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/isodoc/html_function/postprocess_cover.rb', line 15

def htmlstylesheet(file)
  return if file.nil?

  file.open if file.is_a?(Tempfile)
  stylesheet = file.read
  xml = Nokogiri::XML("<style/>")
  xml.children.first << Nokogiri::XML::Comment
    .new(xml, "\n#{stylesheet}\n")
  file.close
  file.unlink if file.is_a?(Tempfile)
  xml.root.to_s
end

#image_parse(node, out, caption) ⇒ Object



137
138
139
140
141
142
143
# File 'lib/isodoc/html_function/html.rb', line 137

def image_parse(node, out, caption)
  if svg = node.at("./m:svg", "m" => "http://www.w3.org/2000/svg")
    svg_parse(svg, out)
    return
  end
  super
end

#image_suffix(img) ⇒ Object



78
79
80
81
82
83
84
85
# File 'lib/isodoc/html_function/postprocess.rb', line 78

def image_suffix(img)
  type = img["mimetype"]&.sub(%r{^[^/*]+/}, "")
  matched = /\.(?<suffix>[^. \r\n\t]+)$/.match img["src"]
  type and !type.empty? and return type

  !matched.nil? and matched[:suffix] and return matched[:suffix]
  "png"
end

#inject_script(doc) ⇒ Object



134
135
136
137
138
139
140
141
142
143
# File 'lib/isodoc/html_function/postprocess_cover.rb', line 134

def inject_script(doc)
  return doc unless @scripts

  scripts = File.read(@scripts, encoding: "UTF-8")
  scripts_override = ""
  @scripts_override and
    scripts_override = File.read(@scripts_override, encoding: "UTF-8")
  a = doc.split(%r{</body>})
  "#{a[0]}#{scripts}#{scripts_override}</body>#{a[1]}"
end

#make_body1(body, _docxml) ⇒ Object



19
20
21
22
23
24
25
26
# File 'lib/isodoc/html_function/html.rb', line 19

def make_body1(body, _docxml)
  return if @bare

  body.div **{ class: "title-section" } do |div1|
    div1.p { |p| p << "&#xa0;" } # placeholder
  end
  section_break(body)
end

#make_body2(body, _docxml) ⇒ Object



28
29
30
31
32
33
34
35
# File 'lib/isodoc/html_function/html.rb', line 28

def make_body2(body, _docxml)
  return if @bare

  body.div **{ class: "prefatory-section" } do |div2|
    div2.p { |p| p << "&#xa0;" } # placeholder
  end
  section_break(body)
end

#make_body3(body, docxml) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/isodoc/html_function/html.rb', line 37

def make_body3(body, docxml)
  body.div **{ class: "main-section" } do |div3|
    boilerplate docxml, div3
    preface_block docxml, div3
    abstract docxml, div3
    foreword docxml, div3
    introduction docxml, div3
    preface docxml, div3
    acknowledgements docxml, div3
    middle docxml, div3
    footnotes div3
    comments div3
  end
end

#mathjax(open, close) ⇒ Object



162
163
164
# File 'lib/isodoc/html_function/postprocess_cover.rb', line 162

def mathjax(open, close)
  MATHJAX.gsub("OPEN", open).gsub("CLOSE", close)
end

#mathml(docxml) ⇒ Object



49
50
51
# File 'lib/isodoc/html_function/postprocess.rb', line 49

def mathml(docxml)
  IsoDoc::HtmlFunction::MathvariantToPlain.new(docxml).convert
end

#move_image1(img) ⇒ Object



87
88
89
90
91
92
93
94
95
# File 'lib/isodoc/html_function/postprocess.rb', line 87

def move_image1(img)
  suffix = image_suffix(img)
  uuid = UUIDTools::UUID.random_create.to_s
  fname = "#{uuid}.#{suffix}"
  new_full_filename = File.join(tmpimagedir, fname)
  local_filename = image_localfile(img)
  FileUtils.cp local_filename, new_full_filename
  img["src"] = File.join(rel_tmpimagedir, fname)
end

#move_images(docxml) ⇒ Object

presupposes that the image source is local



63
64
65
66
67
68
69
70
71
72
# File 'lib/isodoc/html_function/postprocess.rb', line 63

def move_images(docxml)
  FileUtils.rm_rf tmpimagedir
  FileUtils.mkdir tmpimagedir
  docxml.xpath("//*[local-name() = 'img']").each do |i|
    next if /^data:/.match? i["src"]

    @datauriimage ? datauri(i) : move_image1(i)
  end
  docxml
end

#postprocess(result, filename, _dir) ⇒ Object



9
10
11
12
13
# File 'lib/isodoc/html_function/postprocess.rb', line 9

def postprocess(result, filename, _dir)
  result = from_xhtml(cleanup(to_xhtml(textcleanup(result))))
  toHTML(result, filename)
  @files_to_delete.each { |f| FileUtils.rm_rf f }
end

#remove_placeholder_paras(html) ⇒ Object



33
34
35
36
37
38
# File 'lib/isodoc/html_function/postprocess.rb', line 33

def remove_placeholder_paras(html)
  %w(title-section prefatory-section).each do |s|
    html&.at("//div[@class = '#{s}']/p[last()]")&.remove
  end
  html
end

#resize_images(docxml) ⇒ Object



53
54
55
56
57
58
59
60
# File 'lib/isodoc/html_function/postprocess.rb', line 53

def resize_images(docxml)
  docxml.xpath("//*[local-name() = 'img' or local-name() = 'svg']")
    .each do |i|
    i["width"], i["height"] = Html2Doc.new({})
      .image_resize(i, image_localfile(i), @maxheight, @maxwidth)
  end
  docxml
end

#script_cdata(result) ⇒ Object



8
9
10
11
12
13
# File 'lib/isodoc/html_function/postprocess_cover.rb', line 8

def script_cdata(result)
  result.gsub(%r{<script([^>]*)>\s*<!\[CDATA\[}m, "<script\\1>")
    .gsub(%r{\]\]>\s*</script>}, "</script>")
    .gsub(%r{<!\[CDATA\[\s*<script([^>]*)>}m, "<script\\1>")
    .gsub(%r{</script>\s*\]\]>}, "</script>")
end

#sourcecode_highlighterObject



145
146
147
148
# File 'lib/isodoc/html_function/postprocess_cover.rb', line 145

def sourcecode_highlighter
  '<script src="https://cdn.rawgit.com/google/code-prettify/master/'\
    'loader/run_prettify.js"></script>'
end

#sourcecode_parse(node, out) ⇒ Object



112
113
114
115
116
117
118
119
120
121
# File 'lib/isodoc/html_function/html.rb', line 112

def sourcecode_parse(node, out)
  name = node.at(ns("./name"))
  class1 = "prettyprint #{sourcecodelang(node&.at(ns('./@lang'))&.value)}"
  out.pre **sourcecode_attrs(node).merge(class: class1) do |div|
    @sourcecode = true
    node.children.each { |n| parse(n, div) unless n.name == "name" }
    @sourcecode = false
  end
  sourcecode_name_parse(node, out, name)
end

#sourcecodelang(lang) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/isodoc/html_function/html.rb', line 92

def sourcecodelang(lang)
  return unless lang

  case lang.downcase
  when "javascript" then "lang-js"
  when "c" then "lang-c"
  when "c+" then "lang-cpp"
  when "console" then "lang-bsh"
  when "ruby" then "lang-rb"
  when "html" then "lang-html"
  when "java" then "lang-java"
  when "xml" then "lang-xml"
  when "perl" then "lang-perl"
  when "python" then "lang-py"
  when "xsl" then "lang-xsl"
  else
    ""
  end
end

#table_attrs(node) ⇒ Object



131
132
133
134
135
# File 'lib/isodoc/html_function/html.rb', line 131

def table_attrs(node)
  ret = super
  node.at(ns("./colgroup")) and ret[:style] += "table-layout:fixed;"
  ret
end

#table_long_strings_cleanup(docxml) ⇒ Object



129
# File 'lib/isodoc/html_function/html.rb', line 129

def table_long_strings_cleanup(docxml); end

#term_header(docxml) ⇒ Object



97
98
99
100
101
102
103
104
# File 'lib/isodoc/html_function/postprocess.rb', line 97

def term_header(docxml)
  %w(h1 h2 h3 h4 h5 h6 h7 h8).each do |h|
    docxml.xpath("//p[@class = 'TermNum'][../#{h}]").each do |p|
      p.name = "h#{h[1].to_i + 1}"
    end
  end
  docxml
end

#toc_exclude_classObject



129
130
131
132
# File 'lib/isodoc/html_function/postprocess_cover.rb', line 129

def toc_exclude_class
  "[not(@class = 'TermNum')][not(@class = 'noTOC')]"\
    "[string-length(normalize-space(.))>0]"
end

#toclevelObject



105
106
107
108
109
110
111
112
# File 'lib/isodoc/html_function/postprocess_cover.rb', line 105

def toclevel
  ret = toclevel_classes.map do |l|
    "#{l}:not(:empty):not(.TermNum):not(.noTOC)"
  end
  <<~HEAD.freeze
    function toclevel() { return "#{ret.join(',')}";}
  HEAD
end

#toclevel_classesObject



101
102
103
# File 'lib/isodoc/html_function/postprocess_cover.rb', line 101

def toclevel_classes
  (1..@htmlToClevels).reduce([]) { |m, i| m << "h#{i}" }
end

#toHTML(result, filename) ⇒ Object



15
16
17
18
19
20
# File 'lib/isodoc/html_function/postprocess.rb', line 15

def toHTML(result, filename)
  result = from_xhtml(html_cleanup(to_xhtml(result)))
  result = from_xhtml(move_images(resize_images(to_xhtml(result))))
  result = html5(script_cdata(inject_script(result)))
  File.open(filename, "w:UTF-8") { |f| f.write(result) }
end

#underline_parse(node, out) ⇒ Object



123
124
125
126
127
# File 'lib/isodoc/html_function/html.rb', line 123

def underline_parse(node, out)
  out.span **{ style: "text-decoration: underline;" } do |e|
    node.children.each { |n| parse(n, e) }
  end
end

#update_footnote_filter(fn, x, i, seen) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
# File 'lib/isodoc/html_function/postprocess_footnotes.rb', line 3

def update_footnote_filter(fn, x, i, seen)
  if seen[fn.text]
    x.at("./sup").content = seen[fn.text][:num].to_s
    fn.remove unless x["href"] == seen[fn.text][:href]
    x["href"] = seen[fn.text][:href]
  else
    seen[fn.text] = { num: i, href: x["href"] }
    x.at("./sup").content = i.to_s
    i += 1
  end
  [i, seen]
end