metanorma-ietf 2.3.4 → 2.4.1

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,164 +1,174 @@
1
- module IsoDoc::Ietf
2
- class RfcConvert < ::IsoDoc::Convert
3
- # TODO displayreference will be implemented as combination of autofetch and user-provided citations
4
-
5
- def bibliography(isoxml, out)
6
- isoxml.xpath(ns("//references/bibitem/docidentifier")).each do |i|
7
- i.children = docid_prefix(i["type"], i.text)
8
- end
9
- isoxml.xpath(ns("//bibliography/references | "\
10
- "//bibliography/clause[.//references] | "\
11
- "//annex/clause[.//references] | "\
12
- "//annex/references | "\
13
- "//sections/clause[.//references]")).each do |f|
14
- bibliography1(f, out)
1
+ module IsoDoc
2
+ module Ietf
3
+ class RfcConvert < ::IsoDoc::Convert
4
+ # TODO displayreference will be implemented as combination of autofetch and user-provided citations
5
+
6
+ def bibliography(isoxml, out)
7
+ isoxml.xpath(ns("//references/bibitem/docidentifier")).each do |i|
8
+ i.children = docid_prefix(i["type"], i.text)
9
+ end
10
+ isoxml.xpath(ns("//bibliography/references | "\
11
+ "//bibliography/clause[.//references] | "\
12
+ "//annex/clause[.//references] | "\
13
+ "//annex/references | "\
14
+ "//sections/clause[.//references]")).each do |f|
15
+ bibliography1(f, out)
16
+ end
15
17
  end
16
- end
17
18
 
18
- def bibliography1(f, out)
19
- out.references **attr_code(anchor: f["id"]) do |div|
20
- title = f.at(ns("./title")) and div.name do |name|
21
- title.children.each { |n| parse(n, name) }
19
+ def bibliography1(node, out)
20
+ out.references **attr_code(anchor: node["id"]) do |div|
21
+ title = node.at(ns("./title")) and div.name do |name|
22
+ title.children.each { |n| parse(n, name) }
23
+ end
24
+ node.elements.select do |e|
25
+ %w(references clause).include? e.name
26
+ end.each { |e| bibliography1(e, out) }
27
+ node.elements.reject do |e|
28
+ %w(references title bibitem note).include? e.name
29
+ end.each { |e| parse(e, div) }
30
+ biblio_list(node, div, true)
22
31
  end
23
- f.elements.select do |e|
24
- %w(references clause).include? e.name
25
- end.each { |e| bibliography1(e, out) }
26
- f.elements.reject do |e|
27
- %w(references title bibitem note).include? e.name
28
- end.each { |e| parse(e, div) }
29
- biblio_list(f, div, true)
30
32
  end
31
- end
32
33
 
33
- def biblio_list(f, div, biblio)
34
- i = 0
35
- f.xpath(ns("./bibitem | ./note")).each do |b|
36
- next if implicit_reference(b)
34
+ def biblio_list(node, div, biblio)
35
+ i = 0
36
+ node.xpath(ns("./bibitem | ./note")).each do |b|
37
+ next if implicit_reference(b)
37
38
 
38
- i += 1 if b.name == "bibitem"
39
- if b.name == "note" then note_parse(b, div)
40
- elsif is_ietf(b) then ietf_bibitem_entry(div, b, i)
41
- else
42
- nonstd_bibitem(div, b, i, biblio)
39
+ i += 1 if b.name == "bibitem"
40
+ if b.name == "note" then note_parse(b, div)
41
+ elsif ietf?(b) then ietf_bibitem_entry(div, b, i)
42
+ else
43
+ nonstd_bibitem(div, b, i, biblio)
44
+ end
43
45
  end
44
46
  end
45
- end
46
47
 
