metanorma-nist 0.0.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.
- checksums.yaml +7 -0
- data/.hound.yml +3 -0
- data/.rubocop.yml +10 -0
- data/.travis.yml +16 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE +25 -0
- data/README.adoc +358 -0
- data/bin/console +14 -0
- data/bin/rspec +18 -0
- data/bin/setup +8 -0
- data/lib/asciidoctor/nist.rb +7 -0
- data/lib/asciidoctor/nist/biblio.rng +921 -0
- data/lib/asciidoctor/nist/converter.rb +271 -0
- data/lib/asciidoctor/nist/front.rb +150 -0
- data/lib/asciidoctor/nist/isodoc.rng +1063 -0
- data/lib/asciidoctor/nist/isostandard.rng +1071 -0
- data/lib/asciidoctor/nist/nist.rng +196 -0
- data/lib/isodoc/nist/html/commerce-logo-color.png +0 -0
- data/lib/isodoc/nist/html/deptofcommerce.png +0 -0
- data/lib/isodoc/nist/html/header.html +163 -0
- data/lib/isodoc/nist/html/html_nist_intro.html +46 -0
- data/lib/isodoc/nist/html/html_nist_titlepage.html +140 -0
- data/lib/isodoc/nist/html/htmlstyle.scss +1160 -0
- data/lib/isodoc/nist/html/logo.png +0 -0
- data/lib/isodoc/nist/html/nist.scss +749 -0
- data/lib/isodoc/nist/html/scripts.html +82 -0
- data/lib/isodoc/nist/html/scripts.pdf.html +70 -0
- data/lib/isodoc/nist/html/word_nist_intro.html +142 -0
- data/lib/isodoc/nist/html/word_nist_titlepage.html +247 -0
- data/lib/isodoc/nist/html/wordstyle.scss +1134 -0
- data/lib/isodoc/nist/html_convert.rb +454 -0
- data/lib/isodoc/nist/i18n-en.yaml +3 -0
- data/lib/isodoc/nist/metadata.rb +116 -0
- data/lib/isodoc/nist/pdf_convert.rb +456 -0
- data/lib/isodoc/nist/word_convert.rb +472 -0
- data/lib/metanorma-nist.rb +11 -0
- data/lib/metanorma/nist.rb +7 -0
- data/lib/metanorma/nist/processor.rb +43 -0
- data/lib/metanorma/nist/version.rb +5 -0
- data/metanorma-nist.gemspec +44 -0
- metadata +310 -0
@@ -0,0 +1,454 @@
|
|
1
|
+
require "isodoc"
|
2
|
+
require_relative "metadata"
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
module IsoDoc
|
6
|
+
module NIST
|
7
|
+
# A {Converter} implementation that generates HTML output, and a document
|
8
|
+
# schema encapsulation of the document for validation
|
9
|
+
class HtmlConvert < IsoDoc::HtmlConvert
|
10
|
+
def initialize(options)
|
11
|
+
@libdir = File.dirname(__FILE__)
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def convert1(docxml, filename, dir)
|
16
|
+
FileUtils.cp html_doc_path('logo.png'), "#{@localdir}/logo.png"
|
17
|
+
FileUtils.cp html_doc_path('commerce-logo-color.png'), "#{@localdir}/commerce-logo-color.png"
|
18
|
+
@files_to_delete << "#{@localdir}/logo.png"
|
19
|
+
@files_to_delete << "#{@localdir}/commerce-logo-color.png"
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def default_fonts(options)
|
24
|
+
{
|
25
|
+
bodyfont: (options[:script] == "Hans" ? '"SimSun",serif' : '"Libre Baskerville",serif'),
|
26
|
+
headerfont: (options[:script] == "Hans" ? '"SimHei",sans-serif' : '"Libre Baskerville",serif'),
|
27
|
+
monospacefont: '"Space Mono",monospace'
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def default_file_locations(_options)
|
32
|
+
{
|
33
|
+
htmlstylesheet: html_doc_path("htmlstyle.scss"),
|
34
|
+
htmlcoverpage: html_doc_path("html_nist_titlepage.html"),
|
35
|
+
htmlintropage: html_doc_path("html_nist_intro.html"),
|
36
|
+
scripts: html_doc_path("scripts.html"),
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def metadata_init(lang, script, labels)
|
41
|
+
@meta = Metadata.new(lang, script, labels)
|
42
|
+
end
|
43
|
+
|
44
|
+
def html_head
|
45
|
+
<<~HEAD.freeze
|
46
|
+
<title>{{ doctitle }}</title>
|
47
|
+
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
48
|
+
|
49
|
+
<!--TOC script import-->
|
50
|
+
<script type="text/javascript" src="https://cdn.rawgit.com/jgallen23/toc/0.3.2/dist/toc.min.js"></script>
|
51
|
+
|
52
|
+
<!--Google fonts-->
|
53
|
+
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,600,600i" rel="stylesheet">
|
54
|
+
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i|Space+Mono:400,700" rel="stylesheet" />
|
55
|
+
<link href="https://fonts.googleapis.com/css?family=Libre+Baskerville:400,400i,700,700i" rel="stylesheet">
|
56
|
+
|
57
|
+
<!--Font awesome import for the link icon-->
|
58
|
+
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.8/css/solid.css" integrity="sha384-v2Tw72dyUXeU3y4aM2Y0tBJQkGfplr39mxZqlTBDUZAb9BGoC40+rdFCG0m10lXk" crossorigin="anonymous">
|
59
|
+
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.8/css/fontawesome.css" integrity="sha384-q3jl8XQu1OpdLgGFvNRnPdj5VIlCvgsDQTQB6owSOHWlAurxul7f+JpUOVdAiJ5P" crossorigin="anonymous">
|
60
|
+
<style class="anchorjs"></style>
|
61
|
+
HEAD
|
62
|
+
end
|
63
|
+
|
64
|
+
def make_body(xml, docxml)
|
65
|
+
body_attr = { lang: "EN-US", link: "blue", vlink: "#954F72", "xml:lang": "EN-US", class: "container" }
|
66
|
+
xml.body **body_attr do |body|
|
67
|
+
make_body1(body, docxml)
|
68
|
+
make_body2(body, docxml)
|
69
|
+
make_body3(body, docxml)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def html_toc(docxml)
|
74
|
+
docxml
|
75
|
+
end
|
76
|
+
|
77
|
+
def cleanup(docxml)
|
78
|
+
super
|
79
|
+
term_cleanup(docxml)
|
80
|
+
requirement_cleanup(docxml)
|
81
|
+
end
|
82
|
+
|
83
|
+
def make_body3(body, docxml)
|
84
|
+
body.div **{ class: "main-section" } do |div3|
|
85
|
+
abstract docxml, div3
|
86
|
+
keywords docxml, div3
|
87
|
+
preface docxml, div3
|
88
|
+
middle docxml, div3
|
89
|
+
footnotes div3
|
90
|
+
comments div3
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def abstract(isoxml, out)
|
95
|
+
f = isoxml.at(ns("//preface/abstract")) || return
|
96
|
+
page_break(out)
|
97
|
+
out.div **attr_code(id: f["id"]) do |s|
|
98
|
+
clause_name(nil, @abstract_lbl, s, class: "AbstractTitle")
|
99
|
+
f.elements.each { |e| parse(e, s) unless e.name == "title" }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def keywords(_docxml, out)
|
104
|
+
kw = @meta.get[:keywords]
|
105
|
+
kw.empty? and return
|
106
|
+
out.div **{ class: "Section3" } do |div|
|
107
|
+
clause_name(nil, "Keywords", div, class: "IntroTitle")
|
108
|
+
div.p kw.sort.join("; ")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
FRONT_CLAUSE = "//*[parent::preface][not(local-name() = 'abstract')]".freeze
|
113
|
+
|
114
|
+
def preface(isoxml, out)
|
115
|
+
isoxml.xpath(ns(FRONT_CLAUSE)).each do |c|
|
116
|
+
foreword(isoxml, out) and next if c.name == "foreword"
|
117
|
+
next if skip_render(c, isoxml)
|
118
|
+
out.div **attr_code(id: c["id"]) do |s|
|
119
|
+
clause_name(get_anchors[c['id']][:label],
|
120
|
+
c&.at(ns("./title"))&.content, s, nil)
|
121
|
+
c.elements.reject { |c1| c1.name == "title" }.each do |c1|
|
122
|
+
parse(c1, s)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def skip_render(c, isoxml)
|
129
|
+
return false unless c.name == "reviewernote"
|
130
|
+
status = isoxml&.at(ns("//bibdata/status"))&.text
|
131
|
+
return true if status.nil?
|
132
|
+
return ["published", "withdrawn"].include? status
|
133
|
+
end
|
134
|
+
|
135
|
+
def term_defs_boilerplate(div, source, term, preface)
|
136
|
+
if source.empty? && term.nil?
|
137
|
+
div << @no_terms_boilerplate
|
138
|
+
else
|
139
|
+
div << term_defs_boilerplate_cont(source, term)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def i18n_init(lang, script)
|
144
|
+
super
|
145
|
+
end
|
146
|
+
|
147
|
+
def fileloc(loc)
|
148
|
+
File.join(File.dirname(__FILE__), loc)
|
149
|
+
end
|
150
|
+
|
151
|
+
def term_cleanup(docxml)
|
152
|
+
docxml.xpath("//p[@class = 'Terms']").each do |d|
|
153
|
+
h2 = d.at("./preceding-sibling::*[@class = 'TermNum'][1]")
|
154
|
+
h2.add_child(" ")
|
155
|
+
h2.add_child(d.remove)
|
156
|
+
end
|
157
|
+
docxml
|
158
|
+
end
|
159
|
+
|
160
|
+
def requirement_cleanup(docxml)
|
161
|
+
docxml.xpath("//div[@class = 'recommend'][title]").each do |d|
|
162
|
+
title = d.at("./title")
|
163
|
+
title.name = "b"
|
164
|
+
n = title.next_element
|
165
|
+
n&.children&.first&.add_previous_sibling(" ")
|
166
|
+
n&.children&.first&.add_previous_sibling(title.remove)
|
167
|
+
end
|
168
|
+
docxml
|
169
|
+
end
|
170
|
+
|
171
|
+
def figure_parse(node, out)
|
172
|
+
return pseudocode_parse(node, out) if node["type"] == "pseudocode"
|
173
|
+
super
|
174
|
+
end
|
175
|
+
|
176
|
+
def pseudocode_parse(node, out)
|
177
|
+
@in_figure = true
|
178
|
+
name = node.at(ns("./name"))
|
179
|
+
out.div **attr_code(id: node["id"], class: "pseudocode") do |div|
|
180
|
+
node.children.each do |n|
|
181
|
+
parse(n, div) unless n.name == "name"
|
182
|
+
end
|
183
|
+
figure_name_parse(node, div, name) if name
|
184
|
+
end
|
185
|
+
@in_figure = false
|
186
|
+
end
|
187
|
+
|
188
|
+
def dl_parse(node, out)
|
189
|
+
return glossary_parse(node, out) if node["type"] == "glossary"
|
190
|
+
super
|
191
|
+
end
|
192
|
+
|
193
|
+
def glossary_parse(node, out)
|
194
|
+
out.dl **attr_code(id: node["id"], class: "glossary") do |v|
|
195
|
+
node.elements.select { |n| dt_dd? n }.each_slice(2) do |dt, dd|
|
196
|
+
v.dt **attr_code(id: dt["id"]) do |term|
|
197
|
+
dt_parse(dt, term)
|
198
|
+
end
|
199
|
+
v.dd **attr_code(id: dd["id"]) do |listitem|
|
200
|
+
dd.children.each { |n| parse(n, listitem) }
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
node.elements.reject { |n| dt_dd? n }.each { |n| parse(n, out) }
|
205
|
+
end
|
206
|
+
|
207
|
+
def error_parse(node, out)
|
208
|
+
case node.name
|
209
|
+
when "nistvariable" then nistvariable_parse(node, out)
|
210
|
+
when "recommendation" then recommendation_parse(node, out)
|
211
|
+
when "requirement" then requirement_parse(node, out)
|
212
|
+
when "permission" then permission_parse(node, out)
|
213
|
+
when "errata" then errata_parse(node, out)
|
214
|
+
else
|
215
|
+
super
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def nistvariable_parse(node, out)
|
220
|
+
out.span **{class: "nistvariable"} do |s|
|
221
|
+
node.children.each { |n| parse(n, s) }
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def recommendation_parse(node, out)
|
226
|
+
name = node["type"]
|
227
|
+
out.div **{ class: "recommend" } do |t|
|
228
|
+
t.title { |b| b << "Recommendation #{get_anchors[node['id']][:label]}:" }
|
229
|
+
node.children.each do |n|
|
230
|
+
parse(n, t)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def requirement_parse(node, out)
|
236
|
+
name = node["type"]
|
237
|
+
out.div **{ class: "recommend" } do |t|
|
238
|
+
t.title { |b| b << "Requirement #{get_anchors[node['id']][:label]}:" }
|
239
|
+
node.children.each do |n|
|
240
|
+
parse(n, t)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def permission_parse(node, out)
|
246
|
+
name = node["type"]
|
247
|
+
out.div **{ class: "recommend" } do |t|
|
248
|
+
t.title { |b| b << "Permission #{get_anchors[node['id']][:label]}:" }
|
249
|
+
node.children.each do |n|
|
250
|
+
parse(n, t)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def errata_parse(node, out)
|
256
|
+
out.table **make_table_attr(node) do |t|
|
257
|
+
t.thead do |h|
|
258
|
+
h.tr do |tr|
|
259
|
+
%w(Date Type Change Pages).each do |hdr|
|
260
|
+
tr.th hdr
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
t.tbody do |b|
|
265
|
+
node.xpath(ns("./row")).each do |row|
|
266
|
+
b.tr do |tr|
|
267
|
+
tr.td do |td|
|
268
|
+
row&.at(ns("./date"))&.children.each do |n|
|
269
|
+
parse(n, td)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
tr.td do |td|
|
273
|
+
row&.at(ns("./type"))&.children.each do |n|
|
274
|
+
parse(n, td)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
tr.td do |td|
|
278
|
+
row&.at(ns("./change"))&.children.each do |n|
|
279
|
+
parse(n, td)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
tr.td do |td|
|
283
|
+
row&.at(ns("./pages"))&.children.each do |n|
|
284
|
+
parse(n, td)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
MIDDLE_CLAUSE = "//clause[parent::sections]|//terms[parent::sections]".freeze
|
294
|
+
|
295
|
+
def middle(isoxml, out)
|
296
|
+
middle_title(out)
|
297
|
+
clause isoxml, out
|
298
|
+
annex isoxml, out
|
299
|
+
bibliography isoxml, out
|
300
|
+
end
|
301
|
+
|
302
|
+
def bibliography(isoxml, out)
|
303
|
+
f = isoxml.at(ns("//bibliography/clause | //bibliography/references")) || return
|
304
|
+
page_break(out)
|
305
|
+
isoxml.xpath(ns("//bibliography/clause | //bibliography/references")).each do |f|
|
306
|
+
out.div do |div|
|
307
|
+
div.h1 **{ class: "Section3" } do |h1|
|
308
|
+
f&.at(ns("./title"))&.children.each { |n| parse(n, h1) }
|
309
|
+
end
|
310
|
+
f.elements.reject do |e|
|
311
|
+
["reference", "title", "bibitem"].include? e.name
|
312
|
+
end.each { |e| parse(e, div) }
|
313
|
+
biblio_list(f, div, false)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def info(isoxml, out)
|
319
|
+
@meta.keywords isoxml, out
|
320
|
+
super
|
321
|
+
end
|
322
|
+
|
323
|
+
SECTIONS_XPATH =
|
324
|
+
"//foreword | //introduction | //reviewnote | //execsummary | //annex | "\
|
325
|
+
"//sections/clause | //bibliography/references | "\
|
326
|
+
"//bibliography/clause".freeze
|
327
|
+
|
328
|
+
def initial_anchor_names(d)
|
329
|
+
d.xpath("//xmlns:preface/child::*").each do |c|
|
330
|
+
preface_names(c)
|
331
|
+
end
|
332
|
+
sequential_asset_names(d.xpath("//xmlns:preface/child::*"))
|
333
|
+
clause_names(d, 0)
|
334
|
+
middle_section_asset_names(d)
|
335
|
+
termnote_anchor_names(d)
|
336
|
+
termexample_anchor_names(d)
|
337
|
+
end
|
338
|
+
|
339
|
+
def back_anchor_names(docxml)
|
340
|
+
docxml.xpath(ns("//annex")).each_with_index do |c, i|
|
341
|
+
annex_names(c, (65 + i).chr.to_s)
|
342
|
+
end
|
343
|
+
docxml.xpath(ns("//bibliography/clause | "\
|
344
|
+
"//bibliography/references")).each do |b|
|
345
|
+
preface_names(b)
|
346
|
+
end
|
347
|
+
docxml.xpath(ns("//bibitem[not(ancestor::bibitem)]")).each do |ref|
|
348
|
+
reference_names(ref)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
|
353
|
+
def prefaceprefix(nodes)
|
354
|
+
i = 0
|
355
|
+
nodes.each do |n|
|
356
|
+
case n.name
|
357
|
+
when "executivesummary" then @anchors[n["id"]][:prefix] = "ES"
|
358
|
+
when "abstract" then @anchors[n["id"]][:prefix] = "ABS"
|
359
|
+
when "reviewernote" then @anchors[n["id"]][:prefix] = "NTR"
|
360
|
+
else
|
361
|
+
@anchors[n["id"]][:prefix] = "PR" + i.to_s
|
362
|
+
i += 1
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def middle_section_asset_names(d)
|
368
|
+
prefaceprefix(d.xpath("//xmlns:preface/child::*"))
|
369
|
+
d.xpath("//xmlns:preface/child::*").each do |s|
|
370
|
+
hierarchical_asset_names(s, @anchors[s["id"]][:prefix])
|
371
|
+
end
|
372
|
+
d.xpath("//xmlns:sections/child::*").each do |s|
|
373
|
+
hierarchical_asset_names(s, @anchors[s["id"]][:label])
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
def hierarchical_asset_names(clause, num)
|
378
|
+
super
|
379
|
+
hierarchical_permission_names(clause, num)
|
380
|
+
hierarchical_requirement_names(clause, num)
|
381
|
+
hierarchical_recommendation_names(clause, num)
|
382
|
+
end
|
383
|
+
|
384
|
+
def hierarchical_permission_names(clause, num)
|
385
|
+
clause.xpath(ns(".//permission")).each_with_index do |t, i|
|
386
|
+
next if t["id"].nil? || t["id"].empty?
|
387
|
+
@anchors[t["id"]] = anchor_struct("#{num}.#{i + 1}",
|
388
|
+
t, "Permission", "permission")
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def hierarchical_requirement_names(clause, num)
|
393
|
+
clause.xpath(ns(".//requirement")).each_with_index do |t, i|
|
394
|
+
next if t["id"].nil? || t["id"].empty?
|
395
|
+
@anchors[t["id"]] = anchor_struct("#{num}.#{i + 1}",
|
396
|
+
t, "Requirement", "requirement")
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def hierarchical_recommendation_names(clause, num)
|
401
|
+
clause.xpath(ns(".//recommendation")).each_with_index do |t, i|
|
402
|
+
next if t["id"].nil? || t["id"].empty?
|
403
|
+
@anchors[t["id"]] = anchor_struct("#{num}.#{i + 1}",
|
404
|
+
t, "Recommendation", "recommendation")
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def clause_names(docxml, sect_num)
|
409
|
+
q = "//xmlns:sections/child::*"
|
410
|
+
docxml.xpath(q).each_with_index do |c, i|
|
411
|
+
section_names(c, (i + sect_num), 1)
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
def get_linkend(node)
|
416
|
+
link = anchor_linkend(node, docid_l10n(node["target"] || "[#{node['citeas']}]"))
|
417
|
+
link += eref_localities(node.xpath(ns("./locality")), link)
|
418
|
+
contents = node.children.select { |c| c.name != "locality" }
|
419
|
+
return link if contents.nil? || contents.empty?
|
420
|
+
Nokogiri::XML::NodeSet.new(node.document, contents).to_xml
|
421
|
+
# so not <origin bibitemid="ISO7301" citeas="ISO 7301">
|
422
|
+
# <locality type="section"><reference>3.1</reference></locality></origin>
|
423
|
+
end
|
424
|
+
|
425
|
+
def load_yaml(lang, script)
|
426
|
+
y = if @i18nyaml then YAML.load_file(@i18nyaml)
|
427
|
+
elsif lang == "en"
|
428
|
+
YAML.load_file(File.join(File.dirname(__FILE__), "i18n-en.yaml"))
|
429
|
+
else
|
430
|
+
YAML.load_file(File.join(File.dirname(__FILE__), "i18n-en.yaml"))
|
431
|
+
end
|
432
|
+
super.merge(y)
|
433
|
+
end
|
434
|
+
|
435
|
+
def annex_name_lbl(clause, num)
|
436
|
+
l10n("<b>#{@annex_lbl} #{num}</b>")
|
437
|
+
end
|
438
|
+
|
439
|
+
def annex_name(annex, name, div)
|
440
|
+
div.h1 **{ class: "Annex" } do |t|
|
441
|
+
t << "#{get_anchors[annex['id']][:label]} — "
|
442
|
+
t.b do |b|
|
443
|
+
name&.children&.each { |c2| parse(c2, b) }
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
def hiersep
|
449
|
+
"-"
|
450
|
+
end
|
451
|
+
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|