isodoc 1.0.27 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/macos.yml +4 -8
  3. data/.github/workflows/ubuntu.yml +18 -16
  4. data/.github/workflows/windows.yml +4 -8
  5. data/Rakefile +3 -1
  6. data/isodoc.gemspec +2 -2
  7. data/lib/isodoc.rb +2 -0
  8. data/lib/isodoc/base_style/metanorma_word.scss +0 -1
  9. data/lib/isodoc/base_style/reset.scss +3 -3
  10. data/lib/isodoc/common.rb +0 -4
  11. data/lib/isodoc/convert.rb +121 -58
  12. data/lib/isodoc/function/blocks.rb +42 -53
  13. data/lib/isodoc/function/blocks_example_note.rb +108 -0
  14. data/lib/isodoc/function/cleanup.rb +8 -4
  15. data/lib/isodoc/function/i18n.rb +1 -0
  16. data/lib/isodoc/function/inline.rb +70 -90
  17. data/lib/isodoc/function/inline_simple.rb +72 -0
  18. data/lib/isodoc/function/lists.rb +12 -6
  19. data/lib/isodoc/function/references.rb +33 -38
  20. data/lib/isodoc/function/reqt.rb +14 -5
  21. data/lib/isodoc/function/section.rb +8 -11
  22. data/lib/isodoc/function/table.rb +4 -4
  23. data/lib/isodoc/function/terms.rb +3 -3
  24. data/lib/isodoc/function/to_word_html.rb +21 -13
  25. data/lib/isodoc/function/utils.rb +57 -50
  26. data/lib/isodoc/gem_tasks.rb +104 -0
  27. data/lib/isodoc/headlesshtml_convert.rb +7 -6
  28. data/lib/isodoc/html_convert.rb +2 -1
  29. data/lib/isodoc/html_function/footnotes.rb +1 -1
  30. data/lib/isodoc/html_function/html.rb +13 -1
  31. data/lib/isodoc/html_function/postprocess.rb +4 -4
  32. data/lib/isodoc/metadata.rb +74 -62
  33. data/lib/isodoc/pdf_convert.rb +8 -6
  34. data/lib/isodoc/presentation_xml_convert.rb +29 -0
  35. data/lib/isodoc/sassc_importer.rb +11 -0
  36. data/lib/isodoc/version.rb +1 -1
  37. data/lib/isodoc/word_convert.rb +2 -1
  38. data/lib/isodoc/word_function/body.rb +14 -48
  39. data/lib/isodoc/word_function/footnotes.rb +1 -1
  40. data/lib/isodoc/word_function/inline.rb +75 -0
  41. data/lib/isodoc/word_function/postprocess.rb +1 -0
  42. data/lib/isodoc/word_function/table.rb +3 -3
  43. data/lib/isodoc/xref.rb +59 -0
  44. data/lib/isodoc/{function → xref}/xref_anchor.rb +10 -21
  45. data/lib/isodoc/xref/xref_counter.rb +74 -0
  46. data/lib/isodoc/{function → xref}/xref_gen.rb +9 -22
  47. data/lib/isodoc/{function → xref}/xref_gen_seq.rb +41 -32
  48. data/lib/isodoc/{function → xref}/xref_sect_gen.rb +33 -23
  49. data/lib/isodoc/xslfo_convert.rb +16 -4
  50. data/spec/assets/{html.css → html.scss} +0 -0
  51. data/spec/assets/i18n.yaml +4 -1
  52. data/spec/assets/odf.emf +0 -0
  53. data/spec/assets/odf.svg +4 -0
  54. data/spec/assets/odf1.svg +4 -0
  55. data/spec/isodoc/blocks_spec.rb +219 -47
  56. data/spec/isodoc/cleanup_spec.rb +135 -6
  57. data/spec/isodoc/footnotes_spec.rb +22 -7
  58. data/spec/isodoc/inline_spec.rb +262 -2
  59. data/spec/isodoc/lists_spec.rb +8 -8
  60. data/spec/isodoc/metadata_spec.rb +110 -3
  61. data/spec/isodoc/postproc_spec.rb +1321 -1351
  62. data/spec/isodoc/presentation_xml_spec.rb +20 -0
  63. data/spec/isodoc/ref_spec.rb +119 -50
  64. data/spec/isodoc/section_spec.rb +84 -18
  65. data/spec/isodoc/table_spec.rb +6 -6
  66. data/spec/isodoc/terms_spec.rb +7 -7
  67. data/spec/isodoc/xref_spec.rb +165 -45
  68. metadata +36 -27
  69. data/lib/isodoc/function/blocks_example.rb +0 -53
  70. data/lib/isodoc/function/xref_counter.rb +0 -50
