metanorma-standoc 1.10.5 → 1.11.0

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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +19 -23
  3. data/Rakefile +1 -1
  4. data/lib/asciidoctor/standoc/base.rb +14 -17
  5. data/lib/asciidoctor/standoc/basicdoc.rng +21 -4
  6. data/lib/asciidoctor/standoc/blocks.rb +26 -23
  7. data/lib/asciidoctor/standoc/blocks_notes.rb +17 -22
  8. data/lib/asciidoctor/standoc/cleanup.rb +46 -12
  9. data/lib/asciidoctor/standoc/cleanup_block.rb +5 -70
  10. data/lib/asciidoctor/standoc/cleanup_image.rb +6 -7
  11. data/lib/asciidoctor/standoc/cleanup_inline.rb +44 -102
  12. data/lib/asciidoctor/standoc/cleanup_maths.rb +5 -6
  13. data/lib/asciidoctor/standoc/cleanup_ref.rb +5 -0
  14. data/lib/asciidoctor/standoc/cleanup_reqt.rb +51 -33
  15. data/lib/asciidoctor/standoc/cleanup_section_names.rb +5 -5
  16. data/lib/asciidoctor/standoc/cleanup_symbols.rb +48 -0
  17. data/lib/asciidoctor/standoc/cleanup_table.rb +68 -0
  18. data/lib/asciidoctor/standoc/cleanup_terms.rb +38 -78
  19. data/lib/asciidoctor/standoc/cleanup_terms_designations.rb +162 -0
  20. data/lib/asciidoctor/standoc/cleanup_text.rb +5 -2
  21. data/lib/asciidoctor/standoc/cleanup_xref.rb +107 -0
  22. data/lib/asciidoctor/standoc/converter.rb +15 -0
  23. data/lib/asciidoctor/standoc/inline.rb +7 -5
  24. data/lib/asciidoctor/standoc/isodoc.rng +435 -78
  25. data/lib/asciidoctor/standoc/lists.rb +15 -15
  26. data/lib/asciidoctor/standoc/macros.rb +14 -43
  27. data/lib/asciidoctor/standoc/macros_note.rb +45 -0
  28. data/lib/asciidoctor/standoc/macros_plantuml.rb +29 -14
  29. data/lib/asciidoctor/standoc/macros_terms.rb +82 -20
  30. data/lib/asciidoctor/standoc/ref_sect.rb +24 -17
  31. data/lib/asciidoctor/standoc/reqt.rb +2 -2
  32. data/lib/asciidoctor/standoc/reqt.rng +23 -2
  33. data/lib/asciidoctor/standoc/term_lookup_cleanup.rb +50 -11
  34. data/lib/asciidoctor/standoc/terms.rb +21 -3
  35. data/lib/asciidoctor/standoc/utils.rb +36 -23
  36. data/lib/asciidoctor/standoc/validate.rb +45 -27
  37. data/lib/asciidoctor/standoc/validate_section.rb +5 -2
  38. data/lib/metanorma/standoc/version.rb +1 -1
  39. data/metanorma-standoc.gemspec +1 -1
  40. data/spec/asciidoctor/base_spec.rb +4 -36
  41. data/spec/asciidoctor/blank_spec.rb +37 -0
  42. data/spec/asciidoctor/blocks_spec.rb +296 -47
  43. data/spec/asciidoctor/cleanup_blocks_spec.rb +1018 -0
  44. data/spec/asciidoctor/cleanup_sections_spec.rb +153 -12
  45. data/spec/asciidoctor/cleanup_spec.rb +179 -1265
  46. data/spec/asciidoctor/cleanup_terms_spec.rb +990 -0
  47. data/spec/asciidoctor/inline_spec.rb +38 -2
  48. data/spec/asciidoctor/lists_spec.rb +6 -6
  49. data/spec/asciidoctor/macros_plantuml_spec.rb +37 -2
  50. data/spec/asciidoctor/macros_spec.rb +226 -138
  51. data/spec/asciidoctor/refs_spec.rb +4 -26
  52. data/spec/asciidoctor/section_spec.rb +18 -18
  53. data/spec/asciidoctor/validate_spec.rb +109 -1
  54. data/spec/assets/xref_error.adoc +1 -0
  55. data/spec/fixtures/datamodel_description_sections_tree.xml +327 -326
  56. data/spec/spec_helper.rb +6 -7
  57. data/spec/vcr_cassettes/dated_iso_ref_joint_iso_iec.yml +51 -51
  58. data/spec/vcr_cassettes/dated_iso_ref_joint_iso_iec1.yml +13 -13
  59. data/spec/vcr_cassettes/isobib_get_123.yml +13 -13
  60. data/spec/vcr_cassettes/isobib_get_123_1.yml +26 -26
  61. data/spec/vcr_cassettes/isobib_get_123_1_fr.yml +34 -34
  62. data/spec/vcr_cassettes/isobib_get_123_2001.yml +12 -12
  63. data/spec/vcr_cassettes/isobib_get_124.yml +13 -13
  64. data/spec/vcr_cassettes/rfcbib_get_rfc8341.yml +16 -16
  65. data/spec/vcr_cassettes/separates_iev_citations_by_top_level_clause.yml +51 -49
  66. metadata +13 -5
