isodoc 0.5.5 → 0.5.7

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CODE_OF_CONDUCT.md +46 -0
  3. data/LICENSE +25 -0
  4. data/README.adoc +1 -1
  5. data/Rakefile +6 -0
  6. data/isodoc.gemspec +1 -0
  7. data/lib/isodoc.rb +4 -95
  8. data/lib/isodoc/cleanup.rb +14 -10
  9. data/lib/isodoc/{notes.rb → comments.rb} +0 -73
  10. data/lib/isodoc/convert.rb +97 -0
  11. data/lib/isodoc/footnotes.rb +74 -0
  12. data/lib/isodoc/html.rb +41 -4
  13. data/lib/isodoc/i18n-en.yaml +1 -0
  14. data/lib/isodoc/i18n-fr.yaml +1 -0
  15. data/lib/isodoc/i18n-zh-Hans.yaml +1 -0
  16. data/lib/isodoc/i18n.rb +1 -0
  17. data/lib/isodoc/inline.rb +4 -12
  18. data/lib/isodoc/iso2wordhtml.rb +26 -13
  19. data/lib/isodoc/metadata.rb +23 -10
  20. data/lib/isodoc/references.rb +20 -22
  21. data/lib/isodoc/section.rb +4 -3
  22. data/lib/isodoc/table.rb +0 -2
  23. data/lib/isodoc/terms.rb +2 -13
  24. data/lib/isodoc/utils.rb +24 -3
  25. data/lib/isodoc/version.rb +1 -1
  26. data/lib/isodoc/wordconvert/comments.rb +155 -0
  27. data/lib/isodoc/wordconvert/convert.rb +31 -0
  28. data/lib/isodoc/wordconvert/footnotes.rb +80 -0
  29. data/lib/isodoc/wordconvert/wordconvertmodule.rb +212 -0
  30. data/lib/isodoc/xref_gen.rb +50 -79
  31. data/lib/isodoc/xref_sect_gen.rb +82 -0
  32. data/spec/assets/header.html +7 -0
  33. data/spec/assets/html.css +2 -0
  34. data/spec/assets/htmlcover.html +4 -0
  35. data/spec/assets/htmlintro.html +5 -0
  36. data/spec/assets/i18n.yaml +2 -0
  37. data/spec/assets/iso.xml +8 -0
  38. data/spec/assets/rice_image1.png +0 -0
  39. data/spec/assets/std.css +2 -0
  40. data/spec/assets/word.css +2 -0
  41. data/spec/assets/wordcover.html +3 -0
  42. data/spec/assets/wordintro.html +4 -0
  43. data/spec/isodoc/blocks_spec.rb +130 -47
  44. data/spec/isodoc/cleanup_spec.rb +693 -0
  45. data/spec/isodoc/footnotes_spec.rb +282 -0
  46. data/spec/isodoc/i18n_spec.rb +662 -0
  47. data/spec/isodoc/inline_spec.rb +344 -0
  48. data/spec/isodoc/lists_spec.rb +81 -18
  49. data/spec/isodoc/metadata_spec.rb +141 -0
  50. data/spec/isodoc/postproc_spec.rb +444 -0
  51. data/spec/isodoc/ref_spec.rb +158 -0
  52. data/spec/isodoc/section_spec.rb +275 -112
  53. data/spec/isodoc/table_spec.rb +146 -8
  54. data/spec/isodoc/terms_spec.rb +118 -0
  55. data/spec/isodoc/xref_spec.rb +490 -114
  56. metadata +46 -4
  57. data/lib/isodoc/postprocessing.rb +0 -176
@@ -3,7 +3,7 @@ module IsoDoc
3
3
  def inline_header_title(out, node, c1)
4
4
  out.span **{ class: "zzMoveToFollowing" } do |s|
5
5
  s.b do |b|
6
- b << "#{get_anchors[node['id']][:label]}. #{c1.text} "
6
+ b << "#{get_anchors[node['id']][:label]}. #{c1.content} "
7
7
  end
8
8
  end
9
9
  end
@@ -13,7 +13,8 @@ module IsoDoc
13
13
  inline_header_title(out, node, c1)
14
14
  else
15
15
  div.send "h#{get_anchors[node['id']][:level]}" do |h|
