metanorma-ogc 1.1.1 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +2 -0
  3. data/lib/asciidoctor/ogc/boilerplate.xml +3 -3
  4. data/lib/asciidoctor/ogc/converter.rb +12 -8
  5. data/lib/asciidoctor/ogc/front.rb +8 -20
  6. data/lib/asciidoctor/ogc/isodoc.rng +16 -7
  7. data/lib/asciidoctor/ogc/validate.rb +13 -25
  8. data/lib/isodoc/ogc/base_convert.rb +33 -48
  9. data/lib/isodoc/ogc/biblio.rb +33 -4
  10. data/lib/isodoc/ogc/html/_coverpage.css +195 -0
  11. data/lib/isodoc/ogc/html/header_wp.html +210 -0
  12. data/lib/isodoc/ogc/html/htmlstyle.css +1084 -0
  13. data/lib/isodoc/ogc/html/logo.png +0 -0
  14. data/lib/isodoc/ogc/html/ogc.css +838 -0
  15. data/lib/isodoc/ogc/html/ogc.scss +4 -2
  16. data/lib/isodoc/ogc/html/ogc_wp.css +758 -0
  17. data/lib/isodoc/ogc/html/ogc_wp.scss +724 -0
  18. data/lib/isodoc/ogc/html/word_ogc_intro_wp.html +14 -0
  19. data/lib/isodoc/ogc/html/word_ogc_titlepage_wp.html +175 -0
  20. data/lib/isodoc/ogc/html/wordstyle.css +1253 -0
  21. data/lib/isodoc/ogc/html/wordstyle.scss +8 -9
  22. data/lib/isodoc/ogc/html/wordstyle_wp.css +1181 -0
  23. data/lib/isodoc/ogc/html/wordstyle_wp.scss +1093 -0
  24. data/lib/isodoc/ogc/html_convert.rb +3 -0
  25. data/lib/isodoc/ogc/i18n-en.yaml +1 -0
  26. data/lib/isodoc/ogc/i18n.rb +10 -0
  27. data/lib/isodoc/ogc/init.rb +41 -0
  28. data/lib/isodoc/ogc/metadata.rb +31 -28
  29. data/lib/isodoc/ogc/ogc.abstract-specification-topic.xsl +1899 -1618
  30. data/lib/isodoc/ogc/ogc.best-practice.xsl +1899 -1618
  31. data/lib/isodoc/ogc/ogc.change-request-supporting-document.xsl +1899 -1618
  32. data/lib/isodoc/ogc/ogc.community-practice.xsl +1899 -1618
  33. data/lib/isodoc/ogc/ogc.community-standard.xsl +1899 -1618
  34. data/lib/isodoc/ogc/ogc.discussion-paper.xsl +1899 -1618
  35. data/lib/isodoc/ogc/ogc.engineering-report.xsl +1899 -1618
  36. data/lib/isodoc/ogc/ogc.other.xsl +1899 -1618
  37. data/lib/isodoc/ogc/ogc.policy.xsl +1899 -1618
  38. data/lib/isodoc/ogc/ogc.reference-model.xsl +1899 -1618
  39. data/lib/isodoc/ogc/ogc.release-notes.xsl +1899 -1618
  40. data/lib/isodoc/ogc/ogc.standard.xsl +1899 -1618
  41. data/lib/isodoc/ogc/ogc.test-suite.xsl +1899 -1618
  42. data/lib/isodoc/ogc/ogc.user-guide.xsl +1899 -1618
  43. data/lib/isodoc/ogc/ogc.white-paper.xsl +2264 -2218
  44. data/lib/isodoc/ogc/presentation_xml_convert.rb +134 -1
  45. data/lib/isodoc/ogc/reqt.rb +91 -124
  46. data/lib/isodoc/ogc/sections.rb +18 -64
  47. data/lib/isodoc/ogc/word_convert.rb +23 -3
  48. data/lib/isodoc/ogc/xref.rb +28 -23
  49. data/lib/metanorma/ogc/version.rb +1 -1
  50. data/metanorma-ogc.gemspec +3 -4
  51. metadata +34 -34
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2421990413977914ea161a6565552ab89b79410f8565c55c2e075a47d67c23e
4
- data.tar.gz: 84d00c4e381efc9fe62e9b1d9d66950f63133b92e559d7297282afdd4af20ab3
3
+ metadata.gz: c2015d66f65b872e8bd41ffbc65c7a9ebb50228937f7efb5ee32a5e254f41ff2
4
+ data.tar.gz: ca391594fc897a87f4c20c64776660737d6f702eb2a7a58d1125fbb49a7078be
5
5
  SHA512:
6
- metadata.gz: cf9c21efa5e92165362d868acb0dd12db79c99ec47251e49807234641ff06d71161525d06620a9c941471a9fad6ec8ff1133e629de6a88c2382bc8def66235c1
7
- data.tar.gz: a25fe7ef0b9a0db87259fd02683499906a8736e8b97f87973a6173ec25ab201cfb20e44099964a22c1e09d5797f8260b5d2d1e3f8bf7d5e90f253048cccc22a9
6
+ metadata.gz: f8b982136f75e99af1b8f834714aa420f050ba75b85a473cc1175b8f393abcffbd1fc23991e3d13f5888875b1e81e75c2b7bc630e979591eb042562d3540d0c3
7
+ data.tar.gz: 447069d2e3be876e342c3730de38e1e564e42b672e701cdbadbafc331a1cc0bc2ac76fea1c89402f118afcd86eb8e81d7bb0a1657b88822fe515c0f32969ceb8
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
+ require 'isodoc/gem_tasks'
3
4
 
5
+ IsoDoc::GemTasks.install
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
8
  task :default => :spec
@@ -3,16 +3,16 @@
3
3
  <clause>
4
4
  <title>Copyright notice</title>
5
5
 
6
- <p>Copyright
6
+ <p align="center">Copyright
7
7
  © {{ docyear }} Open Geospatial Consortium<br/>
8
8
  To obtain additional rights of use, visit
9
9
  <link target="http://www.opengeospatial.org/legal/">http://www.opengeospatial.org/legal/</link></p>
10
10
  </clause>
11
11
  <clause>
12
12
  <title>Note</title>
13
- <p>Attention is drawn to the possibility that some of the elements of this document may be the subject of patent rights. The Open Geospatial Consortium shall not be held responsible for identifying any or all such patent rights.</p>
13
+ <p align="left">Attention is drawn to the possibility that some of the elements of this document may be the subject of patent rights. The Open Geospatial Consortium shall not be held responsible for identifying any or all such patent rights.</p>
14
14
 
15
- <p>Recipients of this document are requested to submit, with their comments, notification of any relevant patent claims or other intellectual property rights of which they may be aware that might be infringed by any implementation of the standard set forth in this document, and to provide supporting documentation.</p>
15
+ <p align="left">Recipients of this document are requested to submit, with their comments, notification of any relevant patent claims or other intellectual property rights of which they may be aware that might be infringed by any implementation of the standard set forth in this document, and to provide supporting documentation.</p>
16
16
  </clause>
17
17
  </copyright-statement>
18
18
  <license-statement>
@@ -25,7 +25,7 @@ module Asciidoctor
25
25
  end
26
26
 
27
27
  def doctype(node)
28
- d = node.attr("doctype")
28
+ d = super
29
29
  d1 = ::IsoDoc::Ogc::DOCTYPE_ABBR.invert[d] and d = d1
