Top Level Namespace

Defined Under Namespace

Modules: IsoDoc, Isodoc

Constant Summary collapse

COMMENT_IN_COMMENT_LIST1 =

COMMENT_IN_COMMENT_LIST =

'//div[@style="mso-element:comment-list"]//'\
'span[@style="MsoCommentReference"]'.freeze
COMMENT_TARGET_XREFS1 =

COMMENT_TARGET_XREFS =

"//span[@style='mso-special-character:comment']/@target".freeze
SW1 =

SW1 = IsoDoc::SW

"solid windowtext".freeze
WORD_TOC_PREFACE1 =

WORD_TOC_PREFACE = <<~TOC.freeze

<<~TOC.freeze
      <span lang="EN-GB"><span
        style='mso-element:field-begin'></span><span
        style='mso-spacerun:yes'>&#xA0;</span>TOC
        \\o &quot;1-2&quot; \\h \\z \\u <span
        style='mso-element:field-separator'></span></span>
TOC
WORD_TOC_SUFFIX1 =

WORD_TOC_SUFFIX = <<~TOC.freeze

<<~TOC.freeze
      <p class="MsoToc1"><span lang="EN-GB"><span
        style='mso-element:field-end'></span></span><span
        lang="EN-GB"><o:p>&nbsp;</o:p></span></p>
TOC

Instance Method Summary collapse

Instance Method Details

#comment_attributes(docxml, x) ⇒ Object



91
92
93
94
95
96
97
# File 'lib/isodoc/wordconvert/comments.rb', line 91

def comment_attributes(docxml, x)
  fromlink = docxml.at("//*[@id='#{x['from']}']")
  return(nil) if fromlink.nil?
  tolink = docxml.at("//*[@id='#{x['to']}']") || fromlink
  target = docxml.at("//*[@id='#{x['target']}']")
  { from: fromlink, to: tolink, target: target }
end

#comment_cleanup(docxml) ⇒ Object



64
65
66
67
68
# File 'lib/isodoc/wordconvert/comments.rb', line 64

def comment_cleanup(docxml)
  move_comment_link_to_from(docxml)
  reorder_comments_by_comment_link(docxml)
  embed_comment_in_comment_list(docxml)
end


28
29
30
31
32
# File 'lib/isodoc/wordconvert/comments.rb', line 28

def comment_link_attrs(fn, node)
  { style: "MsoCommentReference", target: fn,
    class: "commentLink", from: node["from"],
    to: node["to"] }
end

#comments(div) ⇒ Object



13
14
15
16
17
18
# File 'lib/isodoc/wordconvert/comments.rb', line 13

def comments(div)
  return if @comments.empty?
  div.div **{ style: "mso-element:comment-list" } do |div1|
    @comments.each { |fn| div1.parent << fn }
  end
end

#dl_parse(node, out) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 85

def dl_parse(node, out)
  out.table **{ class: "dl" } do |v|
    node.elements.each_slice(2) do |dt, dd|
      v.tr do |tr|
        tr.td **{ valign: "top", align: "left" } do |term|
          dt_parse(dt, term)
        end
        tr.td **{ valign: "top" } do |listitem|
          dd.children.each { |n| parse(n, listitem) }
        end
      end
    end
  end
end

#dt_parse(dt, term) ⇒ Object



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

def dt_parse(dt, term)
  if dt.elements.empty?
    term.p **attr_code(class: note? ? "Note" : nil,
                       style: "text-align: left;") do |p|
      p << dt.text
    end
  else
    dt.children.each { |n| parse(n, term) }
  end
end

#embed_comment_in_comment_list(docxml) ⇒ Object



75
76
77
78
79
80
81
82
# File 'lib/isodoc/wordconvert/comments.rb', line 75

def embed_comment_in_comment_list(docxml)
  #docxml.xpath(COMMENT_IN_COMMENT_LIST).each do |x|
  docxml.xpath(COMMENT_IN_COMMENT_LIST1).each do |x|
    n = x.next_element
    n&.children&.first&.add_previous_sibling(x.remove)
  end
  docxml
end

#footnote_parse(node, out) ⇒ Object



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

def footnote_parse(node, out)
  return table_footnote_parse(node, out) if @in_table || @in_figure
  fn = node["reference"]
  out.a **{ "epub:type": "footnote", href: "#ftn#{fn}" } do |a|
    a.sup { |sup| sup << fn }
  end
  return if @seen_footnote.include?(fn)
  @in_footnote = true
  @footnotes << make_generic_footnote_text(node, fn)
  @in_footnote = false
  @seen_footnote << fn