47
- def nonstd_bibitem(list, b, _ordinal, _bibliography)
48
- uris = b.xpath(ns("./uri"))
49
- target = nil
50
- uris&.each do |u|
51
- target = u.text if u["type"] == "src"
52
- end
53
- list.reference **attr_code(target: target,
54
- anchor: b["id"]) do |r|
55
- r.front do |f|
56
- relaton_to_title(b, f)
57
- relaton_to_author(b, f)
58
- relaton_to_date(b, f)
59
- relaton_to_keyword(b, f)
60
- relaton_to_abstract(b, f)
61
- end
62
- uris&.each do |u|
63
- r.format nil, **attr_code(target: u.text, type: u["type"])
64
- end
65
- docidentifiers = b.xpath(ns("./docidentifier"))
66
- id = render_identifier(bibitem_ref_code(b))
67
- !id[1].nil? and id[1] != "(NO ID)" and
68
- r.refcontent id[1]
69
- docidentifiers&.each do |u|
70
- if %w(DOI IETF).include? u["type"]
71
- r.seriesInfo nil, **attr_code(value: u.text.sub(/^DOI /, ""),
72
- name: u["type"])
48
+ def nonstd_bibitem(list, bib, _ordinal, _bibliography)
49
+ uris = bib.xpath(ns("./uri"))
50
+ target = nil
51
+ uris&.each { |u| target = u.text if u["type"] == "src" }
52
+ list.reference **attr_code(target: target,
53
+ anchor: bib["id"]) do |r|
54
+ nonstd_bibitem_front(r, bib)
55
+ uris&.each do |u|
56
+ r.format nil, **attr_code(target: u.text, type: u["type"])
57
+ end
58
+ docidentifiers = bib.xpath(ns("./docidentifier"))
59
+ id = render_identifier(bibitem_ref_code(bib))
60
+ !id[1].nil? && id[1] != "(NO ID)" and r.refcontent id[1]
61
+ docidentifiers&.each do |u|
62
+ if %w(DOI IETF).include? u["type"]
63
+ r.seriesInfo nil, **attr_code(value: u.text.sub(/^DOI /, ""),
64
+ name: u["type"])
65
+ end
73
66
  end
74
67
  end
75
68
  end
76
- end
77
69
 
78
- def relaton_to_title(b, f)
79
- title = b&.at(ns("./title")) || b&.at(ns("./formattedref")) or return
80
- f.title do |t|
81
- title.children.each { |n| parse(n, t) }
70
+ def nonstd_bibitem_front(ref, bib)
71
+ ref.front do |f|
72
+ relaton_to_title(bib, f)
73
+ relaton_to_author(bib, f)
74
+ relaton_to_date(bib, f)
75
+ relaton_to_keyword(bib, f)
76
+ relaton_to_abstract(bib, f)
77
+ end
82
78
  end
83
- end
84
79
 
85
- def relaton_to_author(b, f)
86
- auths = b.xpath(ns("./contributor[xmlns:role/@type = 'author' or "\
87
- "xmlns:role/@type = 'editor']"))
88
- auths.empty? and auths = b.xpath(ns("./contributor[xmlns:role/@type = "\
89
- "'publisher']"))
90
- auths.each do |a|
91
- role = a.at(ns("./role[@type = 'editor']")) ? "editor" : nil
92
- p = a&.at(ns("./person/name")) and
93
- relaton_person_to_author(p, role, f) or
94
- relaton_org_to_author(a&.at(ns("./organization")), role, f)
80
+ def relaton_to_title(bib, node)
81
+ title = bib&.at(ns("./title")) || bib&.at(ns("./formattedref")) or
82
+ return
83
+ node.title do |t|
84
+ title.children.each { |n| parse(n, t) }
85
+ end
95
86
  end
96
- end
97
87
 
98
- def relaton_person_to_author(p, role, f)
99
- fullname = p&.at(ns("./completename"))&.text
100
- surname = p&.at(ns("./surname"))&.text
101
- initials = p&.xpath(ns("./initial"))&.map { |i| i.text }&.join(" ") ||
102
- p&.xpath(ns("./forename"))&.map { |i| i.text[0] }&.join(" ")
103
- initials = nil if initials.empty?
104
- f.author nil,
105
- **attr_code(fullname: fullname, asciiFullname: fullname&.transliterate,
106
- role: role, surname: surname, initials: initials,
107
- asciiSurname: fullname ? surname&.transliterate : nil,
108
- asciiInitials: fullname ? initials&.transliterate : nil)
109
- end
88
+ def relaton_to_author(bib, node)
89
+ auths = bib.xpath(ns("./contributor[xmlns:role/@type = 'author' or "\
90
+ "xmlns:role/@type = 'editor']"))
91
+ auths.empty? and
92
+ auths = bib.xpath(ns("./contributor[xmlns:role/@type = "\
93
+ "'publisher']"))
94
+ auths.each do |a|
95
+ role = a.at(ns("./role[@type = 'editor']")) ? "editor" : nil
96
+ p = a&.at(ns("./person/name")) and
97
+ relaton_person_to_author(p, role, node) or
98
+ relaton_org_to_author(a&.at(ns("./organization")), role, node)
99
+ end
100
+ end
110
101
 