30
30
  unless %w{abstract-specification-topic best-practice
31
31
  change-request-supporting-document community-practice
@@ -55,9 +55,12 @@ module Asciidoctor
55
55
  def outputs(node, ret)
56
56
  File.open(@filename + ".xml", "w:UTF-8") { |f| f.write(ret) }
57
57
  presentation_xml_converter(node).convert(@filename + ".xml")
58
- html_converter(node).convert(@filename + ".presentation.xml", nil, false, "#{@filename}.html")
59
- doc_converter(node).convert(@filename + ".presentation.xml", nil, false, "#{@filename}.doc")
60
- pdf_converter(node)&.convert(@filename + ".presentation.xml", nil, false, "#{@filename}.pdf")
58
+ html_converter(node).convert(@filename + ".presentation.xml",
59
+ nil, false, "#{@filename}.html")
60
+ doc_converter(node).convert(@filename + ".presentation.xml",
61
+ nil, false, "#{@filename}.doc")
62
+ pdf_converter(node)&.convert(@filename + ".presentation.xml",
63
+ nil, false, "#{@filename}.pdf")
61
64
  end
62
65
 
63
66
  def validate(doc)
@@ -84,15 +87,16 @@ module Asciidoctor
84
87
  end
85
88
 
86
89
  def clause_parse(attrs, xml, node)
87
- clausetype = node&.attr("heading")&.downcase || node.title.downcase
88
- if clausetype == "submitters" then submitters_parse(attrs, xml, node)
89
- else
90
- super
90
+ case clausetype = node&.attr("heading")&.downcase || node.title.downcase
91
+ when "submitters" then return submitters_parse(attrs, xml, node)
92
+ when "conformance" then attrs = attrs.merge(type: "conformance")
91
93
  end
94
+ super
92
95
  end
93
96
 
94
97
  def submitters_parse(attrs, xml, node)
95
98
  xml.submitters **attr_code(attrs) do |xml_section|
99
+ xml_section.title @i18n.submitters
96
100
  xml_section << node.content
97
101
  end
98
102
  end
@@ -12,8 +12,8 @@ module Asciidoctor
12
12
 
13
13
  def corporate_author(node, xml)
14
14
  return unless node.attr("submitting-organizations")
15
- HTMLEntities.new.decode(node.attr("submitting-organizations")).
16
- split(/;[ ]*/).each do |org|
15
+ csv_split(HTMLEntities.new.decode(node.attr("submitting-organizations")),
16
+ ";")&.each do |org|
17
17
  xml.contributor do |c|
18
18
  c.role **{ type: "author" }
19
19
  c.organization do |a|
@@ -63,13 +63,8 @@ module Asciidoctor
63
63
  end
64
64
  end
65
65
 
66
- def metadata_publisher(node, xml)
67
- xml.contributor do |c|
68
- c.role **{ type: "publisher" }
69
- c.organization do |a|
70
- a.name Metanorma::Ogc::ORGANIZATION_NAME_SHORT
71
- end
72
- end
66
+ def default_publisher
67
+ "Open Geospatial Consortium"
73
68
  end
74
69
 
75
70
  def metadata_committee(node, xml)
@@ -109,7 +104,7 @@ module Asciidoctor
109
104
  end
110
105
 
111
106
  def externalurl(node)
112
- if node.attr("doctype") == "engineering-report"
107
+ if doctype(node) == "engineering-report"
113
108
  "http://www.opengis.net/doc/PER/t14-#{node.attr('referenceurlid')}"
114
109
  else
115
110
  node.attr('referenceurlid')
@@ -123,16 +118,9 @@ module Asciidoctor
123
118
  end
124
119
 
125
120
  def metadata_copyright(node, xml)
126
- from = node.attr("copyright-year") || node.attr("copyrightyear") ||
127
- Date.today.year
128
- xml.copyright do |c|
129
- c.from from
130
- c.owner do |owner|
131
- owner.organization do |o|
132
- o.name Metanorma::Ogc::ORGANIZATION_NAME_SHORT
133
- end
134
- end
135
- end
121
+ node.attr("copyrightyear") and
122
+ node.set_attr("copyright-year", node.attr("copyrightyear"))
123
+ super
136
124
  end
137
125
 
138
126
  def metadata_date(node, xml)
@@ -42,8 +42,11 @@
42
42
  </define>
43
43
  <define name="xref">
44
44
  <element name="xref">
45
+ <!-- attribute target { xsd:IDREF }, -->
45
46
  <attribute name="target">
46
- <data type="IDREF"/>
47
+ <data type="string">
48
+ <param name="pattern">\i\c*|\c+#\c+</param>
49
+ </data>
47
50
  </attribute>
48
51
  <optional>
49
52
  <attribute name="type">
@@ -922,6 +925,9 @@
922
925
  <optional>
923
926
  <attribute name="script"/>
924
927
  </optional>
928
+ <optional>
929
+ <attribute name="type"/>
930
+ </optional>
925
931
  <optional>
926
932
  <attribute name="obligation">
927
933
  <choice>
@@ -961,9 +967,6 @@
961
967
  </define>
962
968
  <define name="content-subsection">
963
969
  <element name="clause">
964
- <optional>
965
- <attribute name="type"/>
966
- </optional>
967
970
  <ref name="Content-Section"/>
968
971
  </element>
969
972
  </define>
@@ -992,6 +995,9 @@
992
995
  </choice>
993
996
  </attribute>
994
997
  </optional>
998
+ <optional>
999
+ <attribute name="type"/>
1000
+ </optional>
995
1001
  <optional>
996
1002
  <ref name="section-title"/>
997
1003
  </optional>
@@ -1011,9 +1017,6 @@
1011
1017
  </define>
1012
1018
  <define name="clause">
1013
1019
  <element name="clause">
1014
- <optional>
1015
- <attribute name="type"/>
1016
- </optional>
1017
1020
  <ref name="Clause-Section"/>
1018
1021
  </element>
1019
1022
  </define>
@@ -1042,6 +1045,9 @@
1042
1045
  </choice>
1043
1046
  </attribute>
1044
1047
  </optional>
1048
+ <optional>
1049
+ <attribute name="type"/>
1050
+ </optional>
1045
1051
  <optional>
1046
1052
  <ref name="section-title"/>
1047
1053
  </optional>
@@ -1180,6 +1186,9 @@
1180
1186
  <optional>
1181
1187
  <attribute name="script"/>
1182
1188
  </optional>
1189
+ <optional>
1190
+ <attribute name="type"/>
1191
+ </optional>
1183
1192
  <optional>
1184
1193
  <attribute name="obligation">
1185
1194
  <choice>
@@ -49,35 +49,25 @@ module Asciidoctor
49
49
  [
50
50
  {
51
51
  msg: "Prefatory material must be followed by (clause) Scope",
52
- val: [{ tag: "clause", title: "Scope" }],
52
+ val: ["./self::clause[@type = 'scope']" ]
53
53
  },
54
54
  {
55
55
  msg: "Scope must be followed by Conformance",
56
- val: [{ tag: "clause", title: "Conformance" }],
56
+ val: ["./self::clause[@type = 'conformance']" ]
57
57
  },
58
58
  {
59
59
  msg: "Normative References must be followed by "\
60
60
  "Terms and Definitions",
61
- val: [
62
- { tag: "terms", title: "Terms and definitions" },
63
- { tag: "clause", title: "Terms and definitions" },
64
- {
65
- tag: "terms",
66
- title: "Terms, definitions, symbols and abbreviated terms",
67
- },
68
- {
69
- tag: "clause",
70
- title: "Terms, definitions, symbols and abbreviated terms",
71
- },
72
- ],
61
+ val: ["./self::terms | .//terms"]
73
62
  },
74
63
  ].freeze
75
64
 
76
65
  def seqcheck(names, msg, accepted)
77
66
  n = names.shift
78
- unless accepted.include? n
67
+ return [] if n.nil?
68
+ test = accepted.map { |a| n.at(a) }
69
+ if test.all? { |a| a.nil? }
79
70
  @log.add("Style", nil, msg)
80
- names = []
81
71
  end
82
72
  names
83
73
  end
@@ -85,23 +75,21 @@ module Asciidoctor
85
75
  def sections_sequence_validate(root)
86
76
  return unless STANDARDTYPE.include?(
87
77
  root&.at("//bibdata/ext/doctype")&.text)
88
- f = root.at("//sections").elements
89
- names = f.map { |s| { tag: s.name, title: s&.at("./title")&.text } }
90
- names = seqcheck(names, SEQ[0][:msg], SEQ[0][:val]) || return
91
- names = seqcheck(names, SEQ[1][:msg], SEQ[1][:val]) || return
92
- names = seqcheck(names, SEQ[2][:msg], SEQ[2][:val]) || return
78
+ names = root.xpath("//sections/* | //bibliography/*")
79
+ names = seqcheck(names, SEQ[0][:msg], SEQ[0][:val])
80
+ names = seqcheck(names, SEQ[1][:msg], SEQ[1][:val])
81
+ names = seqcheck(names, SEQ[2][:msg], SEQ[2][:val])
93
82
  n = names.shift
94
- if !n.nil? && n[:tag] == "definitions"
83
+ if n&.at("./self::definitions")
95
84
  n = names.shift
96
85
  end
97
- unless n
86
+ if n.nil? || n.name != "clause"
98
87
  @log.add("Style", nil, "Document must contain at least one clause")
99
88
  return
100
89
  end
101
90
  root.at("//references | //clause[descendant::references]"\
102
91
  "[not(parent::clause)]") or
103
- seqcheck([{tag: "clause"}],
104
- "Normative References are mandatory", [{tag: "references"}])
92
+ @log.add("Style", nil, "Normative References are mandatory")
105
93
  end
106
94
 
107
95
  def preface_sequence_validate(root)
@@ -1,6 +1,4 @@
1
1
  require "isodoc"
2
- require_relative "metadata"
3
- require_relative "reqt"
4
2
  require_relative "biblio"
5
3
  require_relative "sections"
6
4
  require "fileutils"
@@ -8,44 +6,12 @@ require "fileutils"
8
6
  module IsoDoc
9
7
  module Ogc
10
8
  module BaseConvert
11
- def metadata_init(lang, script, labels)
12
- @meta = Metadata.new(lang, script, labels)
13
- end
14
-
15
- def xref_init(lang, script, klass, labels, options)
16
- @xrefs = Xref.new(lang, script, klass, labels, options)
17
- end
18
-
19
- def fileloc(loc)
20
- File.join(File.dirname(__FILE__), loc)
21
- end
22
-
23
9
  def cleanup(docxml)
24
- requirement_table_cleanup(docxml)
10
+ #requirement_table_cleanup(docxml)
25
11
  super
26
12
  term_cleanup(docxml)
27
13
  end
28
14
 
29
- # table nested in table: merge label and caption into a single row
30
- def requirement_table_cleanup(docxml)
31
- docxml.xpath("//table[@class = 'recommendclass']//table").each do |t|
32
- x = t.at("./thead") and x.replace(x.children)
33
- x = t.at("./tbody") and x.replace(x.children)
34
- x = t.at("./tfoot") and x.replace(x.children)
35
- if x = t.at("./tr/th[@colspan = '2']") and
36
- y = t.at("./tr/td[@colspan = '2']")
37
- x["colspan"] = "1"
38
- y["colspan"] = "1"
39
- x.name = "td"
40
- p = x.at("./p[@class = 'RecommendationTitle']") and
41
- p.delete("class")
42
- x << y.dup
43
- y.parent.remove
44
- end
45
- t.replace(t.children)
46
- end
47
- end
48
-
49
15
  def term_cleanup(docxml)
50
16
  docxml.xpath("//p[@class = 'Terms']").each do |d|
51
17
  h2 = d.at("./preceding-sibling::*[@class = 'TermNum'][1]")
@@ -55,14 +21,6 @@ module IsoDoc
55
21
  docxml
56
22
  end
57
23
 
58
- def load_yaml(lang, script)
59
- y = if @i18nyaml then YAML.load_file(@i18nyaml)
60
- else
61
- YAML.load_file(File.join(File.dirname(__FILE__), "i18n-en.yaml"))
62
- end
63
- super.merge(y)
64
- end
65
-
66
24
  def example_parse(node, out)
67
25
  name = node.at(ns("./name"))
68
26
  example_name_parse(node, out, name) #if name
@@ -73,17 +31,19 @@ module IsoDoc
73
31
  end
74
32
 
75
33
  def example_name_parse(node, div, name)
76
- lbl = @xrefs.anchor(node['id'], :label, false)
77
34
  div.p **{ class: "SourceTitle", style: "text-align:center;" } do |p|
78
- lbl.nil? or p << l10n("#{@example_lbl} #{lbl}")
79
- name and !lbl.nil? and p << "&nbsp;&mdash; "
80
35
  name&.children&.each { |n| parse(n, p) }
81
36
  end
82
37
  end
83
38
 
84
39
  def middle_clause
85
- "//clause[parent::sections][not(xmlns:title = 'Scope' or "\
86
- "xmlns:title = 'Conformance')][not(descendant::terms)]"
40
+ "//clause[parent::sections][not(@type = 'scope' or "\
41
+ "@type = 'conformance')][not(descendant::terms)]"
42
+ end
43
+
44
+ def is_clause?(name)
45
+ return true if name == "submitters"
46
+ super
87
47
  end
88
48
 
89
49
  def middle(isoxml, out)
@@ -98,6 +58,31 @@ module IsoDoc
98
58
  annex isoxml, out
99
59
  bibliography isoxml, out
100
60
  end
61
+
62
+ def table_attrs(node)
63
+ ret = super
64
+ %w(recommendation requirement permission).include?(node["class"]) and
65
+ ret = ret.merge(class: node["type"], style:
66
+ "border-collapse:collapse;border-spacing:0;"\
67
+ "#{keep_style(node)}")
68
+ ret
69
+ end
70
+
71
+ def make_tr_attr(td, row, totalrows, header)
72
+ ret = super
73
+ if td.at("./ancestor::xmlns:table[@class = 'recommendation'] | "\
74
+ "./ancestor::xmlns:table[@class = 'requirement'] | "\
75
+ "./ancestor::xmlns:table[@class = 'permission']")
76
+ ret[:style] = "vertical-align:top;"
77
+ ret[:class] = "recommend"
78
+ end
79
+ ret
80
+ end
81
+
82
+ def para_class(node)
83
+ return node["class"] if node["class"]
84
+ super
85
+ end
101
86
  end
102
87
  end
103
88
  end
@@ -60,13 +60,34 @@ module IsoDoc
60
60
  return [multiplenames_and(names), (abbrs.map { |x| x.text }).join("/")]
61
61
  end
62
62
 
63
+ def extract_author(b)
64
+ c = b.xpath(ns("./contributor[role/@type = 'author']"))
65
+ c = b.xpath(ns("./contributor[role/@type = 'editor']")) if c.empty?
66
+ return nil if c.empty?
67
+ c.map do |c1|
68
+ c1&.at(ns("./organization/name"))&.text || extract_person_name(c1)
69
+ end.reject { |e| e.nil? || e.empty? }.join(", ")
70
+ end
71
+
72
+ def extract_person_name(b)
73
+ p = b.at(ns("./person/name")) or return
74
+ c = p.at(ns("./completename")) and return c.text
75
+ s = p&.at(ns("./surname"))&.text or return
76
+ i = p.xpath(ns("./initial")) and
77
+ front = i.map { |e| e.text.gsub(/[^[:upper:]]/, "") }.join("")
78
+ i.empty? and f = p.xpath(ns("./forename")) and
79
+ front = f.map { |e| e.text[0].upcase }.join("")
80
+ front ? "#{s} #{front}" : s
81
+ end
82
+
83
+
63
84
  def date_render(date)
64
85
  return nil if date.nil?
65
86
  on = date&.at(ns("./on"))&.text
66
87
  from = date&.at(ns("./from"))&.text
67
88
  to = date&.at(ns("./to"))&.text
68
- return on if on
69
- return "#{from}&ndash;#{to}" if from
89
+ return on if on && !on.empty?
90
+ return "#{from}&ndash;#{to}" if from && !from.empty?
70
91
  nil
71
92
  end
72
93
 
@@ -82,17 +103,24 @@ module IsoDoc
82
103
  b.at(ns("./place"))
83
104
  end
84
105
 
106
+ def extract_uri(b)
107
+ b.at(ns("./uri"))
108
+ end
109
+
85
110
  # {author}: {document identifier}, {title}. {publisher}, {city} ({year})
86
111
  def standard_citation(out, b)
87
112
  if ftitle = b.at(ns("./formattedref"))
88
113
  ftitle&.children&.each { |n| parse(n, out) }
89
114
  else
90
115
  pub, pub_abbrev = extract_publisher(b)
116
+ author = extract_author(b)
91
117
  c = extract_city(b)
92
118
  y = extract_year(b)
93
- out << "#{pub_abbrev}: " if pub_abbrev
119
+ u = extract_uri(b)
120
+ out << "#{author || pub_abbrev}: " if author || pub_abbrev
94
121
  id = render_identifier(inline_bibitem_ref_code(b))
95
122
  out << id[1] if id[1]
123
+ out << " (Draft)" if ogc_draft_ref?(b)
96
124
  out << ", "
97
125
  out.i do |i|
98
126
  iso_title(b)&.children&.each { |n| parse(n, i) }
@@ -102,7 +130,8 @@ module IsoDoc
102
130
  out << ", " if pub && c
103
131
  c&.children&.each { |n| parse(n, out) }
104
132
  out << " " if (pub || c) && y
105
- out << "(#{y})." if y
133
+ out << "(#{y}). " if y
134
+ u and out << "<a href='#{u.text}'>#{u.text}</a>"
106
135
  end
107
136
  end
108
137