relaton-bsi 1.7.pre1

Sign up to get free protection for your applications and to get access to all the features.
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