111
- def relaton_org_to_author(o, _role, f)
112
- name = o&.at(ns("./name"))&.text
113
- abbrev = o&.at(ns("./abbreviation"))&.text
114
- f.author do |_a|
115
- f.organization name, **attr_code(ascii: name&.transliterate,
116
- abbrev: abbrev)
102
+ def relaton_person_to_author(pers, role, node)
103
+ full = pers&.at(ns("./completename"))&.text
104
+ surname = pers&.at(ns("./surname"))&.text
105
+ initials = pers&.xpath(ns("./initial"))&.map do |i|
106
+ i.text
107
+ end&.join(" ") ||
108
+ pers&.xpath(ns("./forename"))&.map { |i| i.text[0] }&.join(" ")
109
+ initials = nil if initials.empty?
110
+ node.author nil, **attr_code(
111
+ fullname: full,
112
+ asciiFullname: full&.transliterate,
113
+ role: role, surname: surname,
114
+ initials: initials,
115
+ asciiSurname: full ? surname&.transliterate : nil,
116
+ asciiInitials: full ? initials&.transliterate : nil
117
+ )
117
118
  end
118
- end
119
119
 
120
- def relaton_to_date(b, f)
121
- date = b.at(ns("./date[@type = 'published']")) ||
122
- b.at(ns("./date[@type = 'issued']")) ||
123
- b.at(ns("./date[@type = 'circulated']"))
124
- return unless date
120
+ def relaton_org_to_author(org, _role, node)
121
+ name = org&.at(ns("./name"))&.text
122
+ abbrev = org&.at(ns("./abbreviation"))&.text
123
+ node.author do |_a|
124
+ node.organization name, **attr_code(ascii: name&.transliterate,
125
+ abbrev: abbrev)
126
+ end
127
+ end
125
128
 
126
- attr = date_attr(date&.at(ns("./on | ./from"))&.text) || return
127
- f.date **attr_code(attr)
128
- end
129
+ def relaton_to_date(bib, node)
130
+ date = bib.at(ns("./date[@type = 'published']")) ||
131
+ bib.at(ns("./date[@type = 'issued']")) ||
132
+ bib.at(ns("./date[@type = 'circulated']"))
133
+ return unless date
129
134
 
130
- def relaton_to_keyword(b, f)
131
- b.xpath(ns("./keyword")).each do |k|
132
- f.keyword do |keyword|
133
- k.children.each { |n| parse(n, keyword) }
135
+ attr = date_attr(date&.at(ns("./on | ./from"))&.text) || return
136
+ node.date **attr_code(attr)
137
+ end
138
+
139
+ def relaton_to_keyword(bib, node)
140
+ bib.xpath(ns("./keyword")).each do |k|
141
+ node.keyword do |keyword|
142
+ k.children.each { |n| parse(n, keyword) }
143
+ end
134
144
  end
135
145
  end
136
- end
137
146
 
138
- def relaton_to_abstract(b, f)
139
- b.xpath(ns("./abstract")).each do |k|
140
- f.abstract do |abstract|
141
- if k.at(ns("./p"))
142
- k.children.each { |n| parse(n, abstract) }
143
- else
144
- abstract.t do |t|
145
- k.children.each { |n| parse(n, t) }
147
+ def relaton_to_abstract(bib, node)
148
+ bib.xpath(ns("./abstract")).each do |k|
149
+ node.abstract do |abstract|
150
+ if k.at(ns("./p"))
151
+ k.children.each { |n| parse(n, abstract) }
152
+ else
153
+ abstract.t do |t|
154
+ k.children.each { |n| parse(n, t) }
155
+ end
146
156
  end
147
157
  end
148
158
  end
149
159
  end
150
- end
151
160
 
