metanorma-standoc 1.10.5 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
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