oddb2xml 2.7.1 → 2.7.2

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +1 -1
  3. data/.standard.yml +2 -0
  4. data/Gemfile +3 -3
  5. data/History.txt +8 -0
  6. data/README.md +1 -1
  7. data/Rakefile +24 -23
  8. data/bin/check_artikelstamm +11 -11
  9. data/bin/compare_v5 +23 -23
  10. data/bin/oddb2xml +14 -13
  11. data/lib/oddb2xml.rb +1 -1
  12. data/lib/oddb2xml/builder.rb +1070 -1038
  13. data/lib/oddb2xml/calc.rb +232 -233
  14. data/lib/oddb2xml/chapter_70_hack.rb +38 -32
  15. data/lib/oddb2xml/cli.rb +252 -236
  16. data/lib/oddb2xml/compare.rb +70 -59
  17. data/lib/oddb2xml/compositions_syntax.rb +448 -430
  18. data/lib/oddb2xml/compressor.rb +20 -20
  19. data/lib/oddb2xml/downloader.rb +153 -127
  20. data/lib/oddb2xml/extractor.rb +302 -289
  21. data/lib/oddb2xml/options.rb +34 -35
  22. data/lib/oddb2xml/parslet_compositions.rb +263 -269
  23. data/lib/oddb2xml/semantic_check.rb +39 -33
  24. data/lib/oddb2xml/util.rb +163 -163
  25. data/lib/oddb2xml/version.rb +1 -1
  26. data/lib/oddb2xml/xml_definitions.rb +32 -33
  27. data/oddb2xml.gemspec +31 -32
  28. data/spec/artikelstamm_spec.rb +111 -110
  29. data/spec/builder_spec.rb +489 -505
  30. data/spec/calc_spec.rb +552 -593
  31. data/spec/check_artikelstamm_spec.rb +26 -26
  32. data/spec/cli_spec.rb +173 -174
  33. data/spec/compare_spec.rb +9 -11
  34. data/spec/composition_syntax_spec.rb +390 -409
  35. data/spec/compressor_spec.rb +48 -48
  36. data/spec/data/transfer.dat +1 -0
  37. data/spec/data_helper.rb +47 -49
  38. data/spec/downloader_spec.rb +247 -260
  39. data/spec/extractor_spec.rb +171 -159
  40. data/spec/galenic_spec.rb +233 -256
  41. data/spec/options_spec.rb +116 -119
  42. data/spec/parslet_spec.rb +833 -861
  43. data/spec/spec_helper.rb +154 -153
  44. data/test_options.rb +39 -42
  45. data/tools/win_fetch_cacerts.rb +2 -3
  46. metadata +19 -3
@@ -1,61 +1,60 @@
1
- # encoding: utf-8
2
- require 'optimist'
3
- require 'oddb2xml/version'
1
+ require "optimist"
2
+ require "oddb2xml/version"
4
3
 
5
4
  module Oddb2xml
6
5
  module Options
7
- def self.parse(args =ARGV)
6
+ def self.parse(args = ARGV)
8
7
  if args.is_a?(String)
9
- args = args.split(' ')
8
+ args = args.split(" ")
10
9
  end
11
10
 
12
- @opts = Optimist::options(args) do
13
- version "#$0 ver.#{Oddb2xml::VERSION}"
11
+ @opts = Optimist.options(args) do
12
+ version "#{$0} ver.#{Oddb2xml::VERSION}"
14
13
  banner <<-EOS
15
14
  #{File.expand_path($0)} version #{Oddb2xml::VERSION}
16
15
  Usage:
17
16
  oddb2xml [option]
18
17
  produced files are found under data
19
- EOS
20
- opt :append, "Additional target nonpharma", :default => false
18
+ EOS
19
+ opt :append, "Additional target nonpharma", default: false
21
20
  opt :artikelstamm, "Create Artikelstamm Version 3 and 5 for Elexis >= 3.1"
22
- opt :compress_ext, "format F. {tar.gz|zip}", :type => :string, :default => nil, :short => 'c'
23
- opt :extended, "pharma, non-pharma plus prices and non-pharma from zurrose.
21
+ opt :compress_ext, "format F. {tar.gz|zip}", type: :string, default: nil, short: "c"
22
+ opt :extended, "pharma, non-pharma plus prices and non-pharma from zurrose.
24
23
  Products without EAN-Code will also be listed.
25
24
  File oddb_calc.xml will also be generated"
26
- opt :format, "File format F, default is xml. {xml|dat}
27
- If F is given, -o option is ignored.", :type => :string, :default => 'xml'
28
- opt :include, "Include target option for ean14 for 'dat' format.
29
- 'xml' format includes always ean14 records.", :short => 'i'
30
- opt :increment, "Increment price by x percent. Forces -f dat -p zurrose.
25
+ opt :format, "File format F, default is xml. {xml|dat}
26
+ If F is given, -o option is ignored.", type: :string, default: "xml"
27
+ opt :include, "Include target option for ean14 for 'dat' format.
28
+ 'xml' format includes always ean14 records.", short: "i"
29
+ opt :increment, "Increment price by x percent. Forces -f dat -p zurrose.
31
30
  create additional field price_resellerpub as
32
31
  price_extfactory incremented by x percent (rounded to the next 0.05 francs)
33
32
  in oddb_article.xml. In generated zurrose_transfer.dat PRPU is set to this price