end

#footnotes(div) ⇒ Object

base.class_eval do



9
10
11
12
# File 'lib/isodoc/wordconvert/footnotes.rb', line 9

def footnotes(div)
  return if @footnotes.empty?
  @footnotes.each { |fn| div.parent << fn }
end

#generate_header(filename, _dir) ⇒ Object



149
150
151
152
153
154
155
156
157
158
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 149

def generate_header(filename, _dir)
  return unless @header
  template = Liquid::Template.parse(File.read(@header, encoding: "UTF-8"))
  meta = 
  meta[:filename] = filename
  params = meta.map { |k, v| [k.to_s, v] }.to_h
  File.open("header.html", "w") do |f|
    f.write(template.render(params))
  end
end

#get_comments_from_text(docxml, link_order) ⇒ Object



128
129
130
131
132
133
134
135
136
# File 'lib/isodoc/wordconvert/comments.rb', line 128

def get_comments_from_text(docxml, link_order)
  comments = []
  docxml.xpath("//div[@style='mso-element:comment']").each do |c|
    next unless c["id"] && !link_order[c["id"]].nil?
    comments << { text: c.remove.to_s, id: c["id"] }
  end
  comments.sort! { |a, b| link_order[a[:id]] <=> link_order[b[:id]] }
  # comments
end

#get_table_ancestor_id(node) ⇒ Object



47
48
49
50
51
# File 'lib/isodoc/wordconvert/footnotes.rb', line 47

def get_table_ancestor_id(node)
  table = node.ancestors("table") || node.ancestors("figure")
  return UUIDTools::UUID.random_create.to_s if table.empty?
  table.last["id"]
end

#in_commentObject

base.class_eval do



9
10
11
# File 'lib/isodoc/wordconvert/comments.rb', line 9

def in_comment
  @in_comment
end

#insert_comment_cont(from, to, target) ⇒ Object



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

def insert_comment_cont(from, to, target)
  # includes_to = from.at(".//*[@id='#{to}']")
  while !from.nil? && from["id"] != to
    following = from.xpath("./following::*")
    (from = following.shift) && incl_to = from.at(".//*[@id='#{to}']")
    while !incl_to.nil? && !from.nil? && skip_comment_wrap(from)
      (from = following.shift) && incl_to = from.at(".//*[@id='#{to}']")
    end
    wrap_comment_cont(from, target) if !from.nil?
  end
end

#insert_tab(out, n) ⇒ Object

module IsoDoc class WordConvert < Convert module WordConvertModule def self.included base base.class_eval do



10
11
12
13
14
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 10

def insert_tab(out, n)
  out.span **attr_code(style: "mso-tab-count:#{n}") do |span|
    [1..n].each { span << "&#xA0; " }
  end
end

add in from and to links to move the comment into place



35
36
37
38
39
40
41
42
43
44
# File 'lib/isodoc/wordconvert/comments.rb', line 35

def make_comment_link(out, fn, node)
  out.span(**comment_link_attrs(fn, node)) do |s1|
    s1.span **{ lang: "EN-GB", style: "font-size:9.0pt" } do |s2|
      s2.a **{ style: "mso-comment-reference:SMC_#{fn};"\
               "mso-comment-date:#{node['date']}" }
      s2.span **{ style: "mso-special-character:comment",
                  target: fn } # do |s|
    end
  end
end

#make_comment_target(out) ⇒ Object



46
47
48
49
50
51
52
# File 'lib/isodoc/wordconvert/comments.rb', line 46

def make_comment_target(out)
  out.span **{ style: "MsoCommentReference" } do |s1|
    s1.span **{ lang: "EN-GB", style: "font-size:9.0pt" } do |s2|
      s2.span **{ style: "mso-special-character:comment" }
    end
  end
end

#make_comment_text(node, fn) ⇒ Object



54
55
56
57
58
59
60
61
62
# File 'lib/isodoc/wordconvert/comments.rb', line 54

def make_comment_text(node, fn)
  noko do |xml|
    xml.div **{ style: "mso-element:comment", id: fn } do |div|
      div.span **{ style: %{mso-comment-author:"#{node['reviewer']}"} }
      make_comment_target(div)
      node.children.each { |n| parse(n, div) }
    end
  end.join("\n")
end

#make_generic_footnote_text(node, fnid) ⇒ Object



39
40
41
42
43
44
45
# File 'lib/isodoc/wordconvert/footnotes.rb', line 39