152
- def ietf_bibitem_entry(div, b, _i)
153
- url = b&.at(ns("./uri[@type = 'xml']"))&.text
154
- div << "<xi:include href='#{url}'/>"
155
- end
161
+ def ietf_bibitem_entry(div, bib, _idx)
162
+ url = bib&.at(ns("./uri[@type = 'xml']"))&.text
163
+ div << "<xi:include href='#{url}'/>"
164
+ end
156
165
 
157
- def is_ietf(b)
158
- return false if !@xinclude
166
+ def ietf?(bib)
167
+ return false if !@xinclude
159
168
 
160
- url = b.at(ns("./uri[@type = 'xml']")) or return false
161
- /xml2rfc\.tools\.ietf\.org/.match(url)
169
+ url = bib.at(ns("./uri[@type = 'xml']")) or return false
170
+ /xml2rfc\.tools\.ietf\.org/.match(url)
171
+ end
162
172
  end
163
173
  end
164
174
  end
@@ -15,7 +15,7 @@ require_relative "./init"
15
15
 
16
16
  module IsoDoc::Ietf
17
17
  class RfcConvert < ::IsoDoc::Convert
18
- def convert1(docxml, filename, dir)
18
+ def convert1(docxml, _filename, _dir)
19
19
  @xrefs.parse docxml
20
20
  info docxml, nil
21
21
  xml = noko do |xml|
@@ -51,6 +51,7 @@ module IsoDoc::Ietf
51
51
  def error_parse(node, out)
52
52
  case node.name
53
53
  when "bcp14" then bcp14_parse(node, out)
54
+ when "concept" then concept_parse(node, out)
54
55
  else
55
56
  text = node.to_xml.gsub(/</, "&lt;").gsub(/>/, "&gt;")
56
57
  out.t { |p| p << text }
@@ -59,6 +60,7 @@ module IsoDoc::Ietf
59
60
 
60
61
  def omit_docid_prefix(prefix)
61
62
  return true if prefix == "IETF"
63
+
62
64
  super
63
65
  end
64
66
 
@@ -67,9 +69,9 @@ module IsoDoc::Ietf
67
69
  end
68
70
 
69
71
  def postprocess(result, filename, _dir)
70
- result = from_xhtml(cleanup(to_xhtml(textcleanup(result)))).
71
- sub(/<!DOCTYPE[^>]+>\n/, "").
72
- sub(/(<rfc[^<]+? )lang="[^"]+"/, "\\1")
72
+ result = from_xhtml(cleanup(to_xhtml(textcleanup(result))))
73
+ .sub(/<!DOCTYPE[^>]+>\n/, "")
74
+ .sub(/(<rfc[^<]+? )lang="[^"]+"/, "\\1")
73
75
  File.open(filename, "w:UTF-8") { |f| f.write(result) }
74
76
  schema_validate(filename)
75
77
  @files_to_delete.each { |f| FileUtils.rm_rf f }
@@ -33,7 +33,7 @@ module IsoDoc::Ietf
33
33
  strict: node&.at(ns("//pi/strict"))&.text || "yes",
34
34
  compact: node&.at(ns("//pi/compact"))&.text || "yes",
35
35
  subcompact: node&.at(ns("//pi/subcompact"))&.text || "no",
36
- toc: node&.at(ns("//pi/toc-include"))&.text,
36
+ toc: node&.at(ns("//pi/tocinclude"))&.text,
37
37
  tocdepth: node&.at(ns("//pi/toc-depth"))&.text || "4",
38
38
  symrefs: node&.at(ns("//pi/sym-refs"))&.text || "yes",
39
39
  sortrefs: node&.at(ns("//pi/sort-refs"))&.text || "yes",
@@ -1,6 +1,5 @@
1
1
  module IsoDoc::Ietf
2
2
  class RfcConvert < ::IsoDoc::Convert
3
-
4
3
  def definition_parse(node, out)
5
4
  node.children.each { |n| parse(n, out) }
6
5
  end
@@ -46,7 +45,19 @@ module IsoDoc::Ietf
46
45
  clause_parse(node, out)
47
46
  end
48
47
 