34
- Forces -f dat -p zurrose.", :type => :int, :default => nil, :short => 'I'
35
- opt :fi, "Optional fachinfo output.", :short => 'o'
36
- opt :price, "Price source (transfer.dat) from ZurRose", :default => nil
37
- opt :tag_suffix, "XML tag suffix S. Default is none. [A-z0-9]
38
- If S is given, it is also used as prefix of filename.", :type => :string, :short => 't'
39
- opt :context, "{product|address}. product is default.", :default => 'product', :type => :string, :short => 'x'
40
- opt :calc, "create only oddb_calc.xml with GTIN, name and galenic information"
33
+ Forces -f dat -p zurrose.", type: :int, default: nil, short: "I"
34
+ opt :fi, "Optional fachinfo output.", short: "o"
35
+ opt :price, "Price source (transfer.dat) from ZurRose", default: nil
36
+ opt :tag_suffix, "XML tag suffix S. Default is none. [A-z0-9]
37
+ If S is given, it is also used as prefix of filename.", type: :string, short: "t"
38
+ opt :context, "{product|address}. product is default.", default: "product", type: :string, short: "x"
39
+ opt :calc, "create only oddb_calc.xml with GTIN, name and galenic information"
41
40
 
42
41
  opt :skip_download, "skips downloading files it the file is already under downloads.
43
42
  Downloaded files are saved under downloads"
44
- opt :log, "log important actions", :short => :none
45
- opt :use_ra11zip, "Use the ra11.zip (a zipped transfer.dat from Galexis)",
46
- :default => File.exist?('ra11.zip') ? 'ra11.zip' : nil, :type => :string
43
+ opt :log, "log important actions", short: :none
44
+ opt :use_ra11zip, "Use the ra11.zip (a zipped transfer.dat from Galexis)",
45
+ default: File.exist?("ra11.zip") ? "ra11.zip" : nil, type: :string
47
46
  end
48
-
47
+
49
48
  @opts[:percent] = @opts[:increment]
50
- if @opts[:increment]
49
+ if @opts[:increment]
51
50
  @opts[:nonpharma] = true
52
51
  @opts[:price] = :zurrose
53
52
  end
54
- @opts[:ean14] = @opts[:increment]
53
+ @opts[:ean14] = @opts[:increment]
55
54
  @opts.delete(:increment)
56
55
  @opts[:nonpharma] = @opts[:append]
57
56
  @opts.delete(:append)
58
- if @opts[:extended]
57
+ if @opts[:extended]
59
58
  @opts[:nonpharma] = true
60
59
  @opts[:price] = :zurrose
61
60
  @opts[:calc] = true
@@ -63,22 +62,22 @@ EOS
63
62
  if @opts[:artikelstamm]
64
63
  @opts[:extended] = true
65
64
  @opts[:price] = :zurrose
66
- end
65
+ end
67
66
  @opts[:price] = :zurrose if @opts[:price].is_a?(TrueClass)
68
- @opts[:price] = @opts[:price].to_sym if @opts[:price]
67
+ @opts[:price] = @opts[:price].to_sym if @opts[:price]
69
68
  @opts[:ean14] = @opts[:include]
70
69
  @opts[:format] = @opts[:format].to_sym if @opts[:format]
71
70
  @opts.delete(:include)
72
71
  @opts.delete(:help)
73
72
  @opts.delete(:version)
74
73
 
75
- @opts[:address] = false
76
- @opts[:address] = true if /^addr(ess)*$/i.match(@opts[:context])
74
+ @opts[:address] = false
75
+ @opts[:address] = true if /^addr(ess)*$/i.match?(@opts[:context])
77
76
  @opts.delete(:context)
78
77
 
79
78
  @opts.delete(:price) unless @opts[:price]
80
79
 
81
- @opts.each{|k,v| @opts.delete(k) if /_given$/.match(k.to_s)}
80
+ @opts.each { |k, v| @opts.delete(k) if /_given$/.match?(k.to_s) }
82
81
  end
83
82
  end
84
83
  end
@@ -1,79 +1,74 @@
1
- # encoding: utf-8
2
-
3
1
  # This file is shared since oddb2xml 2.0.0 (lib/oddb2xml/parse_compositions.rb)
4
2
  # with oddb.org src/plugin/parse_compositions.rb
5
3
  #
6
4
  # It allows an easy parsing of the column P Zusammensetzung of the swissmedic packages.xlsx file
7
5
  #
8
6
 
9
- require 'parslet'
10
- require 'parslet/convenience'
11
- require 'oddb2xml/compositions_syntax'
12
- include Parslet
7
+ require "parslet"
8
+ require "parslet/convenience"
9
+ require "oddb2xml/compositions_syntax"
13
10
  VERBOSE_MESSAGES ||= false
14
11
 
15
12
  module ParseUtil
13
+ include Parslet
16
14
  # this class is responsible to patch errors in swissmedic entries after
17
15
  # oddb.org detected them, as it takes sometimes a few days (or more) till they get corrected
18
16
  # Reports the number of occurrences of each entry
19
17
  @@saved_parsed ||= {}
20
- @@nr_saved_parsed_used ||= 0
18
+ @@nr_saved_parsed_used ||= 0
21
19
 
22
20
  class HandleSwissmedicErrors
23
-
24
21
  attr_accessor :nrParsingErrors
25
- class ErrorEntry < Struct.new('ErrorEntry', :pattern, :replacement, :nr_occurrences)
22
+ class ErrorEntry < Struct.new("ErrorEntry", :pattern, :replacement, :nr_occurrences)
26
23
  end
27
24
 
28
25
  def reset_errors
29
26
  @errors = []
30
- @nrLines = 0
31
- @nrParsingErrors = 0
27
+ @nr_lines = 0
28
+ @nr_parsing_errors = 0
32
29
  end
33
30
 
34
31
  # error_entries should be a hash of pattern, replacement
35
32
  def initialize(error_entries)
36
33
  reset_errors
37
- error_entries.each{ |pattern, replacement| @errors << ErrorEntry.new(pattern, replacement, 0) }
34
+ error_entries.each { |pattern, replacement| @errors << ErrorEntry.new(pattern, replacement, 0) }
38
35
  end
39
36
 
40
37
  def report
