Module: IsoDoc::WordFunction::Postprocess

Included in:
IsoDoc::WordConvert
Defined in:
lib/isodoc/word_function/postprocess.rb,
lib/isodoc/word_function/postprocess_cover.rb

Constant Summary collapse

WORD_NOKOHEAD =

add namespaces for Word fragments

<<~HERE.freeze
  <!DOCTYPE html SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  <html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office"
  xmlns:w="urn:schemas-microsoft-com:office:word"
  xmlns:m="http://schemas.microsoft.com/office/2004/12/omml">
  <head> <title></title> <meta charset="UTF-8" /> </head>
  <body> </body> </html>
HERE
WORD_TOC_SUFFIX1 =
<<~TOC.freeze
  <p class="MsoToc1"><span lang="EN-GB"><span
    style='mso-element:field-end'></span></span><span
    lang="EN-GB"><o:p>&#xA0;</o:p></span></p>
TOC
WORD_TOC_RECOMMENDATION_PREFACE1 =
<<~TOC.freeze
  <span lang="EN-GB"><span
  style='mso-element:field-begin'></span><span
  style='mso-spacerun:yes'>&#xA0;</span>TOC
  \\h \\z \\t "RecommendationTitle,RecommendationTestTitle,recommendationtitle,recommendationtesttitle"
  <span style='mso-element:field-separator'></span></span>
TOC
WORD_TOC_TABLE_PREFACE1 =
<<~TOC.freeze
  <span lang="EN-GB"><span
  style='mso-element:field-begin'></span><span
  style='mso-spacerun:yes'>&#xA0;</span>TOC
  \\h \\z \\t "TableTitle,tabletitle" <span
  style='mso-element:field-separator'></span></span>
TOC
WORD_TOC_FIGURE_PREFACE1 =
<<~TOC.freeze
  <span lang="EN-GB"><span
  style='mso-element:field-begin'></span><span
  style='mso-spacerun:yes'>&#xA0;</span>TOC
  \\h \\z \\t "FigureTitle,figuretitle" <span
  style='mso-element:field-separator'></span></span>
TOC

Instance Method Summary collapse

Instance Method Details

#authority_cleanup(docxml) ⇒ Object



173
174
175
176
177
178
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 173

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



161
162
163
164
165
166
167
168
169
170
171
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 161

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 do |h|
    h.name = "p"
    h["class"] = "TitlePageSubhead"
  end
  dest and auth and dest.replace(auth.remove)
end

#colgroup_widths(table) ⇒ Object

assume percentages



132
133
134
135
136
# File 'lib/isodoc/word_function/postprocess.rb', line 132

def colgroup_widths(table)
  table.xpath("./colgroup/col").each_with_object([]) do |c, m|
    m << c["width"].sub(/%$/, "").to_f
  end
end

#coverpage_note_cleanup(docxml) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 180

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

#generate_header(filename, _dir) ⇒ Object



193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 193

def generate_header(filename, _dir)
  return nil unless @header

  template = IsoDoc::Common.liquid(File.read(@header, encoding: "UTF-8"))
  meta = @meta.get.merge(@labels ? { labels: @labels } : {})
    .merge(@meta.labels ? { labels: @meta.labels } : {})
  meta[:filename] = filename
  params = meta.transform_keys(&:to_s)
  Tempfile.open(%w(header html), encoding: "utf-8") do |f|
    f.write(template.render(params))
    f
  end
end

#insert_toc(intro, docxml, level) ⇒ Object



26
27
28
29
30
31
32
33
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 26

def insert_toc(intro, docxml, level)
  toc = ""
  toc += make_WordToC(docxml, level)
  toc += make_TableWordToC(docxml)
  toc += make_FigureWordToC(docxml)
  toc += make_RecommendationWordToC(docxml)
  intro.sub(/WORDTOC/, toc)
end

#list_add(xpath, lvl) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/isodoc/word_function/postprocess.rb', line 171

def list_add(xpath, lvl)
  xpath.each do |list|
    (list.xpath(".//li") - list.xpath(".//ol//li | .//ul//li")).each do |l|
      l.xpath("./p | ./div | ./table").each_with_index do |p, i|
        i.zero? or p.wrap(%{<div class="ListContLevel#{lvl}"/>})
      end
      list_add(l.xpath(".//ul") - l.xpath(".//ul//ul | .//ol//ul"),
               lvl + 1)
      list_add(l.xpath(".//ol") - l.xpath(".//ul//ol | .//ol//ol"),
               lvl + 1)
    end
  end
