isodoc 3.4.7 → 3.4.9

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.
@@ -1,10 +1,10 @@
1
1
  module IsoDoc
2
2
  class PresentationXMLConvert < ::IsoDoc::Convert
3
- def prefix_name(node, delims, label, elem)
3
+ def prefix_name(node, delims, label, elem, fmt_xref_label: true)
4
4
  sem_xml_descendant?(node) and return
5
5
  label, delims = prefix_name_defaults(node, delims, label, elem)
6
6
  name, ins, ids, number = prefix_name_prep(node, elem)
7
- ins.next = fmt_xref_label(label, number, ids)
7
+ fmt_xref_label and ins.next = fmt_xref_label(label, number, ids)
8
8
  # autonum can be empty, e.g single note in clause: "NOTE []"
9
9
  number and node["autonum"] = number.gsub(/<[^>]+>/, "")
10
10
  !node.at(ns("./fmt-#{elem}")) &&
@@ -40,6 +40,7 @@ module IsoDoc
40
40
  node.at(ns("./sentinel"))&.remove
41
41
  strip_duplicate_ids(node, node.at(ns("./#{elem}")),
42
42
  node.at(ns("./fmt-#{elem}")))
43
+ node.at(ns("./fmt-#{elem}"))
43
44
  end
44
45
 
45
46
  def transfer_id(old, new)
@@ -67,7 +68,7 @@ module IsoDoc
67
68
  # remove ids duplicated between sem_title and pres_title
68
69
  # index terms are assumed transferred to pres_title from sem_title
69
70
  def strip_duplicate_ids(_node, sem_title, pres_title)
70
- sem_title && pres_title or return
71
+ (sem_title && pres_title) or return
71
72
  ids = gather_all_ids(pres_title)
72
73
  sem_title.xpath(".//*[@id]").each do |x|
73
74
  ids.include?(x["id"]) or next
@@ -78,7 +79,7 @@ module IsoDoc
78
79
  end
79
80
 
80
81
  def semx(node, label, element = "autonum")
81
- id = node["id"] || node[:id] || elem["original-id"]
82
+ id = node["id"] || node[:id] || node["original-id"]
82
83
  /<semx element='[^']+' source='#{id}'/.match?(label) and return label
83
84
  l = stripsemx(label)
84
85
  %(<semx element='#{element}' source='#{id}'>#{l}</semx>)
@@ -202,7 +203,7 @@ module IsoDoc
202
203
  def sem_xml_descendant_inline?(_node, ancestor_names)
203
204
  %w[xref eref origin link name title newcontent]
204
205
  .any? do |name|
205
- ancestor_names.include?(name)
206
+ ancestor_names.include?(name)
206
207
  end and return true
207
208
  end
208
209
 
@@ -213,7 +214,7 @@ module IsoDoc
213
214
  block?(node) and return false
214
215
  %w[preferred admitted deprecated related definition source]
215
216
  .any? do |name|
216
- ancestor_names.include?(name)
217
+ ancestor_names.include?(name)
217
218
  end
218
219
  end
219
220
 
@@ -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
- labelled_ancestor(elem) and return
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("./newcontent")).each do |a|
166
+ elem.xpath(ns(".//newcontent")).each do |a|
163
167
  a.name = "quote"
164
- a.xpath(ns("./clause")).each do |c|
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.xpath(ns("./title")).reverse_each do |t|
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
- .first.elements.first.children.first
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(/<\?xml[^<>]*>/, ""), &:huge)
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(numberformat_extract(options))
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
- num.sub(/^.*\./, "").size
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
- num.sub(/^0\./, ".").sub(/^.*\./, "").sub(/e.*$/, "").size
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{<[^:/>]+:}, "<").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 = @xrefs.anchor(elem["id"], :level, false) ||
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 annex(docxml)
55
- docxml.xpath(ns("//annex")).each do |f|
56
- @xrefs.klass.single_term_clause?(f) and single_term_clause_retitle(f)
57
- annex1(f)
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
- # TODO: do not alter title, alter semx/@element = title
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)
@@ -153,6 +153,9 @@ module IsoDoc
153
153
  pre.name = "sourcecode"
154
154
  pre.children = to_xml(pre.children).sub(/\s+$/, "")
155
155
  end
156
+ r.xpath(".//tr[@id]").each do |tr| # disambig Rouge ids
157
+ tr["id"] = "#{elem['source']}_#{tr['id']}"
158
+ end
156
159
  r
157
160
  end
158
161
 
@@ -160,7 +163,7 @@ module IsoDoc
160
163
  lexer = Rouge::Lexer.find(lang || "plaintext") ||
161
164
  Rouge::Lexer.find("plaintext")
162
165
  l = Rouge::Lexers::Escape.new(start: "{^^{", end: "}^^}", lang: lexer)
163
- source = to_xml(elem.children).gsub(/</, "{^^{<").gsub(/>/, ">}^^}")
166
+ source = to_xml(elem.children).gsub("<", "{^^{<").gsub(">", ">}^^}")
164
167
  l.lang.reset!
165
168
  l.lex(@c.decode(source))
166
169
  end
@@ -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
- m[b["id"]] = b
64
- m[b["anchor"]] = b
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
@@ -1,3 +1,3 @@
1
1
  module IsoDoc
2
- VERSION = "3.4.7".freeze
2
+ VERSION = "3.4.9".freeze
3
3
  end
@@ -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:[^;]+;/, "border-bottom:0pt;")
7
- .gsub(/mso-border-bottom-alt:[^;]+;/, "mso-border-bottom-alt:0pt;")
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
- "mso-table-overlap:never;#{style}#{keep_style(node)}" }
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