41
- s = ["Report of changed compositions in #{@nrLines} lines. Had #{@nrParsingErrors} parsing errors" ]
42
- @errors.each {
43
- |entry|
44
- s << " replaced #{entry.nr_occurrences} times '#{entry.pattern}' by '#{entry.replacement}'"
38
+ s = ["Report of changed compositions in #{@nr_lines} lines. Had #{@nr_parsing_errors} parsing errors"]
39
+ @errors.each { |entry|
40
+ s << " replaced #{entry.nr_occurrences} times '#{entry.pattern}' by '#{entry.replacement}'"
45
41
  }
46
42
  s
47
43
  end
48
44
 
49
45
  def apply_fixes(string)
50
46
  result = string.clone
51
- @errors.each{
52
- |entry|
47
+ @errors.each { |entry|
53
48
  intermediate = result.clone
54
- result = result.gsub(entry.pattern, entry.replacement)
49
+ result = result.gsub(entry.pattern, entry.replacement)
55
50
  unless result.eql?(intermediate)
56
- entry.nr_occurrences += 1
57
- puts "#{File.basename(__FILE__)}:#{__LINE__}: fixed \nbefore: #{intermediate}\nafter: #{result}" if $VERBOSE
51
+ entry.nr_occurrences += 1
52
+ puts "#{File.basename(__FILE__)}:#{__LINE__}: fixed \nbefore: #{intermediate}\nafter: #{result}" if $VERBOSE
58
53
  end
59
54
  }
60
- @nrLines += 1
55
+ @nr_lines += 1
61
56
  result
62
57
  end
63
58
  # hepar sulfuris D6 2,2 mg hypericum perforatum D2 0,66 mg where itlacks a comma and should be hepar sulfuris D6 2,2 mg, hypericum perforatum D2 0,66 mg
64
59
  end
65
60
 
66
- def ParseUtil.capitalize(string)
67
- string.split(/\s+/u).collect { |word| word.capitalize }.join(' ').strip
61
+ def self.capitalize(string)
62
+ string.split(/\s+/u).collect { |word| word.capitalize }.join(" ").strip
68
63
  end
69
64
 
70
- def ParseUtil.nr_saved_parsed_used
65
+ def self.nr_saved_parsed_used
71
66
  @@nr_saved_parsed_used
72
67
  end
73
68
 
74
- def ParseUtil.parse_compositions(composition_text, active_agents_string = '')
75
- active_agents = active_agents_string ? active_agents_string.gsub('[', '').downcase.split(/,\s+/) : []
76
- key = [ composition_text, active_agents ]
69
+ def self.parse_compositions(composition_text, active_agents_string = "")
70
+ active_agents = active_agents_string ? active_agents_string.delete("[").downcase.split(/,\s+/) : []
71
+ key = [composition_text, active_agents]
77
72
  saved_value = @@saved_parsed[key]
78
73
  if saved_value
79
74
  @@nr_saved_parsed_used += 1
@@ -81,20 +76,22 @@ module ParseUtil
81
76
  end
82
77
  comps = []
83
78
  lines = composition_text.gsub(/\r\n?/u, "\n").split(/\n/u)
84
- lines.select do
85
- |line|
86
- composition = ParseComposition.from_string(line)
79
+ lines.select do |line|
80
+ composition = ParseComposition.from_string(line)
87
81
  if composition.is_a?(ParseComposition)
88
- composition.substances.each do
89
- |substance_item|
90
- active_substance_name = substance_item.name.downcase.sub(/^cum\s/, '')
91
- substance_item.is_active_agent = (active_agents.find {|x| /#{x.downcase.
92
- gsub('(', '\(').
93
- gsub(')', '\)').
94
- gsub('[', '\[').
95
- gsub(']', '\]')}($|\s)/.match(active_substance_name) } != nil)
96
- substance_item.is_active_agent = true if substance_item.chemical_substance and active_agents.find {|x| x.downcase.eql?(substance_item.chemical_substance.name.downcase) }
97
- end
82
+ composition.substances.each do |substance_item|
83
+ active_substance_name = substance_item.name.downcase.sub(/^cum\s/, "")
84
+ substance_item.is_active_agent = !active_agents.find { |x|
85
+ /#{x.downcase
86
+ .gsub('(', '\(')
87
+ .gsub(')', '\)')
88
+ .gsub('[', '\[')
89
+ .gsub(']', '\]')
90
+ }($|\s)/
91
+ .match(active_substance_name)
92
+ }.nil?
93
+ substance_item.is_active_agent = true if substance_item.chemical_substance && active_agents.find { |x| x.downcase.eql?(substance_item.chemical_substance.name.downcase) }
94
+ end
98
95
  comps << composition
99
96
  end
100
97
  end
@@ -103,202 +100,194 @@ module ParseUtil
103
100
  comps
104
101
  rescue => error
105
102
  puts "error #{error}"
106
- # binding.pry
103
+ # binding.pry
107
104
  end
108
-
109
105
  end
110
106
 
111
- class IntLit < Struct.new(:int)
112
- def eval; int.to_i; end
107
+ class IntLit < Struct.new(:int)
108
+ def eval
109
+ int.to_i
110
+ end
113
111
  end
114
- class QtyLit < Struct.new(:qty)
115
- def eval; qty.to_i; end
112
+
113
+ class QtyLit < Struct.new(:qty)
114
+ def eval
115
+ qty.to_i
116
+ end
116
117
  end
117
118
 
118
119
  class CompositionTransformer < Parslet::Transform
119
120
  @@more_info = nil
120
- def CompositionTransformer.get_ratio(parse_info)
121
+ def self.get_ratio(parse_info)
121
122
  if parse_info[:ratio]