end

#make_FigureWordToC(docxml) ⇒ Object



120
121
122
123
124
125
126
127
128
129
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 120

def make_FigureWordToC(docxml)
  (docxml.at("//p[@class = 'FigureTitle']") && @tocfigurestitle) or
    return ""
  toc = %{<p class="TOCTitle">#{@tocfigurestitle}</p>}
  docxml.xpath("//p[@class = 'FigureTitle']").each do |h|
    toc += word_toc_entry(1, header_strip(h))
  end
  toc.sub(/(<p class="MsoToc1">)/,
          %{\\1#{WORD_TOC_FIGURE_PREFACE1}}) + WORD_TOC_SUFFIX1
end

#make_RecommendationWordToC(docxml) ⇒ Object



131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 131

def make_RecommendationWordToC(docxml)
  (docxml.at("//p[@class = 'RecommendationTitle']") &&
    @tocrecommendationstitle) or return ""
  toc = %{<p class="TOCTitle">#{@tocrecommendationstitle}</p>}
  docxml.xpath("//p[@class = 'RecommendationTitle' or @class = 'RecommendationTestTitle']").sort_by do |h|
    recommmendation_sort_key(h.text)
  end.each do |h|
    toc += word_toc_entry(1, header_strip(h))
  end
  toc.sub(/(<p class="MsoToc1">)/,
          %{\\1#{WORD_TOC_RECOMMENDATION_PREFACE1}}) + WORD_TOC_SUFFIX1
end

#make_TableWordToC(docxml) ⇒ Object



109
110
111
112
113
114
115
116
117
118
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 109

def make_TableWordToC(docxml)
  (docxml.at("//p[@class = 'TableTitle']") && @toctablestitle) or
    return ""
  toc = %{<p class="TOCTitle">#{@toctablestitle}</p>}
  docxml.xpath("//p[@class = 'TableTitle']").each do |h|
    toc += word_toc_entry(1, header_strip(h))
  end
  toc.sub(/(<p class="MsoToc1">)/,
          %{\\1#{WORD_TOC_TABLE_PREFACE1}}) + WORD_TOC_SUFFIX1
end

#make_WordToC(docxml, level) ⇒ Object



74
75
76
77
78
79
80
81
82
83
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 74

def make_WordToC(docxml, level)
  toc = ""
  # docxml.xpath("//h1 | //h2[not(ancestor::*[@class = 'Section3'])]").
  xpath = (1..level).each.map { |i| "//h#{i}" }.join (" | ")
  docxml.xpath(xpath).each do |h|
    toc += word_toc_entry(h.name[1].to_i, header_strip(h))
  end
  toc.sub(/(<p class="MsoToc1">)/,
          %{\\1#{word_toc_preface(level)}}) + WORD_TOC_SUFFIX1
end

#postprocess(result, filename, dir) ⇒ Object



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

def postprocess(result, filename, dir)
  filename = filename.sub(/\.doc$/, "")
  header = generate_header(filename, dir)
  result = from_xhtml(cleanup(to_xhtml(textcleanup(result))))
  toWord(result, filename, dir, header)
  @files_to_delete.each { |f| FileUtils.rm_f f }
end

#recommmendation_sort_key(header) ⇒ Object



144
145
146
147
148
149
150
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 144

def recommmendation_sort_key(header)
  m = /^([^0-9]+) (\d+)/.match(header) || /^([^:]+)/.match(header)
  m ||= [header, nil]
  ret = "#{recommmendation_sort_key1(m[1])}::"
  m[2] and ret += ("%04d" % m[2].to_i).to_s
  ret
end

#recommmendation_sort_key1(type) ⇒ Object



152
153
154
155
156
157
158
159
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 152

def recommmendation_sort_key1(type)
  case type&.downcase
  when "requirement" then "04"
  when "recommendation" then "05"
  when "permission" then "06"
  else type
  end
end

#split_at_section_break(docxml, sect, brk, idx) ⇒ Object



227
228
229
230
231
232
233
234
235
236
237
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 227

def split_at_section_break(docxml, sect, brk, idx)
  move = brk.parent.xpath("following::node()") &
    brk.document.xpath("//div[@class = '#{sect}']//*")
  ins = docxml.at("//div[@class = '#{sect}']")
    .after("<div class='#{sect}_#{idx}'/>").next_element
  move.each do |m|
    next if m.at("./ancestor::div[@class = '#{sect}_#{idx}']")

    ins << m.remove
  end
end

#style_update(node, css) ⇒ Object



146
147
148
149
150
151
# File 'lib/isodoc/word_function/postprocess.rb', line 146

def style_update(node, css)
  return unless node

  node["style"] =
    node["style"] ? node["style"].sub(/;?$/, ";#{css}") : css
end

#table_note_cleanup(docxml) ⇒ Object



23
24
25
26
27
28
29
30
# File 'lib/isodoc/word_function/postprocess.rb', line 23

def table_note_cleanup(docxml)
  super
  # preempt html2doc putting MsoNormal there
  docxml.xpath("//p[not(self::*[@class])][ancestor::*[@class = 'Note']]")
    .each do |p|
    p["class"] = "Note"
  end
end

#to_word_xhtml_fragment(xml) ⇒ Object



18
19
20
21
# File 'lib/isodoc/word_function/postprocess.rb', line 18

def to_word_xhtml_fragment(xml)
  doc = ::Nokogiri::XML.parse(WORD_NOKOHEAD)
  ::Nokogiri::XML::DocumentFragment.new(doc, xml, doc.root)
end

#toWord(result, filename, dir, header) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/isodoc/word_function/postprocess.rb', line 40

def toWord(result, filename, dir, header)
  result = from_xhtml(word_cleanup(to_xhtml(result)))
    .gsub(/-DOUBLE_HYPHEN_ESCAPE-/, "--")
  @wordstylesheet = wordstylesheet_update
  Html2Doc.new(
    filename: filename, imagedir: @localdir,
    stylesheet: @wordstylesheet&.path,
    header_file: header&.path, dir: dir,
    asciimathdelims: [@openmathdelim, @closemathdelim],
    liststyles: { ul: @ulstyle, ol: @olstyle }
  ).process(result)
  header&.unlink
  @wordstylesheet.unlink if @wordstylesheet.is_a?(Tempfile)
end

#word_admonition_images(docxml) ⇒ Object



70
71
72
73
74
75
# File 'lib/isodoc/word_function/postprocess.rb', line 70

def word_admonition_images(docxml)
  docxml.xpath("//div[@class = 'Admonition']//img").each do |i|
    i["width"], i["height"] =
      Html2Doc.new({}).image_resize(i, image_localfile(i), @maxheight, 300)
  end
end

#word_annex_cleanup(docxml) ⇒ Object



202
# File 'lib/isodoc/word_function/postprocess.rb', line 202

def word_annex_cleanup(docxml); end

#word_cleanup(docxml) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/isodoc/word_function/postprocess.rb', line 77

def word_cleanup(docxml)
  word_annex_cleanup(docxml)
  word_preface(docxml)
  word_nested_tables(docxml)
  word_colgroup(docxml)
  word_table_align(docxml)
  word_table_separator(docxml)
  word_admonition_images(docxml)
  word_list_continuations(docxml)
  word_example_cleanup(docxml)
  word_pseudocode_cleanup(docxml)
  word_image_caption(docxml)
  word_section_breaks(docxml)
  word_tab_clean(docxml)
  authority_cleanup(docxml)
  word_footnote_format(docxml)
  docxml
end

#word_colgroup(docxml) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/isodoc/word_function/postprocess.rb', line 104

def word_colgroup(docxml)
  cells2d = {}
  docxml.xpath("//table[colgroup]").each do |t|
    w = colgroup_widths(t)
    t.xpath(".//tr").each_with_index { |_tr, r| cells2d[r] = {} }
    t.xpath(".//tr").each_with_index do |tr, r|
      tr.xpath("./td | ./th").each_with_index do |td, _i|
        x = 0
        rs = td.attr("rowspan")&.to_i || 1
        cs = td.attr("colspan")&.to_i || 1
        while cells2d[r][x]
          x += 1
        end
        (r..(r + rs - 1)).each do |y2|
          cells2d[y2].nil? and next
          (x..(x + cs - 1)).each { |x2| cells2d[y2][x2] = 1 }
        end
        width = (x..(x + cs - 1)).each_with_object({ width: 0 }) do |z, m|
          m[:width] += w[z]
        end
        td["width"] = "#{width[:width]}%"
        x += cs
      end
    end
  end
end

#word_cover(docxml) ⇒ Object



9
10
11
12
13
14
15
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 9

def word_cover(docxml)
  cover = File.read(@wordcoverpage, encoding: "UTF-8")
  cover = populate_template(cover, :word)
  coverxml = to_word_xhtml_fragment(cover)
  docxml.at('//div[@class="WordSection1"]').children.first.previous =
    coverxml.to_xml(encoding: "US-ASCII")
end

#word_example_cleanup(docxml) ⇒ Object



204
205
206
207
208
# File 'lib/isodoc/word_function/postprocess.rb', line 204

def word_example_cleanup(docxml)
  docxml.xpath("//div[@class = 'example']//p[not(@class)]").each do |p|
    p["class"] = "example"
  end
end

#word_footnote_format(docxml) ⇒ Object



228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/isodoc/word_function/postprocess.rb', line 228

def word_footnote_format(docxml)
  # the content is in a[@epub:type = 'footnote']//sup, but in Word,
  # we need to inject content around the autonumbered footnote reference
  docxml.xpath("//a[@epub:type = 'footnote']").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

#word_image_caption(docxml) ⇒ Object



153
154
155
156
157
158
159
160
161
162
# File 'lib/isodoc/word_function/postprocess.rb', line 153

def word_image_caption(docxml)
  docxml.xpath("//p[@class = 'FigureTitle' or @class = 'SourceTitle']")
    .each do |t|
    if t&.previous_element&.name == "img"
      img = t.previous_element
      t.previous_element.swap("<p class=\'figure\'>#{img.to_xml}</p>")
    end
    style_update(t&.previous_element, "page-break-after:avoid;")
  end
end

#word_intro(docxml, level) ⇒ Object



17
18
19
20
21
22
23
24
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 17

def word_intro(docxml, level)
  intro = insert_toc(File.read(@wordintropage, encoding: "UTF-8"),
                     docxml, level)
  intro = populate_template(intro, :word)
  introxml = to_word_xhtml_fragment(intro)
  docxml.at('//div[@class="WordSection2"]').children.first.previous =
    introxml.to_xml(encoding: "US-ASCII")
end

#word_list_continuations(docxml) ⇒ Object



164
165
166
167
168
169
# File 'lib/isodoc/word_function/postprocess.rb', line 164

def word_list_continuations(docxml)
  list_add(docxml.xpath("//ul[not(ancestor::ul) and not(ancestor::ol)]"),
           1)
  list_add(docxml.xpath("//ol[not(ancestor::ul) and not(ancestor::ol)]"),
           1)
end

#word_nested_tables(docxml) ⇒ Object



138
139
140
141
142
143
144
# File 'lib/isodoc/word_function/postprocess.rb', line 138

def word_nested_tables(docxml)
  docxml.xpath("//table").each do |t|
    t.xpath(".//table").reverse.each do |tt|
      t.next = tt.remove
    end
  end
end

#word_preface(docxml) ⇒ Object



4
5
6
7
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 4

def word_preface(docxml)
  word_cover(docxml) if @wordcoverpage
  word_intro(docxml, @wordToClevels) if @wordintropage
end

#word_pseudocode_cleanup(docxml) ⇒ Object



210
211
212
213
214
# File 'lib/isodoc/word_function/postprocess.rb', line 210

def word_pseudocode_cleanup(docxml)
  docxml.xpath("//div[@class = 'pseudocode']//p[not(@class)]").each do |p|
    p["class"] = "pseudocode"
  end
end

#word_remove_pb_before_annex(docxml) ⇒ Object

applies for <div class=“WordSectionN_M”><p><pagebreak/></p>…



217
218
219
220
221
222
223
224
225
226
# File 'lib/isodoc/word_function/postprocess.rb', line 217

def word_remove_pb_before_annex(docxml)
  docxml.xpath("//div[p/br]").each do |d|
    /^WordSection\d+_\d+$/.match(d["class"]) or next
    (d.elements[0].name == "p" && !d.elements[0].elements.empty?) or next
    (d.elements[0].elements[0].name == "br" &&
      d.elements[0].elements[0]["style"] ==
        "mso-special-character:line-break;page-break-before:always") or next
    d.elements[0].remove
  end
end

#word_section_breaks(docxml) ⇒ Object



207
208
209
210
211
212
213
214
215
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 207

def word_section_breaks(docxml)
  @landscapestyle = ""
  word_section_breaks1(docxml, "WordSection2")
  word_section_breaks1(docxml, "WordSection3")
  word_remove_pb_before_annex(docxml)
  docxml.xpath("//br[@orientation]").each do |br|
    br.delete("orientation")
  end
end

#word_section_breaks1(docxml, sect) ⇒ Object



217
218
219
220
221
222
223
224
225
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 217

def word_section_breaks1(docxml, sect)
  docxml.xpath("//div[@class = '#{sect}']//br[@orientation]").reverse
    .each_with_index do |br, i|
    @landscapestyle +=
      "\ndiv.#{sect}_#{i} {page:#{sect}"\
      "#{br['orientation'] == 'landscape' ? 'L' : 'P'};}\n"
    split_at_section_break(docxml, sect, br, i)
  end
end

#word_tab_clean(docxml) ⇒ Object



96
97
98
99
100
101
102
# File 'lib/isodoc/word_function/postprocess.rb', line 96

def word_tab_clean(docxml)
  docxml.xpath("//p[@class='Biblio']//span[@style='mso-tab-count:1']")
    .each do |s|
    s.next.text? or next
    s.next.replace(@c.encode(s.next.text.sub(/^\s+/, ""), :hexadecimal))
  end
end

#word_table_align(docxml) ⇒ Object



185
186
187
188
189
190
191
# File 'lib/isodoc/word_function/postprocess.rb', line 185

def word_table_align(docxml)
  docxml.xpath("//td[@align]/p | //th[@align]/p").each do |p|
    next if p["align"]

    style_update(p, "text-align: #{p.parent['align']}")
  end
end

#word_table_separator(docxml) ⇒ Object



193
194
195
196
197
198
199
200
# File 'lib/isodoc/word_function/postprocess.rb', line 193

def word_table_separator(docxml)
  docxml.xpath("//p[@class = 'TableTitle']").each do |t|
    next unless t.children.empty?

    t["style"] = t["style"].sub(/;?$/, ";font-size:0pt;")
    t.children = "&#xa0;"
  end
end

#word_toc_entry(toclevel, heading) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 35

def word_toc_entry(toclevel, heading)
  bookmark = bookmarkid # Random.rand(1000000000)
  <<~TOC
    <p class="MsoToc#{toclevel}"><span class="MsoHyperlink"><span
    lang="EN-GB" style='mso-no-proof:yes'>
    <a href="#_Toc#{bookmark}">#{heading}<span lang="EN-GB"
    class="MsoTocTextSpan">
    <span style='mso-tab-count:1 dotted'>. </span>
    </span><span lang="EN-GB" class="MsoTocTextSpan">
    <span style='mso-element:field-begin'></span></span>
    <span lang="EN-GB"
    class="MsoTocTextSpan"> PAGEREF _Toc#{bookmark} \\h </span>
      <span lang="EN-GB" class="MsoTocTextSpan"><span
      style='mso-element:field-separator'></span></span><span
      lang="EN-GB" class="MsoTocTextSpan">1</span>
      <span lang="EN-GB"
      class="MsoTocTextSpan"></span><span
      lang="EN-GB" class="MsoTocTextSpan"><span
      style='mso-element:field-end'></span></span></a></span></span></p>

  TOC
end

#word_toc_preface(level) ⇒ Object



58
59
60
61
62
63
64
65
66
# File 'lib/isodoc/word_function/postprocess_cover.rb', line 58

def word_toc_preface(level)
  <<~TOC.freeze
    <span lang="EN-GB"><span
      style='mso-element:field-begin'></span><span
      style='mso-spacerun:yes'>&#xA0;</span>TOC
      \\o "1-#{level}" \\h \\z \\u <span
      style='mso-element:field-separator'></span></span>
  TOC
end

#wordstylesheet_updateObject



55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/isodoc/word_function/postprocess.rb', line 55

def wordstylesheet_update
  return if @wordstylesheet.nil?

  f = File.open(@wordstylesheet.path, "a")
  @landscapestyle.empty? or f.write(@landscapestyle)
  if @wordstylesheet_override && @wordstylesheet
    f.write(@wordstylesheet_override.read)
    @wordstylesheet_override.close
  elsif @wordstylesheet_override && !@wordstylesheet
    @wordstylesheet = @wordstylesheet_override
  end
  f.close
  @wordstylesheet
end