relaton 3.0.7 → 3.1.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.
Files changed (240) hide show
  1. checksums.yaml +4 -4
  2. data/bin/relaton +20 -0
  3. data/lib/relaton/3gpp/processor.rb +1 -1
  4. data/lib/relaton/3gpp.rb +3 -3
  5. data/lib/relaton/adobe/processor.rb +1 -1
  6. data/lib/relaton/adobe.rb +3 -3
  7. data/lib/relaton/bib/item_data.rb +2 -2
  8. data/lib/relaton/bib/model/docidentifier.rb +17 -2
  9. data/lib/relaton/bib.rb +2 -2
  10. data/lib/relaton/bibcollection.rb +146 -0
  11. data/lib/relaton/bibdata.rb +92 -0
  12. data/lib/relaton/bipm/data_outcomes_parser.rb +6 -1
  13. data/lib/relaton/bipm/processor.rb +1 -1
  14. data/lib/relaton/bipm.rb +3 -3
  15. data/lib/relaton/bsi/processor.rb +1 -1
  16. data/lib/relaton/bsi.rb +1 -1
  17. data/lib/relaton/calconnect/data_fetcher.rb +2 -2
  18. data/lib/relaton/calconnect/model/item.rb +1 -1
  19. data/lib/relaton/calconnect/processor.rb +1 -1
  20. data/lib/relaton/calconnect.rb +2 -2
  21. data/lib/relaton/ccsds/data/fetcher.rb +1 -1
  22. data/lib/relaton/ccsds/model/item.rb +1 -1
  23. data/lib/relaton/ccsds/processor.rb +1 -1
  24. data/lib/relaton/ccsds/util.rb +1 -1
  25. data/lib/relaton/ccsds.rb +1 -1
  26. data/lib/relaton/cen/processor.rb +1 -1
  27. data/lib/relaton/cen.rb +2 -2
  28. data/lib/relaton/cie/data_fetcher.rb +3 -3
  29. data/lib/relaton/cie/processor.rb +1 -1
  30. data/lib/relaton/cie.rb +2 -2
  31. data/lib/relaton/cli/base_convertor.rb +134 -0
  32. data/lib/relaton/cli/command.rb +396 -0
  33. data/lib/relaton/cli/data_fetcher.rb +20 -0
  34. data/lib/relaton/cli/frontend/dist/404.html +18 -0
  35. data/lib/relaton/cli/frontend/dist/app.iife.js +1 -0
  36. data/lib/relaton/cli/frontend/dist/style.css +3 -0
  37. data/lib/relaton/cli/frontend/index.html +14 -0
  38. data/lib/relaton/cli/frontend/package-lock.json +2945 -0
  39. data/lib/relaton/cli/frontend/package.json +29 -0
  40. data/lib/relaton/cli/frontend/public/404.html +18 -0
  41. data/lib/relaton/cli/frontend/src/App.test.ts +501 -0
  42. data/lib/relaton/cli/frontend/src/App.vue +506 -0
  43. data/lib/relaton/cli/frontend/src/app.ts +90 -0
  44. data/lib/relaton/cli/frontend/src/components/DocumentDetail.test.ts +142 -0
  45. data/lib/relaton/cli/frontend/src/components/DocumentDetail.vue +228 -0
  46. data/lib/relaton/cli/frontend/src/components/DocumentRow.vue +88 -0
  47. data/lib/relaton/cli/frontend/src/components/Icon.test.ts +44 -0
  48. data/lib/relaton/cli/frontend/src/components/Icon.vue +55 -0
  49. data/lib/relaton/cli/frontend/src/components/Pagination.vue +26 -0
  50. data/lib/relaton/cli/frontend/src/dev/main.ts +100 -0
  51. data/lib/relaton/cli/frontend/src/dev/sample-data.json +71 -0
  52. data/lib/relaton/cli/frontend/src/lib/filter.test.ts +88 -0
  53. data/lib/relaton/cli/frontend/src/lib/filter.ts +95 -0
  54. data/lib/relaton/cli/frontend/src/lib/hydrate.test.ts +102 -0
  55. data/lib/relaton/cli/frontend/src/lib/hydrate.ts +55 -0
  56. data/lib/relaton/cli/frontend/src/lib/shards.test.ts +319 -0
  57. data/lib/relaton/cli/frontend/src/lib/shards.ts +183 -0
  58. data/lib/relaton/cli/frontend/src/lib/types.ts +148 -0
  59. data/lib/relaton/cli/frontend/src/lib/url.test.ts +151 -0
  60. data/lib/relaton/cli/frontend/src/lib/url.ts +111 -0
  61. data/lib/relaton/cli/frontend/src/shims-vue.d.ts +5 -0
  62. data/lib/relaton/cli/frontend/src/styles/index.css +27 -0
  63. data/lib/relaton/cli/frontend/src/test/fetch.ts +58 -0
  64. data/lib/relaton/cli/frontend/tsconfig.json +20 -0
  65. data/lib/relaton/cli/frontend/vite.config.ts +42 -0
  66. data/lib/relaton/cli/frontend/vitest.config.ts +11 -0
  67. data/lib/relaton/cli/frontend_assets.rb +58 -0
  68. data/lib/relaton/cli/full_text_search.rb +98 -0
  69. data/lib/relaton/cli/i18n.yaml +1 -0
  70. data/lib/relaton/cli/index_item_normalizer.rb +380 -0
  71. data/lib/relaton/cli/index_site_generator.rb +1043 -0
  72. data/lib/relaton/cli/relaton_file.rb +290 -0
  73. data/lib/relaton/cli/subcommand_collection.rb +263 -0
  74. data/lib/relaton/cli/subcommand_db.rb +81 -0
  75. data/lib/relaton/cli/templates/_document.liquid +111 -0
  76. data/lib/relaton/cli/templates/_index.liquid +61 -0
  77. data/lib/relaton/cli/templates/index/page.liquid +20 -0
  78. data/lib/relaton/cli/templates/index-style.css +1017 -0
  79. data/lib/relaton/cli/util.rb +8 -0
  80. data/lib/relaton/cli/xml_convertor.rb +47 -0
  81. data/lib/relaton/cli/xml_to_html_renderer.rb +117 -0
  82. data/lib/relaton/cli/yaml_convertor.rb +102 -0
  83. data/lib/relaton/cli.rb +103 -0
  84. data/lib/relaton/core/processor.rb +1 -1
  85. data/lib/relaton/core.rb +1 -1
  86. data/lib/relaton/db/registry.rb +1 -1
  87. data/lib/relaton/db.rb +1 -1
  88. data/lib/relaton/doi/CLAUDE.md +1 -1
  89. data/lib/relaton/doi/parser.rb +2 -1
  90. data/lib/relaton/doi/processor.rb +1 -1
  91. data/lib/relaton/doi.rb +4 -4
  92. data/lib/relaton/easc/processor.rb +1 -1
  93. data/lib/relaton/easc.rb +3 -3
  94. data/lib/relaton/ecma/data_fetcher.rb +1 -1
  95. data/lib/relaton/ecma/processor.rb +1 -1
  96. data/lib/relaton/ecma.rb +2 -2
  97. data/lib/relaton/element_finder.rb +29 -0
  98. data/lib/relaton/etsi/data_fetcher.rb +2 -2
  99. data/lib/relaton/etsi/item.rb +1 -1
  100. data/lib/relaton/etsi/processor.rb +2 -2
  101. data/lib/relaton/etsi.rb +1 -1
  102. data/lib/relaton/evs/processor.rb +1 -1
  103. data/lib/relaton/evs.rb +2 -2
  104. data/lib/relaton/gb/CLAUDE.md +1 -1
  105. data/lib/relaton/gb/bibliography.rb +1 -1
  106. data/lib/relaton/gb/processor.rb +1 -1
  107. data/lib/relaton/gb.rb +2 -2
  108. data/lib/relaton/gost/processor.rb +1 -1
  109. data/lib/relaton/gost.rb +3 -3
  110. data/lib/relaton/iala/processor.rb +1 -1
  111. data/lib/relaton/iala.rb +3 -3
  112. data/lib/relaton/iana/data_fetcher.rb +1 -1
  113. data/lib/relaton/iana/processor.rb +1 -1
  114. data/lib/relaton/iana.rb +2 -2
  115. data/lib/relaton/iec/data_fetcher.rb +1 -1
  116. data/lib/relaton/iec/processor.rb +1 -1
  117. data/lib/relaton/iec.rb +2 -2
  118. data/lib/relaton/ieee/processor.rb +1 -1
  119. data/lib/relaton/ieee.rb +2 -2
  120. data/lib/relaton/ietf/CLAUDE.md +1 -1
  121. data/lib/relaton/ietf/data_fetcher.rb +1 -1
  122. data/lib/relaton/ietf/processor.rb +1 -1
  123. data/lib/relaton/ietf.rb +2 -2
  124. data/lib/relaton/iho/CLAUDE.md +3 -3
  125. data/lib/relaton/iho/bibliography.rb +41 -27
  126. data/lib/relaton/iho/hash_parser_v1.rb +1 -1
  127. data/lib/relaton/iho/processor.rb +4 -3
  128. data/lib/relaton/iho.rb +2 -2
  129. data/lib/relaton/index.rb +1 -1
  130. data/lib/relaton/isbn/processor.rb +1 -1
  131. data/lib/relaton/isbn.rb +1 -1
  132. data/lib/relaton/iso/hash_parser_v1.rb +1 -1
  133. data/lib/relaton/iso/processor.rb +1 -1
  134. data/lib/relaton/iso.rb +3 -3
  135. data/lib/relaton/itu/processor.rb +1 -1
  136. data/lib/relaton/itu.rb +3 -3
  137. data/lib/relaton/jcgm/processor.rb +1 -1
  138. data/lib/relaton/jcgm.rb +2 -2
  139. data/lib/relaton/jis/processor.rb +1 -1
  140. data/lib/relaton/jis.rb +2 -2
  141. data/lib/relaton/nist/CLAUDE.md +11 -1
  142. data/lib/relaton/nist/README.adoc +7 -1
  143. data/lib/relaton/nist/hit_collection.rb +25 -1
  144. data/lib/relaton/nist/mods_parser.rb +32 -8
  145. data/lib/relaton/nist/processor.rb +1 -1
  146. data/lib/relaton/nist/pubs_export.rb +69 -11
  147. data/lib/relaton/nist.rb +2 -2
  148. data/lib/relaton/oasis/processor.rb +1 -1
  149. data/lib/relaton/oasis.rb +2 -2
  150. data/lib/relaton/ogc/CLAUDE.md +1 -1
  151. data/lib/relaton/ogc/processor.rb +1 -1
  152. data/lib/relaton/ogc.rb +2 -2
  153. data/lib/relaton/oiml/processor.rb +1 -1
  154. data/lib/relaton/oiml.rb +2 -2
  155. data/lib/relaton/omg/CLAUDE.md +1 -1
  156. data/lib/relaton/omg/processor.rb +1 -1
  157. data/lib/relaton/omg.rb +1 -1
  158. data/lib/relaton/plateau/CLAUDE.md +1 -1
  159. data/lib/relaton/plateau/data_fetcher.rb +1 -1
  160. data/lib/relaton/plateau/processor.rb +1 -1
  161. data/lib/relaton/plateau.rb +2 -2
  162. data/lib/relaton/render/general.rb +319 -0
  163. data/lib/relaton/render/iso690/csl_import.rb +129 -0
  164. data/lib/relaton/render/iso690/element.rb +153 -0
  165. data/lib/relaton/render/iso690/elements/access.rb +44 -0
  166. data/lib/relaton/render/iso690/elements/authorizer.rb +45 -0
  167. data/lib/relaton/render/iso690/elements/citeid.rb +39 -0
  168. data/lib/relaton/render/iso690/elements/component_part.rb +97 -0
  169. data/lib/relaton/render/iso690/elements/creator.rb +267 -0
  170. data/lib/relaton/render/iso690/elements/edition.rb +97 -0
  171. data/lib/relaton/render/iso690/elements/extent.rb +71 -0
  172. data/lib/relaton/render/iso690/elements/identifier.rb +46 -0
  173. data/lib/relaton/render/iso690/elements/location.rb +73 -0
  174. data/lib/relaton/render/iso690/elements/medium.rb +32 -0
  175. data/lib/relaton/render/iso690/elements/numeration.rb +26 -0
  176. data/lib/relaton/render/iso690/elements/production.rb +111 -0
  177. data/lib/relaton/render/iso690/elements/pub_date.rb +63 -0
  178. data/lib/relaton/render/iso690/elements/series.rb +66 -0
  179. data/lib/relaton/render/iso690/elements/size.rb +45 -0
  180. data/lib/relaton/render/iso690/elements/status.rb +30 -0
  181. data/lib/relaton/render/iso690/elements/stddoc.rb +40 -0
  182. data/lib/relaton/render/iso690/elements/title.rb +56 -0
  183. data/lib/relaton/render/iso690/elements/updated.rb +38 -0
  184. data/lib/relaton/render/iso690/elements.rb +67 -0
  185. data/lib/relaton/render/iso690/fields.rb +142 -0
  186. data/lib/relaton/render/iso690/i18n/en.yml +48 -0
  187. data/lib/relaton/render/iso690/i18n/fr.yml +14 -0
  188. data/lib/relaton/render/iso690/i18n/ja.yml +38 -0
  189. data/lib/relaton/render/iso690/i18n.rb +83 -0
  190. data/lib/relaton/render/iso690/identifier_kinds.rb +29 -0
  191. data/lib/relaton/render/iso690/index_renderer.rb +57 -0
  192. data/lib/relaton/render/iso690/kinds.rb +53 -0
  193. data/lib/relaton/render/iso690/renderer.rb +150 -0
  194. data/lib/relaton/render/iso690/rules/bipm.rb +186 -0
  195. data/lib/relaton/render/iso690/rules/jis.rb +103 -0
  196. data/lib/relaton/render/iso690/rules/nist.rb +384 -0
  197. data/lib/relaton/render/iso690/rules.rb +227 -0
  198. data/lib/relaton/render/iso690/style/locale.rb +59 -0
  199. data/lib/relaton/render/iso690/style/name_form.rb +87 -0
  200. data/lib/relaton/render/iso690/style/scheme.rb +33 -0
  201. data/lib/relaton/render/iso690/style/sort_rule.rb +25 -0
  202. data/lib/relaton/render/iso690/style/template_map.rb +40 -0
  203. data/lib/relaton/render/iso690/style/type_template.rb +45 -0
  204. data/lib/relaton/render/iso690/style.rb +227 -0
  205. data/lib/relaton/render/iso690/styles/apa.yml +45 -0
  206. data/lib/relaton/render/iso690/styles/author-date.yml +31 -0
  207. data/lib/relaton/render/iso690/styles/chicago.yml +49 -0
  208. data/lib/relaton/render/iso690/styles/ieee-sa.yml +45 -0
  209. data/lib/relaton/render/iso690/styles/jis-en.yml +68 -0
  210. data/lib/relaton/render/iso690/styles/jis-ja.yml +67 -0
  211. data/lib/relaton/render/iso690/styles/lncs.yml +54 -0
  212. data/lib/relaton/render/iso690/styles/mla.yml +56 -0
  213. data/lib/relaton/render/iso690/template.rb +248 -0
  214. data/lib/relaton/render/iso690.rb +28 -0
  215. data/lib/relaton/sae/CLAUDE.md +89 -0
  216. data/lib/relaton/sae/bibdata.rb +8 -0
  217. data/lib/relaton/sae/bibitem.rb +8 -0
  218. data/lib/relaton/sae/bibliography.rb +128 -0
  219. data/lib/relaton/sae/data_fetcher.rb +219 -0
  220. data/lib/relaton/sae/doi_code.rb +83 -0
  221. data/lib/relaton/sae/item.rb +10 -0
  222. data/lib/relaton/sae/item_data.rb +6 -0
  223. data/lib/relaton/sae/processor.rb +62 -0
  224. data/lib/relaton/sae/util.rb +9 -0
  225. data/lib/relaton/sae.rb +37 -0
  226. data/lib/relaton/un/CLAUDE.md +1 -1
  227. data/lib/relaton/un/processor.rb +1 -1
  228. data/lib/relaton/un.rb +1 -1
  229. data/lib/relaton/version.rb +1 -1
  230. data/lib/relaton/w3c/bibliography.rb +1 -1
  231. data/lib/relaton/w3c/data_fetcher.rb +1 -1
  232. data/lib/relaton/w3c/processor.rb +1 -1
  233. data/lib/relaton/w3c.rb +2 -2
  234. data/lib/relaton/xsf/data_fetcher.rb +1 -1
  235. data/lib/relaton/xsf/processor.rb +1 -1
  236. data/lib/relaton/xsf.rb +2 -2
  237. data/lib/relaton-cli.rb +17 -0
  238. data/lib/relaton-render.rb +12 -0
  239. data/lib/relaton.rb +3 -2
  240. metadata +203 -23
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ module Rules
7
+ # The JIS rule set, moved verbatim from the metanorma-jis
8
+ # flavor's JisElements: the host-title-first component part
9
+ # (with the 1.x orphaned close paren), the host-series
10
+ # fallback, and the book-family extent. Select from pack data:
11
+ # rules: { component_part: jis_component_part, ... }
12
+ module Jis
13
+ # The host of a part citation, 1.x parity: the host title,
14
+ # then the host's principal creator, closed by a paren the
15
+ # 1.x cleanup had orphaned when the host role came out empty
16
+ # ("Collected Essays UNICEF)")
17
+ class JisComponentPart < Elements::ComponentPart
18
+ def render
19
+ return "" if host_title_text.empty?
20
+
21
+ "#{host_title_text} #{host_creators}#{close_paren}".strip
22
+ end
23
+
24
+ private
25
+
26
+ def host_creators
27
+ author = Array(host&.contributor)
28
+ .find { |c| has_role?(c, "author") } or return ""
29
+
30
+ person = author.person
31
+ if person.nil?
32
+ Array(author.organization&.name).map { |n| localized(n) }
33
+ .reject(&:empty?).join(", ")
34
+ else
35
+ host_person_name(author, first: true)
36
+ end
37
+ end
38
+
39
+ def close_paren
40
+ @i18n.punct_fetch("close-paren", ")")
41
+ end
42
+ end
43
+
44
+ # A part cites its host's series when it carries none of its
45
+ # own (the 1.x series fallback to the host document)
46
+ class JisSeries < Elements::Series
47
+ private
48
+
49
+ def series
50
+ super || Array(host_relation&.bibitem&.series).first
51
+ end
52
+
53
+ def host_relation
54
+ Array(@model.relation).find do |r|
55
+ Elements::ComponentPart::HOST_RELATION_TYPES.include?(r.type)
56
+ end
57
+ end
58
+ end
59
+
60
+ # The book-family extent carries the volume and the page
61
+ # only, and repeated localities of one type collapse to the
62
+ # last declared (the 1.x per-type merge)
63
+ class JisExtent < Elements::Extent
64
+ def render
65
+ return super unless book_family?
66
+
67
+ [volume_part, page_part].reject(&:empty?).join(" ")
68
+ end
69
+
70
+ private
71
+
72
+ def book_family?
73
+ %w[book inbook incollection inproceedings proceedings]
74
+ .include?(@model.type.to_s)
75
+ end
76
+
77
+ def volume_part
78
+ loc = pick("volume") or return ""
79
+ loc.reference_from.to_s.empty? ? "" :
80
+ unit("volume", loc.reference_from)
81
+ end
82
+
83
+ def page_part
84
+ loc = pick("page") or return ""
85
+ from = loc.reference_from.to_s
86
+ to = loc.reference_to.to_s
87
+ if to.empty? || to == from
88
+ unit("page", from)
89
+ else
90
+ unit("pages", "#{from}#{@i18n.label('date_range')}#{to}")
91
+ end
92
+ end
93
+
94
+ # The last locality of a type wins (the 1.x merge)
95
+ def pick(type)
96
+ localities.reverse.find { |l| l.type == type }
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,384 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ module Rules
7
+ # The NIST rule set, moved verbatim from the metanorma-nist
8
+ # flavor's NistElements: the corporate creator forms, the
9
+ # series derivation from nist-long identifiers, the draft
10
+ # stage forms, the identifier order, and the component-part
11
+ # host citation. Select from pack data:
12
+ # rules: { creator: nist_series_publisher, ... }
13
+ module Nist
14
+ NIST_AND_ABBREV =
15
+ ["NIST", "National Institute of Standards and Technology"].freeze
16
+
17
+ STAGE_PRINT = {
18
+ "draft-internal" => "Internal Draft",
19
+ "draft-wip" => "Work-in-Progress Draft",
20
+ "draft-prelim" => "Preliminary Draft",
21
+ "draft-public" => "Public Draft",
22
+ "draft-approval" => "Approval Draft",
23
+ "final" => "Final",
24
+ "final-review" => "Under Review",
25
+ }.freeze
26
+
27
+ FIPS_PUBLISHER = "U.S. Department of Commerce, Washington, D.C."
28
+ NIST_PUBLISHER = "National Institute of Standards and Technology, " \
29
+ "Gaithersburg, MD"
30
+
31
+ HOST_RELATION_TYPES = %w[partOf includedIn].freeze
32
+
33
+ # The partOf/includedIn host and its publication year: shared
34
+ # by the component part and the host-date fallback of the
35
+ # date element
36
+ module HostDate
37
+ def relation_host
38
+ Array(@model.relation).find do |r|
39
+ HOST_RELATION_TYPES.include?(r.type)
40
+ end&.bibitem
41
+ end
42
+
43
+ def host_date_year
44
+ h = relation_host or return ""
45
+ d = Array(h.date).find(&:published) ||
46
+ Array(h.date).find { |x| x.type.nil? } ||
47
+ Array(h.date).find { |x| x.type == "issued" } ||
48
+ Array(h.date).first or return ""
49
+ v = d.at || d.from || d.to or return ""
50
+ begin
51
+ v.to_date.year.to_s
52
+ rescue ArgumentError, TypeError
53
+ v.to_s[/\d{4}/].to_s
54
+ end
55
+ end
56
+ end
57
+
58
+ # NIST-aware element base: NIST Standard Reference documents
59
+ # are detected by identifier type or publisher
60
+ class Base < Element
61
+ def present?
62
+ !render.to_s.empty?
63
+ end
64
+
65
+ private
66
+
67
+ def nist?
68
+ return true if Array(@model.docidentifier).any? do |d|
69
+ d.type.to_s.start_with?("NIST")
70
+ end
71
+
72
+ Array(@model.contributor).select { |c| has_role?(c, "publisher") }
73
+ .any? do |c|
74
+ org = c.organization or next false
75
+
76
+ Array(org.name).any? { |n| NIST_AND_ABBREV.include?(n.content) } ||
77
+ org.abbreviation&.content == "NIST"
78
+ end
79
+ end
80
+
81
+ def series_title
82
+ Array(@model.series).first&.title&.first&.content.to_s
83
+ end
84
+ end
85
+
86
+ # The series publisher block, parenthesized, after the title
87
+ class NistPublisher < Base
88
+ def render
89
+ if series_title == "NIST Federal Information Processing Standards"
90
+ return " (#{FIPS_PUBLISHER})"
91
+ end
92
+
93
+ nist? ? " (#{NIST_PUBLISHER})" : ""
94
+ end
95
+ end
96
+
97
+ # The NIST draft form ("Draft (Third Public Draft)")
98
+ class NistDraft < Base
99
+ def render
100
+ stage = @model.status&.stage&.content.to_s
101
+ return "" unless stage.start_with?("draft") && nist?
102
+
103
+ "Draft (#{iteration(stage)} #{STAGE_PRINT.fetch(stage, stage)})"
104
+ end
105
+
106
+ private
107
+
108
+ def iteration(stage)
109
+ iter = @model.status&.iteration.to_s
110
+ return "Initial" if iter.empty? || iter == "1"
111
+ return "Final" if iter.casecmp("final").zero?
112
+
113
+ spellout_ordinal(iter.to_i)
114
+ end
115
+
116
+ # The language packs carry ordinal word labels; beyond
117
+ # their reach the numeric suffix form carries the value
118
+ def spellout_ordinal(num)
119
+ word = @i18n.label("ordinal_word_#{num}")
120
+ return word.capitalize unless word == "ordinal_word_#{num}"
121
+
122
+ "#{num}#{ordinal_suffix(num)}"
123
+ end
124
+
125
+ def ordinal_suffix(num)
126
+ case num % 100
127
+ when 11, 12, 13 then "th"
128
+ else
129
+ case num % 10
130
+ when 1 then "st"
131
+ when 2 then "nd"
132
+ when 3 then "rd"
133
+ else "th"
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ # The NIST series form ("NIST Special Publication (SP)
140
+ # 800-116 Rev. 1"). A series carrying a formattedref renders
141
+ # it verbatim; the number joins the partnumber ("800-116.1"),
142
+ # and the edition carries the revision. NIST documents
143
+ # without a series element derive it from the nist-long
144
+ # identifier
145
+ class NistSeries < Base
146
+ def render
147
+ s = Array(@model.series).first
148
+ return derived_series unless s
149
+
150
+ formatted = localized(s.formattedref)
151
+ return formatted unless formatted.empty?
152
+
153
+ title = localized(Array(s.title).first)
154
+ return "" if title.empty?
155
+
156
+ [title, parens(s), number(s)].reject(&:empty?).join(" ")
157
+ end
158
+
159
+ private
160
+
161
+ def derived_series
162
+ long = Array(@model.docidentifier).find do |d|
163
+ d.type == "nist-long"
164
+ end&.content.to_s
165
+ return "" if long.empty?
166
+
167
+ title = long[/\A(NIST [A-Za-z ]+?) \d/, 1] || ""
168
+ return "" if title.empty?
169
+
170
+ [title, parens_derived(long), number_derived(long)]
171
+ .reject(&:empty?).join(" ")
172
+ end
173
+
174
+ def parens_derived(long)
175
+ # strip the NIST sigla before the whitespace strip: the
176
+ # 1.x order left a bare "NIST" parenthesized
177
+ abbr = long[/\A[A-Z.]+ /, 0].to_s.sub(/\ANIST /, "").strip
178
+ abbr.empty? ? "" : "(#{abbr})"
179
+ end
180
+
181
+ def number_derived(long)
182
+ num = long[/\A[NISTa-z. ]*([0-9][0-9A-Za-z.-]*)/, 1].to_s
183
+ num.empty? ? num : with_revision(num)
184
+ end
185
+
186
+ def parens(s)
187
+ abbr = localized(s.abbreviation).sub(/\ANIST /, "")
188
+ abbr.empty? ? "" : "(#{abbr})"
189
+ end
190
+
191
+ def number(s)
192
+ num = s.number.to_s
193
+ unless s.partnumber.to_s.empty?
194
+ num = num.empty? ? s.partnumber.to_s : "#{num}.#{s.partnumber}"
195
+ end
196
+ num = @model.docnumber.to_s if num.empty? && nist?
197
+ num.empty? ? num : with_revision(num)
198
+ end
199
+
200
+ def with_revision(num)
201
+ rev = @model.edition&.content.to_s.sub(/\ARevision /, "")
202
+ rev.empty? ? num : "#{num} Rev. #{rev}"
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
210
+
211
+ module Relaton
212
+ module Render
213
+ module Iso690
214
+ module Rules
215
+ module Nist
216
+ # The NIST identifier order: the authoritative identifiers
217
+ # (ISO, IEC, ...) before the kind-rendered ISBN/DOI/ISSN
218
+ # group; NIST documents cite the NIST identifier alone (the
219
+ # biblio tag)
220
+ class NistIdentifier < Elements::Identifier
221
+ OTHER_TYPES = %w[ISBN ISSN DOI].freeze
222
+
223
+ def render
224
+ return "" if nist?
225
+
226
+ auth, other = unscoped_ids.partition do |d|
227
+ !OTHER_TYPES.include?(d.type)
228
+ end
229
+ (auth + other).map { |d| render_id(d) }.reject(&:empty?)
230
+ .join(". ")
231
+ end
232
+
233
+ private
234
+
235
+ def nist?
236
+ Array(@model.docidentifier).any? do |d|
237
+ d.type.to_s.start_with?("NIST")
238
+ end || Array(@model.contributor).any? do |c|
239
+ next false unless has_role?(c, "publisher")
240
+
241
+ org = c.organization or next false
242
+
243
+ Array(org.name).any? { |n| NIST_AND_ABBREV.include?(n.content) } ||
244
+ org.abbreviation&.content == "NIST"
245
+ end
246
+ end
247
+
248
+ def unscoped_ids
249
+ Array(@model.docidentifier).reject do |d|
250
+ Elements::Identifier::INTERNAL_TYPES.include?(d.type) ||
251
+ !d.scope.to_s.empty? || d.content.to_s.strip.empty?
252
+ end
253
+ end
254
+
255
+ # The NIST ISBN and ISSN kinds cite the kind label with a
256
+ # colon
257
+ def render_id(docidentifier)
258
+ return "#{docidentifier.type}: #{docidentifier.content}" if
259
+ %w[ISBN ISSN].include?(docidentifier.type)
260
+
261
+ super
262
+ end
263
+ end
264
+
265
+ # The NIST component-part order: the host editors before the
266
+ # host title, the host date after the production
267
+ # ("In: Pellegrini AD, Smith PK (Eds.) <em>The nature of
268
+ # play</em> (New York, NY: Guilford Press), 2005")
269
+ class NistComponentPart < Elements::ComponentPart
270
+ include HostDate
271
+
272
+ def render
273
+ h = relation_host or return ""
274
+ out = +"#{lead}#{host_names(h)}"
275
+ eds = host_editors(h)
276
+ out += " (#{@i18n.label(eds.one? ? "ed" : "eds")})" unless
277
+ eds.empty?
278
+ out += " #{host_title}" unless host_title_text.empty?
279
+ production = host_production_text(h)
280
+ out += " (#{production})" unless production.empty?
281
+ date = host_date_year
282
+ out += ", #{date}" unless date.empty?
283
+ out
284
+ end
285
+
286
+ private
287
+
288
+ def host_editors(h)
289
+ Array(h.contributor).select { |c| has_role?(c, "editor") }
290
+ end
291
+
292
+ def host_names(h)
293
+ names = host_editors(h).map { |c|
294
+ person = c.person or next ""
295
+
296
+ [person_surname(person), person_given(person)].reject(&:empty?)
297
+ .join(" ")
298
+ }.reject(&:empty?)
299
+ join_names(names)
300
+ end
301
+
302
+ def host_production_text(h)
303
+ Elements::Production.new(h, style: @style, i18n: @i18n)
304
+ .render.to_s
305
+ end
306
+ end
307
+
308
+ # A component part with no date of its own cites the host's
309
+ class NistDate < Elements::PubDate
310
+ include HostDate
311
+
312
+ def render
313
+ text = super.to_s
314
+ text.empty? ? host_date_year : text
315
+ end
316
+ end
317
+
318
+ # The series-position date segment of the 1.x templates: the
319
+ # date carries its own leading comma ("<series>, 2018
320
+ # (updated November 2018)"), so an absent date removes the
321
+ # comma with it
322
+ class NistSerialDate < Base
323
+ def render
324
+ text = pub_date or return ""
325
+
326
+ ", #{text}#{updated_date}"
327
+ end
328
+
329
+ private
330
+
331
+ def pub_date
332
+ t = Elements::PubDate.new(@model, style: @style, i18n: @i18n)
333
+ .render.to_s
334
+ t.empty? ? nil : t
335
+ end
336
+
337
+ def updated_date
338
+ Elements::Updated.new(@model, style: @style, i18n: @i18n).render
339
+ end
340
+ end
341
+
342
+ # The FIPS creator form: the series names the corporate
343
+ # creator; standards without personal creators cite their
344
+ # publisher's name, monographs cite nothing (their "(n.d.)"
345
+ # comes from the perType fallback)
346
+ class NistCreator < Elements::Creator
347
+ def render
348
+ return fips_creator if fips_series?
349
+
350
+ super
351
+ end
352
+
353
+ def plain_render
354
+ return fips_creator if fips_series?
355
+
356
+ super
357
+ end
358
+
359
+ private
360
+
361
+ def fallback_name
362
+ return "" if %w[monograph continuing component_part]
363
+ .include?(Kinds.kind_for(@model.type))
364
+
365
+ publishers = contributors("publisher").filter_map do |c|
366
+ localized(Array(c.organization&.name).first)
367
+ end
368
+ publishers.reject(&:empty?).first.to_s
369
+ end
370
+
371
+ def fips_series?
372
+ Array(@model.series).first&.title&.first&.content ==
373
+ "NIST Federal Information Processing Standards"
374
+ end
375
+
376
+ def fips_creator
377
+ "National Institute of Standards and Technology"
378
+ end
379
+ end
380
+ end
381
+ end
382
+ end
383
+ end
384
+ end