def make_generic_footnote_text(node, fnid)
  noko do |xml|
    xml.aside **{ id: "ftn#{fnid}" } do |div|
      node.children.each { |n| parse(n, div) }
    end
  end.join("\n")
end


14
15
16
17
18
19
# File 'lib/isodoc/wordconvert/footnotes.rb', line 14

def make_table_footnote_link(out, fnid, fnref)
  attrs = { href: "##{fnid}", class: "TableFootnoteRef" }
  out.a **attrs do |a|
    a << fnref
  end
end

#make_table_footnote_target(out, fnid, fnref) ⇒ Object



21
22
23
24
25
26
27
# File 'lib/isodoc/wordconvert/footnotes.rb', line 21

def make_table_footnote_target(out, fnid, fnref)
  attrs = { id: fnid, class: "TableFootnoteRef" }
  out.a **attrs do |a|
    a << fnref
    insert_tab(a, 1)
  end
end

#make_table_footnote_text(node, fnid, fnref) ⇒ Object



29
30
31
32
33
34
35
36
37
# File 'lib/isodoc/wordconvert/footnotes.rb', line 29

def make_table_footnote_text(node, fnid, fnref)
  attrs = { id: "ftn#{fnid}" }
  noko do |xml|
    xml.div **attr_code(attrs) do |div|
      make_table_footnote_target(div, fnid, fnref)
      node.children.each { |n| parse(n, div) }
    end
  end.join("\n")
end

#make_tr_attr(td, row, totalrows) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 50

def make_tr_attr(td, row, totalrows)
  style = td.name == "th" ? "font-weight:bold;" : ""
  rowmax = td["rowspan"] ? row + td["rowspan"].to_i - 1 : row
  style += <<~STYLE
        border-top:#{row.zero? ? "#{SW1} 1.5pt;" : 'none;'}
        mso-border-top-alt:#{row.zero? ? "#{SW1} 1.5pt;" : 'none;'}
        border-bottom:#{SW1} #{rowmax == totalrows ? '1.5' : '1.0'}pt;
        mso-border-bottom-alt:#{SW1} #{rowmax == totalrows ? '1.5' : '1.0'}pt;
  STYLE
  { rowspan: td["rowspan"], colspan: td["colspan"],
    align: td["align"], style: style.gsub(/\n/, "") }
end

#make_WordToC(docxml) ⇒ Object



199
200
201
202
203
204
205
206
207
208
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 199

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


120
121
122
123
124
125
126
# File 'lib/isodoc/wordconvert/comments.rb', line 120

def move_comment_link_to_from(docxml)
  docxml.xpath('//span[@style="MsoCommentReference"][@from]').each do |x|
    attrs = comment_attributes(docxml, x) || next
    move_comment_link_to_from1(x, attrs[:from])
    insert_comment_cont(attrs[:from], x["to"], x["target"])
  end
end


84
85
86
87
88
89
# File 'lib/isodoc/wordconvert/comments.rb', line 84

def move_comment_link_to_from1(x, fromlink)
  x.remove
  link = x.at(".//a")
  fromlink.replace(x)
  link.children = fromlink
end

#new_fullcolspan_row(t, tfoot) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 38