16
- h << "#{get_anchors[node['id']][:label]}. #{c1.text}"
16
+ h << "#{get_anchors[node['id']][:label]}. "
17
+ c1.children.each { |c2| parse(c2, h) }
17
18
  end
18
19
  end
19
20
  end
@@ -49,7 +50,7 @@ module IsoDoc
49
50
  c.elements.each do |c1|
50
51
  if c1.name == "title"
51
52
  clause_name("#{get_anchors[c['id']][:label]}.",
52
- c1.text, s, nil)
53
+ c1.content, s, nil)
53
54
  else
54
55
  parse(c1, s)
55
56
  end
@@ -76,9 +76,7 @@ module IsoDoc
76
76
  rowmax = td["rowspan"] ? row + td["rowspan"].to_i - 1 : row
77
77
  style += <<~STYLE
78
78
  border-top:#{row.zero? ? "#{SW} 1.5pt;" : 'none;'}
79
- mso-border-top-alt:#{row.zero? ? "#{SW} 1.5pt;" : 'none;'}
80
79
  border-bottom:#{SW} #{rowmax == totalrows ? '1.5' : '1.0'}pt;
81
- mso-border-bottom-alt:#{SW} #{rowmax == totalrows ? '1.5' : '1.0'}pt;
82
80
  STYLE
83
81
  { rowspan: td["rowspan"], colspan: td["colspan"],
84
82
  align: td["align"], style: style.gsub(/\n/, "") }
@@ -24,7 +24,7 @@ module IsoDoc
24
24
  out.p **{ class: "Terms" } { |p| p << node.text }
25
25
  end
26
26
 
27
- def para_then_remainder(first, node, p)
27
+ def para_then_remainder(first, node, p, div)
28
28
  if first.name == "p"
29
29
  first.children.each { |n| parse(n, p) }
30
30
  node.elements.drop(1).each { |n| parse(n, div) }
@@ -33,23 +33,12 @@ module IsoDoc
33
33
  end
34
34
  end
35
35
 
36
- def termexample_parse(node, out)
37
- out.div **{ class: "Note" } do |div|
38
- first = node.first_element_child
39
- div.p **{ class: "Note" } do |p|
40
- p << l10n("#{@example_lbl}:")
41
- insert_tab(p, 1)
42
- para_then_remainder(first, node, p)
43
- end
44
- end
45
- end
46
-
47
36
  def termnote_parse(node, out)
48
37
  out.div **{ class: "Note" } do |div|
49
38
  first = node.first_element_child
50
39
  div.p **{ class: "Note" } do |p|
51
40
  p << "#{get_anchors[node['id']][:label]}: "
52
- para_then_remainder(first, node, p)
41
+ para_then_remainder(first, node, p, div)
53
42
  end
54
43
  end
55
44
  end
@@ -8,9 +8,7 @@ module IsoDoc
8
8
  end
9
9
 
10
10
  def insert_tab(out, n)
11
- out.span **attr_code(style: "mso-tab-count:#{n}") do |span|
12
- [1..n].each { span << "&#xA0; " }
13
- end
11
+ [1..n].each { out << "&nbsp; " }
14
12
  end
15
13
 
