oddb2xml 2.6.7 → 2.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +40 -0
  3. data/.standard.yml +2 -0
  4. data/Elexis_Artikelstamm_v5.xsd +0 -3
  5. data/Gemfile +3 -3
  6. data/History.txt +30 -0
  7. data/README.md +1 -1
  8. data/Rakefile +24 -23
  9. data/bin/check_artikelstamm +11 -11
  10. data/bin/compare_v5 +23 -23
  11. data/bin/oddb2xml +14 -13
  12. data/lib/oddb2xml.rb +1 -1
  13. data/lib/oddb2xml/builder.rb +1077 -1039
  14. data/lib/oddb2xml/calc.rb +232 -233
  15. data/lib/oddb2xml/chapter_70_hack.rb +38 -32
  16. data/lib/oddb2xml/cli.rb +252 -233
  17. data/lib/oddb2xml/compare.rb +70 -59
  18. data/lib/oddb2xml/compositions_syntax.rb +448 -430
  19. data/lib/oddb2xml/compressor.rb +20 -20
  20. data/lib/oddb2xml/downloader.rb +155 -129
  21. data/lib/oddb2xml/extractor.rb +302 -296
  22. data/lib/oddb2xml/options.rb +34 -35
  23. data/lib/oddb2xml/parslet_compositions.rb +263 -265
  24. data/lib/oddb2xml/semantic_check.rb +39 -33
  25. data/lib/oddb2xml/util.rb +169 -159
  26. data/lib/oddb2xml/version.rb +1 -1
  27. data/lib/oddb2xml/xml_definitions.rb +32 -33
  28. data/oddb2xml.gemspec +32 -30
  29. data/spec/artikelstamm_spec.rb +139 -132
  30. data/spec/builder_spec.rb +495 -524
  31. data/spec/calc_spec.rb +552 -593
  32. data/spec/check_artikelstamm_spec.rb +26 -26
  33. data/spec/cli_spec.rb +182 -157
  34. data/spec/compare_spec.rb +9 -11
  35. data/spec/composition_syntax_spec.rb +390 -409
  36. data/spec/compressor_spec.rb +48 -48
  37. data/spec/data/Preparations.xml +139 -3
  38. data/spec/data/refdata_NonPharma.xml +0 -3
  39. data/spec/data/refdata_Pharma.xml +10 -25
  40. data/spec/data/swissmedic_package.xlsx +0 -0
  41. data/spec/data/transfer.dat +3 -1
  42. data/spec/data/varia_De.htm +2 -2
  43. data/spec/data_helper.rb +47 -49
  44. data/spec/downloader_spec.rb +247 -260
  45. data/spec/extractor_spec.rb +173 -165
  46. data/spec/galenic_spec.rb +233 -256
  47. data/spec/options_spec.rb +116 -119
  48. data/spec/parslet_spec.rb +833 -861
  49. data/spec/spec_helper.rb +154 -153
  50. data/test_options.rb +39 -42
  51. data/tools/win_fetch_cacerts.rb +2 -3
  52. metadata +49 -5
  53. data/.travis.yml +0 -30
@@ -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,16 +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.gsub('(', '\(').gsub(')', '\)')}($|\s)/.match(active_substance_name) } != nil)
92
- 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) }
93
- 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
94
95
  comps << composition
95
96
  end
96
97
  end
@@ -99,202 +100,194 @@ module ParseUtil
99
100
  comps
100
101
  rescue => error
101
102
  puts "error #{error}"
102
- # binding.pry
103
+ # binding.pry
103
104
  end
104
-
105
105
  end
106
106
 
107
- class IntLit < Struct.new(:int)
108
- def eval; int.to_i; end
107
+ class IntLit < Struct.new(:int)
108
+ def eval
109
+ int.to_i
110
+ end
109
111
  end
110
- class QtyLit < Struct.new(:qty)
111
- def eval; qty.to_i; end
112
+
113
+ class QtyLit < Struct.new(:qty)
114
+ def eval
115
+ qty.to_i
116
+ end
112
117
  end
113
118
 
114
119
  class CompositionTransformer < Parslet::Transform
115
120
  @@more_info = nil
116
- def CompositionTransformer.get_ratio(parse_info)
121
+ def self.get_ratio(parse_info)
117
122
  if parse_info[:ratio]
118
- if parse_info[:ratio].to_s.length > 0 and parse_info[:ratio].to_s != ', '
119
- parse_info[:ratio].to_s.sub(/^,\s+/, '').sub(/,\s+$/,'')
120
- else
121
- 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+$/, "")
122
125
  end
