oddb2xml 2.6.7 → 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 (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