metanorma-iso 1.3.25 → 1.4.2
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/.github/workflows/macos.yml +0 -1
- data/.github/workflows/ubuntu.yml +6 -3
- data/.github/workflows/windows.yml +0 -1
- data/Gemfile +1 -0
- data/Rakefile +2 -0
- data/lib/asciidoctor/iso/base.rb +16 -13
- data/lib/asciidoctor/iso/biblio.rng +14 -4
- data/lib/asciidoctor/iso/cleanup.rb +1 -1
- data/lib/asciidoctor/iso/front.rb +3 -155
- data/lib/asciidoctor/iso/front_id.rb +196 -0
- data/lib/asciidoctor/iso/isodoc.rng +444 -1
- data/lib/asciidoctor/iso/isostandard.rng +10 -1
- data/lib/asciidoctor/iso/reqt.rng +23 -0
- data/lib/asciidoctor/iso/term_lookup_cleanup.rb +7 -10
- data/lib/asciidoctor/iso/validate.rb +1 -5
- data/lib/asciidoctor/iso/validate_requirements.rb +1 -1
- data/lib/asciidoctor/iso/validate_style.rb +6 -5
- data/lib/asciidoctor/iso/validate_title.rb +1 -1
- data/lib/isodoc/iso/base_convert.rb +67 -112
- data/lib/isodoc/iso/html/header.html +5 -1
- data/lib/isodoc/iso/html/html_iso_titlepage.html +9 -0
- data/lib/isodoc/iso/html/htmlstyle.scss +0 -1
- data/lib/isodoc/iso/html/isodoc.scss +25 -1
- data/lib/isodoc/iso/html/style-human.scss +8 -1
- data/lib/isodoc/iso/html/style-iso.scss +8 -1
- data/lib/isodoc/iso/html/word_iso_intro.html +4 -0
- data/lib/isodoc/iso/html/word_iso_titlepage.html +4 -4
- data/lib/isodoc/iso/html/wordstyle.scss +20 -7
- data/lib/isodoc/iso/iso.amendment.xsl +5114 -0
- data/lib/isodoc/iso/iso.international-standard.xsl +1258 -530
- data/lib/isodoc/iso/metadata.rb +31 -27
- data/lib/isodoc/iso/pdf_convert.rb +5 -11
- data/lib/isodoc/iso/presentation_xml_convert.rb +13 -0
- data/lib/isodoc/iso/sections.rb +66 -0
- data/lib/isodoc/iso/sts_convert.rb +31 -0
- data/lib/isodoc/iso/xref.rb +111 -0
- data/lib/metanorma-iso.rb +2 -0
- data/lib/metanorma/iso/processor.rb +14 -8
- data/lib/metanorma/iso/version.rb +1 -1
- data/metanorma-iso.gemspec +6 -2
- data/spec/asciidoctor-iso/amd_spec.rb +412 -4
- data/spec/asciidoctor-iso/base_spec.rb +18 -16
- data/spec/asciidoctor-iso/cleanup_spec.rb +2 -2
- data/spec/asciidoctor-iso/macros_spec.rb +33 -17
- data/spec/asciidoctor-iso/refs_spec.rb +1 -1
- data/spec/asciidoctor-iso/table_spec.rb +1 -1
- data/spec/isodoc/amd_spec.rb +652 -0
- data/spec/isodoc/blocks_spec.rb +112 -27
- data/spec/isodoc/inline_spec.rb +2 -2
- data/spec/isodoc/metadata_spec.rb +88 -4
- data/spec/isodoc/postproc_spec.rb +9 -9
- data/spec/isodoc/ref_spec.rb +6 -6
- data/spec/isodoc/section_spec.rb +28 -1
- data/spec/isodoc/table_spec.rb +8 -8
- data/spec/isodoc/terms_spec.rb +4 -4
- data/spec/isodoc/xref_spec.rb +24 -18
- data/spec/metanorma/processor_spec.rb +2 -2
- metadata +73 -11
@@ -91,6 +91,12 @@
|
|
91
91
|
</define>
|
92
92
|
<define name="sections">
|
93
93
|
<element name="sections">
|
94
|
+
<zeroOrMore>
|
95
|
+
<choice>
|
96
|
+
<ref name="note"/>
|
97
|
+
<ref name="admonition"/>
|
98
|
+
</choice>
|
99
|
+
</zeroOrMore>
|
94
100
|
<ref name="clause"/>
|
95
101
|
<optional>
|
96
102
|
<choice>
|
@@ -356,6 +362,9 @@
|
|
356
362
|
<data type="boolean"/>
|
357
363
|
</attribute>
|
358
364
|
</optional>
|
365
|
+
<optional>
|
366
|
+
<attribute name="number"/>
|
367
|
+
</optional>
|
359
368
|
<optional>
|
360
369
|
<attribute name="subsequence"/>
|
361
370
|
</optional>
|
@@ -512,7 +521,7 @@
|
|
512
521
|
</attribute>
|
513
522
|
</optional>
|
514
523
|
<oneOrMore>
|
515
|
-
<ref name="
|
524
|
+
<ref name="BasicBlock"/>
|
516
525
|
</oneOrMore>
|
517
526
|
</element>
|
518
527
|
</define>
|
@@ -30,9 +30,22 @@
|
|
30
30
|
<data type="boolean"/>
|
31
31
|
</attribute>
|
32
32
|
</optional>
|
33
|
+
<optional>
|
34
|
+
<attribute name="number"/>
|
35
|
+
</optional>
|
33
36
|
<optional>
|
34
37
|
<attribute name="subsequence"/>
|
35
38
|
</optional>
|
39
|
+
<optional>
|
40
|
+
<attribute name="keep-with-next">
|
41
|
+
<data type="boolean"/>
|
42
|
+
</attribute>
|
43
|
+
</optional>
|
44
|
+
<optional>
|
45
|
+
<attribute name="keep-lines-together">
|
46
|
+
<data type="boolean"/>
|
47
|
+
</attribute>
|
48
|
+
</optional>
|
36
49
|
<attribute name="id">
|
37
50
|
<data type="ID"/>
|
38
51
|
</attribute>
|
@@ -141,6 +154,16 @@
|
|
141
154
|
<data type="boolean"/>
|
142
155
|
</attribute>
|
143
156
|
</optional>
|
157
|
+
<optional>
|
158
|
+
<attribute name="keep-with-next">
|
159
|
+
<data type="boolean"/>
|
160
|
+
</attribute>
|
161
|
+
</optional>
|
162
|
+
<optional>
|
163
|
+
<attribute name="keep-lines-together">
|
164
|
+
<data type="boolean"/>
|
165
|
+
</attribute>
|
166
|
+
</optional>
|
144
167
|
<oneOrMore>
|
145
168
|
<ref name="BasicBlock"/>
|
146
169
|
</oneOrMore>
|
@@ -31,23 +31,20 @@ module Asciidoctor
|
|
31
31
|
remove_missing_ref(node, target)
|
32
32
|
next
|
33
33
|
end
|
34
|
-
|
35
34
|
modify_ref_node(node, target)
|
36
35
|
end
|
37
36
|
end
|
38
37
|
|
39
38
|
def remove_missing_ref(node, target)
|
40
|
-
log.add('AsciiDoc Input',
|
41
|
-
node,
|
39
|
+
log.add('AsciiDoc Input', node,
|
42
40
|
%(Error: Term reference in `term[#{target}]` missing: \
|
43
41
|
"#{target}" is not defined in document))
|
44
|
-
# Term ref have parentess around it - (ref),
|
45
|
-
# if no target remove parentes and render as text
|
46
|
-
node.next.remove
|
47
42
|
term_name_node = node.previous.previous
|
48
43
|
term_name_node.remove
|
49
|
-
|
50
|
-
|
44
|
+
term_name_node.name = "strong"
|
45
|
+
term_name_node.children.first.content =
|
46
|
+
%(term "#{term_name_node.text}" not resolved)
|
47
|
+
node.add_previous_sibling(term_name_node)
|
51
48
|
node.remove
|
52
49
|
end
|
53
50
|
|
@@ -60,15 +57,15 @@ module Asciidoctor
|
|
60
57
|
|
61
58
|
def replace_automatic_generated_ids_terms
|
62
59
|
xmldoc.xpath('//term').each.with_object({}) do |term_node, res|
|
63
|
-
next if AUTOMATIC_GENERATED_ID_REGEXP.match(term_node['id']).nil?
|
64
|
-
|
65
60
|
normalize_id_and_memorize(term_node, res, './preferred')
|
66
61
|
end
|
67
62
|
end
|
68
63
|
|
69
64
|
def normalize_id_and_memorize(term_node, res_table, text_selector)
|
70
65
|
term_text = normalize_ref_id(term_node.at(text_selector).text)
|
66
|
+
unless AUTOMATIC_GENERATED_ID_REGEXP.match(term_node['id']).nil?
|
71
67
|
term_node['id'] = unique_text_id(term_text)
|
68
|
+
end
|
72
69
|
res_table[term_text] = term_node['id']
|
73
70
|
end
|
74
71
|
|
@@ -170,11 +170,7 @@ module Asciidoctor
|
|
170
170
|
|
171
171
|
def bibitem_validate(xmldoc)
|
172
172
|
xmldoc.xpath("//bibitem[date/on = '–']").each do |b|
|
173
|
-
|
174
|
-
b.xpath("./note").each do |n|
|
175
|
-
found = true if /^ISO DATE:/.match n.text
|
176
|
-
end
|
177
|
-
found or
|
173
|
+
b.at("./note[@type = 'ISO DATE']") or
|
178
174
|
@log.add("Style", b,
|
179
175
|
"Reference #{b&.at("./@id")&.text} does not have an "\
|
180
176
|
"associated footnote indicating unpublished status")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require "metanorma-standoc"
|
2
2
|
require "nokogiri"
|
3
|
-
require "
|
3
|
+
require "tokenizer"
|
4
4
|
|
5
5
|
module Asciidoctor
|
6
6
|
module ISO
|
@@ -70,7 +70,7 @@ module Asciidoctor
|
|
70
70
|
# and a negative match on its preceding token
|
71
71
|
def style_two_regex_not_prev(n, text, re, re_prev, warning)
|
72
72
|
return if text.nil?
|
73
|
-
arr =
|
73
|
+
arr = Tokenizer::WhitespaceTokenizer.new.tokenize(text)
|
74
74
|
arr.each_index do |i|
|
75
75
|
m = re.match arr[i]
|
76
76
|
m_prev = i.zero? ? nil : re_prev.match(arr[i - 1])
|
@@ -91,9 +91,10 @@ module Asciidoctor
|
|
91
91
|
# ISO/IEC DIR 2, 9.1
|
92
92
|
# ISO/IEC DIR 2, Table B.1
|
93
93
|
def style_number(n, t)
|
94
|
-
style_two_regex_not_prev(
|
95
|
-
|
96
|
-
|
94
|
+
style_two_regex_not_prev(
|
95
|
+
n, t, /^(?<num>-?[0-9]{4,}[,0-9]*)$/,
|
96
|
+
%r{\b(ISO|IEC|IEEE/|(in|January|February|March|April|May|June|August|September|October|November|December)\b)$},
|
97
|
+
"number not broken up in threes")
|
97
98
|
style_regex(/\b(?<num>[0-9]+\.[0-9]+)/i,
|
98
99
|
"possible decimal point", n, t)
|
99
100
|
style_regex(/\b(?<num>billion[s]?)\b/i,
|
@@ -36,7 +36,7 @@ module Asciidoctor
|
|
36
36
|
|
37
37
|
# ISO/IEC DIR 2, 11.4
|
38
38
|
def title_subpart_validate(root)
|
39
|
-
docid = root.at("//bibdata/docidentifier[@type = '
|
39
|
+
docid = root.at("//bibdata/docidentifier[@type = 'ISO']")
|
40
40
|
subpart = /-\d+-\d+/.match docid
|
41
41
|
iec = root.at("//bibdata/contributor[role/@type = 'publisher']/"\
|
42
42
|
"organization[abbreviation = 'IEC' or "\
|
@@ -1,5 +1,7 @@
|
|
1
1
|
require "isodoc"
|
2
2
|
require_relative "metadata"
|
3
|
+
require_relative "sections"
|
4
|
+
require_relative "xref"
|
3
5
|
require "fileutils"
|
4
6
|
|
5
7
|
module IsoDoc
|
@@ -9,73 +11,27 @@ module IsoDoc
|
|
9
11
|
@meta = Metadata.new(lang, script, labels)
|
10
12
|
end
|
11
13
|
|
12
|
-
def
|
13
|
-
|
14
|
-
@amd = %w(amendment technical-corrigendum).include? doctype
|
15
|
-
if @amd
|
16
|
-
@oldsuppressheadingnumbers = @suppressheadingnumbers
|
17
|
-
@suppressheadingnumbers = true
|
18
|
-
end
|
19
|
-
super
|
14
|
+
def xref_init(lang, script, klass, labels, options)
|
15
|
+
@xrefs = Xref.new(lang, script, klass, labels, options)
|
20
16
|
end
|
21
17
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
@amd and @suppressheadingnumbers = true
|
18
|
+
def amd(docxml)
|
19
|
+
doctype = docxml&.at(ns("//bibdata/ext/doctype"))&.text
|
20
|
+
%w(amendment technical-corrigendum).include? doctype
|
26
21
|
end
|
27
22
|
|
28
|
-
def
|
29
|
-
if
|
30
|
-
|
31
|
-
|
32
|
-
note_anchor_names(docxml.xpath(ns("//annex")))
|
33
|
-
example_anchor_names(docxml.xpath(ns("//annex")))
|
34
|
-
list_anchor_names(docxml.xpath(ns("//annex")))
|
35
|
-
else
|
36
|
-
super
|
23
|
+
def convert1(docxml, filename, dir)
|
24
|
+
if amd(docxml)
|
25
|
+
@oldsuppressheadingnumbers = @suppressheadingnumbers
|
26
|
+
@suppressheadingnumbers = true
|
37
27
|
end
|
28
|
+
super
|
38
29
|
end
|
39
30
|
|
40
31
|
def implicit_reference(b)
|
41
32
|
b&.at(ns("./docidentifier"))&.text == "IEV"
|
42
33
|
end
|
43
34
|
|
44
|
-
def introduction(isoxml, out)
|
45
|
-
f = isoxml.at(ns("//introduction")) || return
|
46
|
-
num = f.at(ns(".//clause")) ? "0" : nil
|
47
|
-
title_attr = { class: "IntroTitle" }
|
48
|
-
page_break(out)
|
49
|
-
out.div **{ class: "Section3", id: f["id"] } do |div|
|
50
|
-
clause_name(num, @introduction_lbl, div, title_attr)
|
51
|
-
f.elements.each do |e|
|
52
|
-
parse(e, div) unless e.name == "title"
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def foreword(isoxml, out)
|
58
|
-
f = isoxml.at(ns("//foreword")) || return
|
59
|
-
page_break(out)
|
60
|
-
out.div **attr_code(id: f["id"]) do |s|
|
61
|
-
s.h1(**{ class: "ForewordTitle" }) { |h1| h1 << @foreword_lbl }
|
62
|
-
f.elements.each { |e| parse(e, s) unless e.name == "title" }
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def initial_anchor_names(d)
|
67
|
-
super
|
68
|
-
introduction_names(d.at(ns("//introduction")))
|
69
|
-
end
|
70
|
-
|
71
|
-
# we can reference 0-number clauses in introduction
|
72
|
-
def introduction_names(clause)
|
73
|
-
return if clause.nil?
|
74
|
-
clause.xpath(ns("./clause")).each_with_index do |c, i|
|
75
|
-
section_names1(c, "0.#{i + 1}", 2)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
35
|
# terms not defined in standoc
|
80
36
|
def error_parse(node, out)
|
81
37
|
case node.name
|
@@ -85,36 +41,6 @@ module IsoDoc
|
|
85
41
|
end
|
86
42
|
end
|
87
43
|
|
88
|
-
def annex_names(clause, num)
|
89
|
-
appendix_names(clause, num)
|
90
|
-
super
|
91
|
-
end
|
92
|
-
|
93
|
-
def appendix_names(clause, num)
|
94
|
-
clause.xpath(ns("./appendix")).each_with_index do |c, i|
|
95
|
-
@anchors[c["id"]] = anchor_struct(i + 1, nil, @appendix_lbl, "clause")
|
96
|
-
@anchors[c["id"]][:level] = 2
|
97
|
-
@anchors[c["id"]][:container] = clause["id"]
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def section_names1(clause, num, level)
|
102
|
-
@anchors[clause["id"]] =
|
103
|
-
{ label: num, level: level, xref: num }
|
104
|
-
# subclauses are not prefixed with "Clause"
|
105
|
-
clause.xpath(ns("./clause | ./terms | ./term | ./definitions | ./references")).
|
106
|
-
each_with_index do |c, i|
|
107
|
-
section_names1(c, "#{num}.#{i + 1}", level + 1)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
def annex_names1(clause, num, level)
|
112
|
-
@anchors[clause["id"]] = { label: num, xref: num, level: level }
|
113
|
-
clause.xpath(ns("./clause | ./references")).each_with_index do |c, i|
|
114
|
-
annex_names1(c, "#{num}.#{i + 1}", level + 1)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
44
|
def eref_localities1_zh(target, type, from, to, delim)
|
119
45
|
subsection = from&.text&.match(/\./)
|
120
46
|
ret = (delim == ";") ? ";" : (type == "list") ? "" : delim
|
@@ -128,6 +54,7 @@ module IsoDoc
|
|
128
54
|
end
|
129
55
|
|
130
56
|
def eref_localities1(target, type, from, to, delim, lang = "en")
|
57
|
+
return "" if type == "anchor"
|
131
58
|
subsection = from&.text&.match(/\./)
|
132
59
|
type = type.downcase
|
133
60
|
return l10n(eref_localities1_zh(target, type, from, to, delim)) if lang == "zh"
|
@@ -142,12 +69,12 @@ module IsoDoc
|
|
142
69
|
end
|
143
70
|
|
144
71
|
def prefix_container(container, linkend, target)
|
145
|
-
delim = anchor(target, :type) == "listitem" ? " " : ", "
|
146
|
-
l10n(anchor(container, :xref) + delim + linkend)
|
72
|
+
delim = @xrefs.anchor(target, :type) == "listitem" ? " " : ", "
|
73
|
+
l10n(@xrefs.anchor(container, :xref) + delim + linkend)
|
147
74
|
end
|
148
75
|
|
149
76
|
def example_span_label(node, div, name)
|
150
|
-
n =
|
77
|
+
n = @xrefs.get[node["id"]]
|
151
78
|
div.span **{ class: "example_label" } do |p|
|
152
79
|
lbl = (n.nil? || n[:label].nil? || n[:label].empty?) ? @example_lbl :
|
153
80
|
l10n("#{@example_lbl} #{n[:label]}")
|
@@ -219,21 +146,6 @@ module IsoDoc
|
|
219
146
|
""
|
220
147
|
end
|
221
148
|
|
222
|
-
def reference_names(ref)
|
223
|
-
super
|
224
|
-
@anchors[ref["id"]] = { xref: @anchors[ref["id"]][:xref].
|
225
|
-
sub(/ \(All Parts\)/i, "") }
|
226
|
-
end
|
227
|
-
|
228
|
-
def table_footnote_reference_format(a)
|
229
|
-
a.content = a.content + ")"
|
230
|
-
end
|
231
|
-
|
232
|
-
def clause_parse_title(node, div, c1, out)
|
233
|
-
return inline_header_title(out, node, c1) if c1.nil?
|
234
|
-
super
|
235
|
-
end
|
236
|
-
|
237
149
|
def cleanup(docxml)
|
238
150
|
super
|
239
151
|
table_th_center(docxml)
|
@@ -247,14 +159,57 @@ module IsoDoc
|
|
247
159
|
end
|
248
160
|
end
|
249
161
|
|
250
|
-
def
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
162
|
+
def formula_where(dl, out)
|
163
|
+
return if dl.nil?
|
164
|
+
return super unless (dl&.xpath(ns("./dt"))&.size == 1 &&
|
165
|
+
dl&.at(ns("./dd"))&.elements&.size == 1 &&
|
166
|
+
dl&.at(ns("./dd/p")))
|
167
|
+
out.span **{ class: "zzMoveToFollowing" } do |s|
|
168
|
+
s << "#{@where_lbl} "
|
169
|
+
dl.at(ns("./dt")).children.each { |n| parse(n, s) }
|
170
|
+
s << " "
|
171
|
+
end
|
172
|
+
parse(dl.at(ns("./dd/p")), out)
|
173
|
+
end
|
174
|
+
|
175
|
+
def admonition_parse(node, out)
|
176
|
+
type = node["type"]
|
177
|
+
name = admonition_name(node, type)
|
178
|
+
out.div **{ id: node["id"], class: admonition_class(node) } do |div|
|
179
|
+
node.first_element_child.name == "p" ?
|
180
|
+
admonition_p_parse(node, div, name) : admonition_parse1(node, div, name)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def admonition_parse1(node, div, name)
|
185
|
+
div.p do |p|
|
186
|
+
admonition_name_parse(node, p, name) if name
|
187
|
+
end
|
188
|
+
node.children.each { |n| parse(n, div) unless n.name == "name" }
|
189
|
+
end
|
190
|
+
|
191
|
+
def admonition_p_parse(node, div, name)
|
192
|
+
div.p do |p|
|
193
|
+
admonition_name_parse(node, p, name) if name
|
194
|
+
node.first_element_child.children.each { |n| parse(n, p) }
|
195
|
+
end
|
196
|
+
node.element_children[1..-1].each { |n| parse(n, div) }
|
197
|
+
end
|
198
|
+
|
199
|
+
def admonition_name_parse(_node, div, name)
|
200
|
+
name.children.each { |n| parse(n, div) }
|
201
|
+
div << " — "
|
202
|
+
end
|
203
|
+
|
204
|
+
def figure_name_parse(node, div, name)
|
205
|
+
lbl = @xrefs.anchor(node['id'], :label, false)
|
206
|
+
lbl = nil if labelled_ancestor(node) && node.ancestors("figure").empty?
|
207
|
+
return if lbl.nil? && name.nil?
|
208
|
+
div.p **{ class: "FigureTitle", style: "text-align:center;" } do |p|
|
209
|
+
figname = node.parent.name == "figure" ? "" : "#{@figure_lbl} "
|
210
|
+
lbl.nil? or p << l10n("#{figname}#{lbl}")
|
211
|
+
name and !lbl.nil? and p << " — "
|
212
|
+
name and name.children.each { |n| parse(n, div) }
|
258
213
|
end
|
259
214
|
end
|
260
215
|
end
|
@@ -238,8 +238,12 @@ lang=EN-GB style='font-size:10.0pt;mso-bidi-font-size:11.0pt'>
|
|
238
238
|
</div>
|
239
239
|
|
240
240
|
<div style='mso-element:footer' id=f4>
|
241
|
-
|
241
|
+
{% if ics %}
|
242
242
|
<p class=MsoFooter><b style='mso-bidi-font-weight:normal'><span>ICS {{ ics }}<o:p></o:p></span></b></p>
|
243
|
+
{% endif %}
|
244
|
+
{% if keywords.size > 0 %}
|
245
|
+
<p class=MsoFooter><b>Descriptors:</b> {{ keywords | join: ", " }}</p>
|
246
|
+
{% endif %}
|
243
247
|
|
244
248
|
<p class=MsoFooter style='margin-top:0cm'><span lang=EN-AU style='font-size:
|
245
249
|
10.0pt;mso-ansi-language:EN-AU'>Price based on </span><!--[if supportFields]><span
|
@@ -61,3 +61,12 @@ name="CVP_Secretariat_Loca">Secretariat: {{ secretariat }}
|
|
61
61
|
<div id="boilerplate-license-destination"/>
|
62
62
|
</div>
|
63
63
|
{% endif %}
|
64
|
+
|
65
|
+
{% if ics %}
|
66
|
+
<p><b>ICS:</b> {{ ics }}</p>
|
67
|
+
{% endif %}
|
68
|
+
|
69
|
+
{% if keywords.size > 0 %}
|
70
|
+
<p><b>Descriptors:</b> {{ keywords | join: ", " }}
|
71
|
+
{% endif %}
|
72
|
+
|