metanorma-nist 0.0.7 → 0.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.
- checksums.yaml +4 -4
- data/README.adoc +163 -18
- data/lib/asciidoctor/nist/biblio.rng +3 -0
- data/lib/asciidoctor/nist/boilerplate.rb +53 -3
- data/lib/asciidoctor/nist/cleanup.rb +41 -1
- data/lib/asciidoctor/nist/converter.rb +5 -6
- data/lib/asciidoctor/nist/front.rb +124 -63
- data/lib/asciidoctor/nist/isodoc.rng +38 -8
- data/lib/asciidoctor/nist/isostandard.rng +0 -8
- data/lib/asciidoctor/nist/nist.rng +145 -34
- data/lib/asciidoctor/nist/nist_intro.xml +6 -4
- data/lib/isodoc/nist/base_convert.rb +434 -0
- data/lib/isodoc/nist/html/header.html +30 -26
- data/lib/isodoc/nist/html/html_nist_titlepage.html +7 -3
- data/lib/isodoc/nist/html/word_nist_titlepage.html +1219 -155
- data/lib/isodoc/nist/html_convert.rb +7 -411
- data/lib/isodoc/nist/i18n-en.yaml +6 -1
- data/lib/isodoc/nist/metadata.rb +132 -20
- data/lib/isodoc/nist/pdf_convert.rb +19 -426
- data/lib/isodoc/nist/word_convert.rb +9 -442
- data/lib/metanorma/nist/version.rb +1 -1
- data/metanorma-nist.gemspec +1 -0
- metadata +17 -2
@@ -2,6 +2,7 @@ require "isodoc"
|
|
2
2
|
require_relative "metadata"
|
3
3
|
require "fileutils"
|
4
4
|
require "sassc"
|
5
|
+
require_relative "base_convert"
|
5
6
|
|
6
7
|
module IsoDoc
|
7
8
|
module NIST
|
@@ -187,10 +188,15 @@ module IsoDoc
|
|
187
188
|
|
188
189
|
def word_preface_cleanup(docxml)
|
189
190
|
docxml.xpath("//h1[@class = 'AbstractTitle'] | "\
|
190
|
-
"//h1[@class = 'IntroTitle']
|
191
|
+
"//h1[@class = 'IntroTitle'] |
|
192
|
+
//h1[parent::div/@class = 'authority']").each do |h2|
|
191
193
|
h2.name = "p"
|
192
194
|
h2["class"] = "h1Preface"
|
193
195
|
end
|
196
|
+
docxml.xpath("//h2[ancestor::div/@class = 'authority']").each do |h2|
|
197
|
+
h2.name = "p"
|
198
|
+
h2["class"] = "h2Preface"
|
199
|
+
end
|
194
200
|
end
|
195
201
|
|
196
202
|
def word_cleanup(docxml)
|
@@ -223,16 +229,6 @@ module IsoDoc
|
|
223
229
|
end
|
224
230
|
end
|
225
231
|
|
226
|
-
# Henceforth identical to html
|
227
|
-
|
228
|
-
def abstract(isoxml, out)
|
229
|
-
f = isoxml.at(ns("//preface/abstract")) || return
|
230
|
-
out.div **attr_code(id: f["id"]) do |s|
|
231
|
-
clause_name(nil, @abstract_lbl, s, class: "AbstractTitle")
|
232
|
-
f.elements.each { |e| parse(e, s) unless e.name == "title" }
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
232
|
def keywords(_docxml, out)
|
237
233
|
kw = @meta.get[:keywords]
|
238
234
|
kw.empty? and return
|
@@ -243,426 +239,6 @@ module IsoDoc
|
|
243
239
|
end
|
244
240
|
end
|
245
241
|
|
246
|
-
FRONT_CLAUSE = "//*[parent::preface]"\
|
247
|
-
"[not(local-name() = 'abstract')]".freeze
|
248
|
-
|
249
|
-
# All "[preface]" sections should have class "IntroTitle" to prevent
|
250
|
-
# page breaks
|
251
|
-
# But for the Exec Summary
|
252
|
-
def preface(isoxml, out)
|
253
|
-
isoxml.xpath(ns(FRONT_CLAUSE)).each do |c|
|
254
|
-
foreword(isoxml, out) and next if c.name == "foreword"
|
255
|
-
authority_parse(c, out) and next if c.name == "authority"
|
256
|
-
next if skip_render(c, isoxml)
|
257
|
-
title = c&.at(ns("./title"))
|
258
|
-
patent = ["Call for Patent Claims", "Patent Disclosure Notice"].include? title&.text
|
259
|
-
out.div **attr_code(id: c["id"]) do |s|
|
260
|
-
page_break(s) if patent
|
261
|
-
clause_name(get_anchors[c['id']][:label], title&.content, s,
|
262
|
-
class: (c.name == "executivesummary") ? "NormalTitle" :
|
263
|
-
"IntroTitle")
|
264
|
-
c.elements.reject { |c1| c1.name == "title" }.each do |c1|
|
265
|
-
parse(c1, s)
|
266
|
-
end
|
267
|
-
end
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
def skip_render(c, isoxml)
|
272
|
-
return false unless c.name == "reviewernote"
|
273
|
-
status = isoxml&.at(ns("//bibdata/status/stage"))&.text
|
274
|
-
return true if status.nil?
|
275
|
-
/^final/.match status
|
276
|
-
end
|
277
|
-
|
278
|
-
def term_defs_boilerplate(div, source, term, preface)
|
279
|
-
if source.empty? && term.nil?
|
280
|
-
div << @no_terms_boilerplate
|
281
|
-
else
|
282
|
-
div << term_defs_boilerplate_cont(source, term)
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
def i18n_init(lang, script)
|
287
|
-
super
|
288
|
-
end
|
289
|
-
|
290
|
-
def fileloc(loc)
|
291
|
-
File.join(File.dirname(__FILE__), loc)
|
292
|
-
end
|
293
|
-
|
294
|
-
def term_cleanup(docxml)
|
295
|
-
docxml.xpath("//p[@class = 'Terms']").each do |d|
|
296
|
-
h2 = d.at("./preceding-sibling::*[@class = 'TermNum'][1]")
|
297
|
-
h2.add_child(" ")
|
298
|
-
h2.add_child(d.remove)
|
299
|
-
end
|
300
|
-
docxml
|
301
|
-
end
|
302
|
-
|
303
|
-
def requirement_cleanup(docxml)
|
304
|
-
docxml.xpath("//div[@class = 'recommend' or @class = 'require' or "\
|
305
|
-
"@class = 'permission'][title]").each do |d|
|
306
|
-
title = d.at("./title")
|
307
|
-
title.name = "b"
|
308
|
-
n = title.next_element
|
309
|
-
n&.children&.first&.add_previous_sibling(" ")
|
310
|
-
n&.children&.first&.add_previous_sibling(title.remove)
|
311
|
-
end
|
312
|
-
docxml
|
313
|
-
end
|
314
|
-
|
315
|
-
def figure_parse(node, out)
|
316
|
-
return pseudocode_parse(node, out) if node["type"] == "pseudocode"
|
317
|
-
super
|
318
|
-
end
|
319
|
-
|
320
|
-
def pseudocode_parse(node, out)
|
321
|
-
@in_figure = true
|
322
|
-
name = node.at(ns("./name"))
|
323
|
-
out.table **attr_code(id: node["id"], class: "pseudocode") do |div|
|
324
|
-
div.tr do |tr|
|
325
|
-
tr.td do |td|
|
326
|
-
node.children.each do |n|
|
327
|
-
parse(n, td) unless n.name == "name"
|
328
|
-
end
|
329
|
-
end
|
330
|
-
end
|
331
|
-
figure_name_parse(node, div, name) if name
|
332
|
-
end
|
333
|
-
@in_figure = false
|
334
|
-
end
|
335
|
-
|
336
|
-
def dl_parse(node, out)
|
337
|
-
return glossary_parse(node, out) if node["type"] == "glossary"
|
338
|
-
super
|
339
|
-
end
|
340
|
-
|
341
|
-
def glossary_parse(node, out)
|
342
|
-
out.dl **attr_code(id: node["id"], class: "glossary") do |v|
|
343
|
-
node.elements.select { |n| dt_dd? n }.each_slice(2) do |dt, dd|
|
344
|
-
v.dt **attr_code(id: dt["id"]) do |term|
|
345
|
-
dt_parse(dt, term)
|
346
|
-
end
|
347
|
-
v.dd **attr_code(id: dd["id"]) do |listitem|
|
348
|
-
dd.children.each { |n| parse(n, listitem) }
|
349
|
-
end
|
350
|
-
end
|
351
|
-
end
|
352
|
-
node.elements.reject { |n| dt_dd? n }.each { |n| parse(n, out) }
|
353
|
-
end
|
354
|
-
|
355
|
-
def error_parse(node, out)
|
356
|
-
case node.name
|
357
|
-
when "nistvariable" then nistvariable_parse(node, out)
|
358
|
-
when "recommendation" then recommendation_parse(node, out)
|
359
|
-
when "requirement" then requirement_parse(node, out)
|
360
|
-
when "permission" then permission_parse(node, out)
|
361
|
-
when "errata" then errata_parse(node, out)
|
362
|
-
when "authority" then authority_parse(node, out)
|
363
|
-
when "authority1" then authority1_parse(node, out, "authority1")
|
364
|
-
when "authority2" then authority1_parse(node, out, "authority2")
|
365
|
-
when "authority3" then authority1_parse(node, out, "authority3")
|
366
|
-
when "authority4" then authority1_parse(node, out, "authority4")
|
367
|
-
when "authority5" then authority1_parse(node, out, "authority5")
|
368
|
-
else
|
369
|
-
super
|
370
|
-
end
|
371
|
-
end
|
372
|
-
|
373
|
-
def authority_parse(node, out)
|
374
|
-
out.div **{class: "authority"} do |s|
|
375
|
-
node.children.each do |n|
|
376
|
-
if n.name == "title"
|
377
|
-
s.p **{class: "h1Preface"} do |h|
|
378
|
-
n.children.each { |nn| parse(nn, h) }
|
379
|
-
end
|
380
|
-
else
|
381
|
-
parse(n, s)
|
382
|
-
end
|
383
|
-
end
|
384
|
-
end
|
385
|
-
end
|
386
|
-
def authority1_parse(node, out, classname)
|
387
|
-
out.div **{class: classname} do |s|
|
388
|
-
node.children.each do |n|
|
389
|
-
if n.name == "title"
|
390
|
-
s.p **{class: "h2Preface"} do |h|
|
391
|
-
n.children.each { |nn| parse(nn, h) }
|
392
|
-
end
|
393
|
-
else
|
394
|
-
parse(n, s)
|
395
|
-
end
|
396
|
-
end
|
397
|
-
end
|
398
|
-
end
|
399
|
-
|
400
|
-
def nistvariable_parse(node, out)
|
401
|
-
out.span **{class: "nistvariable"} do |s|
|
402
|
-
node.children.each { |n| parse(n, s) }
|
403
|
-
end
|
404
|
-
end
|
405
|
-
|
406
|
-
def recommendation_parse(node, out)
|
407
|
-
name = node["type"]
|
408
|
-
out.div **{ class: "recommend" } do |t|
|
409
|
-
t.title { |b| b << "Recommendation "\
|
410
|
-
"#{get_anchors[node['id']][:label]}:" }
|
411
|
-
node.children.each do |n|
|
412
|
-
parse(n, t)
|
413
|
-
end
|
414
|
-
end
|
415
|
-
end
|
416
|
-
|
417
|
-
def requirement_parse(node, out)
|
418
|
-
name = node["type"]
|
419
|
-
out.div **{ class: "require" } do |t|
|
420
|
-
t.title { |b| b << "Requirement #{get_anchors[node['id']][:label]}:" }
|
421
|
-
node.children.each do |n|
|
422
|
-
parse(n, t)
|
423
|
-
end
|
424
|
-
end
|
425
|
-
end
|
426
|
-
|
427
|
-
def permission_parse(node, out)
|
428
|
-
name = node["type"]
|
429
|
-
out.div **{ class: "permission" } do |t|
|
430
|
-
t.title { |b| b << "Permission #{get_anchors[node['id']][:label]}:" }
|
431
|
-
node.children.each do |n|
|
432
|
-
parse(n, t)
|
433
|
-
end
|
434
|
-
end
|
435
|
-
end
|
436
|
-
|
437
|
-
def errata_parse(node, out)
|
438
|
-
out.table **make_table_attr(node) do |t|
|
439
|
-
t.thead do |h|
|
440
|
-
h.tr do |tr|
|
441
|
-
%w(Date Type Change Pages).each do |hdr|
|
442
|
-
tr.th hdr
|
443
|
-
end
|
444
|
-
end
|
445
|
-
end
|
446
|
-
t.tbody do |b|
|
447
|
-
node.xpath(ns("./row")).each do |row|
|
448
|
-
b.tr do |tr|
|
449
|
-
tr.td do |td|
|
450
|
-
row&.at(ns("./date"))&.children.each do |n|
|
451
|
-
parse(n, td)
|
452
|
-
end
|
453
|
-
end
|
454
|
-
tr.td do |td|
|
455
|
-
row&.at(ns("./type"))&.children.each do |n|
|
456
|
-
parse(n, td)
|
457
|
-
end
|
458
|
-
end
|
459
|
-
tr.td do |td|
|
460
|
-
row&.at(ns("./change"))&.children.each do |n|
|
461
|
-
parse(n, td)
|
462
|
-
end
|
463
|
-
end
|
464
|
-
tr.td do |td|
|
465
|
-
row&.at(ns("./pages"))&.children.each do |n|
|
466
|
-
parse(n, td)
|
467
|
-
end
|
468
|
-
end
|
469
|
-
end
|
470
|
-
end
|
471
|
-
end
|
472
|
-
end
|
473
|
-
end
|
474
|
-
|
475
|
-
MIDDLE_CLAUSE = "//clause[parent::sections] | "\
|
476
|
-
"//terms[parent::sections]".freeze
|
477
|
-
|
478
|
-
def middle(isoxml, out)
|
479
|
-
# NIST documents don't repeat the title
|
480
|
-
# middle_title(out)
|
481
|
-
clause isoxml, out
|
482
|
-
bibliography isoxml, out
|
483
|
-
annex isoxml, out
|
484
|
-
end
|
485
|
-
|
486
|
-
def info(isoxml, out)
|
487
|
-
@meta.keywords isoxml, out
|
488
|
-
@meta.series isoxml, out
|
489
|
-
@meta.commentperiod isoxml, out
|
490
|
-
super
|
491
|
-
end
|
492
|
-
|
493
|
-
SECTIONS_XPATH =
|
494
|
-
"//foreword | //introduction | //reviewnote | //executivesummary | "\
|
495
|
-
"//annex | //sections/clause | //bibliography/references | "\
|
496
|
-
"//bibliography/clause".freeze
|
497
|
-
|
498
|
-
def initial_anchor_names(d)
|
499
|
-
d.xpath("//xmlns:preface/child::*").each do |c|
|
500
|
-
preface_names(c)
|
501
|
-
end
|
502
|
-
sequential_asset_names(d.xpath("//xmlns:preface/child::*"))
|
503
|
-
clause_names(d, 0)
|
504
|
-
middle_section_asset_names(d)
|
505
|
-
termnote_anchor_names(d)
|
506
|
-
termexample_anchor_names(d)
|
507
|
-
end
|
508
|
-
|
509
|
-
def back_anchor_names(docxml)
|
510
|
-
docxml.xpath(ns("//annex")).each_with_index do |c, i|
|
511
|
-
annex_names(c, (65 + i).chr.to_s)
|
512
|
-
end
|
513
|
-
docxml.xpath(ns("//bibliography/clause | "\
|
514
|
-
"//bibliography/references")).each do |b|
|
515
|
-
preface_names(b)
|
516
|
-
end
|
517
|
-
docxml.xpath(ns("//bibitem[not(ancestor::bibitem)]")).each do |ref|
|
518
|
-
reference_names(ref)
|
519
|
-
end
|
520
|
-
end
|
521
|
-
|
522
|
-
def middle_section_asset_names(d)
|
523
|
-
middle_sections =
|
524
|
-
"//xmlns:preface/child::* | //xmlns:sections/child::*"
|
525
|
-
sequential_asset_names(d.xpath(middle_sections))
|
526
|
-
end
|
527
|
-
|
528
|
-
def sequential_asset_names(clause)
|
529
|
-
super
|
530
|
-
sequential_permission_names(clause)
|
531
|
-
sequential_requirement_names(clause)
|
532
|
-
sequential_recommendation_names(clause)
|
533
|
-
end
|
534
|
-
|
535
|
-
def sequential_permission_names(clause)
|
536
|
-
clause.xpath(ns(".//permission")).each_with_index do |t, i|
|
537
|
-
next if t["id"].nil? || t["id"].empty?
|
538
|
-
@anchors[t["id"]] = anchor_struct(i + 1, t, "Permission", "permission")
|
539
|
-
end
|
540
|
-
end
|
541
|
-
|
542
|
-
def sequential_requirement_names(clause)
|
543
|
-
clause.xpath(ns(".//requirement")).each_with_index do |t, i|
|
544
|
-
next if t["id"].nil? || t["id"].empty?
|
545
|
-
@anchors[t["id"]] = anchor_struct(i + 1, t, "Requirement", "requirement")
|
546
|
-
end
|
547
|
-
end
|
548
|
-
|
549
|
-
def sequential_recommendation_names(clause)
|
550
|
-
clause.xpath(ns(".//recommendation")).each_with_index do |t, i|
|
551
|
-
next if t["id"].nil? || t["id"].empty?
|
552
|
-
@anchors[t["id"]] = anchor_struct(i + 1, t, "Recommendation", "recommendation")
|
553
|
-
end
|
554
|
-
end
|
555
|
-
|
556
|
-
|
557
|
-
def hierarchical_asset_names(clause, num)
|
558
|
-
super
|
559
|
-
hierarchical_permission_names(clause, num)
|
560
|
-
hierarchical_requirement_names(clause, num)
|
561
|
-
hierarchical_recommendation_names(clause, num)
|
562
|
-
end
|
563
|
-
|
564
|
-
def hierarchical_permission_names(clause, num)
|
565
|
-
clause.xpath(ns(".//permission")).each_with_index do |t, i|
|
566
|
-
next if t["id"].nil? || t["id"].empty?
|
567
|
-
@anchors[t["id"]] = anchor_struct("#{num}.#{i + 1}",
|
568
|
-
t, "Permission", "permission")
|
569
|
-
end
|
570
|
-
end
|
571
|
-
|
572
|
-
def hierarchical_requirement_names(clause, num)
|
573
|
-
clause.xpath(ns(".//requirement")).each_with_index do |t, i|
|
574
|
-
next if t["id"].nil? || t["id"].empty?
|
575
|
-
@anchors[t["id"]] = anchor_struct("#{num}.#{i + 1}",
|
576
|
-
t, "Requirement", "requirement")
|
577
|
-
end
|
578
|
-
end
|
579
|
-
|
580
|
-
def hierarchical_recommendation_names(clause, num)
|
581
|
-
clause.xpath(ns(".//recommendation")).each_with_index do |t, i|
|
582
|
-
next if t["id"].nil? || t["id"].empty?
|
583
|
-
@anchors[t["id"]] = anchor_struct("#{num}.#{i + 1}", t,
|
584
|
-
"Recommendation", "recommendation")
|
585
|
-
end
|
586
|
-
end
|
587
|
-
|
588
|
-
def clause_names(docxml, sect_num)
|
589
|
-
q = "//xmlns:sections/child::*"
|
590
|
-
docxml.xpath(q).each_with_index do |c, i|
|
591
|
-
section_names(c, (i + sect_num), 1)
|
592
|
-
end
|
593
|
-
end
|
594
|
-
|
595
|
-
def get_linkend(node)
|
596
|
-
link = anchor_linkend(node, docid_l10n(node["target"] ||
|
597
|
-
"[#{node['citeas']}]"))
|
598
|
-
link += eref_localities(node.xpath(ns("./locality")), link)
|
599
|
-
contents = node.children.select { |c| c.name != "locality" }
|
600
|
-
return link if contents.nil? || contents.empty?
|
601
|
-
Nokogiri::XML::NodeSet.new(node.document, contents).to_xml
|
602
|
-
# so not <origin bibitemid="ISO7301" citeas="ISO 7301">
|
603
|
-
# <locality type="section"><reference>3.1</reference></locality></origin>
|
604
|
-
end
|
605
|
-
|
606
|
-
def load_yaml(lang, script)
|
607
|
-
y = if @i18nyaml then YAML.load_file(@i18nyaml)
|
608
|
-
elsif lang == "en"
|
609
|
-
YAML.load_file(File.join(File.dirname(__FILE__), "i18n-en.yaml"))
|
610
|
-
else
|
611
|
-
YAML.load_file(File.join(File.dirname(__FILE__), "i18n-en.yaml"))
|
612
|
-
end
|
613
|
-
super.merge(y)
|
614
|
-
end
|
615
|
-
|
616
|
-
def annex_name_lbl(clause, num)
|
617
|
-
l10n("<b>#{@annex_lbl} #{num}</b>")
|
618
|
-
end
|
619
|
-
|
620
|
-
def annex_name(annex, name, div)
|
621
|
-
div.h1 **{ class: "Annex" } do |t|
|
622
|
-
t << "#{get_anchors[annex['id']][:label]} — "
|
623
|
-
t.b do |b|
|
624
|
-
if @bibliographycount == 1 && annex.at(ns("./references"))
|
625
|
-
b << "References"
|
626
|
-
else
|
627
|
-
name&.children&.each { |c2| parse(c2, b) }
|
628
|
-
end
|
629
|
-
end
|
630
|
-
end
|
631
|
-
end
|
632
|
-
|
633
|
-
def hiersep
|
634
|
-
"-"
|
635
|
-
end
|
636
|
-
|
637
|
-
def annex_names(clause, num)
|
638
|
-
@anchors[clause["id"]] = { label: annex_name_lbl(clause, num), type: "clause",
|
639
|
-
xref: "#{@annex_lbl} #{num}", level: 1 }
|
640
|
-
clause.xpath(ns("./clause | ./terms | ./term | ./references")).each_with_index do |c, i|
|
641
|
-
annex_names1(c, "#{num}.#{i + 1}", 2)
|
642
|
-
end
|
643
|
-
hierarchical_asset_names(clause, num)
|
644
|
-
end
|
645
|
-
|
646
|
-
def annex_names1(clause, num, level)
|
647
|
-
@anchors[clause["id"]] = { label: num, xref: "#{@annex_lbl} #{num}",
|
648
|
-
level: level, type: "clause" }
|
649
|
-
clause.xpath(ns("./clause | ./terms | ./term | ./references")).each_with_index do |c, i|
|
650
|
-
annex_names1(c, "#{num}.#{i + 1}", level + 1)
|
651
|
-
end
|
652
|
-
end
|
653
|
-
|
654
|
-
def terms_parse(node, out)
|
655
|
-
out.div **attr_code(id: node["id"]) do |div|
|
656
|
-
node.at(ns("./title")) and
|
657
|
-
clause_parse_title(node, div, node.at(ns("./title")), out)
|
658
|
-
term_defs_boilerplate(div, node.xpath(ns(".//termdocsource")),
|
659
|
-
node.at(ns(".//term")), node.at(ns("./p")))
|
660
|
-
node.elements.each do |e|
|
661
|
-
parse(e, div) unless %w{title source}.include? e.name
|
662
|
-
end
|
663
|
-
end
|
664
|
-
end
|
665
|
-
|
666
242
|
def termdef_parse(node, out)
|
667
243
|
pref = node.at(ns("./preferred"))
|
668
244
|
out.table **{ class: "terms_dl" } do |dl|
|
@@ -688,17 +264,8 @@ module IsoDoc
|
|
688
264
|
docxml
|
689
265
|
end
|
690
266
|
|
691
|
-
|
692
|
-
title = node&.at(ns("./title"))&.text || ""
|
693
|
-
out.div do |div|
|
694
|
-
node.parent.name == "annex" or
|
695
|
-
div.h2 title, **{ class: "Section3" }
|
696
|
-
node.elements.reject do |e|
|
697
|
-
["reference", "title", "bibitem"].include? e.name
|
698
|
-
end.each { |e| parse(e, div) }
|
699
|
-
biblio_list(node, div, true)
|
700
|
-
end
|
701
|
-
end
|
267
|
+
include BaseConvert
|
702
268
|
end
|
703
269
|
end
|
704
270
|
end
|
271
|
+
|