49
- def termdocsource_parse(_node, _out)
48
+ def termdocsource_parse(_node, _out); end
49
+
50
+ def concept_parse(node, out)
51
+ if d = node.at(ns("./renderterm"))
52
+ out.em do |em|
53
+ d.children.each { |n| parse(n, em) }
54
+ end
55
+ out << " "
56
+ end
57
+ out << "[term defined in "
58
+ r = node.at(ns("./xref | ./eref | ./termref"))
59
+ parse(r, out)
60
+ out << "]"
50
61
  end
51
62
  end
52
63
  end
@@ -1,191 +1,192 @@
1
1
  require "jing"
2
2
  require "fileutils"
3
3
 
4
- module IsoDoc::Ietf
5
- class RfcConvert < ::IsoDoc::Convert
6
- def schema_validate(filename)
7
- begin
8
- errors = Jing.new(File.join(File.dirname(__FILE__), "v3.rng")).
9
- validate(filename)
4
+ module IsoDoc
5
+ module Ietf
6
+ class RfcConvert < ::IsoDoc::Convert
7
+ def schema_validate(filename)
8
+ errors = Jing.new(File.join(File.dirname(__FILE__), "v3.rng"))
9
+ .validate(filename)
10
10
  errors.each do |error|
11
- warn "RFC XML: Line #{"%06d" % error[:line]}:#{error[:column]} "\
12
- "#{error[:message]}"
11
+ warn "RFC XML: Line #{'%06d' % error[:line]}:#{error[:column]} "\
12
+ "#{error[:message]}"
13
13
  end
14
14
  rescue Jing::Error => e
15
15
  abort "Jing failed with error: #{e}"
16
16
  end
17
- end
18
17
 
19
- def content_validate(xml, filename)
20
- err = []
21
- err += numbered_sections_check(xml)
22
- err += toc_sections_check(xml)
23
- err += references_check(xml)
24
- err += xref_check(xml)
25
- err += metadata_check(xml)
26
- return if err.empty?
27
- FileUtils.mv(filename, "#{filename}.err")
28
- err.each { |e| warn "RFC XML: #{e}" }
29
- warn "Cannot continue processing"
30
- end
18
+ def content_validate(xml, filename)
19
+ err = []
20
+ err += numbered_sections_check(xml)
21
+ err += toc_sections_check(xml)
22
+ err += references_check(xml)
23
+ err += xref_check(xml)
24
+ err += metadata_check(xml)
25
+ return if err.empty?
31
26
 
32
- def label(sect)
33
- ret = sect&.at("./name")&.text ||
34
- sect["name"] || sect["anchor"]
35
- end
27
+ FileUtils.mv(filename, "#{filename}.err")
28
+ err.each { |e| warn "RFC XML: #{e}" }
29
+ warn "Cannot continue processing"
30
+ end
36
31
 
37
- # 2.46.2. "numbered" Attribute
38
- def numbered_sections_check(xml)
39
- ret = []
40
- xml.xpath("//section[@numbered = 'false']").each do |s1|
41
- s1.xpath("./section[not(@numbered) or @numbered = 'true']").
42
- each do |s2|
43
- ret << "Numbered section #{label(s2)} under unnumbered section "\
44
- "#{label(s1)}"
45
- end
46
- s1.xpath("./following-sibling::*[name() = 'section']"\
47
- "[not(@numbered) or @numbered = 'true']").each do |s2|
48
- ret << "Numbered section #{label(s2)} following unnumbered section "\
49
- "#{label(s1)}"
50
- end
32
+ def label(sect)
33
+ sect&.at("./name")&.text ||
34
+ sect["name"] || sect["anchor"]
51
35
  end
52
- ret
53
- end
54
36
 
55
- # 5.2.7. Section "toc" attribute
56
- def toc_sections_check(xml)
57
- ret = []
58
- xml.xpath("//section[@toc = 'exclude']").each do |s1|
59
- s1.xpath(".//section[@toc = 'include']").each do |s2|
60
- ret << "Section #{label(s2)} with toc=include is included in "\
61
- "section #{label(s1)} with toc=exclude"
37
+ # 2.46.2. "numbered" Attribute
38
+ def numbered_sections_check(xml)
39
+ ret = []
40
+ xml.xpath("//section[@numbered = 'false']").each do |s1|
41
+ s1.xpath("./section[not(@numbered) or @numbered = 'true']")
42
+ .each do |s2|
43
+ ret << "Numbered section #{label(s2)} under unnumbered section "\
44
+ "#{label(s1)}"
45
+ end
46
+ s1.xpath("./following-sibling::*[name() = 'section']"\
47
+ "[not(@numbered) or @numbered = 'true']").each do |s2|
48
+ ret << "Numbered section #{label(s2)} following unnumbered "\
49
+ "section #{label(s1)}"
50
+ end
62
51
  end
