isodoc 0.5.5 → 0.5.7

Sign up to get free protection for your applications and to get access to all the features.
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