metanorma-ietf 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.hound.yml +3 -0
- data/.oss-guides.rubocop.yml +1077 -0
- data/.rspec +2 -0
- data/.rubocop.ribose.yml +65 -0
- data/.rubocop.tb.yml +650 -0
- data/.rubocop.yml +15 -0
- data/.travis.yml +23 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/Guardfile +22 -0
- data/LICENSE +25 -0
- data/README.adoc +1660 -0
- data/Rakefile +6 -0
- data/bin/asciidoctor-rfc2 +14 -0
- data/bin/asciidoctor-rfc3 +14 -0
- data/bin/console +14 -0
- data/bin/rspec +17 -0
- data/bin/setup +8 -0
- data/docs/installation.md +21 -0
- data/docs/navigation.md +10 -0
- data/docs/overview.md +5 -0
- data/lib/asciidoctor/rfc.rb +8 -0
- data/lib/asciidoctor/rfc/common/base.rb +531 -0
- data/lib/asciidoctor/rfc/common/front.rb +120 -0
- data/lib/asciidoctor/rfc/v2/base.rb +379 -0
- data/lib/asciidoctor/rfc/v2/blocks.rb +261 -0
- data/lib/asciidoctor/rfc/v2/converter.rb +60 -0
- data/lib/asciidoctor/rfc/v2/front.rb +69 -0
- data/lib/asciidoctor/rfc/v2/inline_anchor.rb +111 -0
- data/lib/asciidoctor/rfc/v2/lists.rb +135 -0
- data/lib/asciidoctor/rfc/v2/table.rb +114 -0
- data/lib/asciidoctor/rfc/v2/validate.rb +32 -0
- data/lib/asciidoctor/rfc/v2/validate2.rng +716 -0
- data/lib/asciidoctor/rfc/v3/base.rb +329 -0
- data/lib/asciidoctor/rfc/v3/blocks.rb +246 -0
- data/lib/asciidoctor/rfc/v3/converter.rb +62 -0
- data/lib/asciidoctor/rfc/v3/front.rb +122 -0
- data/lib/asciidoctor/rfc/v3/inline_anchor.rb +89 -0
- data/lib/asciidoctor/rfc/v3/lists.rb +176 -0
- data/lib/asciidoctor/rfc/v3/svg.rng +9081 -0
- data/lib/asciidoctor/rfc/v3/table.rb +65 -0
- data/lib/asciidoctor/rfc/v3/validate.rb +34 -0
- data/lib/asciidoctor/rfc/v3/validate.rng +2143 -0
- data/lib/metanorma-ietf.rb +7 -0
- data/lib/metanorma/ietf.rb +8 -0
- data/lib/metanorma/ietf/processor.rb +89 -0
- data/lib/metanorma/ietf/version.rb +5 -0
- data/metanorma-ietf.gemspec +51 -0
- data/rfc2629-other.ent +61 -0
- data/rfc2629-xhtml.ent +165 -0
- data/rfc2629.dtd +312 -0
- metadata +289 -0
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "metanorma-ietf"
|
4
|
+
require "asciidoctor/cli"
|
5
|
+
|
6
|
+
options = Asciidoctor::Cli::Options.new backend: "rfc2", header_footer: true
|
7
|
+
# FIXME This is a really bizarre API. Please make me simpler.
|
8
|
+
|
9
|
+
exit 0 if options.parse!(ARGV) == 0
|
10
|
+
|
11
|
+
invoker = Asciidoctor::Cli::Invoker.new options
|
12
|
+
GC.start
|
13
|
+
invoker.invoke!
|
14
|
+
exit invoker.code
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "metanorma-ietf"
|
4
|
+
require "asciidoctor/cli"
|
5
|
+
|
6
|
+
options = Asciidoctor::Cli::Options.new backend: "rfc3", header_footer: true
|
7
|
+
# FIXME This is a really bizarre API. Please make me simpler.
|
8
|
+
|
9
|
+
exit 0 if options.parse!(ARGV) == 0
|
10
|
+
|
11
|
+
invoker = Asciidoctor::Cli::Invoker.new options
|
12
|
+
GC.start
|
13
|
+
invoker.invoke!
|
14
|
+
exit invoker.code
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "metanorma-ietf"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/rspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rspec' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require "pathname"
|
10
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path(
|
11
|
+
"../../Gemfile", Pathname.new(__FILE__).realpath
|
12
|
+
)
|
13
|
+
|
14
|
+
require "rubygems"
|
15
|
+
require "bundler/setup"
|
16
|
+
|
17
|
+
load Gem.bin_path("rspec-core", "rspec")
|
data/bin/setup
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
---
|
2
|
+
title: Installation
|
3
|
+
---
|
4
|
+
|
5
|
+
Add this line to your application’s Gemfile:
|
6
|
+
|
7
|
+
```
|
8
|
+
gem "metanorma-ietf"
|
9
|
+
```
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
```
|
14
|
+
$ bundle
|
15
|
+
```
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
```
|
20
|
+
$ gem install metanorma-cli
|
21
|
+
```
|
data/docs/navigation.md
ADDED
data/docs/overview.md
ADDED
@@ -0,0 +1,531 @@
|
|
1
|
+
require "date"
|
2
|
+
require "nokogiri"
|
3
|
+
require "htmlentities"
|
4
|
+
require "json"
|
5
|
+
require "pathname"
|
6
|
+
require "open-uri"
|
7
|
+
require "pp"
|
8
|
+
require "set"
|
9
|
+
|
10
|
+
module Asciidoctor
|
11
|
+
module Rfc::Common
|
12
|
+
module Base
|
13
|
+
def convert(node, transform = nil, opts = {})
|
14
|
+
transform ||= node.node_name
|
15
|
+
opts.empty? ? (send transform, node) : (send transform, node, opts)
|
16
|
+
end
|
17
|
+
|
18
|
+
def document_ns_attributes(_doc)
|
19
|
+
# ' xmlns="http://projectmallard.org/1.0/" xmlns:its="http://www.w3.org/2005/11/its"'
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def content(node)
|
24
|
+
node.content
|
25
|
+
end
|
26
|
+
|
27
|
+
def skip(node, name = nil)
|
28
|
+
warn %(asciidoctor: WARNING (#{current_location(node)}): converter missing for #{name || node.node_name} node in RFC backend)
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
# Syntax:
|
33
|
+
# = Title
|
34
|
+
# Author
|
35
|
+
# :HEADER
|
36
|
+
#
|
37
|
+
# ABSTRACT
|
38
|
+
#
|
39
|
+
# NOTE: note
|
40
|
+
#
|
41
|
+
# @note (boilerplate is ignored)
|
42
|
+
def preamble(node)
|
43
|
+
result = []
|
44
|
+
|
45
|
+
# NOTE: *list is V3, verse is V2, paragraph is both
|
46
|
+
abstractable_contexts = %i{paragraph dlist olist ulist verse open}
|
47
|
+
|
48
|
+
abstract_blocks = node.blocks.take_while do |block|
|
49
|
+
abstractable_contexts.include? block.context
|
50
|
+
end
|
51
|
+
|
52
|
+
remainder_blocks = node.blocks[abstract_blocks.length..-1]
|
53
|
+
|
54
|
+
result << noko do |xml|
|
55
|
+
if abstract_blocks.any?
|
56
|
+
xml.abstract do |xml_abstract|
|
57
|
+
xml_abstract << abstract_blocks.map(&:render).flatten.join("\n")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
xml << remainder_blocks.map(&:render).flatten.join("\n")
|
61
|
+
end
|
62
|
+
|
63
|
+
result << "</front><middle>"
|
64
|
+
result
|
65
|
+
end
|
66
|
+
|
67
|
+
IETF_AREAS = ["art", "Applications and Real-Time",
|
68
|
+
"gen", "General",
|
69
|
+
"int", "Internet",
|
70
|
+
"ops", "Operations and Management",
|
71
|
+
"rtg", "Routing",
|
72
|
+
"sec", "Security",
|
73
|
+
"tsv", "Transport"].freeze
|
74
|
+
|
75
|
+
# Syntax:
|
76
|
+
# = Title
|
77
|
+
# Author
|
78
|
+
# :area x, y
|
79
|
+
def area(node, xml)
|
80
|
+
node.attr("area")&.split(/, ?/)&.each do |ar|
|
81
|
+
if ar =~ / Area$/i
|
82
|
+
warn %(asciidoctor: WARNING (#{current_location(node)}): stripping suffix "Area" from area #{ar})
|
83
|
+
ar = ar.gsub(/ Area$/i, "")
|
84
|
+
end
|
85
|
+
warn %(asciidoctor: WARNING (#{current_location(node)}): unrecognised area #{ar}) unless IETF_AREAS.include?(ar)
|
86
|
+
xml.area { |a| a << ar }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Syntax:
|
91
|
+
# = Title
|
92
|
+
# Author
|
93
|
+
# :workgroup x, y
|
94
|
+
def workgroup(node, xml)
|
95
|
+
workgroups = cache_workgroup(node)
|
96
|
+
node.attr("workgroup")&.split(/, ?/)&.each do |wg|
|
97
|
+
if wg =~ / (Working Group)$/i
|
98
|
+
warn %(asciidoctor: WARNING (#{current_location(node)}): suffix "Working Group" will be stripped in published RFC from #{wg})
|
99
|
+
wg_norm = wg.gsub(/ Working Group$/i, "")
|
100
|
+
end
|
101
|
+
if wg =~ / (Research Group)$/i
|
102
|
+
warn %(asciidoctor: WARNING (#{current_location(node)}): suffix "Research Group" will be stripped from working group #{wg})
|
103
|
+
wg_norm = wg.gsub(/ Research Group$/i, "")
|
104
|
+
end
|
105
|
+
warn %(asciidoctor: WARNING (#{current_location(node)}): unrecognised working group #{wg}) unless workgroups.include?(wg_norm)
|
106
|
+
xml.workgroup { |w| w << wg }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Syntax:
|
111
|
+
# = Title
|
112
|
+
# Author
|
113
|
+
# :keyword x, y
|
114
|
+
def keyword(node, xml)
|
115
|
+
node.attr("keyword")&.split(/, ?/)&.each do |kw|
|
116
|
+
xml.keyword { |k| k << kw }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def paragraph1(node)
|
121
|
+
result = []
|
122
|
+
result1 = node.content
|
123
|
+
if result1 =~ /^(<t>|<dl>|<ol>|<ul>)/
|
124
|
+
result = result1
|
125
|
+
else
|
126
|
+
t_attributes = {
|
127
|
+
anchor: node.id,
|
128
|
+
}
|
129
|
+
result << noko { |xml| xml.t result1, **attr_code(t_attributes) }
|
130
|
+
end
|
131
|
+
result
|
132
|
+
end
|
133
|
+
|
134
|
+
def inline_indexterm(node)
|
135
|
+
# supports only primary and secondary terms
|
136
|
+
# primary attribute (highlighted major entry) not supported
|
137
|
+
if node.type == :visible
|
138
|
+
iref_attributes = {
|
139
|
+
item: node.text,
|
140
|
+
}
|
141
|
+
node.text + noko { |xml| xml.iref **attr_code(iref_attributes) }.join
|
142
|
+
else
|
143
|
+
terms = node.attr "terms"
|
144
|
+
warn %(asciidoctor: WARNING (#{current_location(node)}): only primary and secondary index terms supported: #{terms.join(': ')}) if terms.size > 2
|
145
|
+
iref_attributes = {
|
146
|
+
item: terms[0],
|
147
|
+
subitem: (terms.size > 1 ? terms[1] : nil),
|
148
|
+
}
|
149
|
+
noko { |xml| xml.iref **attr_code(iref_attributes) }.join
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# ulist repurposed as reference list
|
154
|
+
def reflist(node)
|
155
|
+
# ++++
|
156
|
+
# <xml>
|
157
|
+
# ++++
|
158
|
+
result = []
|
159
|
+
if node.context == :pass
|
160
|
+
node.lines.each do |item|
|
161
|
+
# undo XML substitution
|
162
|
+
ref = item.gsub(/\</, "<").gsub(/\>/, ">")
|
163
|
+
result << ref
|
164
|
+
end
|
165
|
+
else
|
166
|
+
warn %(asciidoctor: WARNING (#{current_location(node)}): references are not raw XML: #{node.context})
|
167
|
+
end
|
168
|
+
result
|
169
|
+
end
|
170
|
+
|
171
|
+
def open(node)
|
172
|
+
# open block is a container of multiple blocks, treated as a single block.
|
173
|
+
# We append each contained block to its parent
|
174
|
+
result = []
|
175
|
+
if node.role == "comment"
|
176
|
+
return noko do |xml|
|
177
|
+
xml.comment " " + [flatten_rawtext(node).map { |x| [x, ""] } ].flatten.join("\n") + " "
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
if node.blocks?
|
182
|
+
node.blocks.each do |b|
|
183
|
+
result << send(b.context, b)
|
184
|
+
end
|
185
|
+
else
|
186
|
+
result = paragraph(node)
|
187
|
+
end
|
188
|
+
result
|
189
|
+
end
|
190
|
+
|
191
|
+
# def dash(camel_cased_word)
|
192
|
+
# camel_cased_word.gsub(/([a-z])([A-Z])/, '\1-\2').downcase
|
193
|
+
# end
|
194
|
+
|
195
|
+
def common_rfc_pis(node)
|
196
|
+
# Below are generally applicable Processing Instructions (PIs)
|
197
|
+
# that most I-Ds might want to use, common to v2 and v3.
|
198
|
+
# These are set only if explicitly specified, with the exception
|
199
|
+
# of compact and subcompact
|
200
|
+
rfc_pis = {
|
201
|
+
artworkdelimiter: node.attr("artworkdelimiter"),
|
202
|
+
artworklines: node.attr("artworklines"),
|
203
|
+
authorship: node.attr("authorship"),
|
204
|
+
autobreaks: node.attr("autobreaks"),
|
205
|
+
background: node.attr("background"),
|
206
|
+
colonspace: node.attr("colonspace"),
|
207
|
+
comments: node.attr("comments"),
|
208
|
+
docmapping: node.attr("docmapping"),
|
209
|
+
editing: node.attr("editing"),
|
210
|
+
emoticonic: node.attr("emoticonic"),
|
211
|
+
footer: node.attr("footer"),
|
212
|
+
header: node.attr("header"),
|
213
|
+
inline: node.attr("inline"),
|
214
|
+
iprnotified: node.attr("iprnotified"),
|
215
|
+
linkmailto: node.attr("linkmailto"),
|
216
|
+
linefile: node.attr("linefile"),
|
217
|
+
notedraftinprogress: node.attr("notedraftinprogress"),
|
218
|
+
private: node.attr("private"),
|
219
|
+
refparent: node.attr("refparent"),
|
220
|
+
rfcedstyle: node.attr("rfcedstyle"),
|
221
|
+
slides: node.attr("slides"),
|
222
|
+
"text-list-symbols": node.attr("text-list-symbols"),
|
223
|
+
tocappendix: node.attr("tocappendix"),
|
224
|
+
tocindent: node.attr("tocindent"),
|
225
|
+
tocnarrow: node.attr("tocnarrow"),
|
226
|
+
tocompact: node.attr("tocompact"),
|
227
|
+
topblock: node.attr("topblock"),
|
228
|
+
useobject: node.attr("useobject"),
|
229
|
+
|
230
|
+
# give errors regarding ID-nits and DTD validation
|
231
|
+
strict: node.attr("strict") || "yes",
|
232
|
+
|
233
|
+
# Vertical whitespace control
|
234
|
+
# (using these PIs as follows is recommended by the RFC Editor)
|
235
|
+
|
236
|
+
# do not start each main section on a new page
|
237
|
+
compact: node.attr("compact") || "yes",
|
238
|
+
# keep one blank line between list items
|
239
|
+
subcompact: node.attr("subcompact") || "no",
|
240
|
+
|
241
|
+
# TOC control
|
242
|
+
# generate a ToC
|
243
|
+
toc: node.attr("toc-include") == "false" ? "no" : "yes",
|
244
|
+
|
245
|
+
# the number of levels of subsections in ToC. default: 3
|
246
|
+
tocdepth: node.attr("toc-depth") || "4",
|
247
|
+
|
248
|
+
# use anchors rather than numbers for references
|
249
|
+
symrefs: node.attr("sym-refs") || "yes",
|
250
|
+
# sort references
|
251
|
+
sortrefs: node.attr("sort-refs") || "yes",
|
252
|
+
}
|
253
|
+
|
254
|
+
attr_code(rfc_pis)
|
255
|
+
end
|
256
|
+
|
257
|
+
def set_pis(node, doc)
|
258
|
+
# Below are generally applicable Processing Instructions (PIs)
|
259
|
+
# that most I-Ds might want to use. (Here they are set differently than
|
260
|
+
# their defaults in xml2rfc v1.32)
|
261
|
+
|
262
|
+
if node.attr("rfc2629xslt") != "false"
|
263
|
+
pi = Nokogiri::XML::ProcessingInstruction.new(doc, "xml-stylesheet",
|
264
|
+
'type="text/xsl" href="rfc2629.xslt"')
|
265
|
+
doc.root.add_previous_sibling(pi)
|
266
|
+
end
|
267
|
+
|
268
|
+
doc.create_internal_subset("rfc", nil, "rfc2629.dtd")
|
269
|
+
rfc_pis = common_rfc_pis(node)
|
270
|
+
rfc_pis.each_pair do |k, v|
|
271
|
+
pi = Nokogiri::XML::ProcessingInstruction.new(doc,
|
272
|
+
"rfc",
|
273
|
+
"#{k}=\"#{v}\"")
|
274
|
+
doc.root.add_previous_sibling(pi)
|
275
|
+
end
|
276
|
+
|
277
|
+
doc
|
278
|
+
end
|
279
|
+
|
280
|
+
# extract references which can be expressed as externally defined entities
|
281
|
+
def extract_entities(node, xmldoc)
|
282
|
+
refs = xmldoc.xpath("//reference")
|
283
|
+
ret = []
|
284
|
+
biblio = cache_biblio(node)
|
285
|
+
refs.each do |ref|
|
286
|
+
next if ref.parent.name == "referencegroup"
|
287
|
+
id = ref.at('.//seriesInfo[@name="Internet-Draft"]')
|
288
|
+
anchor = ref["anchor"]
|
289
|
+
url = if id.nil?
|
290
|
+
biblio[anchor]
|
291
|
+
else
|
292
|
+
biblio["I-D.#{id['value']}"] # the specific version reference
|
293
|
+
end
|
294
|
+
if biblio.has_key? anchor
|
295
|
+
ret << { entity: anchor,
|
296
|
+
node: ref,
|
297
|
+
url: url }
|
298
|
+
end
|
299
|
+
end
|
300
|
+
ret
|
301
|
+
end
|
302
|
+
|
303
|
+
# if node contains blocks, flatten them into a single line
|
304
|
+
def flatten(node)
|
305
|
+
result = []
|
306
|
+
result << node.text if node.respond_to?(:text)
|
307
|
+
if node.blocks?
|
308
|
+
node.blocks.each { |b| result << flatten(b) }
|
309
|
+
else
|
310
|
+
result << node.content
|
311
|
+
end
|
312
|
+
result.reject(&:empty?)
|
313
|
+
end
|
314
|
+
|
315
|
+
# if node contains blocks, flatten them into a single line; and extract only raw text
|
316
|
+
def flatten_rawtext(node)
|
317
|
+
result = []
|
318
|
+
if node.respond_to?(:blocks) && node.blocks?
|
319
|
+
node.blocks.each { |b| result << flatten_rawtext(b) }
|
320
|
+
elsif node.respond_to?(:lines)
|
321
|
+
node.lines.each do |x|
|
322
|
+
result << if node.respond_to?(:context) && (node.context == :literal || node.context == :listing)
|
323
|
+
x.gsub(/</, "<").gsub(/>/, ">")
|
324
|
+
else
|
325
|
+
# strip not only HTML tags <tag>, but also Asciidoc crossreferences <<xref>>
|
326
|
+
x.gsub(/<[^>]*>+/, "")
|
327
|
+
end
|
328
|
+
end
|
329
|
+
elsif node.respond_to?(:text)
|
330
|
+
result << node.text.gsub(/<[^>]*>+/, "")
|
331
|
+
else
|
332
|
+
result << node.content.gsub(/<[^>]*>+/, "")
|
333
|
+
end
|
334
|
+
result.reject(&:empty?)
|
335
|
+
end
|
336
|
+
|
337
|
+
# block for processing XML document fragments as XHTML, to allow for HTMLentities
|
338
|
+
def noko(&block)
|
339
|
+
# fragment = ::Nokogiri::XML::DocumentFragment.parse("")
|
340
|
+
# fragment.doc.create_internal_subset("xml", nil, "xhtml.dtd")
|
341
|
+
head = <<HERE
|
342
|
+
<!DOCTYPE html SYSTEM
|
343
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
344
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
345
|
+
<head>
|
346
|
+
<title></title>
|
347
|
+
<meta charset="UTF-8" />
|
348
|
+
</head>
|
349
|
+
<body>
|
350
|
+
</body>
|
351
|
+
</html>
|
352
|
+
HERE
|
353
|
+
doc = ::Nokogiri::XML.parse(head)
|
354
|
+
fragment = doc.fragment("")
|
355
|
+
::Nokogiri::XML::Builder.with fragment, &block
|
356
|
+
fragment.to_xml(encoding: "US-ASCII").lines.map { |l| l.gsub(/\s*\n/, "") }
|
357
|
+
end
|
358
|
+
|
359
|
+
def attr_code(attributes)
|
360
|
+
attributes = attributes.reject { |_, val| val.nil? }.map
|
361
|
+
attributes.map do |k, v|
|
362
|
+
[k, (v.is_a? String) ? HTMLEntities.new.decode(v) : v]
|
363
|
+
end.to_h
|
364
|
+
end
|
365
|
+
|
366
|
+
def current_location(node)
|
367
|
+
return "Line #{node.lineno}" if node.respond_to?(:lineno) && !node.lineno.nil? && !node.lineno.empty?
|
368
|
+
return "ID #{node.id}" if node.respond_to?(:id) && !node.id.nil?
|
369
|
+
while !node.nil? && (!node.respond_to?(:level) || node.level > 0) && node.context != :section
|
370
|
+
node = node.parent
|
371
|
+
return "Section: #{node.title}" if !node.nil? && node.context == :section
|
372
|
+
end
|
373
|
+
"??"
|
374
|
+
end
|
375
|
+
|
376
|
+
def cache_workgroup(node)
|
377
|
+
wgcache_name = "#{Dir.home}/.asciidoc-rfc-workgroup-cache.json"
|
378
|
+
# If we are required to, clear the wg cache
|
379
|
+
if node.attr("flush-caches") == "true"
|
380
|
+
system("rm -f #{wgcache_name}")
|
381
|
+
end
|
382
|
+
# Is there already a wg cache? If not, create it.
|
383
|
+
wg = []
|
384
|
+
if Pathname.new(wgcache_name).file?
|
385
|
+
File.open(wgcache_name, "r") do |f|
|
386
|
+
wg = JSON.parse(f.read)
|
387
|
+
end
|
388
|
+
else
|
389
|
+
File.open(wgcache_name, "w") do |b|
|
390
|
+
STDERR.puts "Reading workgroups from https://tools.ietf.org/wg/..."
|
391
|
+
Kernel.open("https://tools.ietf.org/wg/") do |f|
|
392
|
+
f.each_line do |line|
|
393
|
+
line.scan(%r{<td width="50%" style='padding: 0 1ex'>([^<]+)</td>}) do |w|
|
394
|
+
wg << w[0].gsub(/\s+$/, "").gsub(/ Working Group$/, "")
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
STDERR.puts "Reading workgroups from https://irtf.org/groups..."
|
399
|
+
Kernel.open("https://irtf.org/groups", ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE) do |f|
|
400
|
+
f.each_line do |line|
|
401
|
+
line.scan(%r{<a title="([^"]+) Research Group"[^>]+>([^<]+)<}) do |w|
|
402
|
+
wg << w[0].gsub(/\s+$/, "")
|
403
|
+
wg << w[1].gsub(/\s+$/, "") # abbrev
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
b << wg.to_json
|
408
|
+
end
|
409
|
+
end
|
410
|
+
wg
|
411
|
+
end
|
412
|
+
|
413
|
+
def cache_biblio(node)
|
414
|
+
bibliocache_name = "#{Dir.home}/.asciidoc-rfc-biblio-cache.json"
|
415
|
+
# If we are required to, clear the biblio cache
|
416
|
+
if node.attr("flush-caches") == "true"
|
417
|
+
system("rm -f #{bibliocache_name}")
|
418
|
+
end
|
419
|
+
# Is there already a biblio cache? If not, create it.
|
420
|
+
biblio = {}
|
421
|
+
if Pathname.new(bibliocache_name).file?
|
422
|
+
File.open(bibliocache_name, "r") do |f|
|
423
|
+
biblio = JSON.parse(f.read)
|
424
|
+
end
|
425
|
+
else
|
426
|
+
File.open(bibliocache_name, "w") do |b|
|
427
|
+
STDERR.puts "Reading references from https://xml2rfc.tools.ietf.org/public/rfc/bibxml/..."
|
428
|
+
Kernel.open("https://xml2rfc.tools.ietf.org/public/rfc/bibxml/") do |f|
|
429
|
+
# I'm just working off the ls output
|
430
|
+
f.each_line do |line|
|
431
|
+
line.scan(/a href="reference.RFC.(\d+).xml">/) do |w|
|
432
|
+
biblio["RFC#{w[0]}"] = "https://xml2rfc.tools.ietf.org/public/rfc/bibxml/reference.RFC.#{w[0]}.xml"
|
433
|
+
end
|
434
|
+
end
|
435
|
+
["https://xml2rfc.tools.ietf.org/public/rfc/bibxml2/",
|
436
|
+
"https://xml2rfc.tools.ietf.org/public/rfc/bibxml3/",
|
437
|
+
"https://xml2rfc.tools.ietf.org/public/rfc/bibxml4/",
|
438
|
+
"https://xml2rfc.tools.ietf.org/public/rfc/bibxml5/"].each do |url|
|
439
|
+
STDERR.puts "Reading references from #{url}..."
|
440
|
+
Kernel.open(url) do |f1|
|
441
|
+
f1.each_line do |line|
|
442
|
+
line.scan(/a href="reference.(\S+).xml">/) do |w|
|
443
|
+
biblio[w[0]] = "#{url}/reference.#{w[0]}.xml"
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
449
|
+
b << biblio.to_json
|
450
|
+
end
|
451
|
+
end
|
452
|
+
biblio
|
453
|
+
end
|
454
|
+
|
455
|
+
# insert bibliography based on anchors, references directory, and list of normatives in doc attribute
|
456
|
+
def insert_biblio(node, xmldoc)
|
457
|
+
# we want no references in this document, so we can ignore any anchors of references
|
458
|
+
xmldoc.xpath("//referencegroup | //reference").each(&:remove)
|
459
|
+
refs = Set.new
|
460
|
+
xmldoc.xpath("//xref | //relref").each { |r| refs << r["target"] }
|
461
|
+
anchors1 = Set.new
|
462
|
+
# we have no references in this document, so any remaining anchors are internal cross-refs only
|
463
|
+
xmldoc.xpath("//@anchor").each { |r| anchors1 << r.value }
|
464
|
+
refs = refs - anchors1
|
465
|
+
anchors = {}
|
466
|
+
|
467
|
+
norm_refs_spec = Set.new(node.attr("normative").split(/,[ ]?/))
|
468
|
+
anchors[:norm] = refs.intersection(norm_refs_spec)
|
469
|
+
anchors[:info] = refs - anchors[:norm]
|
470
|
+
seen_refs = { norm: Set.new, info: Set.new }
|
471
|
+
refxml_in = { norm: {}, info: {} }
|
472
|
+
refxml_out = { norm: [], info: [] }
|
473
|
+
|
474
|
+
bibliodir = node.attr("biblio-dir")
|
475
|
+
Dir.foreach bibliodir do |f|
|
476
|
+
next if [".", ".."].include? f
|
477
|
+
text = File.read("#{bibliodir}/#{f}", encoding: "utf-8")
|
478
|
+
next unless text =~ /<reference/
|
479
|
+
text =~ /<reference[^>]*anchor=['"]([^'"]*)/
|
480
|
+
anchor = Regexp.last_match(1)
|
481
|
+
next if anchor.nil? || anchor.empty?
|
482
|
+
if anchors[:norm].include?(anchor)
|
483
|
+
refxml_in[:norm][anchor] = text
|
484
|
+
seen_refs[:norm] << anchor
|
485
|
+
else
|
486
|
+
refxml_in[:info][anchor] = text
|
487
|
+
seen_refs[:info] << anchor
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
biblio = cache_biblio(node)
|
492
|
+
[:norm, :info].each do |reftype|
|
493
|
+
anchors[reftype].each do |r|
|
494
|
+
if refxml_in[reftype].has_key?(r)
|
495
|
+
# priority to on-disk references over skeleton references: they may contain draft information
|
496
|
+
refxml_out[reftype] << refxml_in[reftype][r]
|
497
|
+
elsif biblio.has_key?(r)
|
498
|
+
refxml_out[reftype] << %{<reference anchor="#{r}"/>}
|
499
|
+
else
|
500
|
+
warn "Reference #{r} has not been includes in references directory, and is not a recognised external RFC reference"
|
501
|
+
end
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
xml_location = xmldoc.at('//references[@title="Normative References" or name="Normative References"]')
|
506
|
+
xml_location&.children = Nokogiri::XML.fragment(refxml_out[:norm].join)
|
507
|
+
xml_location = xmldoc.at('//references[@title="Informative References" or name="Informative References"]')
|
508
|
+
xml_location&.children = Nokogiri::XML.fragment(refxml_out[:info].join)
|
509
|
+
xmldoc
|
510
|
+
end
|
511
|
+
|
512
|
+
def smart_quote_cleanup(xmldoc)
|
513
|
+
# smart quotes: handle smart apostrophe
|
514
|
+
xmldoc.traverse do |node|
|
515
|
+
if node.text?
|
516
|
+
node.content = node.content.tr("\u2019", "'")
|
517
|
+
node.content = node.content.gsub(/\’/, "'")
|
518
|
+
node.content = node.content.gsub(/\’/, "'")
|
519
|
+
elsif node.element?
|
520
|
+
node.attributes.each do |k, v|
|
521
|
+
node.set_attribute(k, v.content.tr("\u2019", "'"))
|
522
|
+
node.set_attribute(k, v.content.gsub(/\’/, "'"))
|
523
|
+
node.set_attribute(k, v.content.gsub(/\’/, "'"))
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|
527
|
+
xmldoc
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
end
|