metanorma-standoc 3.0.6 → 3.0.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/metanorma/standoc/anchor.rb +2 -2
  3. data/lib/metanorma/standoc/base.rb +1 -1
  4. data/lib/metanorma/standoc/basicdoc.rng +9 -5
  5. data/lib/metanorma/standoc/blocks.rb +7 -4
  6. data/lib/metanorma/standoc/blocks_image.rb +2 -6
  7. data/lib/metanorma/standoc/blocks_notes.rb +2 -6
  8. data/lib/metanorma/standoc/cleanup_amend.rb +6 -8
  9. data/lib/metanorma/standoc/cleanup_asciibib.rb +17 -13
  10. data/lib/metanorma/standoc/cleanup_bibdata.rb +19 -13
  11. data/lib/metanorma/standoc/cleanup_bibitem.rb +9 -6
  12. data/lib/metanorma/standoc/cleanup_block.rb +6 -6
  13. data/lib/metanorma/standoc/cleanup_boilerplate.rb +18 -7
  14. data/lib/metanorma/standoc/cleanup_footnotes.rb +2 -4
  15. data/lib/metanorma/standoc/cleanup_image.rb +3 -3
  16. data/lib/metanorma/standoc/cleanup_inline.rb +12 -38
  17. data/lib/metanorma/standoc/cleanup_review.rb +7 -5
  18. data/lib/metanorma/standoc/cleanup_section.rb +9 -4
  19. data/lib/metanorma/standoc/cleanup_section_names.rb +1 -0
  20. data/lib/metanorma/standoc/cleanup_table.rb +1 -2
  21. data/lib/metanorma/standoc/cleanup_terms.rb +1 -1
  22. data/lib/metanorma/standoc/cleanup_terms_designations.rb +1 -1
  23. data/lib/metanorma/standoc/cleanup_text.rb +9 -8
  24. data/lib/metanorma/standoc/cleanup_toc.rb +1 -1
  25. data/lib/metanorma/standoc/cleanup_xref.rb +1 -1
  26. data/lib/metanorma/standoc/converter.rb +1 -0
  27. data/lib/metanorma/standoc/front_organisation.rb +13 -4
  28. data/lib/metanorma/standoc/init.rb +15 -7
  29. data/lib/metanorma/standoc/inline.rb +10 -8
  30. data/lib/metanorma/standoc/isodoc.rng +147 -5
  31. data/lib/metanorma/standoc/localbib.rb +1 -2
  32. data/lib/metanorma/standoc/macros_form.rb +21 -3
  33. data/lib/metanorma/standoc/macros_inline.rb +1 -1
  34. data/lib/metanorma/standoc/macros_link.rb +4 -5
  35. data/lib/metanorma/standoc/ref.rb +2 -2
  36. data/lib/metanorma/standoc/ref_sect.rb +1 -1
  37. data/lib/metanorma/standoc/ref_utility.rb +4 -3
  38. data/lib/metanorma/standoc/section.rb +43 -85
  39. data/lib/metanorma/standoc/sectiontype.rb +76 -0
  40. data/lib/metanorma/standoc/table.rb +9 -13
  41. data/lib/metanorma/standoc/term_lookup_cleanup.rb +26 -9
  42. data/lib/metanorma/standoc/terms.rb +1 -1
  43. data/lib/metanorma/standoc/utils.rb +5 -1
  44. data/lib/metanorma/standoc/validate.rb +79 -13
  45. data/lib/metanorma/standoc/validate_schema.rb +2 -0
  46. data/lib/metanorma/standoc/validate_section.rb +5 -6
  47. data/lib/metanorma/standoc/validate_term.rb +8 -7
  48. data/lib/metanorma/standoc/version.rb +1 -1
  49. data/metanorma-standoc.gemspec +1 -1
  50. metadata +5 -4
@@ -32,7 +32,8 @@ module Metanorma
32
32
  end
33
33
 
34
34
  def use_my_anchor(ref, id, opt)
35
- ref.parent.elements.last["id"] = id
35
+ ref.parent.elements.last["anchor"] = id
36
+ add_id(ref.parent.elements.last)
36
37
  a = opt[:hidden] and ref.parent.elements.last["hidden"] = a
37
38
  a = opt[:dropid] and
38
39
  ref.parent.elements.last["suppress_identifier"] = a