@@ -15,9 +15,8 @@ module Asciidoctor
15
15
  def svgmap_moveattrs(xmldoc)
16
16
  xmldoc.xpath("//svgmap").each do |s|
17
17
  f = s.at(".//figure") or next
18
- if (t = s.at("./name")) && !f.at("./name")
18
+ (t = s.at("./name")) && !f.at("./name") and
19
19
  f.children.first.previous = t.remove
20
- end
21
20
  if s["id"] && guid?(f["id"])
22
21
  f["id"] = s["id"]
23
22
  s.delete("id")
@@ -26,13 +25,13 @@ module Asciidoctor
26
25
  end
27
26
  end
28
27
 
29
- def svgmap_moveattrs1(s, f)
28
+ def svgmap_moveattrs1(svgmap, figure)
30
29
  %w(unnumbered number subsequence keep-with-next
31
- keep-lines-together).each do |a|
32
- next if f[a] || !s[a]
30
+ keep-lines-together tag multilingual-rendering).each do |a|
31
+ next if figure[a] || !svgmap[a]
33
32
 
34
- f[a] = s[a]
35
- s.delete(a)
33
+ figure[a] = svgmap[a]
34
+ svgmap.delete(a)
36
35
  end
37
36
  end
38
37
 
@@ -1,4 +1,5 @@
1
1
  require "metanorma-utils"
2
+ require "digest"
2
3
 
3
4
  module Asciidoctor
4
5
  module Standoc
@@ -55,114 +56,30 @@ module Asciidoctor
55
56
  end
56
57
  end
57
58
 