52
+ ret
63
53
  end
64
- ret
65
- end
66
54
 
67
- # 5.4.3 <reference> "target" Insertion
68
- # 5.4.2.4 "Table of Contents" Insertion
69
- def references_check(xml)
70
- ret = []
71
- xml.xpath("//reference[not(@target)]").each do |s|
72
- s.xpath(".//seriesInfo[@name = 'RFC' or @name = 'Internet-Draft' "\
73
- "or @name = 'DOI'][not(@value)]").each do |s1|
74
- ret << "for reference #{s['anchor']}, the seriesInfo with "\
75
- "name=#{s1['name']} has been given no value"
55
+ # 5.2.7. Section "toc" attribute
56
+ def toc_sections_check(xml)
57
+ ret = []
58
+ xml.xpath("//section[@toc = 'exclude']").each do |s1|
59
+ s1.xpath(".//section[@toc = 'include']").each do |s2|
60
+ ret << "Section #{label(s2)} with toc=include is included in "\
61
+ "section #{label(s1)} with toc=exclude"
62
+ end
76
63
  end
64
+ ret
77
65
  end
78
- xml.xpath("//references | //section").each do |s|
79
- s.at("./name") or ret << "Cannot generate table of contents entry "\
80
- "for #{label(s)}, as it has no title"
81
- end
82
- ret
83
- end
84
66
 
85
- # 5.4.8.2. "derivedContent" Insertion (without Content)
86
- def xref_check(xml)
87
- ret = []
88
- xml.xpath("//xref | //relref").each do |x|
89
- t = xml.at(".//*[@anchor = '#{x['target']}']") ||
90
- xml.at(".//*[@pn = '#{x['target']}']") or
91
- ret << "#{x.name} target #{x['target']} does not exist in the document"
92
- next unless t
93
- x.delete("relative") if x["relative"]&.empty?
94
- x.delete("section") if x["section"]&.empty?
95
- if x["format"] == "title" && t.name == "reference"
96
- t.at("./front/title") or
97
- ret << "reference #{t['anchor']} has been referenced by #{x.name} "\
98
- "with format=title, but the reference has no title"
99
- end
100
- if x["format"] == "counter" && !%w(section table figure li
101
- reference references t dt).include?(t.name)
102
- ret << "#{x.to_xml} with format=counter is only allowed for "\
103
- "clauses, tables, figures, list entries, definition terms, "\
104
- "paragraphs, bibliographies, and bibliographic entries"
105
- end
106
- if x["format"] == "counter" && t.name == "reference" && !x["section"]
107
- ret << "reference #{t['anchor']} has been referenced by xref "\
108
- "#{x.to_xml} with format=counter, which requires a "\
109
- "section attribute"
110
- end
111
- if x["format"] == "counter" && t.name == "li" && t.parent.name != "ol"
112
- ret << "#{x.to_xml} with format=counter refers to an unnumbered "\
113
- "list entry"
114
- end
115
- if x["format"] == "title" && %w(u author contact).include?(t.name)
116
- ret << "#{x.to_xml} with format=title cannot reference a "\
117
- "<#{t.name}> element"
118
- end
119
- if x["relative"] && !x["section"]
120
- ret << "#{x.to_xml} with relative attribute requires a section "\
121
- "attribute"
122
- end
123
- if (x["section"]) && t.name != "reference"
124
- ret << "#{x.to_xml} has a section attribute, but #{x['target']} "\
125
- "points to a #{t.name}"
67
+ # 5.4.3 <reference> "target" Insertion
68
+ # 5.4.2.4 "Table of Contents" Insertion
69
+ def references_check(xml)
70
+ ret = []
71
+ xml.xpath("//reference[not(@target)]").each do |s|
72
+ s.xpath(".//seriesInfo[@name = 'RFC' or @name = 'Internet-Draft' "\
73
+ "or @name = 'DOI'][not(@value)]").each do |s1|
74
+ ret << "for reference #{s['anchor']}, the seriesInfo with "\
75
+ "name=#{s1['name']} has been given no value"
76
+ end
126
77
  end