16
14
  STAGE_ABBRS = {
@@ -119,5 +117,28 @@ module IsoDoc
119
117
  end
120
118
  [@openmathdelim, @closemathdelim]
121
119
  end
120
+
121
+ def header_strip(h)
122
+ h = h.to_s.gsub(%r{<br/>}, " ").sub(/<\/?h[12][^>]*>/, "")
123
+ h1 = to_xhtml_fragment(h.dup)
124
+ h1.traverse do |x|
125
+ x.remove if x.name == "span" && x["class"] == "MsoCommentReference"
126
+ x.remove if x.name == "a" && x["epub:type"] == "footnote"
127
+ if x.name == "a"
128
+ x.replace(x.children)
129
+ end
130
+ end
131
+ from_xhtml(h1)
132
+ end
133
+
134
+ def populate_template(docxml, _format)
135
+ meta = get_metadata
136
+ docxml = docxml.
137
+ gsub(/\[TERMREF\]\s*/, l10n("[#{@source_lbl}: ")).
138
+ gsub(/\s*\[\/TERMREF\]\s*/, l10n("]")).
139
+ gsub(/\s*\[MODIFICATION\]/, l10n(", #{@modified_lbl} &mdash; "))
140
+ template = Liquid::Template.parse(docxml)
141
+ template.render(meta.map { |k, v| [k.to_s, v] }.to_h)
142
+ end
122
143
  end
123
144
  end
@@ -1,3 +1,3 @@
1
1
  module IsoDoc
2
- VERSION = "0.5.5".freeze
2
+ VERSION = "0.5.7".freeze
3
3
  end
@@ -0,0 +1,155 @@
1
+ #require "uuidtools"
2
+
3
+ #module IsoDoc
4
+ #class WordConvert
5
+ #module WordConvertModule
6
+ #def self.included base
7
+ #base.class_eval do
8
+
9
+ def in_comment
10
+ @in_comment
11
+ end
12
+
13
+ def comments(div)
14
+ return if @comments.empty?
15
+ div.div **{ style: "mso-element:comment-list" } do |div1|
16
+ @comments.each { |fn| div1.parent << fn }
17
+ end
18
+ end
19
+
20
+ def review_note_parse(node, out)
21
+ fn = @comments.length + 1
22
+ make_comment_link(out, fn, node)
23
+ @in_comment = true
24
+ @comments << make_comment_text(node, fn)
25
+ @in_comment = false
26
+ end
27
+
28
+ def comment_link_attrs(fn, node)
29
+ { style: "MsoCommentReference", target: fn,
30
+ class: "commentLink", from: node["from"],
31
+ to: node["to"] }
32
+ end
33
+
34
+ # add in from and to links to move the comment into place
35
+ def make_comment_link(out, fn, node)
36
+ out.span(**comment_link_attrs(fn, node)) do |s1|
37
+ s1.span **{ lang: "EN-GB", style: "font-size:9.0pt" } do |s2|
38
+ s2.a **{ style: "mso-comment-reference:SMC_#{fn};"\
39
+ "mso-comment-date:#{node['date']}" }
40
+ s2.span **{ style: "mso-special-character:comment",
41
+ target: fn } # do |s|
42
+ end
43
+ end
44
+ end
45
+
46
+ def make_comment_target(out)
47
+ out.span **{ style: "MsoCommentReference" } do |s1|
48
+ s1.span **{ lang: "EN-GB", style: "font-size:9.0pt" } do |s2|
49
+ s2.span **{ style: "mso-special-character:comment" }
50
+ end
51
+ end
52
+ end
53
+
54
+ def make_comment_text(node, fn)
55
+ noko do |xml|
56
+ xml.div **{ style: "mso-element:comment", id: fn } do |div|
57
+ div.span **{ style: %{mso-comment-author:"#{node['reviewer']}"} }
58
+ make_comment_target(div)
59
+ node.children.each { |n| parse(n, div) }
60
+ end
61
+ end.join("\n")
62
+ end
63
+
64
+ def comment_cleanup(docxml)
65
+ move_comment_link_to_from(docxml)
66
+ reorder_comments_by_comment_link(docxml)
67
+ embed_comment_in_comment_list(docxml)
68
+ end
69
+
70
+ #COMMENT_IN_COMMENT_LIST =
71
+ COMMENT_IN_COMMENT_LIST1 =
72
+ '//div[@style="mso-element:comment-list"]//'\
73
+ 'span[@style="MsoCommentReference"]'.freeze
74
+
75
+ def embed_comment_in_comment_list(docxml)
76
+ #docxml.xpath(COMMENT_IN_COMMENT_LIST).each do |x|
77
+ docxml.xpath(COMMENT_IN_COMMENT_LIST1).each do |x|
78
+ n = x.next_element
79
+ n&.children&.first&.add_previous_sibling(x.remove)
80
+ end
81
+ docxml
82
+ end
83
+
84
+ def move_comment_link_to_from1(x, fromlink)
85
+ x.remove
86
+ link = x.at(".//a")
87
+ fromlink.replace(x)
88
+ link.children = fromlink
89
+ end
90
+
91
+ def comment_attributes(docxml, x)
92
+ fromlink = docxml.at("//*[@id='#{x['from']}']")
93
+ return(nil) if fromlink.nil?
94
+ tolink = docxml.at("//*[@id='#{x['to']}']") || fromlink
95
+ target = docxml.at("//*[@id='#{x['target']}']")
96
+ { from: fromlink, to: tolink, target: target }
97
+ end
98
+
99
+ def wrap_comment_cont(from, target)
100
+ s = from.replace("<span style='mso-comment-continuation:#{target}'>")
101
+ s.first.children = from
102
+ end
103
+
104
+ def skip_comment_wrap(from)
105
+ from["style"] != "mso-special-character:comment"
106
+ end
107
+
108
+ def insert_comment_cont(from, to, target)
109
+ # includes_to = from.at(".//*[@id='#{to}']")
110
+ while !from.nil? && from["id"] != to
111
+ following = from.xpath("./following::*")
112
+ (from = following.shift) && incl_to = from.at(".//*[@id='#{to}']")
113
+ while !incl_to.nil? && !from.nil? && skip_comment_wrap(from)
114
+ (from = following.shift) && incl_to = from.at(".//*[@id='#{to}']")
115
+ end
116
+ wrap_comment_cont(from, target) if !from.nil?
117
+ end
118
+ end
119
+
120
+ def move_comment_link_to_from(docxml)
121
+ docxml.xpath('//span[@style="MsoCommentReference"][@from]').each do |x|
122
+ attrs = comment_attributes(docxml, x) || next
123
+ move_comment_link_to_from1(x, attrs[:from])
124
+ insert_comment_cont(attrs[:from], x["to"], x["target"])
125
+ end
126
+ end
127
+
128
+ def get_comments_from_text(docxml, link_order)
129
+ comments = []
130
+ docxml.xpath("//div[@style='mso-element:comment']").each do |c|
131
+ next unless c["id"] && !link_order[c["id"]].nil?
132
+ comments << { text: c.remove.to_s, id: c["id"] }
133
+ end
134
+ comments.sort! { |a, b| link_order[a[:id]] <=> link_order[b[:id]] }
135
+ # comments
136
+ end
137
+
138
+ #COMMENT_TARGET_XREFS =
139
+ COMMENT_TARGET_XREFS1 =
140
+ "//span[@style='mso-special-character:comment']/@target".freeze
141
+
142
+ def reorder_comments_by_comment_link(docxml)
143
+ link_order = {}
144
+ #docxml.xpath(COMMENT_TARGET_XREFS).each_with_index do |target, i|
145
+ docxml.xpath(COMMENT_TARGET_XREFS1).each_with_index do |target, i|
146
+ link_order[target.value] = i
147
+ end
148
+ comments = get_comments_from_text(docxml, link_order)
149
+ list = docxml.at("//*[@style='mso-element:comment-list']") || return
150
+ list.children = comments.map { |c| c[:text] }.join("\n")
151
+ end
152
+ #end
153
+ #end
154
+ #end
155
+ #end
@@ -0,0 +1,31 @@
1
+ require "uuidtools"
2
+ require "html2doc"
3
+ require "liquid"
4
+
5
+ require_relative "wordconvertmodule"
6
+ require_relative "comments"
7
+ require_relative "footnotes"
8
+
9
+ module IsoDoc
10
+
11
+ module WordConvertModule
12
+ # http://tech.tulentsev.com/2012/02/ruby-how-to-override-class-method-with-a-module/
13
+ # https://www.ruby-forum.com/topic/148303
14
+ #
15
+ # The following is ugly indeed, but the only way I can split module override methods
16
+ # across files
17
+ def self.included base
18
+ base.class_eval do
19
+
20
+ eval File.open(File.join(File.dirname(__FILE__),"wordconvertmodule.rb")).read
21
+ eval File.open(File.join(File.dirname(__FILE__),"comments.rb")).read
22
+ eval File.open(File.join(File.dirname(__FILE__),"footnotes.rb")).read
23
+ end
24
+ end
25
+ end
26
+
27
+ class WordConvert < Convert
28
+ include WordConvertModule
29
+ end
30
+ end
31
+
@@ -0,0 +1,80 @@
1
+ #require "uuidtools"
2
+
3
+ #module IsoDoc
4
+ #class WordConvert
5
+ #module WordConvertModule
6
+ #def self.included base
7
+ #base.class_eval do
8
+
9
+ def footnotes(div)
10
+ return if @footnotes.empty?
11
+ @footnotes.each { |fn| div.parent << fn }
12
+ end
13
+
14
+ def make_table_footnote_link(out, fnid, fnref)
15
+ attrs = { href: "##{fnid}", class: "TableFootnoteRef" }
16
+ out.a **attrs do |a|
17
+ a << fnref
18
+ end
19
+ end
20
+
21
+ def make_table_footnote_target(out, fnid, fnref)
22
+ attrs = { id: fnid, class: "TableFootnoteRef" }
23
+ out.a **attrs do |a|
24
+ a << fnref
25
+ insert_tab(a, 1)
26
+ end
27
+ end
28
+
29
+ def make_table_footnote_text(node, fnid, fnref)
30
+ attrs = { id: "ftn#{fnid}" }
31
+ noko do |xml|
32
+ xml.div **attr_code(attrs) do |div|
33
+ make_table_footnote_target(div, fnid, fnref)
34
+ node.children.each { |n| parse(n, div) }
35
+ end
36
+ end.join("\n")
37
+ end
38
+
39
+ def make_generic_footnote_text(node, fnid)
40
+ noko do |xml|
41
+ xml.aside **{ id: "ftn#{fnid}" } do |div|
42
+ node.children.each { |n| parse(n, div) }
43
+ end
44
+ end.join("\n")
45
+ end
46
+
47
+ def get_table_ancestor_id(node)
48
+ table = node.ancestors("table") || node.ancestors("figure")
49
+ return UUIDTools::UUID.random_create.to_s if table.empty?
50
+ table.last["id"]
51
+ end
52
+
53
+ def table_footnote_parse(node, out)
54
+ fn = node["reference"]
55
+ tid = get_table_ancestor_id(node)
56
+ make_table_footnote_link(out, tid + fn, fn)
57
+ # do not output footnote text if we have already seen it for this table
58
+ return if @seen_footnote.include?(tid + fn)
59
+ @in_footnote = true
60
+ out.aside { |a| a << make_table_footnote_text(node, tid + fn, fn) }
61
+ @in_footnote = false
62
+ @seen_footnote << (tid + fn)
63
+ end
64
+
65
+ def footnote_parse(node, out)
66
+ return table_footnote_parse(node, out) if @in_table || @in_figure
67
+ fn = node["reference"]
68
+ out.a **{ "epub:type": "footnote", href: "#ftn#{fn}" } do |a|
69
+ a.sup { |sup| sup << fn }
70
+ end
71
+ return if @seen_footnote.include?(fn)
72
+ @in_footnote = true
73
+ @footnotes << make_generic_footnote_text(node, fn)
74
+ @in_footnote = false
75
+ @seen_footnote << fn
76
+ end
77
+ #end
78
+ #end
79
+ #end
80
+ #end
@@ -0,0 +1,212 @@
1
+
2
+ #require "html2doc"
3
+ #require "liquid"
4
+
5
+ #module IsoDoc
6
+ #class WordConvert < Convert
7
+ #module WordConvertModule
8
+ #def self.included base
9
+ #base.class_eval do
10
+ def insert_tab(out, n)
11
+ out.span **attr_code(style: "mso-tab-count:#{n}") do |span|
12
+ [1..n].each { span << "&#xA0; " }
13
+ end
14
+ end
15
+
16
+ def para_attrs(node)
17
+ classtype = nil
18
+ classtype = "Note" if @note
19
+ classtype = "MsoCommentText" if in_comment
20
+ classtype = "Sourcecode" if @annotation
21
+ attrs = { class: classtype, id: node["id"] }
22
+ unless node["align"].nil?
23
+ attrs[:align] = node["align"] unless node["align"] == "justify"
24
+ attrs[:style] = "text-align:#{node['align']}"
25
+ end
26
+ attrs
27
+ end
28
+
29
+ def remove_bottom_border(td)
30
+ td["style"] =
31
+ td["style"].gsub(/border-bottom:[^;]+;/, "border-bottom:0pt;").
32
+ gsub(/mso-border-bottom-alt:[^;]+;/, "mso-border-bottom-alt:0pt;")
33
+ end
34
+
35
+ #SW1 = IsoDoc::SW
36
+ SW1 = "solid windowtext".freeze
37
+
38
+ def new_fullcolspan_row(t, tfoot)
39
+ # how many columns in the table?
40
+ cols = 0
41
+ t.at(".//tr").xpath("./td | ./th").each do |td|
42
+ cols += (td["colspan"] ? td["colspan"].to_i : 1)
43
+ end
44
+ style = %{border-top:0pt;mso-border-top-alt:0pt;
45
+ border-bottom:#{SW1} 1.5pt;mso-border-bottom-alt:#{SW1} 1.5pt;}
46
+ tfoot.add_child("<tr><td colspan='#{cols}' style='#{style}'/></tr>")
47
+ tfoot.xpath(".//td").last
48
+ end
49
+
50
+ def make_tr_attr(td, row, totalrows)
51
+ style = td.name == "th" ? "font-weight:bold;" : ""
52
+ rowmax = td["rowspan"] ? row + td["rowspan"].to_i - 1 : row
53
+ style += <<~STYLE
54
+ border-top:#{row.zero? ? "#{SW1} 1.5pt;" : 'none;'}
55
+ mso-border-top-alt:#{row.zero? ? "#{SW1} 1.5pt;" : 'none;'}
56
+ border-bottom:#{SW1} #{rowmax == totalrows ? '1.5' : '1.0'}pt;
57
+ mso-border-bottom-alt:#{SW1} #{rowmax == totalrows ? '1.5' : '1.0'}pt;
58
+ STYLE
59
+ { rowspan: td["rowspan"], colspan: td["colspan"],
60
+ align: td["align"], style: style.gsub(/\n/, "") }
61
+ end
62
+
63
+ def section_break(body)
64
+ body.br **{ clear: "all", class: "section" }
65
+ end
66
+
67
+ def page_break(out)
68
+ out.br **{
69
+ clear: "all",
70
+ style: "mso-special-character:line-break;page-break-before:always",
71
+ }
72
+ end
73
+
74
+ def dt_parse(dt, term)
75
+ if dt.elements.empty?
76
+ term.p **attr_code(class: note? ? "Note" : nil,
77
+ style: "text-align: left;") do |p|
78
+ p << dt.text
79
+ end
80
+ else
81
+ dt.children.each { |n| parse(n, term) }
82
+ end
83
+ end
84
+
85
+ def dl_parse(node, out)
86
+ out.table **{ class: "dl" } do |v|
87
+ node.elements.each_slice(2) do |dt, dd|
88
+ v.tr do |tr|
89
+ tr.td **{ valign: "top", align: "left" } do |term|
90
+ dt_parse(dt, term)
91
+ end
92
+ tr.td **{ valign: "top" } do |listitem|
93
+ dd.children.each { |n| parse(n, listitem) }
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+
101
+
102
+ def postprocess(result, filename, dir)
103
+ generate_header(filename, dir)
104
+ result = from_xhtml(cleanup(to_xhtml(result)))
105
+ toWord(result, filename, dir)
106
+ end
107
+
108
+ def toWord(result, filename, dir)
109
+ result = from_xhtml(word_cleanup(to_xhtml(result)))
110
+ result = populate_template(result, :word)
111
+ Html2Doc.process(result, filename: filename, stylesheet: @wordstylesheet,
112
+ header_file: "header.html", dir: dir,
113
+ asciimathdelims: [@openmathdelim, @closemathdelim],
114
+ liststyles: { ul: @ulstyle, ol: @olstyle })
115
+ end
116
+
117
+ def word_cleanup(docxml)
118
+ word_preface(docxml)
119
+ word_annex_cleanup(docxml)
120
+ docxml
121
+ end
122
+
123
+ # force Annex h2 to be p.h2Annex, so it is not picked up by ToC
124
+ def word_annex_cleanup(docxml)
125
+ docxml.xpath("//h2[ancestor::*[@class = 'Section3']]").each do |h2|
126
+ h2.name = "p"
127
+ h2["class"] = "h2Annex"
128
+ end
129
+ end
130
+
131
+ def word_preface(docxml)
132
+ word_cover(docxml) if @wordcoverpage
133
+ word_intro(docxml) if @wordintropage
134
+ end
135
+
136
+ def word_cover(docxml)
137
+ cover = to_xhtml_fragment(File.read(@wordcoverpage, encoding: "UTF-8"))
138
+ docxml.at('//div[@class="WordSection1"]').children.first.previous =
139
+ cover.to_xml(encoding: "US-ASCII")
140
+ end
141
+
142
+ def word_intro(docxml)
143
+ intro = to_xhtml_fragment(File.read(@wordintropage, encoding: "UTF-8").
144
+ sub(/WORDTOC/, make_WordToC(docxml)))
145
+ docxml.at('//div[@class="WordSection2"]').children.first.previous =
146
+ intro.to_xml(encoding: "US-ASCII")
147
+ end
148
+
149
+ def generate_header(filename, _dir)
150
+ return unless @header
151
+ template = Liquid::Template.parse(File.read(@header, encoding: "UTF-8"))
152
+ meta = get_metadata
153
+ meta[:filename] = filename
154
+ params = meta.map { |k, v| [k.to_s, v] }.to_h
155
+ File.open("header.html", "w") do |f|
156
+ f.write(template.render(params))
157
+ end
158
+ end
159
+
160
+ def word_toc_entry(toclevel, heading)
161
+ bookmark = Random.rand(1000000000)
162
+ <<~TOC
163
+ <p class="MsoToc#{toclevel}"><span class="MsoHyperlink"><span
164
+ lang="EN-GB" style='mso-no-proof:yes'>
165
+ <a href="#_Toc#{bookmark}">#{heading}<span lang="EN-GB"
166
+ class="MsoTocTextSpan">
167
+ <span style='mso-tab-count:1 dotted'>. </span>
168
+ </span><span lang="EN-GB" class="MsoTocTextSpan">
169
+ <span style='mso-element:field-begin'></span></span>
170
+ <span lang="EN-GB"
171
+ class="MsoTocTextSpan"> PAGEREF _Toc#{bookmark} \\h </span>
172
+ <span lang="EN-GB" class="MsoTocTextSpan"><span
173
+ style='mso-element:field-separator'></span></span><span
174
+ lang="EN-GB" class="MsoTocTextSpan">1</span>
175
+ <span lang="EN-GB"
176
+ class="MsoTocTextSpan"></span><span
177
+ lang="EN-GB" class="MsoTocTextSpan"><span
178
+ style='mso-element:field-end'></span></span></a></span></span></p>
179
+
180
+ TOC
181
+ end
182
+
183
+ #WORD_TOC_PREFACE = <<~TOC.freeze
184
+ WORD_TOC_PREFACE1 = <<~TOC.freeze
185
+ <span lang="EN-GB"><span
186
+ style='mso-element:field-begin'></span><span
187
+ style='mso-spacerun:yes'>&#xA0;</span>TOC
188
+ \\o &quot;1-2&quot; \\h \\z \\u <span
189
+ style='mso-element:field-separator'></span></span>
190
+ TOC
191
+
192
+ #WORD_TOC_SUFFIX = <<~TOC.freeze
193
+ WORD_TOC_SUFFIX1 = <<~TOC.freeze
194
+ <p class="MsoToc1"><span lang="EN-GB"><span
195
+ style='mso-element:field-end'></span></span><span
196
+ lang="EN-GB"><o:p>&nbsp;</o:p></span></p>
197
+ TOC
198
+
199
+ def make_WordToC(docxml)
200
+ toc = ""
201
+ docxml.xpath("//h1 | //h2[not(ancestor::*[@class = 'Section3'])]").
202
+ each do |h|
203
+ toc += word_toc_entry(h.name == "h1" ? 1 : 2, header_strip(h))
204
+ end
205
+ toc.sub(/(<p class="MsoToc1">)/,
206
+ #%{\\1#{WORD_TOC_PREFACE}}) + WORD_TOC_SUFFIX
207
+ %{\\1#{WORD_TOC_PREFACE1}}) + WORD_TOC_SUFFIX1
208
+ end
209
+ #end
210
+ #end
211
+ #end
212
+ #end