def new_fullcolspan_row(t, tfoot)
  # how many columns in the table?
  cols = 0
  t.at(".//tr").xpath("./td | ./th").each do |td|
    cols += (td["colspan"] ? td["colspan"].to_i : 1)
  end
  style = %{border-top:0pt;mso-border-top-alt:0pt;
      border-bottom:#{SW1} 1.5pt;mso-border-bottom-alt:#{SW1} 1.5pt;}
  tfoot.add_child("<tr><td colspan='#{cols}' style='#{style}'/></tr>")
  tfoot.xpath(".//td").last
end

#page_break(out) ⇒ Object



67
68
69
70
71
72
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 67

def page_break(out)
  out.br **{
    clear: "all",
    style: "mso-special-character:line-break;page-break-before:always",
  }
end

#para_attrs(node) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 16

def para_attrs(node)
  classtype = nil
  classtype = "Note" if @note
  classtype = "MsoCommentText" if in_comment
  classtype = "Sourcecode" if @annotation
  attrs = { class: classtype, id: node["id"] }
  unless node["align"].nil?
    attrs[:align] = node["align"] unless node["align"] == "justify"
    attrs[:style] = "text-align:#{node['align']}"
  end
  attrs
end

#postprocess(result, filename, dir) ⇒ Object



102
103
104
105
106
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 102

def postprocess(result, filename, dir)
  generate_header(filename, dir)
  result = from_xhtml(cleanup(to_xhtml(result)))
  toWord(result, filename, dir)
end

#remove_bottom_border(td) ⇒ Object



29
30
31
32
33
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 29

def remove_bottom_border(td)
  td["style"] =
    td["style"].gsub(/border-bottom:[^;]+;/, "border-bottom:0pt;").
    gsub(/mso-border-bottom-alt:[^;]+;/, "mso-border-bottom-alt:0pt;")
end


142
143
144
145
146
147
148
149
150
151
# File 'lib/isodoc/wordconvert/comments.rb', line 142

def reorder_comments_by_comment_link(docxml)
  link_order = {}
  #docxml.xpath(COMMENT_TARGET_XREFS).each_with_index do |target, i|
  docxml.xpath(COMMENT_TARGET_XREFS1).each_with_index do |target, i|
    link_order[target.value] = i
  end
  comments = get_comments_from_text(docxml, link_order)
  list = docxml.at("//*[@style='mso-element:comment-list']") || return
  list.children = comments.map { |c| c[:text] }.join("\n")
end

#review_note_parse(node, out) ⇒ Object



20
21
22
23
24
25
26
# File 'lib/isodoc/wordconvert/comments.rb', line 20

def review_note_parse(node, out)
  fn = @comments.length + 1
  make_comment_link(out, fn, node)
  @in_comment = true
  @comments << make_comment_text(node, fn)
  @in_comment = false
end

#section_break(body) ⇒ Object



63
64
65
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 63

def section_break(body)
  body.br **{ clear: "all", class: "section" }
end

#skip_comment_wrap(from) ⇒ Object



104
105
106
# File 'lib/isodoc/wordconvert/comments.rb', line 104

def skip_comment_wrap(from)
  from["style"] != "mso-special-character:comment"
end

#table_footnote_parse(node, out) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
# File 'lib/isodoc/wordconvert/footnotes.rb', line 53

def table_footnote_parse(node, out)
  fn = node["reference"]
  tid = get_table_ancestor_id(node)
  make_table_footnote_link(out, tid + fn, fn)
  # do not output footnote text if we have already seen it for this table
  return if @seen_footnote.include?(tid + fn)
  @in_footnote = true
  out.aside { |a| a << make_table_footnote_text(node, tid + fn, fn) }
  @in_footnote = false
  @seen_footnote << (tid + fn)
end

#toWord(result, filename, dir) ⇒ Object



108
109
110
111
112
113
114
115
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 108

def toWord(result, filename, dir)
  result = from_xhtml(word_cleanup(to_xhtml(result)))
  result = populate_template(result, :word)
  Html2Doc.process(result, filename: filename, stylesheet: @wordstylesheet,
                   header_file: "header.html", dir: dir,
                   asciimathdelims: [@openmathdelim, @closemathdelim],
                   liststyles: { ul: @ulstyle, ol: @olstyle })
end

#word_annex_cleanup(docxml) ⇒ Object

force Annex h2 to be p.h2Annex, so it is not picked up by ToC



124
125
126
127
128
129
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 124

def word_annex_cleanup(docxml)
  docxml.xpath("//h2[ancestor::*[@class = 'Section3']]").each do |h2|
    h2.name = "p"
    h2["class"] = "h2Annex"
  end
end

#word_cleanup(docxml) ⇒ Object



117
118
119
120
121
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 117

def word_cleanup(docxml)
  word_preface(docxml)
  word_annex_cleanup(docxml)
  docxml
end

#word_cover(docxml) ⇒ Object



136
137
138
139
140
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 136

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

#word_intro(docxml) ⇒ Object



142
143
144
145
146
147
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 142

def word_intro(docxml)
  intro = to_xhtml_fragment(File.read(@wordintropage, encoding: "UTF-8").
                            sub(/WORDTOC/, make_WordToC(docxml)))
  docxml.at('//div[@class="WordSection2"]').children.first.previous =
    intro.to_xml(encoding: "US-ASCII")
end

#word_preface(docxml) ⇒ Object



131
132
133
134
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 131

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

#word_toc_entry(toclevel, heading) ⇒ Object



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/isodoc/wordconvert/wordconvertmodule.rb', line 160

def word_toc_entry(toclevel, heading)
  bookmark = 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

#wrap_comment_cont(from, target) ⇒ Object



99
100
101
102
# File 'lib/isodoc/wordconvert/comments.rb', line 99

def wrap_comment_cont(from, target)
  s = from.replace("<span style='mso-comment-continuation:#{target}'>")
  s.first.children = from
end