127
- if (x["relative"]) && t.name != "reference"
128
- ret << "#{x.to_xml} has a relative attribute, but #{x['target']} "\
129
- "points to a #{t.name}"
78
+ xml.xpath("//references | //section").each do |s|
79
+ s.at("./name") or ret << "Cannot generate table of contents entry "\
80
+ "for #{label(s)}, as it has no title"
130
81
  end
131
- if !x["relative"] && x["section"]
132
- unless t.at(".//seriesInfo[@name = 'RFC' or @name = "\
133
- "'Internet-Draft']")
82
+ ret
83
+ end
84
+
85
+ # 5.4.8.2. "derivedContent" Insertion (without Content)
86
+ def xref_check(xml)
87
+ ret = []
88
+ xml.xpath("//xref | //relref").each do |x|
89
+ t = xml.at(".//*[@anchor = '#{x['target']}']") ||
90
+ xml.at(".//*[@pn = '#{x['target']}']") or
91
+ ret << "#{x.name} target #{x['target']} does not exist in the document"
92
+ next unless t
93
+
94
+ x.delete("relative") if x["relative"] && x["relative"].empty?
95
+ x.delete("section") if x["section"] && x["section"].empty?
96
+ if x["format"] == "title" && t.name == "reference"
97
+ t.at("./front/title") or
98
+ ret << "reference #{t['anchor']} has been referenced by #{x.name} "\
99
+ "with format=title, but the reference has no title"
100
+ end
101
+ if x["format"] == "counter" && !%w(section table figure li
102
+ reference references t dt).include?(t.name)
103
+ ret << "#{x.to_xml} with format=counter is only allowed for "\
104
+ "clauses, tables, figures, list entries, definition terms, "\
105
+ "paragraphs, bibliographies, and bibliographic entries"
106
+ end
107
+ if x["format"] == "counter" && t.name == "reference" && !x["section"]
108
+ ret << "reference #{t['anchor']} has been referenced by xref "\
109
+ "#{x.to_xml} with format=counter, which requires a "\
110
+ "section attribute"
111
+ end
112
+ if x["format"] == "counter" && t.name == "li" && t.parent.name != "ol"
113
+ ret << "#{x.to_xml} with format=counter refers to an unnumbered "\
114
+ "list entry"
115
+ end
116
+ if x["format"] == "title" && %w(u author contact).include?(t.name)
117
+ ret << "#{x.to_xml} with format=title cannot reference a "\
118
+ "<#{t.name}> element"
119
+ end
120
+ if x["relative"] && !x["section"]
121
+ ret << "#{x.to_xml} with relative attribute requires a section "\
122
+ "attribute"
123
+ end
124
+ if (x["section"]) && t.name != "reference"
125
+ ret << "#{x.to_xml} has a section attribute, but #{x['target']} "\
126
+ "points to a #{t.name}"
127
+ end
128
+ if (x["relative"]) && t.name != "reference"
129
+ ret << "#{x.to_xml} has a relative attribute, but #{x['target']} "\
130
+ "points to a #{t.name}"
131
+ end
132
+ if !x["relative"] && x["section"] && !t.at(".//seriesInfo[@name = 'RFC' or @name = "\
133
+ "'Internet-Draft']")
134
134
  ret << "#{x.to_xml} must use a relative attribute, "\
135
- "since it does not point to a RFC or Internet-Draft reference"
135
+ "since it does not point to a RFC or Internet-Draft reference"
136
136
  end
137
- end
138
- if x["relative"]
139
- unless t.at(".//seriesInfo[@name = 'RFC' or @name = "\
140
- "'Internet-Draft']") || t["target"]
137
+ if x["relative"] && !(t.at(".//seriesInfo[@name = 'RFC' or @name = "\
138
+ "'Internet-Draft']") || t["target"])
141
139
  ret << "need an explicit target= URL attribute in the reference "\
142
- "pointed to by #{x.to_xml}"
140
+ "pointed to by #{x.to_xml}"
143
141
  end
144
142
  end
143
+ ret
145
144
  end
