isodoc 3.4.6 → 3.4.8
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/lib/isodoc/base_style/reset.scss +1 -1
- data/lib/isodoc/css.rb +6 -8
- data/lib/isodoc/css_border_parser.rb +13 -5
- data/lib/isodoc/function/blocks.rb +9 -3
- data/lib/isodoc/function/cleanup.rb +8 -5
- data/lib/isodoc/function/lists.rb +13 -3
- data/lib/isodoc/function/section_titles.rb +3 -1
- data/lib/isodoc/function/utils.rb +35 -51
- data/lib/isodoc/function/utils_img.rb +55 -0
- data/lib/isodoc/gem_tasks.rb +9 -9
- data/lib/isodoc/html_function/postprocess.rb +2 -2
- data/lib/isodoc/html_function/postprocess_cover.rb +12 -9
- data/lib/isodoc/presentation_function/annex.rb +97 -0
- data/lib/isodoc/presentation_function/autonum.rb +7 -6
- data/lib/isodoc/presentation_function/block.rb +26 -11
- data/lib/isodoc/presentation_function/footnotes.rb +1 -1
- data/lib/isodoc/presentation_function/image.rb +1 -1
- data/lib/isodoc/presentation_function/list_to_table.rb +250 -0
- data/lib/isodoc/presentation_function/math.rb +10 -32
- data/lib/isodoc/presentation_function/section.rb +7 -32
- data/lib/isodoc/presentation_function/xrefs.rb +1 -1
- data/lib/isodoc/presentation_xml_convert.rb +7 -4
- data/lib/isodoc/version.rb +1 -1
- data/lib/isodoc/word_function/table.rb +7 -4
- data/lib/isodoc/xref/xref_counter.rb +36 -3
- data/lib/isodoc/xref/xref_gen.rb +23 -14
- data/lib/isodoc/xref/xref_sect_gen.rb +21 -7
- metadata +5 -3
- data/Gemfile.devel +0 -2
|
@@ -107,11 +107,16 @@ module IsoDoc
|
|
|
107
107
|
docxml.xpath(ns("//table")).each { |f| table1(f) }
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
+
def table1_caption?(elem)
|
|
111
|
+
!labelled_ancestor(elem) &&
|
|
112
|
+
!(elem["unnumbered"] && !elem.at(ns("./name"))) &&
|
|
113
|
+
!%w(fmt-ol fmt-ul).include?(elem.parent.name)
|
|
114
|
+
end
|
|
115
|
+
|
|
110
116
|
def table1(elem)
|
|
111
117
|
table_fn(elem)
|
|
112
118
|
table_css(elem)
|
|
113
|
-
|
|
114
|
-
elem["unnumbered"] && !elem.at(ns("./name")) and return
|
|
119
|
+
table1_caption?(elem) or return
|
|
115
120
|
n = @xrefs.anchor(elem["id"] || elem["original-id"], :label, false)
|
|
116
121
|
lbl = labelled_autonum(lower2cap(@i18n.table),
|
|
117
122
|
elem["id"] || elem["original-id"], n)
|
|
@@ -153,32 +158,42 @@ module IsoDoc
|
|
|
153
158
|
"./classification | ./contributor | ./fmt-name | " \
|
|
154
159
|
"./fmt-xref-label")).each(&:remove)
|
|
155
160
|
amend_newcontent(ret)
|
|
156
|
-
ret.xpath(ns("./newcontent")).each { |a| a.name = "quote" }
|
|
157
161
|
ret.xpath(ns("./description")).each { |a| a.replace(a.children) }
|
|
158
162
|
elem.next = ret
|
|
159
163
|
end
|
|
160
164
|
|
|
161
165
|
def amend_newcontent(elem)
|
|
162
|
-
elem.xpath(ns("
|
|
166
|
+
elem.xpath(ns(".//newcontent")).each do |a|
|
|
163
167
|
a.name = "quote"
|
|
164
|
-
a
|
|
168
|
+
a["type"] = "newcontent"
|
|
169
|
+
a.xpath(ns("./clause")).reverse_each do |c|
|
|
165
170
|
amend_subclause(c, 1)
|
|
166
171
|
a.next = c
|
|
167
172
|
end
|
|
168
173
|
end
|
|
174
|
+
elem.xpath(ns("./quote[not(node())]")).each(&:remove)
|
|
169
175
|
end
|
|
170
176
|
|
|
171
177
|
def amend_subclause(clause, depth)
|
|
172
|
-
clause
|
|
173
|
-
# t.name = "floating-title"
|
|
174
|
-
# t["depth"] ||= depth || "1"
|
|
175
|
-
t.name = "p"
|
|
176
|
-
t["type"] = "floating-title"
|
|
177
|
-
end
|
|
178
|
+
amend_subclause_title(clause, depth)
|
|
178
179
|
clause.name = depth == 1 ? "quote" : "quote" # "div"
|
|
180
|
+
clause["type"] = "newcontent"
|
|
179
181
|
clause.xpath(ns("./clause")).each { |c| amend_subclause(c, depth + 1) }
|
|
180
182
|
end
|
|
181
183
|
|
|
184
|
+
def amend_subclause_title(clause, _depth)
|
|
185
|
+
if clause["type"] == "annex"
|
|
186
|
+
annex1(clause)
|
|
187
|
+
else
|
|
188
|
+
clause1(clause) # insert title prefix
|
|
189
|
+
end
|
|
190
|
+
clause.xpath(ns("./title | ./variant-title")).each(&:remove)
|
|
191
|
+
t = clause.at(ns("./fmt-title")) or return
|
|
192
|
+
t.name = "p"
|
|
193
|
+
t["type"] = "floating-title"
|
|
194
|
+
# t["depth"] ||= depth || "1"
|
|
195
|
+
end
|
|
196
|
+
|
|
182
197
|
def quote(docxml)
|
|
183
198
|
docxml.xpath(ns("//quote")).each { |f| quote1(f) }
|
|
184
199
|
end
|
|
@@ -157,7 +157,7 @@ module IsoDoc
|
|
|
157
157
|
elem.at(ns(".//dl/name"))&.next ||
|
|
158
158
|
elem.at(ns(".//dl"))&.children&.first ||
|
|
159
159
|
elem.add_child("<key><dl> </dl></key>")
|
|
160
|
-
|
|
160
|
+
.first.elements.first.children.first
|
|
161
161
|
end
|
|
162
162
|
|
|
163
163
|
def comments(docxml)
|
|
@@ -48,7 +48,7 @@ module IsoDoc
|
|
|
48
48
|
|
|
49
49
|
svg = Base64.strict_decode64(elem["src"]
|
|
50
50
|
.sub(%r{^data:image/svg\+xml;(charset=[^;]+;)?base64,}, ""))
|
|
51
|
-
x = Nokogiri::XML.fragment(svg.sub(
|
|
51
|
+
x = Nokogiri::XML.fragment(svg.sub(/\A<\?xml[^<>]*>\n?/, ""), &:huge)
|
|
52
52
|
elem["src"] = ""
|
|
53
53
|
elem.children = x
|
|
54
54
|
end
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
module IsoDoc
|
|
2
|
+
class PresentationXMLConvert < ::IsoDoc::Convert
|
|
3
|
+
# Entry point: find all top-level (ol|ul)[@display='table'] and convert each
|
|
4
|
+
def list_to_table(docxml)
|
|
5
|
+
docxml.xpath(ns("//ol[@display='table'] | //ul[@display='table']"))
|
|
6
|
+
.each do |elem|
|
|
7
|
+
list_table_convert(elem)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Convert a single display="table" list: build table, insert under fmt-ol
|
|
12
|
+
def list_table_convert(elem)
|
|
13
|
+
n = list_table_depth(elem)
|
|
14
|
+
table = list_table_build(elem, n)
|
|
15
|
+
elem << "<fmt-#{elem.name}></fmt-#{elem.name}>"
|
|
16
|
+
elem.elements.last << table
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Maximum depth of nested ol/ul: elem itself = depth 1
|
|
20
|
+
def list_table_depth(elem)
|
|
21
|
+
depths = [1]
|
|
22
|
+
elem.xpath(ns(".//ol | .//ul")).each do |sub|
|
|
23
|
+
d = sub.ancestors.take_while { |a| a != elem }
|
|
24
|
+
.count { |a| %w[ol ul].include?(a.name) } + 2
|
|
25
|
+
depths << d
|
|
26
|
+
end
|
|
27
|
+
depths.max
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Build the full table as a Nokogiri node
|
|
31
|
+
def list_table_build(elem, cellcount)
|
|
32
|
+
xml = "<table>"
|
|
33
|
+
xml += list_table_name(elem)
|
|
34
|
+
xml += list_table_colgroup(elem, cellcount)
|
|
35
|
+
xml += list_table_header(elem, cellcount)
|
|
36
|
+
xml += list_table_body(elem, cellcount)
|
|
37
|
+
xml += "</table>"
|
|
38
|
+
Nokogiri::XML(xml).root
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# global table title if there are no nested titled in the list:
|
|
42
|
+
# move list/fmt-name to ol/fmt-ol/table/fmt-name: ol/fmt-ol is all we render
|
|
43
|
+
def list_table_name(elem)
|
|
44
|
+
list_only_one_title(elem) or return ""
|
|
45
|
+
ret = elem.at(ns("./fmt-name")) or return ""
|
|
46
|
+
to_xml(ret.remove)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def list_table_colgroup(elem, cellcount)
|
|
50
|
+
n = elem["display-directives"] or return ""
|
|
51
|
+
attrs = csv_attribute_extract(n)
|
|
52
|
+
attrs[:colgroup] or return ""
|
|
53
|
+
vals = attrs[:colgroup].split(",").map(&:to_f)
|
|
54
|
+
vals = list_table_normalise_colgroup(vals, cellcount)
|
|
55
|
+
ret = vals.map { |n| "<col width='#{n}%'/>" }.join
|
|
56
|
+
"<colgroup>#{ret}</colgroup>"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def list_table_normalise_colgroup(vals, cellcount)
|
|
60
|
+
vals.size > cellcount and vals = vals[0...cellcount]
|
|
61
|
+
if vals.size < cellcount
|
|
62
|
+
(vals.size...cellcount).each do |_i|
|
|
63
|
+
vals << 10.0
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
sum = vals.sum
|
|
67
|
+
vals.map { |i| 100 * i / sum }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Build <thead><tr> with n <th> cells, one per depth level
|
|
71
|
+
def list_table_header(elem, cellcount)
|
|
72
|
+
list_only_one_title(elem) and return ""
|
|
73
|
+
ths = (1..cellcount).map { |i| list_table_th(elem, i) }.join
|
|
74
|
+
"<thead><tr>#{ths}</tr></thead>"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def list_only_one_title(elem)
|
|
78
|
+
(!elem.at(ns(".//ol//name")) && !elem.at(ns(".//ul//name"))) or return nil
|
|
79
|
+
elem.at(ns("./name"))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def list_table_th(elem, depth)
|
|
83
|
+
name = list_table_col_name(elem, depth)
|
|
84
|
+
name or return "<th/>"
|
|
85
|
+
add_id(name)
|
|
86
|
+
src = name["original-id"] || name["id"]
|
|
87
|
+
children = to_xml(name.children)
|
|
88
|
+
<<~XML
|
|
89
|
+
<th><fmt-name><semx element='name' source='#{src}'>#{children}</semx></fmt-name></th>
|
|
90
|
+
XML
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Find the <name> element of the first ol/ul at the given depth within elem
|
|
94
|
+
def list_table_col_name(elem, depth)
|
|
95
|
+
list_at_depth(elem, depth)&.at(ns("./name"))
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Return the first ol/ul at the given depth (depth 1 = elem itself)
|
|
99
|
+
def list_at_depth(elem, target_depth)
|
|
100
|
+
target_depth == 1 and return elem
|
|
101
|
+
elem.xpath(ns(".//ol | .//ul")).find do |sub|
|
|
102
|
+
d = sub.ancestors.take_while { |a| a != elem }
|
|
103
|
+
.count { |a| %w[ol ul].include?(a.name) } + 2
|
|
104
|
+
d == target_depth
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Build <tbody> with one <tr> per leaf (terminal sublist) path
|
|
109
|
+
def list_table_body(elem, cellcount)
|
|
110
|
+
paths = list_table_leaf_paths(elem, 1)
|
|
111
|
+
emitted = {} # li object_id => true when already emitted under a rowspan
|
|
112
|
+
rows = paths.map do |path|
|
|
113
|
+
list_table_row(path, cellcount, emitted)
|
|
114
|
+
end.join
|
|
115
|
+
"<tbody>#{rows}</tbody>"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def list_table_row(path, cellcount, emitted)
|
|
119
|
+
cells = path.map do |step|
|
|
120
|
+
if step[:terminal]
|
|
121
|
+
if step[:li]
|
|
122
|
+
# Degenerate: single li with no child sublist —
|
|
123
|
+
# render just that li with colspan
|
|
124
|
+
list_table_degen_terminal_td(step[:list], step[:li], step[:li_idx],
|
|
125
|
+
step[:depth], cellcount)
|
|
126
|
+
else
|
|
127
|
+
list_table_terminal_td(step[:list], step[:depth], cellcount)
|
|
128
|
+
end
|
|
129
|
+
else
|
|
130
|
+
li = step[:li]
|
|
131
|
+
emitted[li.object_id] and next
|
|
132
|
+
rowspan = list_table_count_terminals(li)
|
|
133
|
+
emitted[li.object_id] = true
|
|
134
|
+
list_table_nonterminal_td(step[:list], li, step[:li_idx], rowspan,
|
|
135
|
+
step[:depth])
|
|
136
|
+
end
|
|
137
|
+
end.compact.join
|
|
138
|
+
"<tr>#{cells}</tr>"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Recursively collect all leaf paths from xl downward.
|
|
142
|
+
# Each path is an array of step hashes; the last step has terminal: true.
|
|
143
|
+
# Non-terminal step: { list:, li:, depth:, li_idx: }
|
|
144
|
+
# Terminal step: { list:, depth:, terminal: true }
|
|
145
|
+
def list_table_leaf_paths(xl, depth)
|
|
146
|
+
paths = []
|
|
147
|
+
xl.xpath(ns("./li")).each_with_index do |li, idx|
|
|
148
|
+
li_idx = idx + 1
|
|
149
|
+
sub_xls = li.children.select { |c| %w[ol ul].include?(c.name) }
|
|
150
|
+
if sub_xls.empty?
|
|
151
|
+
# Degenerate: li with no child sublist — treat as its own terminal row
|
|
152
|
+
paths << [{ list: xl, li:, depth:, li_idx:, terminal: true }]
|
|
153
|
+
else
|
|
154
|
+
sub_xls.each do |sub_xl|
|
|
155
|
+
step = { list: xl, li: li, depth: depth, li_idx: li_idx }
|
|
156
|
+
if (sub_xl.xpath(ns(".//ol")) + sub_xl.xpath(ns(".//ul"))).empty?
|
|
157
|
+
paths << [step,
|
|
158
|
+
{ list: sub_xl, depth: depth + 1, terminal: true }]
|
|
159
|
+
else
|
|
160
|
+
list_table_leaf_paths(sub_xl, depth + 1).each do |sub_path|
|
|
161
|
+
paths << ([step] + sub_path)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
paths
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Count terminal sublists reachable from a li element (used for rowspan)
|
|
171
|
+
def list_table_count_terminals(listitem)
|
|
172
|
+
sub_xls = listitem.children.select { |c| %w[ol ul].include?(c.name) }
|
|
173
|
+
sub_xls.empty? and return 1
|
|
174
|
+
count = 0
|
|
175
|
+
sub_xls.each do |sub_xl|
|
|
176
|
+
count += list_table_count_terminals_recurse(sub_xl)
|
|
177
|
+
end
|
|
178
|
+
[count, 1].max
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def list_table_count_terminals_recurse(sub_xl)
|
|
182
|
+
ret = 0
|
|
183
|
+
(sub_xl.xpath(ns(".//ol")) + sub_xl.xpath(ns(".//ul"))).empty? and
|
|
184
|
+
return 1
|
|
185
|
+
sub_xl.xpath(ns("./li")).each do |sub_li|
|
|
186
|
+
ret += list_table_count_terminals(sub_li)
|
|
187
|
+
end
|
|
188
|
+
ret
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Build a nonterminal <td>: wraps a single li (minus nested sublists)
|
|
192
|
+
# in an ol/ul with appropriate start, type, and rowspan
|
|
193
|
+
def list_table_nonterminal_td(list, listitem, li_idx, rowspan, depth)
|
|
194
|
+
rowspan_attr = rowspan > 1 ? " rowspan='#{rowspan}'" : ""
|
|
195
|
+
li_content = list_table_li_content(listitem)
|
|
196
|
+
if list.name == "ol"
|
|
197
|
+
start = list_table_calc_start(list, li_idx)
|
|
198
|
+
type = @counter.ol_type(list, depth).to_s
|
|
199
|
+
"<td#{rowspan_attr}><ol start='#{start}' type='#{type}'>" \
|
|
200
|
+
"<li>#{li_content}</li></ol></td>"
|
|
201
|
+
else
|
|
202
|
+
"<td#{rowspan_attr}><ul><li>#{li_content}</li></ul></td>"
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Serialize a li's content, excluding any direct ol/ul children
|
|
207
|
+
def list_table_li_content(listitem)
|
|
208
|
+
listitem.children.reject { |c| %w[ol ul].include?(c.name) }
|
|
209
|
+
.map { |c| to_xml(c) }.join
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Build a terminal <td> for a degenerate li (no child sublist),
|
|
213
|
+
# wrapping just that single li with the correct colspan
|
|
214
|
+
def list_table_degen_terminal_td(list, listitem, li_idx, depth, cellcount)
|
|
215
|
+
colspan = cellcount - depth + 1
|
|
216
|
+
colspan_attr = colspan > 1 ? " colspan='#{colspan}'" : ""
|
|
217
|
+
li_content = list_table_li_content(listitem)
|
|
218
|
+
if list.name == "ol"
|
|
219
|
+
start = list_table_calc_start(list, li_idx)
|
|
220
|
+
type = @counter.ol_type(list, depth).to_s
|
|
221
|
+
"<td#{colspan_attr}><ol start='#{start}' type='#{type}'>" \
|
|
222
|
+
"<li>#{li_content}</li></ol></td>"
|
|
223
|
+
else
|
|
224
|
+
"<td#{colspan_attr}><ul><li>#{li_content}</li></ul></td>"
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Build a terminal <td>: contains the whole sublist,
|
|
229
|
+
# with colspan if depth < cellcount
|
|
230
|
+
def list_table_terminal_td(xl, depth, cellcount)
|
|
231
|
+
colspan = cellcount - depth + 1
|
|
232
|
+
colspan_attr = colspan > 1 ? " colspan='#{colspan}'" : ""
|
|
233
|
+
xl_dup = xl.dup
|
|
234
|
+
# Remove <name> from the copy (names go in thead, not in the cell body)
|
|
235
|
+
xl_dup.children.each { |c| c.name == "name" and c.remove }
|
|
236
|
+
if xl.name == "ol"
|
|
237
|
+
type = @counter.ol_type(xl, depth).to_s
|
|
238
|
+
xl_dup["type"] = type
|
|
239
|
+
end
|
|
240
|
+
"<td#{colspan_attr}>#{to_xml(xl_dup)}</td>"
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Calculate the start number for a wrapped ol cell
|
|
244
|
+
# (original ol start) + (0-based position of this li) = start
|
|
245
|
+
# for this li's number
|
|
246
|
+
def list_table_calc_start(list, li_idx)
|
|
247
|
+
(list["start"] || 1).to_i + li_idx - 1
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
@@ -50,32 +50,6 @@ module IsoDoc
|
|
|
50
50
|
precision: num_precision(num.text))
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
COMMA_PLACEHOLDER = "##COMMA##".freeze
|
|
54
|
-
|
|
55
|
-
# Temporarily replace commas inside quotes with a placeholder
|
|
56
|
-
def comma_placeholder(options)
|
|
57
|
-
processed = ""
|
|
58
|
-
in_quotes = false
|
|
59
|
-
options.each_char do |c|
|
|
60
|
-
c == "'" and in_quotes = !in_quotes
|
|
61
|
-
processed << if c == "," && in_quotes
|
|
62
|
-
COMMA_PLACEHOLDER
|
|
63
|
-
else c end
|
|
64
|
-
end
|
|
65
|
-
processed
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def numberformat_extract(options)
|
|
69
|
-
options.gsub!(/([a-z_]+)='/, %('\\1=))
|
|
70
|
-
processed = comma_placeholder(options)
|
|
71
|
-
CSV.parse_line(processed,
|
|
72
|
-
quote_char: "'").each_with_object({}) do |x, acc|
|
|
73
|
-
x.gsub!(COMMA_PLACEHOLDER, ",")
|
|
74
|
-
m = /^(.+?)=(.+)?$/.match(x) or next
|
|
75
|
-
acc[m[1].to_sym] = m[2].sub(/^(["'])(.+)\1$/, "\\2")
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
53
|
def numberformat_type(ret)
|
|
80
54
|
%i(precision significant digit_count group_digits fraction_group_digits)
|
|
81
55
|
.each do |i|
|
|
@@ -88,7 +62,7 @@ module IsoDoc
|
|
|
88
62
|
end
|
|
89
63
|
|
|
90
64
|
def explicit_number_formatter(num, locale, options)
|
|
91
|
-
ret = numberformat_type(
|
|
65
|
+
ret = numberformat_type(csv_attribute_extract(options))
|
|
92
66
|
l = ret[:locale] || locale
|
|
93
67
|
precision, symbols, significant = explicit_number_formatter_cfg(num, ret)
|
|
94
68
|
n = normalise_number(num.text)
|
|
@@ -137,7 +111,9 @@ module IsoDoc
|
|
|
137
111
|
precision = nil
|
|
138
112
|
/\.(?!\d+e)/.match?(num) and
|
|
139
113
|
precision = twitter_cldr_localiser_symbols[:precision] ||
|
|
140
|
-
|
|
114
|
+
# [^.]* excludes the delimiter itself, preventing polynomial
|
|
115
|
+
# backtracking on strings with multiple dots.
|
|
116
|
+
num.sub(/\A[^.]*\./, "").size
|
|
141
117
|
precision
|
|
142
118
|
end
|
|
143
119
|
|
|
@@ -145,7 +121,9 @@ module IsoDoc
|
|
|
145
121
|
totaldigits = nil
|
|
146
122
|
/\.(?=\d+e)/.match?(num) and
|
|
147
123
|
totaldigits = twitter_cldr_localiser_symbols[:significant] ||
|
|
148
|
-
|
|
124
|
+
# [^.]* and [^e]* exclude their respective delimiters,
|
|
125
|
+
# preventing polynomial backtracking.
|
|
126
|
+
num.sub(/\A0\./, ".").sub(/\A[^.]*\./, "").sub(/e[^e]*\z/, "").size
|
|
149
127
|
totaldigits
|
|
150
128
|
end
|
|
151
129
|
|
|
@@ -155,9 +133,9 @@ module IsoDoc
|
|
|
155
133
|
|
|
156
134
|
def asciimath_dup(node)
|
|
157
135
|
@suppressasciimathdup || node.parent.at(ns("./asciimath")) and return
|
|
158
|
-
math = node.to_xml.gsub(/ xmlns=["'][^"']
|
|
159
|
-
.gsub(%r{<[^:/>]
|
|
160
|
-
.gsub(%r{ data-metanorma-numberformat="[^"]
|
|
136
|
+
math = node.to_xml.gsub(/ xmlns=["'][^"']*["']/, "")
|
|
137
|
+
.gsub(%r{<[^:/>]*:}, "<").gsub(%r{</[^:/>]*:}, "</")
|
|
138
|
+
.gsub(%r{ data-metanorma-numberformat="[^"]*"}, "")
|
|
161
139
|
ret = Plurimath::Math.parse(math, "mathml").to_asciimath
|
|
162
140
|
node.next = "<asciimath>#{@c.encode(ret, :basic)}</asciimath>"
|
|
163
141
|
rescue StandardError => e
|
|
@@ -33,15 +33,12 @@ module IsoDoc
|
|
|
33
33
|
|
|
34
34
|
def clausedelim
|
|
35
35
|
ret = super
|
|
36
|
-
ret && !ret.empty? or return ret
|
|
36
|
+
(ret && !ret.empty?) or return ret
|
|
37
37
|
"<span class='fmt-autonum-delim'>#{ret}</span>"
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def clause1(elem)
|
|
41
|
-
level =
|
|
42
|
-
(elem.ancestors("clause, annex").size + 1)
|
|
43
|
-
is_unnumbered = unnumbered_clause?(elem)
|
|
44
|
-
lbl = @xrefs.anchor(elem["id"], :label, false)
|
|
41
|
+
level, is_unnumbered, lbl = clause1_prep(elem)
|
|
45
42
|
if is_unnumbered || !lbl
|
|
46
43
|
prefix_name(elem, {}, nil, "title")
|
|
47
44
|
else
|
|
@@ -51,34 +48,12 @@ module IsoDoc
|
|
|
51
48
|
t = elem.at(ns("./fmt-title")) and t["depth"] = level
|
|
52
49
|
end
|
|
53
50
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
@xrefs.klass.single_term_clause?(f) and single_term_clause_unnest(f)
|
|
59
|
-
end
|
|
60
|
-
@xrefs.parse_inclusions(clauses: true).parse(docxml)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def annex1(elem)
|
|
51
|
+
def clause1_prep(elem)
|
|
52
|
+
level = @xrefs.anchor(elem["id"], :level, false) ||
|
|
53
|
+
(elem.ancestors("clause, annex").size + 1)
|
|
54
|
+
is_unnumbered = unnumbered_clause?(elem)
|
|
64
55
|
lbl = @xrefs.anchor(elem["id"], :label, false)
|
|
65
|
-
|
|
66
|
-
t = elem.at(ns("./title")) and
|
|
67
|
-
t.children = "<strong>#{to_xml(t.children)}</strong>"
|
|
68
|
-
if unnumbered_clause?(elem)
|
|
69
|
-
prefix_name(elem, {}, nil, "title")
|
|
70
|
-
else
|
|
71
|
-
prefix_name(elem, { caption: annex_delim_override(elem) }, lbl, "title")
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def annex_delim_override(elem)
|
|
76
|
-
m = elem.document.root.at(ns("//presentation-metadata/annex-delim"))
|
|
77
|
-
m ? to_xml(m.children) : annex_delim(elem)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def annex_delim(_elem)
|
|
81
|
-
"<br/><br/>"
|
|
56
|
+
[level, is_unnumbered, lbl]
|
|
82
57
|
end
|
|
83
58
|
|
|
84
59
|
def single_term_clause_retitle(elem)
|
|
@@ -194,7 +194,7 @@ module IsoDoc
|
|
|
194
194
|
end
|
|
195
195
|
|
|
196
196
|
def capitalise_xref(node, linkend, label)
|
|
197
|
-
linktext = linkend.gsub(/<[^<>]
|
|
197
|
+
linktext = linkend.gsub(/<[^<>]*>/, "")
|
|
198
198
|
(label && !label.empty? && /^#{Regexp.escape(label)}/.match?(linktext)) ||
|
|
199
199
|
linktext[0, 1].match?(/\p{Upper}/) and return linkend
|
|
200
200
|
node["case"] and
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require_relative "presentation_function/block"
|
|
2
2
|
require_relative "presentation_function/source"
|
|
3
3
|
require_relative "presentation_function/list"
|
|
4
|
+
require_relative "presentation_function/list_to_table"
|
|
4
5
|
require_relative "presentation_function/reqt"
|
|
5
6
|
require_relative "presentation_function/concepts"
|
|
6
7
|
require_relative "presentation_function/designations"
|
|
@@ -11,6 +12,7 @@ require_relative "presentation_function/inline"
|
|
|
11
12
|
require_relative "presentation_function/math"
|
|
12
13
|
require_relative "presentation_function/section"
|
|
13
14
|
require_relative "presentation_function/section_refs"
|
|
15
|
+
require_relative "presentation_function/annex"
|
|
14
16
|
require_relative "presentation_function/title"
|
|
15
17
|
require_relative "presentation_function/refs"
|
|
16
18
|
require_relative "presentation_function/docid"
|
|
@@ -60,8 +62,8 @@ module IsoDoc
|
|
|
60
62
|
def bibitem_lookup(docxml)
|
|
61
63
|
@bibitem_lookup ||= docxml.xpath(ns("//references/bibitem"))
|
|
62
64
|
.each_with_object({}) do |b, m|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
m[b["id"]] = b
|
|
66
|
+
m[b["anchor"]] = b
|
|
65
67
|
end
|
|
66
68
|
end
|
|
67
69
|
|
|
@@ -106,8 +108,9 @@ module IsoDoc
|
|
|
106
108
|
note docxml
|
|
107
109
|
admonition docxml
|
|
108
110
|
source docxml
|
|
109
|
-
ul docxml
|
|
110
|
-
ol docxml
|
|
111
|
+
ul docxml # feeds list_to_table
|
|
112
|
+
ol docxml # feeds list_to_table
|
|
113
|
+
list_to_table docxml
|
|
111
114
|
dl docxml
|
|
112
115
|
quote docxml
|
|
113
116
|
permission docxml
|
data/lib/isodoc/version.rb
CHANGED
|
@@ -2,9 +2,12 @@ module IsoDoc
|
|
|
2
2
|
module WordFunction
|
|
3
3
|
module Body
|
|
4
4
|
def remove_bottom_border(cell)
|
|
5
|
+
# [^;]* (not +): the preceding property name is the unambiguous
|
|
6
|
+
# delimiter, so zero-or-more is equivalent and avoids polynomial
|
|
7
|
+
# backtracking on the value portion.
|
|
5
8
|
cell["style"] =
|
|
6
|
-
cell["style"].gsub(/border-bottom:[^;]
|
|
7
|
-
.gsub(/mso-border-bottom-alt:[^;]
|
|
9
|
+
cell["style"].gsub(/border-bottom:[^;]*;/, "border-bottom:0pt;")
|
|
10
|
+
.gsub(/mso-border-bottom-alt:[^;]*;/, "mso-border-bottom-alt:0pt;")
|
|
8
11
|
end
|
|
9
12
|
|
|
10
13
|
SW1 = "solid windowtext".freeze
|
|
@@ -40,7 +43,7 @@ module IsoDoc
|
|
|
40
43
|
border-top:#{top}mso-border-top-alt:#{top}
|
|
41
44
|
border-bottom:#{bottom}mso-border-bottom-alt:#{bottom}
|
|
42
45
|
STYLE
|
|
43
|
-
opt[:bordered] && !cell["style"] or ret = ""
|
|
46
|
+
(opt[:bordered] && !cell["style"]) or ret = ""
|
|
44
47
|
pb = keep_rows_together(cell, rowmax, totalrows, opt) ? "avoid" : "auto"
|
|
45
48
|
"#{ret}page-break-after:#{pb};"
|
|
46
49
|
end
|
|
@@ -76,7 +79,7 @@ module IsoDoc
|
|
|
76
79
|
ret = { summary: node["summary"], width: node["width"],
|
|
77
80
|
class: table_class(node),
|
|
78
81
|
style: "mso-table-anchor-horizontal:column;" \
|
|
79
|
-
|
|
82
|
+
"mso-table-overlap:never;#{style}#{keep_style(node)}" }
|
|
80
83
|
style or ret.delete(:class)
|
|
81
84
|
super.merge(attr_code(ret))
|
|
82
85
|
end
|
|
@@ -18,6 +18,7 @@ module IsoDoc
|
|
|
18
18
|
|
|
19
19
|
class Counter
|
|
20
20
|
include OlTypeProvider
|
|
21
|
+
|
|
21
22
|
attr_accessor :prefix_override
|
|
22
23
|
|
|
23
24
|
def initialize(num = 0, opts = { numerals: :arabic })
|
|
@@ -57,7 +58,7 @@ module IsoDoc
|
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
def new_subseq_increment1(node)
|
|
60
|
-
|
|
61
|
+
b, n, a = parse_number_suffix(node["number"])
|
|
61
62
|
if !n.empty? || !a.empty?
|
|
62
63
|
@letter_override = @letter = a unless a.empty?
|
|
63
64
|
@number_override = @num = n.to_i unless n.empty?
|
|
@@ -73,7 +74,10 @@ module IsoDoc
|
|
|
73
74
|
@prefix_override = node["branch-number"]
|
|
74
75
|
elsif node["number"]
|
|
75
76
|
@base = @letter_override = @number_override = ""
|
|
76
|
-
|
|
77
|
+
b, n, a = parse_number_suffix(node["number"])
|
|
78
|
+
# Original required digits at the absolute end (no trailing letters).
|
|
79
|
+
# If there are trailing letters, treat as a no-digit-at-end match.
|
|
80
|
+
n = "" unless a.empty?
|
|
77
81
|
if blank?(n)
|
|
78
82
|
@num = nil
|
|
79
83
|
@base = node["number"][0..-2]
|
|
@@ -92,7 +96,16 @@ module IsoDoc
|
|
|
92
96
|
|
|
93
97
|
@base = ""
|
|
94
98
|
@letter_override = node["number"]
|
|
95
|
-
/^(?<b>.*?)(?<n>\d*)(?<a>[a-zA-Z])$/
|
|
99
|
+
# Replace polynomial /^(?<b>.*?)(?<n>\d*)(?<a>[a-zA-Z])$/ with
|
|
100
|
+
# sequential parsing from the right: last char must be a single letter;
|
|
101
|
+
# before it are optional digits; before that is the base prefix b.
|
|
102
|
+
a = /[a-zA-Z]/.match?(node["number"][-1]) ? node["number"][-1] : nil
|
|
103
|
+
if a
|
|
104
|
+
rest = node["number"][..-2]
|
|
105
|
+
j = rest.rindex(/[^\d]/)
|
|
106
|
+
n = j.nil? ? rest : rest[(j + 1)..]
|
|
107
|
+
b = j.nil? ? "" : rest[..j]
|
|
108
|
+
end
|
|
96
109
|
if blank?(a) then subsequence_increment_no_letter(node)
|
|
97
110
|
else
|
|
98
111
|
@letter_override = @letter = a
|
|
@@ -174,6 +187,26 @@ module IsoDoc
|
|
|
174
187
|
"#{prefix}#{@base}#{out}#{@letter_override || @letter}"
|
|
175
188
|
end
|
|
176
189
|
|
|
190
|
+
# Decompose a counter number string into [prefix, digits, letters]
|
|
191
|
+
# by scanning from the right. Examples:
|
|
192
|
+
# "A1b" => ["A", "1", "b" ]
|
|
193
|
+
# "prefix-3" => ["prefix-", "3", "" ]
|
|
194
|
+
# "ABC" => ["", "", "ABC" ]
|
|
195
|
+
# "42" => ["", "42", "" ]
|
|
196
|
+
# Replaces polynomial regexes like /^(?<b>.*?)(?<n>\d*)(?<a>[a-zA-Z]*)$/
|
|
197
|
+
# with O(n) rindex operations that cannot backtrack.
|
|
198
|
+
def parse_number_suffix(str)
|
|
199
|
+
# Step 1: split off all trailing letters
|
|
200
|
+
i = str.rindex(/[^a-zA-Z]/) # index of last non-letter char
|
|
201
|
+
a = i.nil? ? str : str[(i + 1)..]
|
|
202
|
+
rest = i.nil? ? "" : str[..i]
|
|
203
|
+
# Step 2: split off all trailing digits from the remaining prefix
|
|
204
|
+
j = rest.rindex(/[^\d]/) # index of last non-digit char
|
|
205
|
+
n = j.nil? ? rest : rest[(j + 1)..]
|
|
206
|
+
b = j.nil? ? "" : rest[..j]
|
|
207
|
+
[b, n, a]
|
|
208
|
+
end
|
|
209
|
+
|
|
177
210
|
def listlabel(list, depth)
|
|
178
211
|
case ol_type(list, depth)
|
|
179
212
|
when :arabic then @num.to_s
|