122
- if parse_info[:ratio].to_s.length > 0 and parse_info[:ratio].to_s != ', '
123
- parse_info[:ratio].to_s.sub(/^,\s+/, '').sub(/,\s+$/,'')
124
- else
125
- nil
123
+ if (parse_info[:ratio].to_s.length > 0) && (parse_info[:ratio].to_s != ", ")
124
+ parse_info[:ratio].to_s.sub(/^,\s+/, "").sub(/,\s+$/, "")
126
125
  end
127
- else
128
- nil
129
126
  end
130
127
  end
131
128
 
132
- def CompositionTransformer.check_e_substance(substance)
133
- return unless /^E \d\d\d/.match(substance.name)
129
+ def self.check_e_substance(substance)
130
+ return unless /^E \d\d\d/.match?(substance.name)
134
131
  unless substance.more_info
135
132
  case substance.name[2]
136
133
  when "1"
137
- substance.more_info = 'color.'
134
+ substance.more_info = "color."
138
135
  when "2"
139
- substance.more_info = 'conserv.'
140
- else
136
+ substance.more_info = "conserv."
141
137
  end
142
138
  substance.more_info ||= @@more_info
143
139
  end
144
140
  @@more_info = substance.more_info
145
141
  end
146
142
 
147
- def CompositionTransformer.add_excipiens(info)
148
- @@more_info = nil
149
- @@excipiens = ParseSubstance.new(info[:excipiens_description] ? info[:excipiens_description] : 'Excipiens')
150
- @@excipiens.dose = info[:dose] if info[:dose]
143
+ def self.add_excipiens(info)
144
+ @@more_info = nil
145
+ @@excipiens = ParseSubstance.new(info[:excipiens_description] || "Excipiens")
146
+ @@excipiens.dose = info[:dose] if info[:dose]
151
147
  @@excipiens.more_info = CompositionTransformer.get_ratio(info)
152
- @@excipiens.cdose = info[:dose_corresp] if info[:dose_corresp]
148
+ @@excipiens.cdose = info[:dose_corresp] if info[:dose_corresp]
153
149
  @@excipiens.more_info = info[:more_info] if info[:more_info]
154
150
  end
155
151
 
