isodoc 1.6.3 → 1.6.4
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.
- checksums.yaml +4 -4
- data/.github/workflows/rake.yml +2 -12
- data/.hound.yml +3 -1
- data/.rubocop.yml +3 -5
- data/isodoc.gemspec +1 -1
- data/lib/isodoc.rb +0 -2
- data/lib/isodoc/convert.rb +4 -0
- data/lib/isodoc/function/cleanup.rb +52 -43
- data/lib/isodoc/function/inline.rb +7 -7
- data/lib/isodoc/function/section.rb +29 -16
- data/lib/isodoc/function/to_word_html.rb +6 -3
- data/lib/isodoc/function/utils.rb +181 -163
- data/lib/isodoc/headlesshtml_convert.rb +8 -7
- data/lib/isodoc/html_function/comments.rb +2 -0
- data/lib/isodoc/html_function/footnotes.rb +14 -7
- data/lib/isodoc/html_function/html.rb +30 -26
- data/lib/isodoc/html_function/postprocess.rb +16 -13
- data/lib/isodoc/pdf_convert.rb +11 -13
- data/lib/isodoc/presentation_function/bibdata.rb +43 -22
- data/lib/isodoc/presentation_function/inline.rb +20 -15
- data/lib/isodoc/version.rb +1 -1
- data/lib/isodoc/word_function/postprocess.rb +50 -36
- data/lib/isodoc/xref/xref_gen_seq.rb +60 -35
- data/lib/isodoc/xref/xref_sect_gen.rb +4 -4
- data/spec/assets/scripts_override.html +3 -0
- data/spec/isodoc/blocks_spec.rb +196 -245
- data/spec/isodoc/inline_spec.rb +442 -289
- data/spec/isodoc/postproc_spec.rb +111 -50
- data/spec/isodoc/presentation_xml_spec.rb +354 -277
- data/spec/isodoc/terms_spec.rb +83 -83
- metadata +5 -4
@@ -21,9 +21,10 @@ module IsoDoc::Function
|
|
21
21
|
filename = filepath.sub_ext("").sub(/\.presentation$/, "").to_s
|
22
22
|
dir = init_dir(filename, debug)
|
23
23
|
@filename = filename
|
24
|
-
@localdir = filepath.parent
|
24
|
+
@localdir = "#{filepath.parent}/"
|
25
25
|
@sourcedir = @localdir
|
26
|
-
@sourcefilename and
|
26
|
+
@sourcefilename and
|
27
|
+
@sourcedir = "#{Pathname.new(@sourcefilename).parent}/"
|
27
28
|
[filename, dir]
|
28
29
|
end
|
29
30
|
|
@@ -47,7 +48,7 @@ module IsoDoc::Function
|
|
47
48
|
|
48
49
|
# isodoc.css overrides any CSS injected by Html2Doc, which
|
49
50
|
# is inserted before this CSS.
|
50
|
-
def define_head(head,
|
51
|
+
def define_head(head, _filename, _dir)
|
51
52
|
if @standardstylesheet
|
52
53
|
head.style do |style|
|
53
54
|
@standardstylesheet.open
|
@@ -128,6 +129,8 @@ module IsoDoc::Function
|
|
128
129
|
end
|
129
130
|
|
130
131
|
def boilerplate(node, out)
|
132
|
+
return if @bare
|
133
|
+
|
131
134
|
boilerplate = node.at(ns("//boilerplate")) or return
|
132
135
|
out.div **{ class: "authority" } do |s|
|
133
136
|
boilerplate.children.each do |n|
|
@@ -1,193 +1,211 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module IsoDoc
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
module IsoDoc
|
4
|
+
module Function
|
5
|
+
module Utils
|
6
|
+
def date_range(date)
|
7
|
+
self.class.date_range(date)
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
def ns(xpath)
|
11
|
+
self.class.ns(xpath)
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
def insert_tab(out, count)
|
15
|
+
tab = %w(Hans Hant).include?(@script) ? " " : " "
|
16
|
+
[1..count].each { out << tab }
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
19
|
+
# add namespaces for Word fragments
|
20
|
+
NOKOHEAD = <<~HERE
|
21
|
+
<!DOCTYPE html SYSTEM
|
22
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
23
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
24
|
+
<head> <title></title> <meta charset="UTF-8" /> </head>
|
25
|
+
<body> </body> </html>
|
26
|
+
HERE
|
27
|
+
|
28
|
+
# block for processing XML document fragments as XHTML,
|
29
|
+
# to allow for HTMLentities
|
30
|
+
def noko(&block)
|
31
|
+
doc = ::Nokogiri::XML.parse(NOKOHEAD)
|
32
|
+
fragment = doc.fragment("")
|
33
|
+
::Nokogiri::XML::Builder.with fragment, &block
|
34
|
+
fragment.to_xml(encoding: "US-ASCII").lines.map do |l|
|
35
|
+
l.gsub(/\s*\n/, "")
|
36
|
+
end
|
35
37
|
end
|
36
|
-
end
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
39
|
+
def attr_code(attributes)
|
40
|
+
attributes = attributes.reject { |_, val| val.nil? }.map
|
41
|
+
attributes.map do |k, v|
|
42
|
+
[k, v.is_a?(String) ? HTMLEntities.new.decode(v) : v]
|
43
|
+
end.to_h
|
44
|
+
end
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
abort "Malformed Output XML for #{@format}: #{e} "\
|
60
|
-
"(see #{@filename}.#{@format}.err)"
|
46
|
+
DOCTYPE_HDR = "<!DOCTYPE html SYSTEM "\
|
47
|
+
'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
|
48
|
+
|
49
|
+
def to_xhtml(xml)
|
50
|
+
xml = to_xhtml_prep(xml)
|
51
|
+
begin
|
52
|
+
Nokogiri::XML.parse(xml, &:strict)
|
53
|
+
rescue Nokogiri::XML::SyntaxError => e
|
54
|
+
File.open("#{@filename}.#{@format}.err", "w:UTF-8") do |f|
|
55
|
+
f.write xml
|
56
|
+
end
|
57
|
+
abort "Malformed Output XML for #{@format}: #{e} "\
|
58
|
+
"(see #{@filename}.#{@format}.err)"
|
59
|
+
end
|
61
60
|
end
|
62
|
-
end
|
63
61
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
62
|
+
def to_xhtml_prep(xml)
|
63
|
+
xml.gsub!(/<\?xml[^>]*>/, "")
|
64
|
+
/<!DOCTYPE /.match(xml) || (xml = DOCTYPE_HDR + xml)
|
65
|
+
xml.split(/(&[^ \r\n\t#;]+;)/).map do |t|
|
66
|
+
if /^(&[^ \t\r\n#;]+;)/.match?(t)
|
67
|
+
HTMLEntities.new.encode(HTMLEntities.new.decode(t), :hexadecimal)
|
68
|
+
else t
|
69
|
+
end
|
70
|
+
end.join("")
|
71
|
+
end
|
69
72
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
+
def to_xhtml_fragment(xml)
|
74
|
+
doc = ::Nokogiri::XML.parse(NOKOHEAD)
|
75
|
+
doc.fragment(xml)
|
76
|
+
end
|
73
77
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
"local-name() = 'acknowledgements' or local-name() = 'term' or "\
|
78
|
-
"local-name() = 'appendix' or local-name() = 'foreword' or "\
|
79
|
-
"local-name() = 'introduction' or local-name() = 'terms' or "\
|
80
|
-
"local-name() = 'clause' or local-name() = 'references']/@id"
|
81
|
-
|
82
|
-
def get_clause_id(node)
|
83
|
-
clause = node.xpath(CLAUSE_ANCESTOR)
|
84
|
-
clause&.last&.text || nil
|
85
|
-
end
|
78
|
+
def from_xhtml(xml)
|
79
|
+
xml.to_xml.sub(%r{ xmlns="http://www.w3.org/1999/xhtml"}, "")
|
80
|
+
end
|
86
81
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
end
|
82
|
+
CLAUSE_ANCESTOR =
|
83
|
+
".//ancestor::*[local-name() = 'annex' or "\
|
84
|
+
"local-name() = 'definitions' or "\
|
85
|
+
"local-name() = 'acknowledgements' or local-name() = 'term' or "\
|
86
|
+
"local-name() = 'appendix' or local-name() = 'foreword' or "\
|
87
|
+
"local-name() = 'introduction' or local-name() = 'terms' or "\
|
88
|
+
"local-name() = 'clause' or local-name() = 'references']/@id"
|
89
|
+
|
90
|
+
def get_clause_id(node)
|
91
|
+
clause = node.xpath(CLAUSE_ANCESTOR)
|
92
|
+
clause&.last&.text || nil
|
93
|
+
end
|
100
94
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
95
|
+
NOTE_CONTAINER_ANCESTOR =
|
96
|
+
".//ancestor::*[local-name() = 'annex' or "\
|
97
|
+
"local-name() = 'foreword' or local-name() = 'appendix' or "\
|
98
|
+
"local-name() = 'introduction' or local-name() = 'terms' or "\
|
99
|
+
"local-name() = 'acknowledgements' or local-name() = 'term' or "\
|
100
|
+
"local-name() = 'clause' or local-name() = 'references' or "\
|
101
|
+
"local-name() = 'figure' or local-name() = 'formula' or "\
|
102
|
+
"local-name() = 'table' or local-name() = 'example']/@id"
|
103
|
+
|
104
|
+
def get_note_container_id(node)
|
105
|
+
container = node.xpath(NOTE_CONTAINER_ANCESTOR)
|
106
|
+
container&.last&.text || nil
|
108
107
|
end
|
109
|
-
end
|
110
108
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
109
|
+
def sentence_join(array)
|
110
|
+
return "" if array.nil? || array.empty?
|
111
|
+
|
112
|
+
if array.length == 1 then array[0]
|
113
|
+
else
|
114
|
+
@i18n.l10n("#{array[0..-2].join(', ')} "\
|
115
|
+
"#{@i18n.and} #{array.last}",
|
116
|
+
@lang, @script)
|
117
|
+
end
|
118
118
|
end
|
119
|
-
[@openmathdelim, @closemathdelim]
|
120
|
-
end
|
121
119
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
x.replace(x.children) if x.name == "a"
|
133
|
-
end
|
134
|
-
from_xhtml(h1)
|
135
|
-
end
|
120
|
+
# avoid `; avoid {{ (Liquid Templates); avoid [[ (Javascript)
|
121
|
+
def extract_delims(text)
|
122
|
+
@openmathdelim = "(#("
|
123
|
+
@closemathdelim = ")#)"
|
124
|
+
while text.include?(@openmathdelim) || text.include?(@closemathdelim)
|
125
|
+
@openmathdelim += "("
|
126
|
+
@closemathdelim += ")"
|
127
|
+
end
|
128
|
+
[@openmathdelim, @closemathdelim]
|
129
|
+
end
|
136
130
|
|
137
|
-
|
138
|
-
|
139
|
-
|
131
|
+
def header_strip(hdr)
|
132
|
+
h1 = to_xhtml_fragment(hdr.to_s.gsub(%r{<br\s*/>}, " ")
|
133
|
+
.gsub(/<\/?h[123456][^>]*>/, "").gsub(/<\/?b[^>]*>/, "").dup)
|
134
|
+
h1.traverse do |x|
|
135
|
+
if x.name == "span" && /mso-tab-count/.match(x["style"])
|
136
|
+
x.replace(" ")
|
137
|
+
elsif header_strip_elem?(x) then x.remove
|
138
|
+
elsif x.name == "a" then x.replace(x.children)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
from_xhtml(h1)
|
142
|
+
end
|
140
143
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
end
|
147
|
-
Liquid::Template.parse(doc)
|
148
|
-
end
|
144
|
+
def header_strip_elem?(elem)
|
145
|
+
elem.name == "img" ||
|
146
|
+
elem.name == "span" && elem["class"] == "MsoCommentReference" ||
|
147
|
+
elem.name == "a" && elem["class"] == "FootnoteRef" ||
|
148
|
+
elem.name == "span" && /mso-bookmark/.match(elem["style"])
|
149
|
+
end
|
149
150
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
151
|
+
=begin
|
152
|
+
def liquid(doc)
|
153
|
+
self.class.liquid(doc)
|
154
|
+
end
|
155
|
+
=end
|
156
|
+
|
157
|
+
def liquid(doc)
|
158
|
+
# unescape HTML escapes in doc
|
159
|
+
doc = doc.split(%r<(\{%|%\})>).each_slice(4).map do |a|
|
160
|
+
a[2] = a[2].gsub(/</, "<").gsub(/>/, ">") if a.size > 2
|
161
|
+
a.join("")
|
162
|
+
end.join("")
|
163
|
+
Liquid::Template.parse(doc)
|
164
|
+
end
|
154
165
|
|
155
|
-
|
156
|
-
|
157
|
-
.get
|
158
|
-
.merge(@labels ? {labels: @labels} : {})
|
159
|
-
.merge(@meta.labels ? {labels: @meta.labels} : {})
|
160
|
-
.merge(fonts_options || {})
|
161
|
-
template = liquid(docxml)
|
162
|
-
template.render(meta.map { |k, v| [k.to_s, empty2nil(v)] }.to_h)
|
163
|
-
.gsub("<", "<").gsub(">", ">").gsub("&", "&")
|
164
|
-
end
|
166
|
+
def empty2nil(str)
|
167
|
+
return nil if !str.nil? && str.is_a?(String) && str.empty?
|
165
168
|
|
166
|
-
|
167
|
-
%r{^data:(image|application)/(?<imgtype>[^;]+);base64,(?<imgdata>.+)$} =~ uri
|
168
|
-
imgtype.sub!(/\+[a-z0-9]+$/, "") # svg+xml
|
169
|
-
imgtype = "png" unless /^[a-z0-9]+$/.match imgtype
|
170
|
-
Tempfile.open(["image", ".#{imgtype}"]) do |f|
|
171
|
-
f.binmode
|
172
|
-
f.write(Base64.strict_decode64(imgdata))
|
173
|
-
@tempfile_cache << f # persist to the end
|
174
|
-
f.path
|
169
|
+
str
|
175
170
|
end
|
176
|
-
end
|
177
171
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
172
|
+
def populate_template(docxml, _format = nil)
|
173
|
+
meta = @meta
|
174
|
+
.get
|
175
|
+
.merge(@labels ? { labels: @labels } : {})
|
176
|
+
.merge(@meta.labels ? { labels: @meta.labels } : {})
|
177
|
+
.merge(fonts_options || {})
|
178
|
+
template = liquid(docxml)
|
179
|
+
template.render(meta.map { |k, v| [k.to_s, empty2nil(v)] }.to_h)
|
180
|
+
.gsub("<", "<").gsub(">", ">").gsub("&", "&")
|
185
181
|
end
|
186
|
-
end
|
187
182
|
|
188
|
-
|
189
|
-
|
190
|
-
|
183
|
+
def save_dataimage(uri, _relative_dir = true)
|
184
|
+
%r{^data:(image|application)/(?<imgtype>[^;]+);base64,(?<imgdata>.+)$} =~ uri
|
185
|
+
imgtype.sub!(/\+[a-z0-9]+$/, "") # svg+xml
|
186
|
+
imgtype = "png" unless /^[a-z0-9]+$/.match? imgtype
|
187
|
+
Tempfile.open(["image", ".#{imgtype}"]) do |f|
|
188
|
+
f.binmode
|
189
|
+
f.write(Base64.strict_decode64(imgdata))
|
190
|
+
@tempfile_cache << f # persist to the end
|
191
|
+
f.path
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def image_localfile(img)
|
196
|
+
if /^data:/.match? img["src"]
|
197
|
+
save_dataimage(img["src"], false)
|
198
|
+
elsif %r{^([A-Z]:)?/}.match? img["src"]
|
199
|
+
img["src"]
|
200
|
+
else
|
201
|
+
File.join(@localdir, img["src"])
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def labelled_ancestor(node)
|
206
|
+
!node.ancestors("example, requirement, recommendation, permission, "\
|
207
|
+
"note, table, figure, sourcecode").empty?
|
208
|
+
end
|
191
209
|
end
|
192
210
|
end
|
193
211
|
end
|
@@ -1,11 +1,10 @@
|
|
1
|
-
require_relative "html_function/comments
|
2
|
-
require_relative "html_function/footnotes
|
3
|
-
require_relative "html_function/html
|
1
|
+
require_relative "html_function/comments"
|
2
|
+
require_relative "html_function/footnotes"
|
3
|
+
require_relative "html_function/html"
|
4
4
|
require "fileutils"
|
5
5
|
|
6
6
|
module IsoDoc
|
7
7
|
class HeadlessHtmlConvert < ::IsoDoc::Convert
|
8
|
-
|
9
8
|
include HtmlFunction::Comments
|
10
9
|
include HtmlFunction::Footnotes
|
11
10
|
include HtmlFunction::Html
|
@@ -26,16 +25,18 @@ module IsoDoc
|
|
26
25
|
docxml, filename, dir = convert_init(file, input_filename, debug)
|
27
26
|
result = convert1(docxml, filename, dir)
|
28
27
|
return result if debug
|
29
|
-
|
28
|
+
|
29
|
+
postprocess(result, "#{filename}.tmp.html", dir)
|
30
30
|
FileUtils.rm_rf dir
|
31
|
-
strip_head(filename
|
31
|
+
strip_head("#{filename}.tmp.html",
|
32
|
+
output_filename || "#{filename}.#{@suffix}")
|
32
33
|
FileUtils.rm_rf ["#{filename}.tmp.html", tmpimagedir]
|
33
34
|
end
|
34
35
|
|
35
36
|
def strip_head(input, output)
|
36
37
|
file = File.read(input, encoding: "utf-8")
|
37
38
|
doc = Nokogiri::XML(file)
|
38
|
-
doc.xpath("//head").each
|
39
|
+
doc.xpath("//head").each(&:remove)
|
39
40
|
doc.xpath("//html").each { |x| x.name = "div" }
|
40
41
|
body = doc.at("//body")
|
41
42
|
body.replace(body.children)
|
@@ -84,6 +84,7 @@ module IsoDoc::HtmlFunction
|
|
84
84
|
def comment_attributes(docxml, x)
|
85
85
|
fromlink = docxml.at("//*[@id='#{x['from']}']")
|
86
86
|
return(nil) if fromlink.nil?
|
87
|
+
|
87
88
|
tolink = docxml.at("//*[@id='#{x['to']}']") || fromlink
|
88
89
|
target = docxml.at("//*[@id='#{x['target']}']")
|
89
90
|
{ from: fromlink, to: tolink, target: target }
|
@@ -122,6 +123,7 @@ module IsoDoc::HtmlFunction
|
|
122
123
|
comments = []
|
123
124
|
docxml.xpath("//div[@style='mso-element:comment']").each do |c|
|
124
125
|
next unless c["id"] && !link_order[c["id"]].nil?
|
126
|
+
|
125
127
|
comments << { text: c.remove.to_s, id: c["id"] }
|
126
128
|
end
|
127
129
|
comments.sort! { |a, b| link_order[a[:id]] <=> link_order[b[:id]] }
|