@@ -195,8 +196,8 @@ module Metanorma
195
196
 
196
197
  def ref_attributes(match)
197
198
  code = analyse_ref_code(match[:code])
198
-
199
- { id: match[:anchor], type: "standard",
199
+ { anchor: match[:anchor], id: "_#{UUIDTools::UUID.random_create}",
200
+ type: "standard",
200
201
  suppress_identifier: code[:dropid] || nil }
201
202
  end
202
203
 
@@ -1,6 +1,7 @@
1
1
  require "uri" if /^2\./.match?(RUBY_VERSION)
2
2
  require_relative "ref_sect"
3
3
  require_relative "terms"
4
+ require_relative "sectiontype"
4
5
 
5
6
  module Metanorma
6
7
  module Standoc
@@ -9,85 +10,13 @@ module Metanorma
9
10
  @term_def = false
10
11
  @norm_ref = false
11
12
 
12
- def sectiontype1(node)
13
- node.attr("style") == "abstract" and return "abstract"
14
- node.attr("heading")&.downcase ||
15
- node.title
16
- .gsub(%r{<index>.*?</index>}m, "")
17
- .gsub(%r{<fn[^<>]*>.*?</fn>}m, "")
18
- .gsub(/<[^<>]+>/, "")
19
- .strip.downcase.sub(/\.$/, "")
20
- end
21
-
22
- def sectiontype(node, level = true)
23
- ret = sectiontype1(node)
24
- ret1 = preface_main_filter(sectiontype_streamline(ret), node)
25
- ret1 == "symbols and abbreviated terms" and return ret1
26
- !level || node.level == 1 || node.attr("heading") or return nil
27
- !node.attr("heading") && @seen_headers.include?(ret) and return nil
28
- @seen_headers << ret unless ret1.nil?
29
- @seen_headers_canonical << ret1 unless ret1.nil?
30
- ret1
31
- end
32
-
33
- def sectiontype_streamline(ret)
34
- case ret
35
- when "terms and definitions",
36
- "terms, definitions, symbols and abbreviated terms",
37
- "terms, definitions, symbols and abbreviations",
38
- "terms, definitions and symbols",
39
- "terms, definitions and abbreviations",
40
- "terms, definitions and abbreviated terms"
41
- "terms and definitions"
42
- when "symbols and abbreviated terms",
43
- "symbols", "abbreviated terms", "abbreviations",
44
- "symbols and abbreviations"
45
- "symbols and abbreviated terms"
46
- when "acknowledgements", "acknowledgments"
47
- "acknowledgements"
48
- else
49
- ret
50
- end
51
- end
52
-
53
- PREFACE_CLAUSE_NAMES =
54
- %w(abstract foreword introduction metanorma-extension termdocsource
55
- misc-container metanorma-extension acknowledgements).freeze
56
-
57
- MAIN_CLAUSE_NAMES =
58
- ["normative references", "terms and definitions", "scope",
59
- "symbols and abbreviated terms", "clause", "bibliography"].freeze
60
-
61
- def role_style(node, value)
62
- node.role == value || node.attr("style") == value
63
- end
64
-
65
- def start_main_section(ret, node)
66
- role_style(node, "preface") and return
67
- @preface = false if self.class::MAIN_CLAUSE_NAMES.include?(ret)
68
- @preface = false if self.class::PREFACE_CLAUSE_NAMES
69
- .intersection(@seen_headers_canonical + [ret]).empty?
70
- end
71
-
72
- def preface_main_filter(ret, node)
73
- start_main_section(ret, node)
74
- if @preface
75
- self.class::MAIN_CLAUSE_NAMES.include?(ret) and return nil
76
- else
77
- self.class::PREFACE_CLAUSE_NAMES.include?(ret) and return nil
78
- end
79
-
80
- ret
81
- end
82
-
83
13
  def section_attributes(node)
84
- ret =
85
- { id: Metanorma::Utils::anchor_or_uuid(node),
86
- unnumbered: node.option?("unnumbered") ? "true" : nil,
87
- annex: role_style(node, "appendix") && node.level == 1 ? true : nil,
14
+ ret = id_unnum_attrs(node).merge(
15
+ { annex: role_style(node, "appendix") && node.level == 1 ? true : nil,
88
16
  colophon: role_style(node, "colophon") ? true : nil,
89
- preface: role_style(node, "preface") ? true : nil }
90
- %w(language script number branch-number type tag keeptitle
17
+ preface: role_style(node, "preface") ? true : nil },
18
+ )
19
+ %w(language script branch-number type tag keeptitle
91
20
  multilingual-rendering).each do |k|
92
21
  a = node.attr(k) and ret[k.to_sym] = a
93
22
  end
@@ -120,6 +49,8 @@ module Metanorma
120
49
  symbols_parse(symbols_attrs(node, a), xml, node)
121
50
  when "acknowledgements"
122
51
  acknowledgements_parse(a, xml, node)
52
+ when "executivesummary"
53
+ executivesummary_parse(a, xml, node)
123
54
  when "bibliography"
124
55
  bibliography_parse(a, xml, node)
125
56
  else
@@ -188,47 +119,74 @@ module Metanorma
188
119
  clause_parse(attrs.merge(type: "scope"), xml, node)
189
120
  end
190
121
 
191
- def clause_parse(attrs, xml, node)
122
+ def clause_attrs_preprocess(attrs, node)
192
123
  attrs[:"inline-header"] = node.option? "inline-header"
193
124
  attrs[:bibitem] = true if node.option? "bibitem"
194
125
  attrs[:level] = node.attr("level")
195
126
  set_obligation(attrs, node)
127
+ end
128
+
129
+ def clause_parse(attrs, xml, node)
130
+ clause_attrs_preprocess(attrs, node)
131
+ node.option?("appendix") && support_appendix?(node) and
132
+ return appendix_parse(attrs, xml, node)
196
133
  xml.send :clause, **attr_code(attrs) do |xml_section|
197
134
  xml_section.title { |n| n << node.title } unless node.title.nil?
198
135
  xml_section << node.content
199
136
  end
200
137
  end
201
138
 
202
- def annex_parse(attrs, xml, node)
139
+ def annex_attrs_preprocess(attrs, node)
203
140
  attrs[:"inline-header"] = node.option? "inline-header"
204
141
  set_obligation(attrs, node)
142
+ end
143
+
144
+ def annex_parse(attrs, xml, node)
145
+ annex_attrs_preprocess(attrs, node)
205
146
  xml.annex **attr_code(attrs) do |xml_section|
206
147
  xml_section.title { |name| name << node.title }
207
148
  xml_section << node.content
208
149
  end
209
150
  end
210
151
 
152
+ def support_appendix?(_node)
153
+ false
154
+ end
155
+
156
+ def appendix_parse(attrs, xml, node)
157
+ attrs[:"inline-header"] = node.option? "inline-header"
158
+ set_obligation(attrs, node)
159
+ xml.appendix **attr_code(attrs) do |xml_section|
160
+ xml_section.title { |name| name << node.title }
161
+ xml_section << node.content
162
+ end
163
+ end
164
+
211
165
  def introduction_parse(attrs, xml, node)
212
166
  xml.introduction **attr_code(attrs) do |xml_section|
213
167
  xml_section.title { |t| t << @i18n.introduction }
214
- content = node.content
215
- xml_section << content
168
+ xml_section << node.content
216
169
  end
217
170
  end
218
171
 
219
172
  def foreword_parse(attrs, xml, node)
220
173
  xml.foreword **attr_code(attrs) do |xml_section|
221
174
  xml_section.title { |t| t << node.title }
222
- content = node.content
223
- xml_section << content
175
+ xml_section << node.content
224
176
  end
225
177
  end
226
178
 
227
179
  def acknowledgements_parse(attrs, xml, node)
228
180
  xml.acknowledgements **attr_code(attrs) do |xml_section|
229
181
  xml_section.title { |t| (t << node.title) || @i18n.acknowledgements }
230
- content = node.content
231
- xml_section << content
182
+ xml_section << node.content
183
+ end
184
+ end
185
+
186
+ def executivesummary_parse(attrs, xml, node)
187
+ xml.executivesummary **attr_code(attrs) do |xml_section|
188
+ xml_section.title { |t| (t << node.title) || @i18n.executivesummary }
189
+ xml_section << node.content
232
190
  end
233
191
  end
234
192
 
@@ -0,0 +1,76 @@
1
+ module Metanorma
2
+ module Standoc
3
+ module Section
4
+ def sectiontype1(node)
5
+ node.attr("style") == "abstract" and return "abstract"
6
+ node.attr("heading")&.downcase ||
7
+ node.title
8
+ .gsub(%r{<index>.*?</index>}m, "")
9
+ .gsub(%r{<fn[^<>]*>.*?</fn>}m, "")
10
+ .gsub(/<[^<>]+>/, "")
11
+ .strip.downcase.sub(/\.$/, "")
12
+ end
13
+
14
+ def sectiontype(node, level = true)
15
+ ret = sectiontype1(node)
16
+ ret1 = preface_main_filter(sectiontype_streamline(ret), node)
17
+ ret1 == "symbols and abbreviated terms" and return ret1
18
+ !level || node.level == 1 || node.attr("heading") or return nil
19
+ !node.attr("heading") && @seen_headers.include?(ret) and return nil
20
+ @seen_headers << ret unless ret1.nil?
21
+ @seen_headers_canonical << ret1 unless ret1.nil?
22
+ ret1
23
+ end
24
+
25
+ def sectiontype_streamline(ret)
26
+ case ret
27
+ when "terms and definitions",
28
+ "terms, definitions, symbols and abbreviated terms",
29
+ "terms, definitions, symbols and abbreviations",
30
+ "terms, definitions and symbols",
31
+ "terms, definitions and abbreviations",
32
+ "terms, definitions and abbreviated terms"
33
+ "terms and definitions"
34
+ when "symbols and abbreviated terms",
35
+ "symbols", "abbreviated terms", "abbreviations",
36
+ "symbols and abbreviations"
37
+ "symbols and abbreviated terms"
38
+ when "acknowledgements", "acknowledgments"
39
+ "acknowledgements"
40
+ when "executive summary", "executive-summary", "executive_summary"
41
+ "executivesummary"
42
+ else
43
+ ret
44
+ end
45
+ end
46
+
47
+ PREFACE_CLAUSE_NAMES =
48
+ %w(abstract foreword introduction metanorma-extension termdocsource
49
+ misc-container metanorma-extension acknowledgements executivesummary)
50
+ .freeze
51
+
52
+ MAIN_CLAUSE_NAMES =
53
+ ["normative references", "terms and definitions", "scope",
54
+ "symbols and abbreviated terms", "clause", "bibliography"].freeze
55
+
56
+ def role_style(node, value)
57
+ node.role == value || node.attr("style") == value
58
+ end
59
+
60
+ def start_main_section(ret, node)
61
+ role_style(node, "preface") and return
62
+ @preface = false if self.class::MAIN_CLAUSE_NAMES.include?(ret)
63
+ @preface = false if self.class::PREFACE_CLAUSE_NAMES
64
+ .intersection(@seen_headers_canonical + [ret]).empty?
65
+ end
66
+
67
+ def preface_main_filter(ret, node)
68
+ start_main_section(ret, node)
69
+ @preface && self.class::MAIN_CLAUSE_NAMES.include?(ret) and return nil
70
+ !@preface && self.class::PREFACE_CLAUSE_NAMES.include?(ret) and
71
+ return nil
72
+ ret
73
+ end
74
+ end
75
+ end
76
+ end
@@ -3,11 +3,8 @@ module Metanorma
3
3
  module Table
4
4
  def table_attrs(node)
5
5
  keep_attrs(node)
6
- .merge(id: Metanorma::Utils::anchor_or_uuid(node),
7
- headerrows: node.attr("headerrows"),
8
- unnumbered: node.option?("unnumbered") ? "true" : nil,
9
- number: node.attr("number"),
10
- subsequence: node.attr("subsequence"),
6
+ .merge(id_unnum_attrs(node))
7
+ .merge(headerrows: node.attr("headerrows"),
11
8
  alt: node.attr("alt"),
12
9
  summary: node.attr("summary"),
13
10
  width: node.attr("width"))
@@ -28,11 +25,9 @@ module Metanorma
28
25
  private
29
26
 
30
27
  def colgroup(node, xml_table)
31
- return if node.option? "autowidth"
32
-
33
- cols = node&.attr("cols")&.split(/,/) or return
34
- return unless (cols.size > 1) && cols.all? { |c| /\d/.match(c) }
35
-
28
+ node.option? "autowidth" and return
29
+ cols = node.attr("cols")&.split(",") or return
30
+ (cols.size > 1) && cols.all? { |c| /\d/.match(c) } or return
36
31
  xml_table.colgroup do |cg|
37
32
  node.columns.each do |col|
38
33
  cg.col width: "#{col.attr 'colpcwidth'}%"
@@ -57,9 +52,10 @@ module Metanorma
57
52
  end
58
53
 
59
54
  def table_cell(node, xml_tr, tblsec)
60
- cell_attributes =
61
- { id: node.id, colspan: node.colspan, valign: node.attr("valign"),
62
- rowspan: node.rowspan, align: node.attr("halign") }
55
+ cell_attributes = id_attr(node).merge(
56
+ { colspan: node.colspan, valign: node.attr("valign"),
57
+ rowspan: node.rowspan, align: node.attr("halign") },
58
+ )
63
59
  cell_tag = "td"
64
60
  cell_tag = "th" if tblsec == :head || node.style == :header
65
61
  xml_tr.send cell_tag, **attr_code(cell_attributes) do |thd|
@@ -16,11 +16,13 @@ module Metanorma
16
16
  @unique_designs = {}
17
17
  @c = HTMLEntities.new
18
18
  @terms_tags = xmldoc.xpath("//terms").each_with_object({}) do |t, m|
19
- m[t["id"]] = true
19
+ #m[t["id"]] = true
20
+ m[t["anchor"]] = true
20
21
  end
21
22
  end
22
23
 
23
24
  def call
25
+ #require "debug"; binding.b
24
26
  @idhash = populate_idhash
25
27
  @unique_designs = unique_designators
26
28
  @lookup = replace_automatic_generated_ids_terms
@@ -29,6 +31,7 @@ module Metanorma
29
31
  related_cleanup
30
32
  remove_missing_refs
31
33
  concept_cleanup2
34
+ anchor_to_id
32
35
  end
33
36
 
34
37
  private
@@ -36,7 +39,7 @@ module Metanorma
36
39
  def unique_designators
37
40
  ret = xmldoc
38
41
  .xpath("//preferred/expression/name | //admitted/expression/name | " \
39
- "//deprecated/expression/name").each_with_object({}) do |n, m|
42
+ "//deprecates/expression/name").each_with_object({}) do |n, m|
40
43
  m[n.text] ||= 0
41
44
  m[n.text] += 1
42
45
  end
@@ -69,9 +72,12 @@ module Metanorma
69
72
  end
70
73
 
71
74
  def populate_idhash
72
- xmldoc.xpath("//*[@id]").each_with_object({}) do |n, mem|
73
- /^(term|symbol)-/.match?(n["id"]) or next
74
- mem[n["id"]] = true
75
+ #xmldoc.xpath("//*[@id]").each_with_object({}) do |n, mem|
76
+ xmldoc.xpath("//*[@anchor]").each_with_object({}) do |n, mem|
77
+ #/^(term|symbol)-/.match?(n["id"]) or next
78
+ /^(term|symbol)-/.match?(n["anchor"]) or next
79
+ #mem[n["id"]] = true
80
+ mem[n["anchor"]] = true
75
81
  end
76
82
  end
77
83
 
@@ -207,18 +213,22 @@ module Metanorma
207
213
 
208
214
  def norm_id_memorize_init(node, res_table, selector, prefix, use_domain)
209
215
  term_text = norm_ref_id(node, selector, use_domain) or return
210
- unless AUTO_GEN_ID_REGEXP.match(node["id"]).nil? && !node["id"].nil?
216
+ #unless AUTO_GEN_ID_REGEXP.match(node["id"]).nil? && !node["id"].nil?
217
+ unless AUTO_GEN_ID_REGEXP.match(node["anchor"]).nil? && !node["anchor"].nil?
211
218
  id = unique_text_id(term_text, prefix)
212
- node["id"] = id
219
+ #node["id"] = id
220
+ node["anchor"] = id
213
221
  @idhash[id] = true
214
222
  end
215
- res_table[term_text] = node["id"]
223
+ #res_table[term_text] = node["id"]
224
+ res_table[term_text] = node["anchor"]
216
225
  end
217
226
 
218
227
  def memorize_other_pref_terms(node, res_table, text_selector, use_domain)
219
228
  node.xpath(text_selector).each_with_index do |p, i|
220
229
  i.positive? or next
221
- res_table[norm_ref_id1(p, use_domain ? node : nil)] = node["id"]
230
+ #res_table[norm_ref_id1(p, use_domain ? node : nil)] = node["id"]
231
+ res_table[norm_ref_id1(p, use_domain ? node : nil)] = node["anchor"]
222
232
  end
223
233
  end
224
234
 
@@ -255,6 +265,13 @@ module Metanorma
255
265
  end
256
266
  end
257
267
 
268
+ def anchor_to_id
269
+ xmldoc.xpath("//*[@anchor]").each do |n|
270
+ /^(term|symbol)-/.match?(n["anchor"]) or next
271
+ n["id"] or add_id(n)
272
+ end
273
+ end
274
+
258
275
  include ::Metanorma::Standoc::Utils
259
276
  end
260
277
  end
@@ -157,7 +157,7 @@ module Metanorma
157
157
  def termsource(node)
158
158
  matched = extract_termsource_refs(node.content, node) or return
159
159
  noko do |xml|
160
- xml.termsource **termsource_attrs(node, matched) do |xml_t|
160
+ xml.source **termsource_attrs(node, matched) do |xml_t|
161
161
  seen_xref = Nokogiri::XML.fragment(matched[:xref])
162
162
  add_term_source(node, xml_t, seen_xref, matched)
163
163
  end
@@ -35,6 +35,10 @@ module Metanorma
35
35
  Metanorma::Utils::attr_code(attributes)
36
36
  end
37
37
 
38
+ def add_id(node)
39
+ node["id"] = "_#{UUIDTools::UUID.random_create}"
40
+ end
41
+
38
42
  def csv_split(text, delim = ";")
39
43
  Metanorma::Utils::csv_split(@c.decode(text), delim)
40
44
  .map { |x| @c.encode(x, :basic, :hexadecimal) }
@@ -119,7 +123,7 @@ module Metanorma
119
123
  end
120
124
 
121
125
  SECTION_CONTAINERS =
122
- %w(foreword introduction acknowledgements abstract
126
+ %w(foreword introduction acknowledgements executivesummary abstract
123
127
  clause references terms definitions annex appendix indexsect
124
128
  executivesummary).freeze
125
129
 
@@ -12,7 +12,7 @@ module Metanorma
12
12
  module Validate
13
13
  def content_validate(doc)
14
14
  @doctype = doc.at("//bibdata/ext/doctype")&.text
15
- repeat_id_validate(doc.root) # feeds xref_validate
15
+ repeat_id_validate(doc.root) # feeds xref_validate, termsect_validate
16
16
  xref_validate(doc) # feeds nested_asset_validate
17
17
  nested_asset_validate(doc)
18
18
  section_validate(doc)
@@ -92,7 +92,7 @@ module Metanorma
92
92
  end
93
93
 
94
94
  def nested_asset_xref_report(outer, inner, _doc)
95
- i = @doc_xrefs[inner["id"]] or return
95
+ i = @doc_xrefs[inner["anchor"]] or return
96
96
  err2 = "There is a crossreference to an instance of #{inner.name} " \
97
97
  "nested within #{outer.name}: #{i.to_xml}"
98
98
  @log.add("Style", i, err2)
@@ -158,32 +158,98 @@ module Metanorma
158
158
  schema_validate(formattedstr_strip(doc.dup), schema_location)
159
159
  end
160
160
 
161
+ # Check should never happen with content ids, but will check it anyway
162
+ # since consequences are so catastrophic
161
163
  def repeat_id_validate1(elem)
162
164
  if @doc_ids[elem["id"]]
163
165
  @log.add("Anchors", elem,
164
- "Anchor #{elem['id']} has already been " \
165
- "used at line #{@doc_ids[elem['id']]}", severity: 0)
166
+ "ID #{elem['id']} has already been " \
167
+ "used at line #{@doc_ids[elem['id']][:line]}", severity: 0)
168
+ else
169
+ @doc_ids[elem["id"]] =
170
+ { line: elem.line, anchor: elem["anchor"] }.compact
166
171
  end
167
- @doc_ids[elem["id"]] = elem.line
168
172
  end
169
173
 
174
+ def repeat_anchor_validate1(elem)
175
+ if @doc_anchors[elem["anchor"]]
176
+ @log.add("Anchors", elem,
177
+ "Anchor #{elem['anchor']} has already been used at line " \
178
+ "#{@doc_anchors[elem['anchor']][:line]}", severity: 0)
179
+ else
180
+ @doc_anchors[elem["anchor"]] = { line: elem.line, id: elem["id"] }
181
+ @doc_anchor_seq << elem["anchor"]
182
+ end
183
+ end
184
+
185
+ # Check should never happen with content ids, but will check it anyway
170
186
  def repeat_id_validate(doc)
171
- @doc_ids = {}
187
+ repeat_id_validate_prep
172
188
  doc.xpath("//*[@id]").each do |x|
189
+ @doc_id_seq << x["id"]
173
190
  repeat_id_validate1(x)
191
+ x["anchor"] and repeat_anchor_validate1(x)
174
192
  end
193
+ @doc_id_seq_hash = @doc_id_seq.each_with_index
194
+ .with_object({}) do |(x, i), m|
195
+ m[x] = i
196
+ end
197
+ @doc_anchor_seq_hash = @doc_anchor_seq.each_with_index
198
+ .with_object({}) do |(x, i), m|
199
+ m[x] = i
200
+ end
201
+ end
202
+
203
+ def repeat_id_validate_prep
204
+ @doc_ids = {} # hash of all ids in document to line number, anchor
205
+ @doc_anchors = {} # hash of all anchors in document to line number, id
206
+ @doc_id_seq = [] # ordered list of all ids in document
207
+ @doc_anchor_seq = [] # ordered list of all anchors in document
175
208
  end
176
209
 
177
- # manually check for xref/@target, xref/@to integrity
210
+ # Retrieve anchors between two nominated values
211
+ # (exclusive of start_id AND exclusive of end_id)
212
+ def get_anchors_between(start_id, end_id)
213
+ start_index = @doc_anchor_seq_hash[start_id]
214
+ end_index = @doc_anchor_seq_hash[end_id]
215
+ start_index.nil? || end_index.nil? and return []
216
+ start_index >= end_index and return []
217
+ @doc_anchor_seq[start_index...end_index]
218
+ end
219
+
220
+ # manually check for xref/@target et sim. integrity
178
221
  def xref_validate(doc)
179
- @doc_xrefs = doc.xpath("//xref/@target | //xref/@to | //index/@to")
180
- .each_with_object({}) do |x, m|
181
- m[x.text] = x
182
- @doc_ids[x.text] and next
183
- @log.add("Anchors", x.parent,
184
- "Crossreference target #{x} is undefined", severity: 1)
222
+ xref_validate_exists(doc)
223
+ xref_range_record(doc)
224
+ end
225
+
226
+ def xref_validate_exists(doc)
227
+ @doc_xrefs = {}
228
+ Metanorma::Utils::anchor_attributes.each do |a|
229
+ doc.xpath("//#{a[0]}/@#{a[1]}").each do |x|
230
+ @doc_xrefs[x.text] = x.parent
231
+ @doc_anchors[x.text] and next
232
+ @log.add("Anchors", x.parent,
233
+ "Crossreference target #{x} is undefined", severity: 1)
234
+ end
235
+ end
236
+ end
237
+
238
+ # If there is an xref range, record the IDs between the two targets
239
+ def xref_range_record(doc)
240
+ doc.xpath("//xref//location[@connective = 'to']").each do |to|
241
+ process_range_location(to)
185
242
  end
186
243
  end
244
+
245
+ def process_range_location(to_location)
246
+ # Get the preceding location element if it exists
247
+ from = to_location.previous_element
248
+ from && from.name == "location" or return
249
+ from["target"] && to_location["target"] or return
250
+ get_anchors_between(from["target"], to_location["target"])
251
+ .each { |id| @doc_xrefs[id] = from }
252
+ end
187
253
  end
188
254
  end
189
255
  end
@@ -99,6 +99,8 @@ module Metanorma
99
99
  doc.xpath("//m:svg", "m" => SVG_NS).each { |n| n.replace("<svg/>") }
100
100
  doc
101
101
  end
102
+
103
+ include ::Metanorma::Standoc::Utils
102
104
  end
103
105
  end
104
106
  end
@@ -46,12 +46,11 @@ module Metanorma
46
46
 
47
47
  def hanging_para_style(root)
48
48
  root.xpath("//clause | //annex | //foreword | //introduction | " \
49
- "//acknowledgements").each do |c|
50
- next unless c.at("./clause")
51
- next if c.elements.reject do |n|
52
- %w(clause title).include? n.name
53
- end.empty?
54
-
49
+ "//acknowledgements | //executivesummary").each do |c|
50
+ c.at("./clause") or next
51
+ c.elements.reject do |n|
52
+ %w(clause title).include? n.name
53
+ end.empty? and next
55
54
  style_warning(c, "Hanging paragraph in clause")
56
55
  end
57
56
  end
@@ -16,7 +16,7 @@ module Metanorma
16
16
  def iev_validate(xmldoc)
17
17
  @iev = init_iev or return
18
18
  xmldoc.xpath("//term").each do |t|
19
- t.xpath(".//termsource").each do |src|
19
+ t.xpath("./source | ./preferred/source | ./admitted/source | ./deprecates/source | ./related/source").each do |src|
20
20
  (/^IEC[  ]60050-/.match(src.at("./origin/@citeas")&.text) &&
21
21
  loc = src.xpath(SOURCELOCALITY)&.text) or next
22
22
  iev_validate1(t, loc, xmldoc)
@@ -46,15 +46,16 @@ module Metanorma
46
46
 
47
47
  def concept_validate_ids(doc)
48
48
  @concept_ids ||= doc.xpath("//term | //definitions//dt")
49
- .each_with_object({}) { |x, m| m[x["id"]] = true }
49
+ .each_with_object({}) { |x, m| m[x["anchor"]] = true }
50
50
  @concept_terms_tags ||= doc.xpath("//terms")
51
- .each_with_object({}) { |t, m| m[t["id"]] = true }
51
+ .each_with_object({}) { |t, m| m[t["anchor"]] = true }
52
52
  nil
53
53
  end
54
54
 
55
55
  def concept_validate_msg(_doc, tag, refterm, xref)
56
+ t = @doc_ids.dig(xref["target"], :anchor) || xref["target"]
56
57
  ret = <<~LOG
57
- #{tag.capitalize} #{xref.at("../#{refterm}")&.text} is pointing to #{xref['target']}, which is not a term or symbol
58
+ #{tag.capitalize} #{xref.at("../#{refterm}")&.text} is pointing to #{t}, which is not a term or symbol
58
59
  LOG
59
60
  if @concept_terms_tags[xref["target"]]
60
61
  ret = ret.strip
@@ -79,7 +80,7 @@ module Metanorma
79
80
  def preferred_validate_report(terms)
80
81
  terms.each do |k, v|
81
82
  v.size > 1 or next
82
- loc = v.map { |x| x["id"] }.join(", ")
83
+ loc = v.map { |x| x["anchor"] }.join(", ")
83
84
  err = "Term #{k} occurs twice as preferred designation: #{loc}"
84
85
  @log.add("Terms", v.first, err, severity: 1)
85
86
  end
@@ -92,7 +93,7 @@ module Metanorma
92
93
  c = d.ancestors.detect do |x|
93
94
  section_containers.include?(x.name)
94
95
  end
95
- c["id"] ||= "_#{UUIDTools::UUID.random_create}"
96
+ c["id"] or add_id(c["id"])
96
97
  m[c["id"]] ||= { clause: c, designations: [] }
97
98
  m[c["id"]][:designations] << d
98
99
  end
@@ -106,7 +107,7 @@ module Metanorma
106
107
  end.join(", ")
107
108
  err = <<~ERROR
108
109
  Clause not recognised as a term clause, but contains designation markup
109
- (preferred:[], admitted:[], alt:[], deprecated:[]):<br/>
110
+ (<code>preferred:[], admitted:[], alt:[], deprecated:[]</code>):<br/>
110
111
  #{desgns}</br>
111
112
  Ensure the parent clause is recognised as a terms clause by inserting <code>[heading=terms and definitions]</code> above the title,
112
113
  in case the heading is not automatically recognised. See also <a href="https://www.metanorma.org/author/topics/sections/concepts/#clause-title">Metanorma documentation</a>.
@@ -19,6 +19,6 @@ module Metanorma
19
19
  end
20
20
 
21
21
  module Standoc
22
- VERSION = "3.0.6".freeze
22
+ VERSION = "3.0.8".freeze
23
23
  end
24
24
  end