@@ -24,19 +24,21 @@ module IsoDoc
24
24
 
25
25
  def initialize(options)
26
26
  @format = :pdf
27
+ @suffix = "pdf"
27
28
  super
28
29
  end
29
30
 
30
- def convert(filename, file = nil, debug = false)
31
- file = File.read(filename, encoding: "utf-8") if file.nil?
31
+ def convert(input_filename, file = nil, debug = false, output_filename = nil)
32
+ file = File.read(input_filename, encoding: "utf-8") if file.nil?
32
33
  @openmathdelim, @closemathdelim = extract_delims(file)
33
- docxml, outname_html, dir = convert_init(file, filename, debug)
34
+ docxml, filename, dir = convert_init(file, input_filename, debug)
34
35
  result = convert1(docxml, filename, dir)
35
36
  return result if debug
36
- postprocess(result, filename, dir)
37
+ postprocess(result, filename + ".tmp.html", dir)
37
38
  FileUtils.rm_rf dir
38
- ::Metanorma::Output::Pdf.new.convert("#{filename}.html", outname_html + ".pdf")
39
- FileUtils.rm_rf ["#{filename}.html", tmpimagedir]
39
+ ::Metanorma::Output::Pdf.new.convert("#{filename}.tmp.html",
40
+ output_filename || "#{filename}.#{@suffix}")
41
+ FileUtils.rm_rf ["#{filename}.tmp.html", tmpimagedir]
40
42
  end
41
43
 
42
44
  def xref_parse(node, out)
@@ -0,0 +1,29 @@
1
+ module IsoDoc
2
+ class PresentationXMLConvert < ::IsoDoc::Convert
3
+ def initialize(options)
4
+ @format = :presentation
5
+ @suffix = "presentation.xml"
6
+ super
7
+ end
8
+
9
+ def convert1(docxml, filename, dir)
10
+ @xrefs.parse docxml
11
+ info docxml, nil
12
+ docxml.to_xml
13
+ end
14
+
15
+ def postprocess(result, filename, dir)
16
+ #result = from_xhtml(cleanup(to_xhtml(textcleanup(result))))
17
+ toXML(result, filename)
18
+ @files_to_delete.each { |f| FileUtils.rm_rf f }
19
+ end
20
+
21
+ def toXML(result, filename)
22
+ #result = (from_xhtml(html_cleanup(to_xhtml(result))))
23
+ #result = from_xhtml(move_images(to_xhtml(result)))
24
+ #result = html5(script_cdata(inject_script(result)))
25
+ File.open(filename, "w:UTF-8") { |f| f.write(result) }
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sassc'
4
+
5
+ class SasscImporter < SassC::Importer
6
+ def imports(path, _parent_path)
7
+ unless path.match?(/(css|scss)$/)
8
+ Import.new("#{path}.scss")
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module IsoDoc
2
- VERSION = "1.0.27".freeze
2
+ VERSION = "1.1.2".freeze
3
3
  end
@@ -32,10 +32,11 @@ module IsoDoc
32
32
 
33
33
  def initialize(options)
34
34
  @format = :doc
35
+ @suffix = "doc"
35
36
  super
36
37
  end
37
38
 
38
- def convert(filename, file = nil, debug = false)
39
+ def convert(filename, file = nil, debug = false, output_filename = nil)
39
40
  ret = super
40
41
  FileUtils.rm_rf tmpimagedir
41
42
  ret
@@ -1,4 +1,5 @@
1
1
  require_relative "./table.rb"
2
+ require_relative "./inline.rb"
2
3
 