123
- else
124
- nil
125
126
  end
126
127
  end
127
128
 
128
- def CompositionTransformer.check_e_substance(substance)
129
- 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)
130
131
  unless substance.more_info
131
132
  case substance.name[2]
132
133
  when "1"
133
- substance.more_info = 'color.'
134
+ substance.more_info = "color."
134
135
  when "2"
135
- substance.more_info = 'conserv.'
136
- else
136
+ substance.more_info = "conserv."
137
137
  end
138
138
  substance.more_info ||= @@more_info
139
139
  end
140
140
  @@more_info = substance.more_info
141
141
  end
142
142
 
143
- def CompositionTransformer.add_excipiens(info)
144
- @@more_info = nil
145
- @@excipiens = ParseSubstance.new(info[:excipiens_description] ? info[:excipiens_description] : 'Excipiens')
146
- @@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]
147
147
  @@excipiens.more_info = CompositionTransformer.get_ratio(info)
148
- @@excipiens.cdose = info[:dose_corresp] if info[:dose_corresp]
148
+ @@excipiens.cdose = info[:dose_corresp] if info[:dose_corresp]
149
149
  @@excipiens.more_info = info[:more_info] if info[:more_info]
150
150
  end
151
151
 
152
- rule(:corresp => simple(:corresp),
153
- ) {
154
- |dictionary|
155
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
156
- @@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
157
155
  }
158
- rule( :substance_name => simple(:substance_name),
159
- :dose => simple(:dose),
160
- ) {
161
- |dictionary|
162
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
163
- dose = dictionary[:dose].is_a?(ParseDose) ? dictionary[:dose] : nil
164
- substance = ParseSubstance.new(dictionary[:substance_name], dose)
165
- @@substances << substance
166
- 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
167
163
  }
168
164
 
169
- rule( :more_info => simple(:more_info),
170
- ) {
171
- |dictionary|
172
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
173
- @@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(/:$/, "")
174
168
  }
175
- rule( :more_info => simple(:more_info),
176
- :substance_name => simple(:substance_name),
177
- :dose => simple(:dose),
178
- ) {
179
- |dictionary|
180
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
181
- dose = dictionary[:dose].is_a?(ParseDose) ? dictionary[:dose] : nil
182
- substance = ParseSubstance.new(dictionary[:substance_name].to_s, dose)
183
- substance.more_info = dictionary[:more_info].to_s.strip.sub(/:$/, '') if dictionary[:more_info] and dictionary[:more_info].to_s.length > 0
184
- CompositionTransformer.check_e_substance(substance)
185
- @@substances << substance
186
- 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
187
179
  }
188
180
 
189
- rule(:lebensmittel_zusatz => simple(:lebensmittel_zusatz),
190
- :more_info => simple(:more_info),
191
- :digits => simple(:digits)) {
192
- |dictionary|
193
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
194
- substance = ParseSubstance.new("#{dictionary[:lebensmittel_zusatz]} #{dictionary[:digits]}")
195
- substance.more_info = dictionary[:more_info].to_s.strip.sub(/:$/, '') if dictionary[:more_info] and dictionary[:more_info].to_s.length > 0
196
- CompositionTransformer.check_e_substance(substance)
197
- @@substances << substance
198
- 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
199
190
  }
200
- rule(:excipiens => subtree(:excipiens),
201
- ) {
202
- |dictionary|
203
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
204
- info = dictionary[:excipiens].is_a?(Hash) ? dictionary[:excipiens] : dictionary[:excipiens].first
205
- if info[:excipiens_description] or
206
- info[:dose] or
207
- info[:dose_corresp] or
208
- info[:more_info] or
209
- CompositionTransformer.get_ratio(dictionary)
210
- CompositionTransformer.add_excipiens(info)
211
- info
212
- end
213
- 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
214
203
  }