156
- rule(:corresp => simple(:corresp),
157
- ) {
158
- |dictionary|
159
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
160
- @@corresp = dictionary[:corresp].to_s
152
+ rule(corresp: simple(:corresp)) { |dictionary|
153
+ puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
154
+ @@corresp = dictionary[:corresp].to_s
161
155
  }
162
- rule( :substance_name => simple(:substance_name),
163
- :dose => simple(:dose),
164
- ) {
165
- |dictionary|
166
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
167
- dose = dictionary[:dose].is_a?(ParseDose) ? dictionary[:dose] : nil
168
- substance = ParseSubstance.new(dictionary[:substance_name], dose)
169
- @@substances << substance
170
- substance
156
+ rule(substance_name: simple(:substance_name),
157
+ dose: simple(:dose)) { |dictionary|
158
+ puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
159
+ dose = dictionary[:dose].is_a?(ParseDose) ? dictionary[:dose] : nil
160
+ substance = ParseSubstance.new(dictionary[:substance_name], dose)
161
+ @@substances << substance
162
+ substance
171
163
  }
172
164
 
173
- rule( :more_info => simple(:more_info),
174
- ) {
175
- |dictionary|
176
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
177
- @@corresp = dictionary[:more_info].to_s.strip.sub(/:$/, '')
165
+ rule(more_info: simple(:more_info)) { |dictionary|
166
+ puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
167
+ @@corresp = dictionary[:more_info].to_s.strip.sub(/:$/, "")
178
168
  }
179
- rule( :more_info => simple(:more_info),
180
- :substance_name => simple(:substance_name),
181
- :dose => simple(:dose),
182
- ) {
183
- |dictionary|
184
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
185
- dose = dictionary[:dose].is_a?(ParseDose) ? dictionary[:dose] : nil
186
- substance = ParseSubstance.new(dictionary[:substance_name].to_s, dose)
187
- substance.more_info = dictionary[:more_info].to_s.strip.sub(/:$/, '') if dictionary[:more_info] and dictionary[:more_info].to_s.length > 0
188
- CompositionTransformer.check_e_substance(substance)
189
- @@substances << substance
190
- substance
169
+ rule(more_info: simple(:more_info),
170
+ substance_name: simple(:substance_name),
171
+ dose: simple(:dose)) { |dictionary|
172
+ puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
173
+ dose = dictionary[:dose].is_a?(ParseDose) ? dictionary[:dose] : nil
174
+ substance = ParseSubstance.new(dictionary[:substance_name].to_s, dose)
175
+ substance.more_info = dictionary[:more_info].to_s.strip.sub(/:$/, "") if dictionary[:more_info] && (dictionary[:more_info].to_s.length > 0)
176
+ CompositionTransformer.check_e_substance(substance)
177
+ @@substances << substance
178
+ substance
191
179
  }
192
180
 
193
- rule(:lebensmittel_zusatz => simple(:lebensmittel_zusatz),
194
- :more_info => simple(:more_info),
195
- :digits => simple(:digits)) {
196
- |dictionary|
197
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
198
- substance = ParseSubstance.new("#{dictionary[:lebensmittel_zusatz]} #{dictionary[:digits]}")
199
- substance.more_info = dictionary[:more_info].to_s.strip.sub(/:$/, '') if dictionary[:more_info] and dictionary[:more_info].to_s.length > 0
200
- CompositionTransformer.check_e_substance(substance)
201
- @@substances << substance
202
- substance
181
+ rule(lebensmittel_zusatz: simple(:lebensmittel_zusatz),
182
+ more_info: simple(:more_info),
183
+ digits: simple(:digits)) { |dictionary|
184
+ puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
185
+ substance = ParseSubstance.new("#{dictionary[:lebensmittel_zusatz]} #{dictionary[:digits]}")
186
+ substance.more_info = dictionary[:more_info].to_s.strip.sub(/:$/, "") if dictionary[:more_info] && (dictionary[:more_info].to_s.length > 0)
187
+ CompositionTransformer.check_e_substance(substance)
188
+ @@substances << substance
189
+ substance
203
190
  }
204
- rule(:excipiens => subtree(:excipiens),
205
- ) {
206
- |dictionary|
207
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
208
- info = dictionary[:excipiens].is_a?(Hash) ? dictionary[:excipiens] : dictionary[:excipiens].first
209
- if info[:excipiens_description] or
210
- info[:dose] or
211
- info[:dose_corresp] or
212
- info[:more_info] or
213
- CompositionTransformer.get_ratio(dictionary)
214
- CompositionTransformer.add_excipiens(info)
215
- info
216
- end
217
- nil
191
+ rule(excipiens: subtree(:excipiens)) { |dictionary|
192
+ puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
193
+ info = dictionary[:excipiens].is_a?(Hash) ? dictionary[:excipiens] : dictionary[:excipiens].first
194
+ if info[:excipiens_description] ||
195
+ info[:dose] ||
196
+ info[:dose_corresp] ||
197
+ info[:more_info] ||
198
+ CompositionTransformer.get_ratio(dictionary)
199
+ CompositionTransformer.add_excipiens(info)
200
+ info
201
+ end
202
+ nil
218
203
  }
219
- rule(:composition => subtree(:composition),
220
- ) {
221
- |dictionary|
222
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
223
- info = dictionary[:composition].is_a?(Hash) ? dictionary[:composition] : dictionary[:composition].first
224
- CompositionTransformer.add_excipiens(info) if info.is_a?(Hash)
225
- info
226
- }
227
- rule(:substance => simple(:substance),
228
- :chemical_substance => simple(:chemical_substance),
229
- :substance_ut => sequence(:substance_ut),
230
- :ratio => simple(:ratio),
231
- ) {
232
- |dictionary|
233
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
234
- ratio = CompositionTransformer.get_ratio(dictionary)
235
- if ratio and ratio.length > 0
236
- if dictionary[:substance].more_info
237
- dictionary[:substance].more_info += ' ' + ratio.strip
238
- else
239
- dictionary[:substance].more_info = ratio.strip
240
- end
241
- end
242
- if dictionary[:chemical_substance]
243
- dictionary[:substance].chemical_substance = dictionary[:chemical_substance]
244
- @@substances -= [dictionary[:chemical_substance]]
245
- end
246
- if dictionary[:substance_ut].size > 0
247
- dictionary[:substance].salts += dictionary[:substance_ut].last.salts
248
- dictionary[:substance_ut].last.salts = []
249
- dictionary[:substance].salts << dictionary[:substance_ut].last
250
- @@substances -= dictionary[:substance_ut]
251
- end
252
- dictionary[:substance]
204
+ rule(composition: subtree(:composition)) { |dictionary|
205
+ puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
206
+ info = dictionary[:composition].is_a?(Hash) ? dictionary[:composition] : dictionary[:composition].first
207
+ CompositionTransformer.add_excipiens(info) if info.is_a?(Hash)
208
+ info
209
+ }
210
+ rule(substance: simple(:substance),
211
+ chemical_substance: simple(:chemical_substance),
212
+ substance_ut: sequence(:substance_ut),
213
+ ratio: simple(:ratio)) { |dictionary|
214
+ puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
215
+ ratio = CompositionTransformer.get_ratio(dictionary)
216
+ if ratio && (ratio.length > 0)
217
+ if dictionary[:substance].more_info
218
+ dictionary[:substance].more_info += " " + ratio.strip
219
+ else
220
+ dictionary[:substance].more_info = ratio.strip
221
+ end
222
+ end
223
+ if dictionary[:chemical_substance]
224
+ dictionary[:substance].chemical_substance = dictionary[:chemical_substance]
225
+ @@substances -= [dictionary[:chemical_substance]]
226
+ end
227
+ if dictionary[:substance_ut].size > 0
228
+ dictionary[:substance].salts += dictionary[:substance_ut].last.salts
229
+ dictionary[:substance_ut].last.salts = []
230
+ dictionary[:substance].salts << dictionary[:substance_ut].last
231
+ @@substances -= dictionary[:substance_ut]
232
+ end
233
+ dictionary[:substance]
253
234
  }
254
235
 
255
- rule(:int => simple(:int)) { IntLit.new(int) }
256
- rule(:number => simple(:nb)) {
257
- nb.match(/[eE\.]/) ? Float(nb) : Integer(nb)
236
+ rule(int: simple(:int)) { IntLit.new(int) }
237
+ rule(number: simple(:nb)) {
238
+ /[eE.]/.match?(nb) ? Float(nb) : Integer(nb)
258
239
  }
259
240
  rule(
260
- :qty_range => simple(:qty_range),
261
- :unit => simple(:unit)) {
262
- ParseDose.new(qty_range, unit)
263
- }
241
+ qty_range: simple(:qty_range),
242
+ unit: simple(:unit)
243
+ ) {
244
+ ParseDose.new(qty_range, unit)
245
+ }
264
246
  rule(
265
- :qty_range => simple(:qty_range)) {
266
- ParseDose.new(qty_range)
267
- }
247
+ qty_range: simple(:qty_range)
248
+ ) {
249
+ ParseDose.new(qty_range)
250
+ }
268
251
  rule(
269
- :qty => simple(:qty),
270
- :unit => simple(:unit)) {
271
- ParseDose.new(qty, unit)
272
- }
252
+ qty: simple(:qty),
253
+ unit: simple(:unit)
254
+ ) {
255
+ ParseDose.new(qty, unit)
256
+ }
273
257
  rule(
274
- :unit => simple(:unit)) { ParseDose.new(nil, unit) }
258
+ unit: simple(:unit)
259
+ ) { ParseDose.new(nil, unit) }
275
260
  rule(
276
- :qty => simple(:qty)) { ParseDose.new(qty, nil) }
261
+ qty: simple(:qty)
262
+ ) { ParseDose.new(qty, nil) }
277
263
  rule(
278
- :qty => simple(:qty),
279
- :unit => simple(:unit),
280
- :dose_right => simple(:dose_right),
281
- ) {
264
+ qty: simple(:qty),
265
+ unit: simple(:unit),
266
+ dose_right: simple(:dose_right)
267
+ ) {
282
268
  dose = ParseDose.new(qty, unit)
283
- dose.unit = dose.unit.to_s + ' et ' + ParseDose.new(dose_right).to_s
269
+ dose.unit = dose.unit.to_s + " et " + ParseDose.new(dose_right).to_s
284
270
  dose
285
271
  }
286
272
 
287
273
  @@substances ||= []
288
- @@excipiens = nil
289
- def CompositionTransformer.clear_substances
290
- @@more_info = nil
274
+ @@excipiens = nil
275
+ def self.clear_substances
276
+ @@more_info = nil
291
277
  @@substances = []
292
- @@excipiens = nil
293
- @@corresp = nil
278
+ @@excipiens = nil
279
+ @@corresp = nil
294
280
  end
295
- def CompositionTransformer.substances
281
+
282
+ def self.substances
296
283
  @@substances.clone
297
284
  end
298
- def CompositionTransformer.excipiens
285
+
286
+ def self.excipiens
299
287
  @@excipiens ? @@excipiens.clone : nil
300
288
  end
301
- def CompositionTransformer.corresp
289
+
290
+ def self.corresp
302
291
  @@corresp ? @@corresp.clone : nil
303
292
  end
304
293
  end
@@ -306,29 +295,31 @@ end
306
295
  class ParseDose
307
296
  attr_reader :qty, :qty_range
308
297
  attr_accessor :unit
309
- def initialize(qty=nil, unit=nil)
298
+ def initialize(qty = nil, unit = nil)
310
299
  puts "ParseDose.new from #{qty.inspect} #{unit.inspect} #{unit.inspect}" if VERBOSE_MESSAGES
311
- if qty and (qty.is_a?(String) || qty.is_a?(Parslet::Slice))
312
- string = qty.to_s.gsub("'", '')
313
- if string.index('-') and (string.index('-') > 0)
300
+ if qty && (qty.is_a?(String) || qty.is_a?(Parslet::Slice))
301
+ string = qty.to_s.delete("'")
302
+ if string.index("-") && (string.index("-") > 0)
314
303
  @qty_range = string
315
304
  elsif string.index(/\^|\*|\//)
316
- @qty = string
305
+ @qty = string
317
306
  else
318
- @qty = string.index('.') ? string.to_f : string.to_i
307
+ @qty = string.index(".") ? string.to_f : string.to_i
319
308
  end
320
309
  elsif qty
321
- @qty = qty.eval
310
+ @qty = qty.eval
322
311
  else
323
312
  @qty = 1
324
313
  end
325
314
  @unit = unit ? unit.to_s : nil
326
315
  end
316
+
327
317
  def eval
328
318
  self
329
319
  end
320
+
330
321
  def to_s
331
- return @unit unless @qty or @qty_range
322
+ return @unit unless @qty || @qty_range
332
323
  res = "#{@qty}#{@qty_range}"
333
324
  res = "#{res} #{@unit}" if @unit
334
325
  res
@@ -336,32 +327,36 @@ class ParseDose
336
327
  end
337
328
 
338
329
  class ParseSubstance
339
- attr_accessor :name, :qty, :unit, :chemical_substance, :chemical_qty, :chemical_unit, :is_active_agent, :dose, :cdose, :is_excipiens
340
- attr_accessor :description, :more_info, :salts
341
- def initialize(name, dose=nil)
330
+ attr_accessor :name, :chemical_substance, :chemical_qty, :chemical_unit, :is_active_agent, :dose, :cdose, :is_excipiens
331
+ attr_accessor :description, :more_info, :salts
332
+ attr_writer :unit, :qty
333
+ def initialize(name, dose = nil)
342
334
  puts "ParseSubstance.new from #{name.inspect} #{dose.inspect}" if VERBOSE_MESSAGES
343
335
  @name = ParseUtil.capitalize(name.to_s)
344
- @name.sub!(/\baqua\b/i, 'aqua')
345
- @name.sub!(/\bDER\b/i, 'DER')
346
- @name.sub!(/\bad pulverem\b/i, 'ad pulverem')
347
- @name.sub!(/\bad iniectabilia\b/i, 'ad iniectabilia')
348
- @name.sub!(/\bad suspensionem\b/i, 'ad suspensionem')
349
- @name.sub!(/\bad solutionem\b/i, 'ad solutionem')
350
- @name.sub!(/\bpro compresso\b/i, 'pro compresso')
351
- @name.sub!(/\bpro\b/i, 'pro')
352
- @name.sub!(/ Q\.S\. /i, ' q.s. ')
353
- @name.sub!(/\s+\bpro$/i, '')
336
+ @name.sub!(/\baqua\b/i, "aqua")
337
+ @name.sub!(/\bDER\b/i, "DER")
338
+ @name.sub!(/\bad pulverem\b/i, "ad pulverem")
339
+ @name.sub!(/\bad iniectabilia\b/i, "ad iniectabilia")
340
+ @name.sub!(/\bad suspensionem\b/i, "ad suspensionem")
341
+ @name.sub!(/\bad solutionem\b/i, "ad solutionem")
342
+ @name.sub!(/\bpro compresso\b/i, "pro compresso")
343
+ @name.sub!(/\bpro\b/i, "pro")
344
+ @name.sub!(/ Q\.S\. /i, " q.s. ")
345
+ @name.sub!(/\s+\bpro$/i, "")
354
346
  @dose = dose if dose
355
347
  @salts = []
356
348
  end
349
+
357
350
  def qty
358
- return @dose.qty_range if @dose and @dose.qty_range
351
+ return @dose.qty_range if @dose&.qty_range
359
352
  @dose ? @dose.qty : @qty
360
353
  end
354
+
361
355
  def unit
362
356
  return @unit if @unit
363
357
  @dose ? @dose.unit : @unit
364
358
  end
359
+
365
360
  def to_string
366
361
  s = "#{@name}:"
367
362
  s = " #{@qty}" if @qty
@@ -372,51 +367,54 @@ class ParseSubstance
372
367
  end
373
368
 
374
369
  class ParseComposition
375
- attr_accessor :source, :label, :label_description, :substances, :galenic_form, :route_of_administration,
376
- :corresp, :excipiens
377
-
378
- ErrorsToFix = { /(\d+)\s+\-\s*(\d+)/ => '\1-\2',
379
- 'o.1' => '0.1',
380
- /\s+(mg|g) DER:/ => ' \1, DER:',
381
- ' mind. ' => ' min. ',
382
- ' streptococci pyogen. ' => ' streptococci pyogen ',
383
- ' ut excipiens' => ', excipiens',
384
- ' Corresp. ' => ' corresp. ',
385
- ',,' => ',',
386
- 'avena elatior,dactylis glomerata' => 'avena elatior, dactylis glomerata',
387
- ' color.: corresp. ' => ' corresp.',
388
- / U\.: (excipiens) / => ' U. \1 ',
389
- / U\.: (alnus|betula|betula|betulae) / => ' U., \1 ',
390
- /^(acari allergeni extractum (\(acarus siro\)|).+\s+U\.\:)/ => 'A): \1',
391
- 'Solvens: alprostadilum' => 'alprostadilum',
392
- }
393
- @@errorHandler = ParseUtil::HandleSwissmedicErrors.new( ErrorsToFix )
370
+ attr_accessor :source, :label, :label_description, :substances, :galenic_form, :route_of_administration,
371
+ :corresp, :excipiens
372
+
373
+ ERRORS_TO_FIX = {
374
+ /(\d+)\s+-\s*(\d+)/ => '\1-\2',
375
+ "o.1" => "0.1",
376
+ /\s+(mg|g) DER:/ => ' \1, DER:',
377
+ " mind. " => " min. ",
378
+ " streptococci pyogen. " => " streptococci pyogen ",
379
+ " ut excipiens" => ", excipiens",
380
+ " Corresp. " => " corresp. ",
381
+ ",," => ",",
382
+ "avena elatior,dactylis glomerata" => "avena elatior, dactylis glomerata",
383
+ " color.: corresp. " => " corresp.",
384
+ / U\.: (excipiens) / => ' U. \1 ',
385
+ / U\.: (alnus|betula|betula|betulae) / => ' U., \1 ',
386
+ /^(acari allergeni extractum (\(acarus siro\)|).+\s+U\.:)/ => 'A): \1',
387
+ "Solvens: alprostadilum" => "alprostadilum"
388
+ }
389
+ @@error_handler = ParseUtil::HandleSwissmedicErrors.new(ERRORS_TO_FIX)
394
390
 
395
391
  def initialize(source)
396
392
  @substances ||= []
397
393
  puts "ParseComposition.new from #{source.inspect} @substances #{@substances.inspect}" if VERBOSE_MESSAGES
398
394
  @source = source.to_s
399
395
  end
400
- def ParseComposition.reset
401
- @@errorHandler = ParseUtil::HandleSwissmedicErrors.new( ErrorsToFix )
396
+
397
+ def self.reset
398
+ @@error_handler = ParseUtil::HandleSwissmedicErrors.new(ERRORS_TO_FIX)
402
399
  end
403
- def ParseComposition.report
404
- @@errorHandler.report
400
+
401
+ def self.report
402
+ @@error_handler.report
405
403
  end
406
- def ParseComposition.from_string(string)
407
- return nil if string == nil or string.eql?('.') or string.eql?('')
408
- stripped = string.gsub(/^"|["\n]+$/, '')
404
+
405
+ def self.from_string(string)
406
+ return nil if string.nil? || string.eql?(".") || string.eql?("")
407
+ stripped = string.gsub(/^"|["\n]+$/, "")
409
408
  return nil unless stripped
410
- if /(U\.I\.|U\.)$/.match(stripped)
411
- cleaned = stripped
409
+ cleaned = if /(U\.I\.|U\.)$/.match?(stripped)
410
+ stripped
412
411
  else
413
- cleaned = stripped.sub(/[\.]+$/, '')
412
+ stripped.sub(/\.+$/, "")
414
413
  end
415
- value = nil
416
414
  puts "ParseComposition.from_string #{string}" if VERBOSE_MESSAGES # /ng-tr/.match(Socket.gethostbyname(Socket.gethostname).first)
417
415
 
418
- cleaned = @@errorHandler.apply_fixes(cleaned)
419
- puts "ParseComposition.new cleaned #{cleaned}" if VERBOSE_MESSAGES and not cleaned.eql?(stripped)
416
+ cleaned = @@error_handler.apply_fixes(cleaned)
417
+ puts "ParseComposition.new cleaned #{cleaned}" if VERBOSE_MESSAGES && !cleaned.eql?(stripped)
420
418
  CompositionTransformer.clear_substances
421
419
  result = ParseComposition.new(cleaned)
422
420
  parser = CompositionParser.new
@@ -430,8 +428,8 @@ class ParseComposition
430
428
  ast = transf.apply(parser.parse(cleaned))
431
429
  end
432
430
  rescue Parslet::ParseFailed => error
433
- @@errorHandler.nrParsingErrors += 1
434
- puts "#{File.basename(__FILE__)}:#{__LINE__}: failed parsing ==> #{cleaned}"
431
+ @@error_handler.nrParsingErrors += 1
432
+ puts "#{File.basename(__FILE__)}:#{__LINE__}: failed parsing ==> #{cleaned} #{error}"
435
433
  return nil
436
434
  end
437
435
  result.source = string
@@ -441,50 +439,46 @@ class ParseComposition
441
439
  result.substances = CompositionTransformer.substances
442
440
  result.excipiens = CompositionTransformer.excipiens
443
441
  result.corresp = CompositionTransformer.corresp if CompositionTransformer.corresp
444
- if result.excipiens and result.excipiens.unit
445
- pro_qty = "/#{result.excipiens.qty} #{result.excipiens.unit}".sub(/\/1\s+/, '/')
446
- result.substances.each {
447
- |substance|
448
- next unless substance.is_a?(ParseSubstance)
449
- substance.chemical_substance.unit = "#{substance.chemical_substance.unit}#{pro_qty}" if substance.chemical_substance
450
- substance.dose.unit = "#{substance.dose.unit}#{pro_qty}" if substance.unit and not substance.unit.eql?(result.excipiens.unit)
442
+ if result&.excipiens&.unit
443
+ pro_qty = "/#{result.excipiens.qty} #{result.excipiens.unit}".sub(/\/1\s+/, "/")
444
+ result.substances.each { |substance|
445
+ next unless substance.is_a?(ParseSubstance)
446
+ substance.chemical_substance.unit = "#{substance.chemical_substance.unit}#{pro_qty}" if substance.chemical_substance
447
+ substance.dose.unit = "#{substance.dose.unit}#{pro_qty}" if substance.unit && !substance.unit.eql?(result.excipiens.unit)
451
448
  }
452
449
  end
453
- if ast.is_a?(Array) and ast.first.is_a?(Hash)
450
+ if ast.is_a?(Array) && ast.first.is_a?(Hash)
454
451
  label = ast.first[:label].to_s if ast.first[:label]
455
452
  label_description = ast.first[:label_description].to_s if ast.first[:label_description]
456
- elsif ast and ast.is_a?(Hash)
457
- label = ast[:label].to_s if ast[:label]
453
+ elsif ast&.is_a?(Hash)
454
+ label = ast[:label].to_s if ast[:label]
458
455
  label_description = ast[:label_description].to_s if ast[:label_description]
459
456
  end
460
457
  if label
461
- if label and not /((A|B|C|D|E|I|II|III|IV|\)+)\s+et\s+(A|B|C|D|E|I|II|III|IV|\))+)/.match(label)
462
- result.label = label
458
+ if label && !/((A|B|C|D|E|I|II|III|IV|\)+)\s+et\s+(A|B|C|D|E|I|II|III|IV|\))+)/.match(label)
459
+ result.label = label
463
460
  end