3
4
  module IsoDoc::WordFunction
4
5
  module Body
@@ -69,28 +70,6 @@ module IsoDoc::WordFunction
69
70
  node.xpath(ns("./note")).each { |n| parse(n, out) }
70
71
  end
71
72
 
72
- def section_break(body)
73
- body.p do |p|
74
- p.br **{ clear: "all", class: "section" }
75
- end
76
- end
77
-
78
- def page_break(out)
79
- out.p do |p|
80
- p.br **{ clear: "all",
81
- style: "mso-special-character:line-break;"\
82
- "page-break-before:always" }
83
- end
84
- end
85
-
86
- def pagebreak_parse(node, out)
87
- return page_break(out) if node["orientation"].nil?
88
- out.p do |p|
89
- p.br **{clear: "all", class: "section",
90
- orientation: node["orientation"] }
91
- end
92
- end
93
-
94
73
  WORD_DT_ATTRS = {class: @note ? "Note" : nil, align: "left",
95
74
  style: "margin-left:0pt;text-align:left;"}.freeze
96
75
 
@@ -174,10 +153,10 @@ module IsoDoc::WordFunction
174
153
  end
175
154
 
176
155
  def termnote_parse(node, out)
177
- out.div **{ class: "Note" } do |div|
156
+ out.div **note_attrs(node) do |div|
178
157
  first = node.first_element_child
179
158
  div.p **{ class: "Note" } do |p|
180
- anchor = get_anchors[node['id']]
159
+ anchor = @xrefs.get[node['id']]
181
160
  p << "#{anchor&.dig(:label) || '???'}: "
182
161
  para_then_remainder(first, node, p, div)
183
162
  end
@@ -185,41 +164,23 @@ module IsoDoc::WordFunction
185
164
  end
186
165
 
187
166
  def para_attrs(node)
188
- attrs = { class: para_class(node), id: node["id"] }
167
+ attrs = { class: para_class(node), id: node["id"], style: "" }
189
168
  unless node["align"].nil?
190
169
  attrs[:align] = node["align"] unless node["align"] == "justify"
191
- attrs[:style] = "text-align:#{node['align']}"
170
+ attrs[:style] += "text-align:#{node['align']};"
192
171
  end
172
+ attrs[:style] += "#{keep_style(node)}"
173
+ attrs[:style] = nil if attrs[:style].empty?
193
174
  attrs
194
175
  end
195
176
 
196
- def imgsrc(uri)
197
- return uri unless %r{^data:image/}.match uri
198
- save_dataimage(uri)
199
- end
200
-
201
- def image_parse(node, out, caption)
202
- attrs = { src: imgsrc(node["src"]),
203
- height: node["height"],
204
- alt: node["alt"],
205
- title: node["title"],
206
- width: node["width"] }
207
- out.img **attr_code(attrs)
208
- image_title_parse(out, caption)
209
- end
210
-
211
- def xref_parse(node, out)
212
- target = /#/.match(node["target"]) ? node["target"].sub(/#/, ".doc#") :
213
- "##{node["target"]}"
214
- out.a(**{ "href": target }) { |l| l << get_linkend(node) }
215
- end
216
-
217
177
  def example_table_attr(node)
218
178
  super.merge({
219
179
  style: "mso-table-lspace:15.0cm;margin-left:423.0pt;"\
220
180
  "mso-table-rspace:15.0cm;margin-right:423.0pt;"\
221
181
  "mso-table-anchor-horizontal:column;"\
222
- "mso-table-overlap:never;border-collapse:collapse;"
182
+ "mso-table-overlap:never;border-collapse:collapse;"\
183
+ "#{keep_style(node)}"
223
184
  })
224
185
  end
225
186
 
@@ -240,5 +201,10 @@ module IsoDoc::WordFunction
240
201
  node.children.each { |n| parse(n, li) }
241
202
  end
242
203
  end