215
- rule(:composition => subtree(:composition),
216
- ) {
217
- |dictionary|
218
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
219
- info = dictionary[:composition].is_a?(Hash) ? dictionary[:composition] : dictionary[:composition].first
220
- CompositionTransformer.add_excipiens(info) if info.is_a?(Hash)
221
- info
222
- }
223
- rule(:substance => simple(:substance),
224
- :chemical_substance => simple(:chemical_substance),
225
- :substance_ut => sequence(:substance_ut),
226
- :ratio => simple(:ratio),
227
- ) {
228
- |dictionary|
229
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
230
- ratio = CompositionTransformer.get_ratio(dictionary)
231
- if ratio and ratio.length > 0
232
- if dictionary[:substance].more_info
233
- dictionary[:substance].more_info += ' ' + ratio.strip
234
- else
235
- dictionary[:substance].more_info = ratio.strip
236
- end
237
- end
238
- if dictionary[:chemical_substance]
239
- dictionary[:substance].chemical_substance = dictionary[:chemical_substance]
240
- @@substances -= [dictionary[:chemical_substance]]
241
- end
242
- if dictionary[:substance_ut].size > 0
243
- dictionary[:substance].salts += dictionary[:substance_ut].last.salts
244
- dictionary[:substance_ut].last.salts = []
245
- dictionary[:substance].salts << dictionary[:substance_ut].last
246
- @@substances -= dictionary[:substance_ut]
247
- end
248
- 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]
249
234
  }
250
235
 
251
- rule(:int => simple(:int)) { IntLit.new(int) }
252
- rule(:number => simple(:nb)) {
253
- 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)
254
239
  }
255
240
  rule(
256
- :qty_range => simple(:qty_range),
257
- :unit => simple(:unit)) {
258
- ParseDose.new(qty_range, unit)
259
- }
241
+ qty_range: simple(:qty_range),
242
+ unit: simple(:unit)
243
+ ) {
244
+ ParseDose.new(qty_range, unit)
245
+ }
260
246
  rule(
261
- :qty_range => simple(:qty_range)) {
262
- ParseDose.new(qty_range)
263
- }
247
+ qty_range: simple(:qty_range)
248
+ ) {
249
+ ParseDose.new(qty_range)
250
+ }
264
251
  rule(
265
- :qty => simple(:qty),
266
- :unit => simple(:unit)) {
267
- ParseDose.new(qty, unit)
268
- }
252
+ qty: simple(:qty),
253
+ unit: simple(:unit)
254
+ ) {
255
+ ParseDose.new(qty, unit)
256
+ }
269
257
  rule(
270
- :unit => simple(:unit)) { ParseDose.new(nil, unit) }
258
+ unit: simple(:unit)
259
+ ) { ParseDose.new(nil, unit) }
271
260
  rule(
272
- :qty => simple(:qty)) { ParseDose.new(qty, nil) }
261
+ qty: simple(:qty)
262
+ ) { ParseDose.new(qty, nil) }
273
263
  rule(
274
- :qty => simple(:qty),
275
- :unit => simple(:unit),
276
- :dose_right => simple(:dose_right),
277
- ) {
264
+ qty: simple(:qty),
265
+ unit: simple(:unit),
266
+ dose_right: simple(:dose_right)
267
+ ) {
278
268
  dose = ParseDose.new(qty, unit)
279
- 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
280
270
  dose
281
271
  }
282
272
 
283
273
  @@substances ||= []
284
- @@excipiens = nil
285
- def CompositionTransformer.clear_substances
286
- @@more_info = nil
274
+ @@excipiens = nil
275
+ def self.clear_substances
276
+ @@more_info = nil
287
277
  @@substances = []
288
- @@excipiens = nil
289
- @@corresp = nil
278
+ @@excipiens = nil
279
+ @@corresp = nil
290
280
  end
291
- def CompositionTransformer.substances
281
+
282
+ def self.substances
292
283
  @@substances.clone
293
284
  end
294
- def CompositionTransformer.excipiens
285
+
286
+ def self.excipiens
295
287
  @@excipiens ? @@excipiens.clone : nil
296
288
  end
297
- def CompositionTransformer.corresp
289
+
290
+ def self.corresp
298
291
  @@corresp ? @@corresp.clone : nil
299
292
  end
300
293
  end
@@ -302,29 +295,31 @@ end
302
295
  class ParseDose
303
296
  attr_reader :qty, :qty_range
304
297
  attr_accessor :unit
305
- def initialize(qty=nil, unit=nil)
298
+ def initialize(qty = nil, unit = nil)
306
299
  puts "ParseDose.new from #{qty.inspect} #{unit.inspect} #{unit.inspect}" if VERBOSE_MESSAGES
307
- if qty and (qty.is_a?(String) || qty.is_a?(Parslet::Slice))
308
- string = qty.to_s.gsub("'", '')
309
- 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)
310
303
  @qty_range = string