464
- result.label_description = label_description.gsub(/:+$/, '').strip if label_description
461
+ result.label_description = label_description.gsub(/:+$/, "").strip if label_description
465
462
  end
466
- result.corresp = ast[:corresp].to_s.sub(/:\s+/, '') if not result.corresp and ast.is_a?(Hash) and ast[:corresp]
467
- return result
463
+ result.corresp = ast[:corresp].to_s.sub(/:\s+/, "") if !result.corresp && ast.is_a?(Hash) && ast[:corresp]
464
+ result
468
465
  end
469
466
  end
470
467
 
471
468
  class GalenicFormTransformer < CompositionTransformer
472
-
473
- rule( :preparation_name => simple(:preparation_name),
474
- :galenic_form => simple(:preparation_name),
475
- ) {
476
- |dictionary|
477
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
478
- name = dictionary[:preparation_name] ? dictionary[:preparation_name].to_s : nil
479
- form = dictionary[:galenic_form] ? dictionary[:galenic_form].to_s : nil
480
- # name, form
469
+ rule(preparation_name: simple(:preparation_name),
470
+ galenic_form: simple(:preparation_name)) { |dictionary|
471
+ puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
472
+ dictionary[:preparation_name] ? dictionary[:preparation_name].to_s : nil
473
+ dictionary[:galenic_form] ? dictionary[:galenic_form].to_s : nil
474
+ # name, form
481
475
  }
482
476
  end
483
477
 
484
478
  class ParseGalenicForm
485
- def ParseGalenicForm.from_string(string)
486
- return nil if string == nil
487
- stripped = string.gsub(/^"|["\n]+$/, '')
479
+ def self.from_string(string)
480
+ return nil if string.nil?
481
+ stripped = string.gsub(/^"|["\n]+$/, "")
488
482
  return nil unless stripped
489
483
  puts "ParseGalenicForm.from_string #{string}" if VERBOSE_MESSAGES # /ng-tr/.match(Socket.gethostbyname(Socket.gethostname).first)
490
484
 
@@ -499,13 +493,13 @@ class ParseGalenicForm
499
493
  ast = transf.apply(parser.parse(string))
500
494
  end
501
495
  rescue Parslet::ParseFailed => error
502
- @@errorHandler.nrParsingErrors += 1
503
- puts "#{File.basename(__FILE__)}:#{__LINE__}: failed parsing ==> #{string}"
496
+ @@error_handler.nrParsingErrors += 1
497
+ puts "#{File.basename(__FILE__)}:#{__LINE__}: failed parsing ==> #{string} #{error}"
504
498
  return nil
505
499
  end
506
500
  return [] unless ast
507
- form = ast[:galenic_form] ? ast[:galenic_form].to_s.sub(/^\/\s+/, '') : nil
501
+ form = ast[:galenic_form] ? ast[:galenic_form].to_s.sub(/^\/\s+/, "") : nil
508
502
  name = ast[:prepation_name] ? ast[:prepation_name].to_s.strip : nil
509
- return [name, form]
503
+ [name, form]
510
504
  end
511
505
  end