146
- ret
147
- end
148
145
 
149
- def metadata_check(xml)
150
- ret = []
151
- ret += link_check(xml)
152
- ret += seriesInfo_check(xml)
153
- ret += ipr_check(xml)
154
- ret
155
- end
146
+ def metadata_check(xml)
147
+ ret = []
148
+ ret += link_check(xml)
149
+ ret += seriesInfo_check(xml)
150
+ ret += ipr_check(xml)
151
+ ret
152
+ end
156
153
 
157
- # 5.6.3. <link> Processing
158
- def link_check(xml)
159
- l = xml&.at("//link[@rel = 'convertedFrom']")&.text
160
- !l || %r{^https://datatracker\.ietf\.org/doc/draft-}.match(l) or
161
- return ["<link rel='convertedFrom'> (:derived-from: document "\
162
- "attribute) must start with "\
163
- "https://datatracker.ietf.org/doc/draft-"]
164
- []
165
- end
154
+ # 5.6.3. <link> Processing
155
+ def link_check(xml)
156
+ l = xml&.at("//link[@rel = 'convertedFrom']")&.text
157
+ !l || %r{^https://datatracker\.ietf\.org/doc/draft-}.match(l) or
158
+ return ["<link rel='convertedFrom'> (:derived-from: document "\
159
+ "attribute) must start with "\
160
+ "https://datatracker.ietf.org/doc/draft-"]
161
+ []
162
+ end
166
163
 
167
- # 5.2.2. "seriesInfo" Insertion
168
- def seriesInfo_check(xml)
169
- ret = []
170
- xml.root["ipr"] == "none" and return []
171
- rfcinfo = xml.at("//seriesInfo[@name = 'RFC']")
172
- rfcnumber = xml.root["number"]
173
- rfcinfo && rfcnumber && rfcnumber != rfcinfo["value"] and
174
- ret << "Mismatch between <rfc number='#{rfcnumber}'> (:docnumber: NUMBER) "\
175
- "and <seriesInfo name='RFC' value='#{rfcinfo['value']}'> "\
176
- "(:intended-series: TYPE NUMBER)"
177
- rfcinfo && !/^\d+$/.match(rfcnumber) and
178
- ret << "RFC identifier <rfc number='#{rfcnumber}'> (:docnumber: NUMBER) "\
179
- "must be a number"
180
- ret
181
- end
164
+ # 5.2.2. "seriesInfo" Insertion
165
+ def seriesInfo_check(xml)
166
+ ret = []
167
+ xml.root["ipr"] == "none" and return []
168
+ rfcinfo = xml.at("//seriesInfo[@name = 'RFC']")
169
+ rfcnumber = xml.root["number"]
170
+ rfcinfo && rfcnumber && rfcnumber != rfcinfo["value"] and
171
+ ret << "Mismatch between <rfc number='#{rfcnumber}'> "\
172
+ "(:docnumber: NUMBER) "\
173
+ "and <seriesInfo name='RFC' value='#{rfcinfo['value']}'> "\
174
+ "(:intended-series: TYPE NUMBER)"
175
+ rfcinfo && !/^\d+$/.match(rfcnumber) and
176
+ ret << "RFC identifier <rfc number='#{rfcnumber}'> "\
177
+ "(:docnumber: NUMBER) must be a number"
178
+ ret
179
+ end
182
180
 
183
- # 5.4.2.3. "Copyright Notice" Insertion
184
- def ipr_check(xml)
185
- xml.root["ipr"] or return ["Missing ipr attribute on <rfc> element (:ipr:)"]
186
- /trust200902$/.match(xml.root["ipr"]) or
187
- return ["Unknown ipr attribute on <rfc> element (:ipr:): #{xml.root['ipr']}"]
188
- []
181
+ # 5.4.2.3. "Copyright Notice" Insertion
182
+ def ipr_check(xml)
183
+ xml.root["ipr"] or
184
+ return ["Missing ipr attribute on <rfc> element (:ipr:)"]
185
+ /trust200902$/.match(xml.root["ipr"]) or
186
+ return ["Unknown ipr attribute on <rfc> element (:ipr:): "\
187
+ "#{xml.root['ipr']}"]
188
+ []
189
+ end
189
190
  end
190
191
  end
191
192
  end