204
+
205
+ def suffix_url(url)
206
+ return url if %r{^http[s]?://}.match(url)
207
+ url.sub(/#{File.extname(url)}$/, ".doc")
208
+ end
243
209
  end
244
210
  end
@@ -84,7 +84,7 @@ module IsoDoc::WordFunction
84
84
  return seen_footnote_parse(node, out, fn) if @seen_footnote.include?(fn)
85
85
  @fn_bookmarks[fn] = bookmarkid
86
86
  out.span **{style: "mso-bookmark:_Ref#{@fn_bookmarks[fn]}"} do |s|
87
- s.a **{ "epub:type": "footnote", href: "#ftn#{fn}" } do |a|
87
+ s.a **{ "class": "FootnoteRef", "epub:type": "footnote", href: "#ftn#{fn}" } do |a|
88
88
  a.sup { |sup| sup << fn }
89
89
  end
90
90
  end
@@ -0,0 +1,75 @@
1
+ module IsoDoc::WordFunction
2
+ module Body
3
+ def section_break(body)
4
+ body.p do |p|
5
+ p.br **{ clear: "all", class: "section" }
6
+ end
7
+ end
8
+
9
+ def page_break(out)
10
+ out.p do |p|
11
+ p.br **{ clear: "all",
12
+ style: "mso-special-character:line-break;"\
13
+ "page-break-before:always" }
14
+ end
15
+ end
16
+
17
+ def pagebreak_parse(node, out)
18
+ return page_break(out) if node["orientation"].nil?
19
+ out.p do |p|
20
+ p.br **{clear: "all", class: "section",
21
+ orientation: node["orientation"] }
22
+ end
23
+ end
24
+
25
+ def imgsrc(node)
26
+ ret = svg_to_emf(node) and return ret
27
+ return node["src"] unless %r{^data:image/}.match node["src"]
28
+ save_dataimage(node["src"])
29
+ end
30
+
31
+ def image_parse(node, out, caption)
32
+ attrs = { src: imgsrc(node),
33
+ height: node["height"],
34
+ alt: node["alt"],
35
+ title: node["title"],
36
+ width: node["width"] }
37
+ out.img **attr_code(attrs)
38
+ image_title_parse(out, caption)
39
+ end
40
+
41
+ def svg_to_emf_filename(uri)
42
+ File.join(File.dirname(uri), File.basename(uri, ".*")) + ".emf"
43
+ end
44
+
45
+ def svg_to_emf(node)
46
+ return unless node["mimetype"] == "image/svg+xml"
47
+ uri = node["src"]
48
+ %r{^data:image/}.match(uri) and uri = save_dataimage(uri)
49
+ ret = svg_to_emf_filename(uri)
50
+ File.exists?(ret) and return ret
51
+ exe = inkscape_installed? or return nil
52
+ system %(#{exe} --export-type="emf" #{uri}) and
53
+ return ret
54
+ nil
55
+ end
56
+
57
+ def inkscape_installed?
58
+ cmd = "inkscape"
59
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
60
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
61
+ exts.each do |ext|
62
+ exe = File.join(path, "#{cmd}#{ext}")
63
+ return exe if File.executable?(exe) && !File.directory?(exe)
64
+ end
65
+ end
66
+ nil
67
+ end
68
+
69
+ def xref_parse(node, out)
70
+ target = /#/.match(node["target"]) ? node["target"].sub(/#/, ".doc#") :
71
+ "##{node["target"]}"
72
+ out.a(**{ "href": target }) { |l| l << get_linkend(node) }
73
+ end
74
+ end
75
+ end
@@ -32,6 +32,7 @@ xmlns:m="http://schemas.microsoft.com/office/2004/12/omml">
32
32
  end
33
33
 
34
34
  def postprocess(result, filename, dir)
35
+ filename = filename.sub(/\.doc$/, "")
35
36
  header = generate_header(filename, dir)
36
37
  result = from_xhtml(cleanup(to_xhtml(textcleanup(result))))
37
38
  toWord(result, filename, dir, header)
@@ -33,12 +33,12 @@ module IsoDoc::WordFunction
33
33
  align: td["align"], style: style.gsub(/\n/, "") }
34
34
  end
35
35
 
36
- def make_table_attr(node)
36
+ def table_attrs(node)
37
37
  super.merge(attr_code({
38
38
  summary: node["summary"],
39
39
  width: node["width"],
40
40
  style: "mso-table-anchor-horizontal:column;"\
41
- "mso-table-overlap:never;border-spacing:0;border-width:1px;"
41
+ "mso-table-overlap:never;border-spacing:0;border-width:1px;#{keep_style(node)}"
42
42
  }))
43
43
  end
44
44
 
@@ -46,7 +46,7 @@ module IsoDoc::WordFunction
46
46
  @in_table = true
47
47
  table_title_parse(node, out)
48
48
  out.div **{ align: "center", class: "table_container" } do |div|
49
- div.table **make_table_attr(node) do |t|
49
+ div.table **table_attrs(node) do |t|
50
50
  thead_parse(node, t)
51
51
  tbody_parse(node, t)
52
52
  tfoot_parse(node, t)
@@ -0,0 +1,59 @@
1
+ require_relative "xref/xref_anchor"
2
+ require_relative "xref/xref_counter"
3
+ require_relative "xref/xref_gen_seq"
4
+ require_relative "xref/xref_gen"
5
+ require_relative "xref/xref_sect_gen"
6
+ require_relative "class_utils"
7
+
8
+ module IsoDoc
9
+ class Xref
10
+ include XrefGen::Anchor
11
+ include XrefGen::Blocks
12
+ include XrefGen::Sections
13
+
14
+ def initialize(lang, script, klass, labels, options = {})
15
+ @anchors = {}
16
+ @lang = lang
17
+ @script = script
18
+ @klass = klass
19
+ @labels = labels
20
+ @options = options
21
+ end
22
+
23
+ def get
24
+ @anchors
25
+ end
26
+
27
+ def anchor(id, lbl, warning = true)
28
+ return nil if id.nil? || id.empty?
29
+ unless @anchors[id]
30
+ if warning
31
+ @seen ||= Seen_Anchor.instance
32
+ @seen.seen(id) or warn "No label has been processed for ID #{id}"
33
+ @seen.add(id)
34
+ return "[#{id}]"
35
+ end
36
+ end
37
+ @anchors.dig(id, lbl)
38
+ end
39
+
40
+ # extract names for all anchors, xref and label
41
+ def parse(docxml)
42
+ initial_anchor_names(docxml)
43
+ back_anchor_names(docxml)
44
+ # preempt clause notes with all other types of note (ISO default)
45
+ note_anchor_names(docxml.xpath(ns("//table | //figure")))
46
+ note_anchor_names(docxml.xpath(ns(SECTIONS_XPATH)))
47
+ example_anchor_names(docxml.xpath(ns(SECTIONS_XPATH)))
48
+ list_anchor_names(docxml.xpath(ns(SECTIONS_XPATH)))
49
+ end
50
+
51
+ def ns(xpath)
52
+ Common::ns(xpath)
53
+ end
54
+
55
+ def l10n(a, lang = @lang, script = @script)
56
+ IsoDoc::Function::I18n::l10n(a, lang, script)
57
+ end
58
+ end
59
+ end
@@ -1,7 +1,7 @@
1
1
  require "singleton"
2
2
 
3
- module IsoDoc::Function
4
- module XrefGen
3
+ module IsoDoc::XrefGen
4
+ module Anchor
5
5
  class Seen_Anchor
6
6
  include Singleton
7
7
 
@@ -12,34 +12,23 @@ module IsoDoc::Function
12
12
  def seen(x)
13
13
  @seen.has_key?(x)
14
14
  end
15
-
15
+
16
16
  def add(x)
17
17
  @seen[x] = true
18
18
  end
19
19
  end
20
20
 
21
- @anchors = {}
21
+ def initialize()
22
+ @anchors = {}
23
+ end
22
24
 
23
25
  def get_anchors
24
26
  @anchors
25
27
  end
26
28
 
27
- def anchor(id, lbl, warning = true)
28
- return nil if id.nil? || id.empty?
29
- unless @anchors[id]
30
- if warning
31
- @seen ||= Seen_Anchor.instance
32
- @seen.seen(id) or warn "No label has been processed for ID #{id}"
33
- @seen.add(id)
34
- return "[#{id}]"
35
- end
36
- end
37
- @anchors.dig(id, lbl)
38
- end
39
-
40
29
  def anchor_struct_label(lbl, elem)
41
30
  case elem
42
- when @appendix_lbl then l10n("#{elem} #{lbl}")
31
+ when @labels["appendix"] then l10n("#{elem} #{lbl}")
43
32
  else
44
33
  lbl.to_s
45
34
  end
@@ -47,8 +36,8 @@ module IsoDoc::Function
47
36
 
48
37
  def anchor_struct_xref(lbl, elem)
49
38
  case elem
50
- when @formula_lbl then l10n("#{elem} (#{lbl})")
51
- when @inequality_lbl then l10n("#{elem} (#{lbl})")
39
+ when @labels["formula"] then l10n("#{elem} (#{lbl})")
40
+ when @labels["inequality"] then l10n("#{elem} (#{lbl})")
52
41
  else
53
42
  l10n("#{elem} #{lbl}")
54
43
  end
@@ -59,7 +48,7 @@ module IsoDoc::Function
59
48
  ret[:label] = unnumbered == "true" ? nil : anchor_struct_label(lbl, elem)
60
49
  ret[:xref] = anchor_struct_xref(unnumbered == "true" ? "(??)" : lbl, elem)
61
50
  ret[:xref].gsub!(/ $/, "")
62
- ret[:container] = get_clause_id(container) unless container.nil?
51
+ ret[:container] = @klass.get_clause_id(container) unless container.nil?
63
52
  ret[:type] = type
64
53
  ret
65
54
  end
@@ -0,0 +1,74 @@
1
+ require "roman-numerals"
2
+
3
+ module IsoDoc::XrefGen
4
+ class Counter
5
+ def initialize
6
+ @num = 0
7
+ @letter = ""
8
+ @subseq = ""
9
+ @letter_override = nil
10
+ @number_override = nil
11
+ end
12
+
13
+ def new_subseq_increment(node)
14
+ @subseq = node["subsequence"]
15
+ @num += 1
16
+ @letter = node["subsequence"] ? "a" : ""
17
+ if node["number"]
18
+ /^(?<n>\d*)(?<a>[a-z]*)$/ =~ node["number"]
19
+ if n || a
20
+ @letter_override = @letter = a if a
21
+ @number_override = @num = n.to_i if n
22
+ else
23
+ @letter_override = node["number"]
24
+ @letter = @letter_override if /^[a-z]$/.match(@letter_override)
25
+ end
26
+ end
27
+ end
28
+
29
+ def sequence_increment(node)
30
+ if node["number"]
31
+ @number_override = node["number"]
32
+ @num = @number_override.to_i if /^\d+$/.match(@number_override)
33
+ else
34
+ @num += 1
35
+ end
36
+ end
37
+
38
+ def subsequence_increment(node)
39
+ if node["number"]
40
+ @letter_override = node["number"]
41
+ @letter = @letter_override if /^[a-z]$/.match(@letter_override)
42
+ else
43
+ @letter = (@letter.ord + 1).chr.to_s
44
+ end
45
+ end
46
+
47
+ def increment(node)
48
+ return self if node["unnumbered"]
49
+ @letter_override = nil
50
+ @number_override = nil
51
+ if node["subsequence"] != @subseq
52
+ new_subseq_increment(node)
53
+ elsif @letter.empty?
54
+ sequence_increment(node)
55
+ else
56
+ subsequence_increment(node)
57
+ end
58
+ self
59
+ end
60
+
61
+ def print
62
+ "#{@number_override || @num}#{@letter_override || @letter}"
63
+ end
64
+
65
+ def listlabel(depth)
66
+ return @num.to_s if [2, 7].include? depth
67
+ return (96 + @num).chr.to_s if [1, 6].include? depth
68
+ return (64 + @num).chr.to_s if [4, 9].include? depth
69
+ return RomanNumerals.to_roman(@num).downcase if [3, 8].include? depth
70
+ return RomanNumerals.to_roman(@num).upcase if [5, 10].include? depth
71
+ return @num.to_s
72
+ end
73
+ end
74
+ end