relaton-bsi 1.7.pre1

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.
data/grammars/reqt.rng ADDED
@@ -0,0 +1,165 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
3
+ <!--
4
+ Presupposes isodoc.rnc, is included in it
5
+ include "isodoc.rnc" { }
6
+ -->
7
+ <define name="requirement">
8
+ <element name="requirement">
9
+ <ref name="RequirementType"/>
10
+ </element>
11
+ </define>
12
+ <define name="recommendation">
13
+ <element name="recommendation">
14
+ <ref name="RequirementType"/>
15
+ </element>
16
+ </define>
17
+ <define name="permission">
18
+ <element name="permission">
19
+ <ref name="RequirementType"/>
20
+ </element>
21
+ </define>
22
+ <define name="RequirementType">
23
+ <optional>
24
+ <attribute name="obligation">
25
+ <ref name="ObligationType"/>
26
+ </attribute>
27
+ </optional>
28
+ <optional>
29
+ <attribute name="unnumbered">
30
+ <data type="boolean"/>
31
+ </attribute>
32
+ </optional>
33
+ <optional>
34
+ <attribute name="subsequence"/>
35
+ </optional>
36
+ <attribute name="id">
37
+ <data type="ID"/>
38
+ </attribute>
39
+ <optional>
40
+ <attribute name="filename"/>
41
+ </optional>
42
+ <optional>
43
+ <ref name="reqtitle"/>
44
+ </optional>
45
+ <optional>
46
+ <ref name="label"/>
47
+ </optional>
48
+ <optional>
49
+ <ref name="subject"/>
50
+ </optional>
51
+ <optional>
52
+ <ref name="reqinherit"/>
53
+ </optional>
54
+ <zeroOrMore>
55
+ <ref name="classification"/>
56
+ </zeroOrMore>
57
+ <zeroOrMore>
58
+ <choice>
59
+ <ref name="measurementtarget"/>
60
+ <ref name="specification"/>
61
+ <ref name="verification"/>
62
+ <ref name="import"/>
63
+ <ref name="description"/>
64
+ </choice>
65
+ </zeroOrMore>
66
+ <optional>
67
+ <ref name="reqt_references"/>
68
+ </optional>
69
+ <zeroOrMore>
70
+ <choice>
71
+ <ref name="requirement"/>
72
+ <ref name="recommendation"/>
73
+ <ref name="permission"/>
74
+ </choice>
75
+ </zeroOrMore>
76
+ </define>
77
+ <define name="reqtitle">
78
+ <element name="title">
79
+ <ref name="FormattedString"/>
80
+ </element>
81
+ </define>
82
+ <define name="label">
83
+ <element name="label">
84
+ <text/>
85
+ </element>
86
+ </define>
87
+ <define name="subject">
88
+ <element name="subject">
89
+ <text/>
90
+ </element>
91
+ </define>
92
+ <define name="reqinherit">
93
+ <element name="inherit">
94
+ <text/>
95
+ </element>
96
+ </define>
97
+ <define name="measurementtarget">
98
+ <element name="measurement-target">
99
+ <ref name="RequirementSubpart"/>
100
+ </element>
101
+ </define>
102
+ <define name="specification">
103
+ <element name="specification">
104
+ <ref name="RequirementSubpart"/>
105
+ </element>
106
+ </define>
107
+ <define name="verification">
108
+ <element name="verification">
109
+ <ref name="RequirementSubpart"/>
110
+ </element>
111
+ </define>
112
+ <define name="import">
113
+ <element name="import">
114
+ <ref name="RequirementSubpart"/>
115
+ </element>
116
+ </define>
117
+ <define name="description">
118
+ <element name="description">
119
+ <ref name="RequirementSubpart"/>
120
+ </element>
121
+ </define>
122
+ <define name="reqt_references">
123
+ <element name="references">
124
+ <oneOrMore>
125
+ <ref name="bibitem"/>
126
+ </oneOrMore>
127
+ </element>
128
+ </define>
129
+ <define name="RequirementSubpart">
130
+ <optional>
131
+ <attribute name="type"/>
132
+ </optional>
133
+ <optional>
134
+ <attribute name="exclude">
135
+ <data type="boolean"/>
136
+ </attribute>
137
+ </optional>
138
+ <oneOrMore>
139
+ <ref name="BasicBlock"/>
140
+ </oneOrMore>
141
+ </define>
142
+ <define name="ObligationType">
143
+ <choice>
144
+ <value>requirement</value>
145
+ <value>recommendation</value>
146
+ <value>permission</value>
147
+ </choice>
148
+ </define>
149
+ <define name="classification">
150
+ <element name="classification">
151
+ <ref name="classification_tag"/>
152
+ <ref name="classification_value"/>
153
+ </element>
154
+ </define>
155
+ <define name="classification_tag">
156
+ <element name="tag">
157
+ <text/>
158
+ </element>
159
+ </define>
160
+ <define name="classification_value">
161
+ <element name="value">
162
+ <text/>
163
+ </element>
164
+ </define>
165
+ </grammar>
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "relaton_bsi/version"
4
+ require "relaton_bsi/bsi_bibliography"
5
+
6
+ module RelatonBsi
7
+ # Returns hash of XML reammar
8
+ # @return [String]
9
+ def self.grammar_hash
10
+ gem_path = File.expand_path "..", __dir__
11
+ grammars_path = File.join gem_path, "grammars", "*"
12
+ grammars = Dir[grammars_path].sort.map { |gp| File.read gp }.join
13
+ Digest::MD5.hexdigest grammars
14
+ end
15
+ end
@@ -0,0 +1,46 @@
1
+ module RelatonBsi
2
+ class BsiBibliographicItem < RelatonIsoBib::IsoBibliographicItem
3
+ TYPES = %w[
4
+ specification management-systems-standard code-of-practice guide
5
+ method-of-test method-of-specifying vocabulary classification
6
+ ].freeze
7
+
8
+ # @return [String, nil]
9
+ attr_reader :price_code
10
+
11
+ # @return [Boolean, nil]
12
+ attr_reader :cen_processing
13
+
14
+ # @params price_code [String, nil]
15
+ # @param cen_processing [Boolean, nil]
16
+ def initialize(**args)
17
+ # if args[:doctype] && !TYPES.include?(args[:doctype])
18
+ # warn "[relaton-bsi] WARNING: invalid doctype: #{args[:doctype]}"
19
+ # end
20
+ @price_code = args.delete :price_code
21
+ @cen_processing = args.delete :cen_processing
22
+ super
23
+ end
24
+
25
+ # @param opts [Hash]
26
+ # @option opts [Nokogiri::XML::Builder] :builder XML builder
27
+ # @option opts [Boolean] :bibdata
28
+ # @option opts [String] :lang language
29
+ # @return [String] XML
30
+ def to_xml(**opts)
31
+ super **opts do |b|
32
+ if opts[:bibdata]
33
+ b.send "price-code", price_code if price_code
34
+ b.send "cen-processing", cen_processing unless cen_processing.nil?
35
+ end
36
+ end
37
+ end
38
+
39
+ # @param hash [Hash]
40
+ # @return [RelatonBsi::BsiBibliographicItem]
41
+ def self.from_hash(hash)
42
+ item_hash = ::RelatonBsi::HashConverter.hash_to_bib(hash)
43
+ new **item_hash
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mechanize"
4
+ require "relaton_iso_bib"
5
+ require "relaton_bsi/bsi_bibliographic_item"
6
+ require "relaton_bsi/scrapper"
7
+ require "relaton_bsi/hit_collection"
8
+ require "relaton_bsi/hit"
9
+ require "relaton_bsi/xml_parser"
10
+ require "relaton_bsi/hash_converter"
11
+
12
+ module RelatonBsi
13
+ # Class methods for search ISO standards.
14
+ class BsiBibliography
15
+ class << self
16
+ # @param text [String]
17
+ # @return [RelatonBsi::HitCollection]
18
+ def search(text, year = nil)
19
+ code = text.sub(/^BSI\s/, "")
20
+ HitCollection.new code, year
21
+ rescue SocketError, Timeout::Error, Errno::EINVAL, Errno::ECONNRESET,
22
+ EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
23
+ Net::ProtocolError
24
+ raise RelatonBib::RequestError, "Could not access #{HitCollection::DOMAIN}"
25
+ end
26
+
27
+ # @param code [String] the BSI standard Code to look up
28
+ # @param year [String] the year the standard was published (optional)
29
+ # @param opts [Hash] options; restricted to :all_parts if all-parts reference is required
30
+ # @return [String] Relaton XML serialisation of reference
31
+ def get(code, year = nil, opts = {}) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
32
+ # if year.nil?
33
+ # /^(?<code1>[^\s]+\s[^\s]+)\s\((\d{2}\/)?(?<year1>\d+)\)$/ =~ code
34
+ # unless code1.nil?
35
+ # code = code1
36
+ # year = year1
37
+ # end
38
+ # end
39
+
40
+ ret = bib_get1(code, year, opts)
41
+ return nil if ret.nil?
42
+
43
+ # ret = ret.to_most_recent_reference unless year || opts[:keep_year]
44
+ # ret = ret.to_all_parts if opts[:all_parts]
45
+ ret
46
+ end
47
+
48
+ private
49
+
50
+ def fetch_ref_err(code, year, missed_years) # rubocop:disable Metrics/MethodLength
51
+ id = year ? "#{code}:#{year}" : code
52
+ warn "[relaton-bsi] WARNING: no match found online for #{id}. "\
53
+ "The code must be exactly like it is on the standards website."
54
+ unless missed_years.empty?
55
+ warn "[relaton-bsi] (There was no match for #{year}, though there were matches "\
56
+ "found for #{missed_years.join(', ')}.)"
57
+ end
58
+ # if /\d-\d/.match? code
59
+ # warn "[relaton-bsi] The provided document part may not exist, or the document "\
60
+ # "may no longer be published in parts."
61
+ # else
62
+ # warn "[relaton-bsi] If you wanted to cite all document parts for the reference, "\
63
+ # "use \"#{code} (all parts)\".\nIf the document is not a standard, "\
64
+ # "use its document type abbreviation (TS, TR, PAS, Guide)."
65
+ # end
66
+ nil
67
+ end
68
+
69
+ def search_filter(code) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
70
+ %r{^BSI\s(?<code1>[^:]+)} =~ code
71
+ warn "[relaton-bsi] (\"#{code}\") fetching..."
72
+ result = search(code)
73
+ result.select do |i|
74
+ next true unless i.hit[:code]
75
+
76
+ %r{^(?<code2>[^:]+)} =~ i.hit[:code]
77
+ code2.include?(code1)
78
+ end
79
+ end
80
+
81
+ # Sort through the results from Isobib, fetching them three at a time,
82
+ # and return the first result that matches the code,
83
+ # matches the year (if provided), and which # has a title (amendments do not).
84
+ # Only expects the first page of results to be populated.
85
+ # Does not match corrigenda etc (e.g. ISO 3166-1:2006/Cor 1:2007)
86
+ # If no match, returns any years which caused mismatch, for error reporting
87
+ def isobib_results_filter(result, year)
88
+ missed_years = []
89
+ result.each do |r|
90
+ /:(?<pyear>\d{4})/ =~ r.hit[:code]
91
+ if !year || year == pyear
92
+ ret = r.fetch
93
+ return { ret: ret } if ret
94
+ end
95
+
96
+ missed_years << pyear
97
+ end
98
+ { years: missed_years }
99
+ end
100
+
101
+ def bib_get1(code, year, _opts)
102
+ result = search_filter(code) || return
103
+ ret = isobib_results_filter(result, year)
104
+ if ret[:ret]
105
+ warn "[relaton-bsi] (\"#{code}\") found #{ret[:ret].docidentifier.first&.id}"
106
+ ret[:ret]
107
+ else
108
+ fetch_ref_err(code, year, ret[:years])
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,16 @@
1
+ module RelatonBsi
2
+ class HashConverter < RelatonIsoBib::HashConverter
3
+ class << self
4
+ private
5
+
6
+ #
7
+ # Ovverides superclass's method
8
+ #
9
+ # @param item [Hash]
10
+ # @retirn [RelatonBsi::BsiBibliographicItem]
11
+ def bib_item(item)
12
+ BsiBibliographicItem.new(**item)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RelatonBsi
4
+ # Hit.
5
+ class Hit < RelatonBib::Hit
6
+ attr_writer :fetch
7
+
8
+ # Parse page.
9
+ # @return [RelatonBsi::BsiBibliographicItem]
10
+ def fetch
11
+ @fetch ||= Scrapper.parse_page self
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "relaton_bsi/hit"
4
+
5
+ module RelatonBsi
6
+ # Page of hit collection.
7
+ class HitCollection < RelatonBib::HitCollection
8
+ DOMAIN = "https://shop.bsigroup.com"
9
+
10
+ # @return [Mechanize]
11
+ attr_reader :agent
12
+
13
+ # @param ref [String]
14
+ # @param year [String]
15
+ def initialize(ref, year = nil)
16
+ super ref, year
17
+ @agent = Mechanize.new
18
+ resp = agent.get "#{DOMAIN}/SearchResults/?q=#{ref}"
19
+ @array = hits resp
20
+ end
21
+
22
+ private
23
+
24
+ # @param resp [Mechanize::Page]
25
+ # @return [Array<RelatonBsi::Hit>]
26
+ def hits(resp)
27
+ resp.xpath("//div[@class='resultsInd']").map do |h|
28
+ ref = h.at('div/h2/a')
29
+ code = ref.text.strip
30
+ url = ref[:href]
31
+ d = h.at("//div/strong[.='Published Date:']/following-sibling::strong").text
32
+ date = Date.parse(d)
33
+ Hit.new({ code: code, url: url, date: date }, self)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ require "relaton/processor"
2
+
3
+ module RelatonBsi
4
+ class Processor < Relaton::Processor
5
+ def initialize
6
+ @short = :relaton_bsi
7
+ @prefix = "BSI"
8
+ @defaultprefix = %r{^BSI\s}
9
+ @idtype = "BSI"
10
+ end
11
+
12
+ # @param code [String]
13
+ # @param date [String, NilClass] year
14
+ # @param opts [Hash]
15
+ # @return [RelatonBsi::BsiBibliographicItem]
16
+ def get(code, date, opts)
17
+ ::RelatonBsi::BsiBibliography.get(code, date, opts)
18
+ end
19
+
20
+ # @param xml [String]
21
+ # @return [RelatonBsi::BsiBibliographicItem]
22
+ def from_xml(xml)
23
+ ::RelatonBsi::XMLParser.from_xml xml
24
+ end
25
+
26
+ # @param hash [Hash]
27
+ # @return [RelatonBsi::BsiBibliographicItem]
28
+ def hash_to_bib(hash)
29
+ ::RelatonBsi::BsiBibliographicItem.from_hash hash
30
+ end
31
+
32
+ # Returns hash of XML grammar
33
+ # @return [String]
34
+ def grammar_hash
35
+ @grammar_hash ||= ::RelatonBsi.grammar_hash
36
+ end
37
+ end
38
+ end