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.
- checksums.yaml +7 -0
- data/.github/workflows/rake.yml +46 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +10 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.adoc +168 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/rspec +29 -0
- data/bin/setup +8 -0
- data/grammars/basicdoc.rng +986 -0
- data/grammars/biblio.rng +1235 -0
- data/grammars/bsi.rng +120 -0
- data/grammars/isodoc.rng +1870 -0
- data/grammars/isostandard.rng +477 -0
- data/grammars/reqt.rng +165 -0
- data/lib/relaton_bsi.rb +15 -0
- data/lib/relaton_bsi/bsi_bibliographic_item.rb +46 -0
- data/lib/relaton_bsi/bsi_bibliography.rb +113 -0
- data/lib/relaton_bsi/hash_converter.rb +16 -0
- data/lib/relaton_bsi/hit.rb +14 -0
- data/lib/relaton_bsi/hit_collection.rb +37 -0
- data/lib/relaton_bsi/processor.rb +38 -0
- data/lib/relaton_bsi/scrapper.rb +156 -0
- data/lib/relaton_bsi/version.rb +5 -0
- data/lib/relaton_bsi/xml_parser.rb +28 -0
- data/relaton_bsi.gemspec +49 -0
- metadata +187 -0
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>
|
data/lib/relaton_bsi.rb
ADDED
|
@@ -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,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
|