311
304
  elsif string.index(/\^|\*|\//)
312
- @qty = string
305
+ @qty = string
313
306
  else
314
- @qty = string.index('.') ? string.to_f : string.to_i
307
+ @qty = string.index(".") ? string.to_f : string.to_i
315
308
  end
316
309
  elsif qty
317
- @qty = qty.eval
310
+ @qty = qty.eval
318
311
  else
319
312
  @qty = 1
320
313
  end
321
314
  @unit = unit ? unit.to_s : nil
322
315
  end
316
+
323
317
  def eval
324
318
  self
325
319
  end
320
+
326
321
  def to_s
327
- return @unit unless @qty or @qty_range
322
+ return @unit unless @qty || @qty_range
328
323
  res = "#{@qty}#{@qty_range}"
329
324
  res = "#{res} #{@unit}" if @unit
330
325
  res
@@ -332,32 +327,36 @@ class ParseDose
332
327
  end
333
328
 
334
329
  class ParseSubstance
335
- attr_accessor :name, :qty, :unit, :chemical_substance, :chemical_qty, :chemical_unit, :is_active_agent, :dose, :cdose, :is_excipiens
336
- attr_accessor :description, :more_info, :salts
337
- 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)
338
334
  puts "ParseSubstance.new from #{name.inspect} #{dose.inspect}" if VERBOSE_MESSAGES
339
335
  @name = ParseUtil.capitalize(name.to_s)
340
- @name.sub!(/\baqua\b/i, 'aqua')
341
- @name.sub!(/\bDER\b/i, 'DER')
342
- @name.sub!(/\bad pulverem\b/i, 'ad pulverem')
343
- @name.sub!(/\bad iniectabilia\b/i, 'ad iniectabilia')
344
- @name.sub!(/\bad suspensionem\b/i, 'ad suspensionem')
345
- @name.sub!(/\bad solutionem\b/i, 'ad solutionem')
346
- @name.sub!(/\bpro compresso\b/i, 'pro compresso')
347
- @name.sub!(/\bpro\b/i, 'pro')
348
- @name.sub!(/ Q\.S\. /i, ' q.s. ')
349
- @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, "")
350
346
  @dose = dose if dose
351
347
  @salts = []
352
348
  end
349
+
353
350
  def qty
354
- return @dose.qty_range if @dose and @dose.qty_range
351
+ return @dose.qty_range if @dose&.qty_range
355
352
  @dose ? @dose.qty : @qty
356
353
  end
354
+
357
355
  def unit
358
356
  return @unit if @unit
359
357
  @dose ? @dose.unit : @unit
360
358
  end
359
+
361
360
  def to_string
362
361
  s = "#{@name}:"
363
362
  s = " #{@qty}" if @qty
@@ -368,51 +367,54 @@ class ParseSubstance
368
367
  end
369
368
 
370
369
  class ParseComposition
371
- attr_accessor :source, :label, :label_description, :substances, :galenic_form, :route_of_administration,
372
- :corresp, :excipiens
373
-
374
- ErrorsToFix = { /(\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
- @@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)
390
390
 
391
391
  def initialize(source)
392
392
  @substances ||= []
393
393
  puts "ParseComposition.new from #{source.inspect} @substances #{@substances.inspect}" if VERBOSE_MESSAGES
394
394
  @source = source.to_s
395
395
  end
396
- def ParseComposition.reset
397
- @@errorHandler = ParseUtil::HandleSwissmedicErrors.new( ErrorsToFix )
396
+
397
+ def self.reset
398
+ @@error_handler = ParseUtil::HandleSwissmedicErrors.new(ERRORS_TO_FIX)
398
399
  end
399
- def ParseComposition.report
400
- @@errorHandler.report
400
+
401
+ def self.report
402
+ @@error_handler.report
401
403
  end
402
- def ParseComposition.from_string(string)
403
- return nil if string == nil or string.eql?('.') or string.eql?('')
404
- 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]+$/, "")
405
408
  return nil unless stripped
406
- if /(U\.I\.|U\.)$/.match(stripped)
407
- cleaned = stripped
409
+ cleaned = if /(U\.I\.|U\.)$/.match?(stripped)
410
+ stripped
408
411
  else
409
- cleaned = stripped.sub(/[\.]+$/, '')
412
+ stripped.sub(/\.+$/, "")
410
413
  end
411
- value = nil
412
414
  puts "ParseComposition.from_string #{string}" if VERBOSE_MESSAGES # /ng-tr/.match(Socket.gethostbyname(Socket.gethostname).first)
413
415
 
414
- cleaned = @@errorHandler.apply_fixes(cleaned)
415
- 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)
416
418
  CompositionTransformer.clear_substances
417
419
  result = ParseComposition.new(cleaned)
418
420
  parser = CompositionParser.new