58
- # extending localities to cover ISO referencing
59
- LOCALITY_REGEX_STR = <<~REGEXP.freeze
60
- ^((?<locality>section|clause|part|paragraph|chapter|page|
61
- table|annex|figure|example|note|formula|list|time|anchor|
62
- locality:[^ \\t\\n\\r:,;=]+)(\\s+|=)
63
- (?<ref>[^"][^ \\t\\n,:-]*|"[^"]+")
64
- (-(?<to>[^"][^ \\t\\n,:-]*|"[^"]"))?|
65
- (?<locality2>whole|locality:[^ \\t\\n\\r:,;=]+))(?<punct>[,:;]?)\\s*
66
- (?<text>.*)$
67
- REGEXP
68
- LOCALITY_RE = Regexp.new(LOCALITY_REGEX_STR.gsub(/\s/, ""),
69
- Regexp::IGNORECASE | Regexp::MULTILINE)
70
-
71
- def tq(text)
72
- text.sub(/^"/, "").sub(/"$/, "")
73
- end
74
-
75
- def extract_localities(elem)
76
- f = elem&.children&.first or return
77
- f.text? or return
78
- head = f.remove.text
79
- tail = elem&.children&.remove
80
- extract_localities1(elem, head)
81
- tail and elem << tail
82
- end
83
-
84
- def extract_localities1(elem, text)
85
- b = elem.add_child("<localityStack/>").first if LOCALITY_RE.match text
86
- while (m = LOCALITY_RE.match text)
87
- ref = m[:ref] ? "<referenceFrom>#{tq m[:ref]}</referenceFrom>" : ""
88
- refto = m[:to] ? "<referenceTo>#{tq m[:to]}</referenceTo>" : ""
89
- loc = m[:locality]&.downcase || m[:locality2]&.downcase
90
- b.add_child("<locality type='#{loc}'>#{ref}#{refto}</locality>")
91
- text = m[:text]
92
- b = elem.add_child("<localityStack/>").first if m[:punct] == ";"
93
- end
94
- elem.add_child(text) if text
95
- end
96
-
97
- def xref_to_eref(elem)
98
- elem["bibitemid"] = elem["target"]
99
- unless elem["citeas"] = @anchors&.dig(elem["target"], :xref)
100
- @internal_eref_namespaces.include?(elem["type"]) or
101
- @log.add("Crossreferences", elem,
102
- "#{elem['target']} does not have a corresponding "\
103
- "anchor ID in the bibliography!")
104
- end
105
- elem.delete("target")
106
- extract_localities(elem) unless elem.children.empty?
107
- end
108
-
109
- def xref_cleanup(xmldoc)
110
- xmldoc.xpath("//xref").each do |x|
111
- /:/.match(x["target"]) and xref_to_internal_eref(x)
112
- next unless x.name == "xref"
113
-
114
- if refid? x["target"]
115
- x.name = "eref"
116
- xref_to_eref(x)
117
- else x.delete("type")
118
- end
119
- end
120
- end
121
-
122
- def xref_to_internal_eref(elem)
123
- a = elem["target"].split(":", 3)
124
- unless a.size < 2 || a[0].empty? || a[1].empty?
125
- elem["target"] = "#{a[0]}_#{a[1]}"
126
- a.size > 2 and
127
- elem.children = %{anchor="#{a[2..-1].join}",#{elem&.children&.text}}
128
- elem["type"] = a[0]
129
- @internal_eref_namespaces << a[0]
130
- elem.name = "eref"
131
- xref_to_eref(elem)
132
- end
133
- end
134
-
135
- def quotesource_cleanup(xmldoc)
136
- xmldoc.xpath("//quote/source | //terms/source").each do |x|
137
- xref_to_eref(x)
59
+ def concept_cleanup(xmldoc)
60
+ xmldoc.xpath("//concept[not(termxref)]").each do |x|
61
+ term = x.at("./refterm")
62
+ term&.remove if term&.text&.empty?
63
+ concept_cleanup1(x)
138
64
  end
139
65
  end
140
66
 
141
- def origin_cleanup(xmldoc)
142
- xmldoc.xpath("//origin/concept[termref]").each do |x|
143
- t = x.at("./termref")
144
- x.replace(t)
145
- end
146
- xmldoc.xpath("//origin").each do |x|
147
- x["citeas"] = @anchors&.dig(x["bibitemid"], :xref) or
148
- @log.add("Crossreferences", x,
149
- "#{x['bibitemid']} does not have a corresponding anchor "\
150
- "ID in the bibliography!")
151
- extract_localities(x) unless x.children.empty?
67
+ def concept_cleanup1(elem)
68
+ elem.children.remove if elem&.children&.text&.strip&.empty?
69
+ key_extract_locality(elem)
70
+ if /:/.match?(elem["key"]) then concept_termbase_cleanup(elem)
71
+ elsif refid? elem["key"] then concept_eref_cleanup(elem)
72
+ else concept_xref_cleanup(elem)
152
73
  end
74
+ elem.delete("key")
153
75
  end
154
76
 
155
- def concept_cleanup(xmldoc)
156
- xmldoc.xpath("//concept[not(termxref)]").each do |x|
77
+ def related_cleanup(xmldoc)
78
+ xmldoc.xpath("//related[not(termxref)]").each do |x|
157
79
  term = x.at("./refterm")
158
- term&.remove if term&.text&.empty?
159
- x.children.remove if x&.children&.text&.strip&.empty?
160
- key_extract_locality(x)
161
- if /:/.match?(x["key"]) then concept_termbase_cleanup(x)
162
- elsif refid? x["key"] then concept_eref_cleanup(x)
163
- else concept_xref_cleanup(x)
164
- end
165
- x.delete("key")
80
+ term.replace("<preferred>#{term_expr(term.children.to_xml)}"\
81
+ "</preferred>")
82
+ concept_cleanup1(x)
166
83
  end
167
84
  end
168
85
 
@@ -203,11 +120,13 @@ module Asciidoctor
203
120
  end
204
121
 
205
122
  IDREF = "//*/@id | //review/@from | //review/@to | "\
206
- "//callout/@target | //citation/@bibitemid | //eref/@bibitemid".freeze
123
+ "//callout/@target | //citation/@bibitemid | "\
124
+ "//eref/@bibitemid".freeze
207
125
 
208
126
  def anchor_cleanup(elem)
209
127
  anchor_cleanup1(elem)
210
128
  xreftarget_cleanup(elem)
129
+ contenthash_id_cleanup(elem)
211
130
  end
212
131
 
213
132
  def anchor_cleanup1(elem)
@@ -233,6 +152,29 @@ module Asciidoctor
233
152
  end
234
153
  end
235
154
  end
155
+
156
+ def guid?(str)
157
+ /^_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
158
+ .match?(str)
159
+ end
160
+
161
+ def contenthash_id_cleanup(doc)
162
+ ids = doc.xpath("//*[@id]").each_with_object({}) do |x, m|
163
+ next unless guid?(x["id"])
164
+
165
+ m[x["id"]] = contenthash(x)
166
+ x["id"] = m[x["id"]]
167
+ end
168
+ [%w(review from), %(review to), %(callout target), %(eref bibitemid),
169
+ %(citation bibitemid), %(xref target), %(xref to)].each do |a|
170
+ doc.xpath("//#{a[0]}").each { |x| ids[a[1]] and x[a[1]] = ids[a[1]] }
171
+ end
172
+ end
173
+
174
+ def contenthash(elem)
175
+ Digest::MD5.hexdigest("#{elem.path}////#{elem.text}")
176
+ .sub(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, "_\\1-\\2-\\3-\\4-\\5")
177
+ end
236
178
  end
237
179
  end
238
180
  end
@@ -69,13 +69,12 @@ module Asciidoctor
69
69
  return false if char.length > 1
70
70
 
71
71
  if /\p{Greek}/.match?(char)
72
- /\p{Lower}/.match(char) && !mathml_mi_italics[:lowergreek] ||
73
- /\p{Upper}/.match(char) && !mathml_mi_italics[:uppergreek]
72
+ (/\p{Lower}/.match(char) && !mathml_mi_italics[:lowergreek]) ||
73
+ (/\p{Upper}/.match(char) && !mathml_mi_italics[:uppergreek])
74
74
  elsif /\p{Latin}/.match?(char)
75
- /\p{Lower}/.match(char) && !mathml_mi_italics[:lowerroman] ||
76
- /\p{Upper}/.match(char) && !mathml_mi_italics[:upperroman]
77
- else
78
- false
75
+ (/\p{Lower}/.match(char) && !mathml_mi_italics[:lowerroman]) ||
76
+ (/\p{Upper}/.match(char) && !mathml_mi_italics[:upperroman])
77
+ else false
79
78
  end
80
79
  end
81
80
 
@@ -84,6 +84,11 @@ module Asciidoctor
84
84
  biblio_reorder(xmldoc)
85
85
  biblio_nested(xmldoc)
86
86
  biblio_renumber(xmldoc)
87
+ biblio_no_ext(xmldoc)
88
+ end
89
+
90
+ def biblio_no_ext(xmldoc)
91
+ xmldoc.xpath("//bibitem/ext").each(&:remove)
87
92
  end
88
93
 
89
94
  def biblio_nested(xmldoc)
@@ -3,37 +3,61 @@ module Asciidoctor
3
3
  module Cleanup
4
4
  def requirement_cleanup(xmldoc)
5
5
  requirement_metadata(xmldoc)
6
- requirement_descriptions(xmldoc)
7
6
  requirement_inherit(xmldoc)
7
+ requirement_descriptions(xmldoc)
8
8
  end
9
9
 
10
10
  REQRECPER = "//requirement | //recommendation | //permission".freeze
11
11
 
12
12
  def requirement_inherit(xmldoc)
13
13
  xmldoc.xpath(REQRECPER).each do |r|
14
- ins = r.at("./classification") ||
15
- r.at("./description | ./measurementtarget | ./specification | "\
16
- "./verification | ./import | ./description | ./requirement | "\
17
- "./recommendation | ./permission | ./component")
14
+ ins = requirement_inherit_insert(r)
18
15
  r.xpath("./*//inherit").each { |i| ins.previous = i }
19
16
  end
20
17
  end
21
18
 
19
+ def requirement_inherit_insert(reqt)
20
+ ins = reqt.at("./classification") || reqt.at(
21
+ "./description | ./measurementtarget | ./specification | "\
22
+ "./verification | ./import | ./description | ./component | "\
23
+ "./requirement | ./recommendation | ./permission",
24
+ ) and return ins
25
+ requirement_inherit_insert1(reqt)
26
+ end
27
+
28
+ def requirement_inherit_insert1(reqt)
29
+ if t = reqt.at("./title")
30
+ t.next = " "
31
+ t.next
32
+ else
33
+ if reqt.children.empty? then reqt.add_child(" ")
34
+ else reqt.children.first.previous = " "
35
+ end
36
+ reqt.children.first
37
+ end
38
+ end
39
+
22
40
  def requirement_descriptions(xmldoc)
23
41
  xmldoc.xpath(REQRECPER).each do |r|
42
+ r.xpath(".//p[not(./*)][normalize-space(.)='']").each(&:remove)
24
43
  r.children.each do |e|
25
- unless e.element? && (reqt_subpart(e.name) ||
26
- %w(requirement recommendation permission).include?(e.name))
27
- next if e.text.strip.empty?
28
- t = Nokogiri::XML::Element.new("description", r)
29
- e.before(t)
30
- t.children = e.remove
31
- end
44
+ requirement_description_wrap(r, e)
32
45
  end
33
46
  requirement_description_cleanup1(r)
34
47
  end
35
48
  end
36
49
 
50
+ def requirement_description_wrap(reqt, text)
51
+ return if text.element? && (reqt_subpart(text.name) ||
52
+ %w(requirement recommendation
53
+ permission).include?(text.name)) ||
54
+ text.text.strip.empty?
55
+
56
+ t = Nokogiri::XML::Element.new("description", reqt)
57
+ text.before(t)
58
+ t.children = text.remove
59
+ end
60
+
37
61
  def requirement_description_cleanup1(reqt)
38
62
  while d = reqt.at("./description[following-sibling::*[1]"\
39
63
  "[self::description]]")
@@ -48,45 +72,39 @@ module Asciidoctor
48
72
  def requirement_metadata(xmldoc)
49
73
  xmldoc.xpath(REQRECPER).each do |r|
50
74
  dl = r&.at("./dl[@metadata = 'true']")&.remove or next
51
- requirement_metadata1(r, dl)
75
+ requirement_metadata1(r, dl, r.at("./title"))
52
76
  end
53
77
  end
54
78
 
55
- def requirement_metadata1(reqt, dlist)
56
- unless ins = reqt.at("./title")
79
+ def requirement_metadata1_tags
80
+ %w(label subject inherit)
81
+ end
82
+
83
+ def requirement_metadata1(reqt, dlist, ins)
84
+ unless ins
57
85
  reqt.children.first.previous = " "
58
86
  ins = reqt.children.first
59
87
  end
60
- %w(label subject inherit).each do |a|
61
- ins = reqt_dl_to_elems(ins, reqt, dlist, a)
88
+ %w(obligation model type).each do |a|
89
+ dl_to_attrs(reqt, dlist, a)
62
90
  end
63
- reqt_dl_to_classif(ins, reqt, dlist)
64
- end
65
-
66
- def reqt_dl_to_elems(ins, reqt, dlist, name)
67
- if a = reqt.at("./#{name}[last()]")
68
- ins = a
91
+ requirement_metadata1_tags.each do |a|
92
+ ins = dl_to_elems(ins, reqt, dlist, a)
69
93
  end
70
- dlist.xpath("./dt[text()='#{name}']").each do |e|
71
- val = e.at("./following::dd/p") || e.at("./following::dd")
72
- val.name = name
73
- ins.next = val
74
- ins = ins.next
75
- end
76
- ins
94
+ reqt_dl_to_classif(ins, reqt, dlist)
77
95
  end
78
96
 
79
97
  def reqt_dl_to_classif(ins, reqt, dlist)
80
- if a = reqt.at("./classification[last()]")
81
- ins = a
82
- end
98
+ if a = reqt.at("./classification[last()]") then ins = a end
83
99
  dlist.xpath("./dt[text()='classification']").each do |e|
84
100
  val = e.at("./following::dd/p") || e.at("./following::dd")
85
101
  req_classif_parse(val.text).each do |r|
86
102
  ins.next = "<classification><tag>#{r[0]}</tag>"\
87
103
  "<value>#{r[1]}</value></classification>"
104
+ ins = ins.next
88
105
  end
89
106
  end
107
+ ins
90
108
  end
91
109
  end
92
110
  end
@@ -78,12 +78,12 @@ module Asciidoctor
78
78
  def sections_variant_title_cleanup(xml)
79
79
  path = SECTION_CONTAINERS.map { |x| "./ancestor::#{x}" }.join(" | ")
80
80
  xml.xpath("//p[@variant_title]").each do |p|
81
+ p.name = "variant-title"
82
+ p.delete("id")
83
+ p.delete("variant_title")
81
84
  p.xpath("(#{path})[last()]").each do |sect|
82
- p.name = "variant-title"
83
- p.delete("id")
84
- if ins = sect.at("./title") then ins.next = p
85
- else sect.children.first.previous = p
86
- end
85
+ ins = sect.at("./title") and ins.next = p or
86
+ sect.children.first.previous = p
87
87
  end
88
88
  end
89
89
  end
@@ -0,0 +1,48 @@
1
+ module Asciidoctor
2
+ module Standoc
3
+ module Cleanup
4
+ # Indices sort after letter but before any following
5
+ # letter (x, x_m, x_1, xa); we use colon to force that sort order.
6
+ # Numbers sort *after* letters; we use thorn to force that sort order.
7
+ def symbol_key(sym)
8
+ key = sym.dup
9
+ key.traverse do |n|
10
+ n.name == "math" and
11
+ n.replace(grkletters(MathML2AsciiMath.m2a(n.to_xml)))
12
+ end
13
+ ret = Nokogiri::XML(key.to_xml)
14
+ HTMLEntities.new.decode(ret.text.downcase)
15
+ .gsub(/[\[\]{}<>()]/, "").gsub(/\s/m, "")
16
+ .gsub(/[[:punct:]]|[_^]/, ":\\0").gsub(/`/, "")
17
+ .gsub(/[0-9]+/, "þ\\0")
18
+ end
19
+
20
+ def grkletters(text)
21
+ text.gsub(/\b(alpha|beta|gamma|delta|epsilon|zeta|eta|theta|iota|kappa|
22
+ lambda|mu|nu|xi|omicron|pi|rho|sigma|tau|upsilon|phi|chi|
23
+ psi|omega)\b/xi, "&\\1;")
24
+ end
25
+
26
+ def extract_symbols_list(dlist)
27
+ dl_out = []
28
+ dlist.xpath("./dt | ./dd").each do |dtd|
29
+ if dtd.name == "dt"
30
+ dl_out << { dt: dtd.remove, key: symbol_key(dtd) }
31
+ else
32
+ dl_out.last[:dd] = dtd.remove
33
+ end
34
+ end
35
+ dl_out
36
+ end
37
+
38
+ def symbols_cleanup(docxml)
39
+ docxml.xpath("//definitions/dl").each do |dl|
40
+ dl_out = extract_symbols_list(dl)
41
+ dl_out.sort! { |a, b| a[:key] <=> b[:key] || a[:dt] <=> b[:dt] }
42
+ dl.children = dl_out.map { |d| d[:dt].to_s + d[:dd].to_s }.join("\n")
43
+ end
44
+ docxml
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,68 @@
1
+ module Asciidoctor
2
+ module Standoc
3
+ module Cleanup
4
+ def dl1_table_cleanup(xmldoc)
5
+ q = "//table/following-sibling::*[1][self::dl]"
6
+ xmldoc.xpath(q).each do |s|
7
+ s["key"] == "true" and s.previous_element << s.remove
8
+ end
9
+ end
10
+
11
+ # move Key dl after table footer
12
+ def dl2_table_cleanup(xmldoc)
13
+ q = "//table/following-sibling::*[1][self::p]"
14
+ xmldoc.xpath(q).each do |s|
15
+ if s.text =~ /^\s*key[^a-z]*$/i && s&.next_element&.name == "dl"
16
+ s.next_element["key"] = "true"
17
+ s.previous_element << s.next_element.remove
18
+ s.remove
19
+ end
20
+ end
21
+ end
22
+
23
+ def insert_thead(table)
24
+ thead = table.at("./thead")
25
+ return thead unless thead.nil?
26
+
27
+ if tname = table.at("./name")
28
+ thead = tname.add_next_sibling("<thead/>").first
29
+ return thead
30
+ end
31
+ table.children.first.add_previous_sibling("<thead/>").first
32
+ end
33
+
34
+ def header_rows_cleanup(xmldoc)
35
+ xmldoc.xpath("//table[@headerrows]").each do |s|
36
+ thead = insert_thead(s)
37
+ (thead.xpath("./tr").size...s["headerrows"].to_i).each do
38
+ row = s.at("./tbody/tr")
39
+ row.parent = thead
40
+ end
41
+ thead.xpath(".//td").each { |n| n.name = "th" }
42
+ s.delete("headerrows")
43
+ end
44
+ end
45
+
46
+ def table_cleanup(xmldoc)
47
+ dl1_table_cleanup(xmldoc)
48
+ dl2_table_cleanup(xmldoc)
49
+ notes_table_cleanup(xmldoc)
50
+ header_rows_cleanup(xmldoc)
51
+ end
52
+
53
+ # move notes into table
54
+ def notes_table_cleanup(xmldoc)
55
+ nomatches = false
56
+ until nomatches
57
+ nomatches = true
58
+ xmldoc.xpath("//table/following-sibling::*[1]"\
59
+ "[self::note[not(@keep-separate = 'true')]]").each do |n|
60
+ n.delete("keep-separate")
61
+ n.previous_element << n.remove
62
+ nomatches = false
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end