@@ -426,8 +428,8 @@ class ParseComposition
426
428
  ast = transf.apply(parser.parse(cleaned))
427
429
  end
428
430
  rescue Parslet::ParseFailed => error
429
- @@errorHandler.nrParsingErrors += 1
430
- puts "#{File.basename(__FILE__)}:#{__LINE__}: failed parsing ==> #{cleaned}"
431
+ @@error_handler.nrParsingErrors += 1
432
+ puts "#{File.basename(__FILE__)}:#{__LINE__}: failed parsing ==> #{cleaned} #{error}"
431
433
  return nil
432
434
  end
433
435
  result.source = string
@@ -437,50 +439,46 @@ class ParseComposition
437
439
  result.substances = CompositionTransformer.substances
438
440
  result.excipiens = CompositionTransformer.excipiens
439
441
  result.corresp = CompositionTransformer.corresp if CompositionTransformer.corresp
440
- if result.excipiens and result.excipiens.unit
441
- pro_qty = "/#{result.excipiens.qty} #{result.excipiens.unit}".sub(/\/1\s+/, '/')
442
- result.substances.each {
443
- |substance|
444
- next unless substance.is_a?(ParseSubstance)
445
- substance.chemical_substance.unit = "#{substance.chemical_substance.unit}#{pro_qty}" if substance.chemical_substance
446
- 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)
447
448
  }
448
449
  end
449
- if ast.is_a?(Array) and ast.first.is_a?(Hash)
450
+ if ast.is_a?(Array) && ast.first.is_a?(Hash)
450
451
  label = ast.first[:label].to_s if ast.first[:label]
451
452
  label_description = ast.first[:label_description].to_s if ast.first[:label_description]
452
- elsif ast and ast.is_a?(Hash)
453
- label = ast[:label].to_s if ast[:label]
453
+ elsif ast&.is_a?(Hash)
454
+ label = ast[:label].to_s if ast[:label]
454
455
  label_description = ast[:label_description].to_s if ast[:label_description]
455
456
  end
456
457
  if label
457
- 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)
458
- 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
459
460
  end
460
- result.label_description = label_description.gsub(/:+$/, '').strip if label_description
461
+ result.label_description = label_description.gsub(/:+$/, "").strip if label_description
461
462
  end
462
- result.corresp = ast[:corresp].to_s.sub(/:\s+/, '') if not result.corresp and ast.is_a?(Hash) and ast[:corresp]
463
- return result
463
+ result.corresp = ast[:corresp].to_s.sub(/:\s+/, "") if !result.corresp && ast.is_a?(Hash) && ast[:corresp]
464
+ result
464
465
  end
465
466
  end
466
467
 
467
468
  class GalenicFormTransformer < CompositionTransformer
468
-
469
- rule( :preparation_name => simple(:preparation_name),
470
- :galenic_form => simple(:preparation_name),
471
- ) {
472
- |dictionary|
473
- puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
474
- name = dictionary[:preparation_name] ? dictionary[:preparation_name].to_s : nil
475
- form = dictionary[:galenic_form] ? dictionary[:galenic_form].to_s : nil
476
- # 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
477
475
  }
478
476
  end
479
477
 
480
478
  class ParseGalenicForm
481
- def ParseGalenicForm.from_string(string)
482
- return nil if string == nil
483
- stripped = string.gsub(/^"|["\n]+$/, '')
479
+ def self.from_string(string)
480
+ return nil if string.nil?
481
+ stripped = string.gsub(/^"|["\n]+$/, "")
484
482
  return nil unless stripped
485
483
  puts "ParseGalenicForm.from_string #{string}" if VERBOSE_MESSAGES # /ng-tr/.match(Socket.gethostbyname(Socket.gethostname).first)
486
484
 
@@ -495,13 +493,13 @@ class ParseGalenicForm
495
493
  ast = transf.apply(parser.parse(string))
496
494
  end
497
495
  rescue Parslet::ParseFailed => error
498
- @@errorHandler.nrParsingErrors += 1
499
- puts "#{File.basename(__FILE__)}:#{__LINE__}: failed parsing ==> #{string}"
496
+ @@error_handler.nrParsingErrors += 1
497
+ puts "#{File.basename(__FILE__)}:#{__LINE__}: failed parsing ==> #{string} #{error}"
500
498
  return nil
501
499
  end
502
500
  return [] unless ast
503
- 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
504
502
  name = ast[:prepation_name] ? ast[:prepation_name].to_s.strip : nil
505
- return [name, form]
503
+ [name, form]
506
504
  end
507
505
  end