oddb2xml 2.7.1 → 2.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +1 -2
- data/.standard.yml +2 -0
- data/Gemfile +3 -3
- data/History.txt +24 -0
- data/README.md +3 -3
- data/Rakefile +24 -23
- data/bin/check_artikelstamm +11 -11
- data/bin/compare_v5 +23 -23
- data/bin/oddb2xml +14 -13
- data/lib/oddb2xml/builder.rb +1070 -1038
- data/lib/oddb2xml/calc.rb +232 -233
- data/lib/oddb2xml/chapter_70_hack.rb +38 -32
- data/lib/oddb2xml/cli.rb +252 -236
- data/lib/oddb2xml/compare.rb +70 -59
- data/lib/oddb2xml/compositions_syntax.rb +451 -430
- data/lib/oddb2xml/compressor.rb +20 -20
- data/lib/oddb2xml/downloader.rb +157 -129
- data/lib/oddb2xml/extractor.rb +295 -295
- data/lib/oddb2xml/options.rb +34 -35
- data/lib/oddb2xml/parslet_compositions.rb +265 -269
- data/lib/oddb2xml/semantic_check.rb +39 -33
- data/lib/oddb2xml/util.rb +163 -163
- data/lib/oddb2xml/version.rb +1 -1
- data/lib/oddb2xml/xml_definitions.rb +32 -33
- data/lib/oddb2xml.rb +1 -1
- data/oddb2xml.gemspec +34 -34
- data/shell.nix +17 -0
- data/spec/artikelstamm_spec.rb +111 -110
- data/spec/builder_spec.rb +490 -505
- data/spec/calc_spec.rb +552 -593
- data/spec/check_artikelstamm_spec.rb +26 -26
- data/spec/cli_spec.rb +173 -174
- data/spec/compare_spec.rb +9 -11
- data/spec/composition_syntax_spec.rb +390 -409
- data/spec/compressor_spec.rb +48 -48
- data/spec/data/transfer.dat +1 -0
- data/spec/data_helper.rb +47 -49
- data/spec/downloader_spec.rb +251 -260
- data/spec/extractor_spec.rb +171 -159
- data/spec/fixtures/vcr_cassettes/oddb2xml.json +1 -1
- data/spec/galenic_spec.rb +233 -256
- data/spec/options_spec.rb +116 -119
- data/spec/parslet_spec.rb +896 -863
- data/spec/spec_helper.rb +153 -153
- data/test_options.rb +39 -42
- data/tools/win_fetch_cacerts.rb +2 -3
- metadata +42 -12
@@ -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
|
10
|
-
require
|
11
|
-
|
12
|
-
include Parslet
|
7
|
+
require "parslet"
|
8
|
+
require "parslet/convenience"
|
9
|
+
require_relative "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
|
18
|
+
@@nr_saved_parsed_used ||= 0
|
21
19
|
|
22
20
|
class HandleSwissmedicErrors
|
23
|
-
|
24
21
|
attr_accessor :nrParsingErrors
|
25
|
-
class ErrorEntry
|
22
|
+
class ErrorEntry < Struct.new("ErrorEntry", :pattern, :replacement, :nr_occurrences)
|
26
23
|
end
|
27
24
|
|
28
25
|
def reset_errors
|
29
26
|
@errors = []
|
30
|
-
@
|
31
|
-
@
|
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 #{@
|
42
|
-
@errors.each {
|
43
|
-
|
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,
|
49
|
+
result = result.gsub(entry.pattern, entry.replacement)
|
55
50
|
unless result.eql?(intermediate)
|
56
|
-
|
57
|
-
|
51
|
+
entry.nr_occurrences += 1
|
52
|
+
puts "#{File.basename(__FILE__)}:#{__LINE__}: fixed \nbefore: #{intermediate}\nafter: #{result}" if $VERBOSE
|
58
53
|
end
|
59
54
|
}
|
60
|
-
@
|
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
|
67
|
-
string.split(/\s+/u).collect { |word| word.capitalize }.join(
|
61
|
+
def self.capitalize(string)
|
62
|
+
string.split(/\s+/u).collect { |word| word.capitalize }.join(" ").strip
|
68
63
|
end
|
69
64
|
|
70
|
-
def
|
65
|
+
def self.nr_saved_parsed_used
|
71
66
|
@@nr_saved_parsed_used
|
72
67
|
end
|
73
68
|
|
74
|
-
def
|
75
|
-
active_agents = active_agents_string ? active_agents_string.
|
76
|
-
key = [
|
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
|
-
|
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
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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,195 @@ module ParseUtil
|
|
103
100
|
comps
|
104
101
|
rescue => error
|
105
102
|
puts "error #{error}"
|
106
|
-
|
103
|
+
# binding.pry
|
104
|
+
raise error
|
107
105
|
end
|
108
|
-
|
109
106
|
end
|
110
107
|
|
111
|
-
class IntLit
|
112
|
-
def eval
|
108
|
+
class IntLit < Struct.new(:int)
|
109
|
+
def eval
|
110
|
+
int.to_i
|
111
|
+
end
|
113
112
|
end
|
114
|
-
|
115
|
-
|
113
|
+
|
114
|
+
class QtyLit < Struct.new(:qty)
|
115
|
+
def eval
|
116
|
+
qty.to_i
|
117
|
+
end
|
116
118
|
end
|
117
119
|
|
118
120
|
class CompositionTransformer < Parslet::Transform
|
119
121
|
@@more_info = nil
|
120
|
-
def
|
122
|
+
def self.get_ratio(parse_info)
|
121
123
|
if parse_info[:ratio]
|
122
|
-
if parse_info[:ratio].to_s.length > 0
|
123
|
-
parse_info[:ratio].to_s.sub(/^,\s+/,
|
124
|
-
else
|
125
|
-
nil
|
124
|
+
if (parse_info[:ratio].to_s.length > 0) && (parse_info[:ratio].to_s != ", ")
|
125
|
+
parse_info[:ratio].to_s.sub(/^,\s+/, "").sub(/,\s+$/, "")
|
126
126
|
end
|
127
|
-
else
|
128
|
-
nil
|
129
127
|
end
|
130
128
|
end
|
131
129
|
|
132
|
-
def
|
133
|
-
return unless /^E \d\d\d/.match(substance.name)
|
130
|
+
def self.check_e_substance(substance)
|
131
|
+
return unless /^E \d\d\d/.match?(substance.name)
|
134
132
|
unless substance.more_info
|
135
133
|
case substance.name[2]
|
136
134
|
when "1"
|
137
|
-
substance.more_info =
|
135
|
+
substance.more_info = "color."
|
138
136
|
when "2"
|
139
|
-
substance.more_info =
|
140
|
-
else
|
137
|
+
substance.more_info = "conserv."
|
141
138
|
end
|
142
139
|
substance.more_info ||= @@more_info
|
143
140
|
end
|
144
141
|
@@more_info = substance.more_info
|
145
142
|
end
|
146
143
|
|
147
|
-
def
|
148
|
-
@@more_info
|
149
|
-
@@excipiens
|
150
|
-
@@excipiens.dose
|
144
|
+
def self.add_excipiens(info)
|
145
|
+
@@more_info = nil
|
146
|
+
@@excipiens = ParseSubstance.new(info[:excipiens_description] || "Excipiens")
|
147
|
+
@@excipiens.dose = info[:dose] if info[:dose]
|
151
148
|
@@excipiens.more_info = CompositionTransformer.get_ratio(info)
|
152
|
-
@@excipiens.cdose
|
149
|
+
@@excipiens.cdose = info[:dose_corresp] if info[:dose_corresp]
|
153
150
|
@@excipiens.more_info = info[:more_info] if info[:more_info]
|
154
151
|
end
|
155
152
|
|
156
|
-
rule(:
|
157
|
-
|
158
|
-
|
159
|
-
puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
|
160
|
-
@@corresp = dictionary[:corresp].to_s
|
153
|
+
rule(corresp: simple(:corresp)) { |dictionary|
|
154
|
+
puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
|
155
|
+
@@corresp = dictionary[:corresp].to_s
|
161
156
|
}
|
162
|
-
rule(
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
@@substances << substance
|
170
|
-
substance
|
157
|
+
rule(substance_name: simple(:substance_name),
|
158
|
+
dose: simple(:dose)) { |dictionary|
|
159
|
+
puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
|
160
|
+
dose = dictionary[:dose].is_a?(ParseDose) ? dictionary[:dose] : nil
|
161
|
+
substance = ParseSubstance.new(dictionary[:substance_name], dose)
|
162
|
+
@@substances << substance
|
163
|
+
substance
|
171
164
|
}
|
172
165
|
|
173
|
-
rule(
|
174
|
-
|
175
|
-
|
176
|
-
puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
|
177
|
-
@@corresp = dictionary[:more_info].to_s.strip.sub(/:$/, '')
|
166
|
+
rule(more_info: simple(:more_info)) { |dictionary|
|
167
|
+
puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
|
168
|
+
@@corresp = dictionary[:more_info].to_s.strip.sub(/:$/, "")
|
178
169
|
}
|
179
|
-
rule(
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
@@substances << substance
|
190
|
-
substance
|
170
|
+
rule(more_info: simple(:more_info),
|
171
|
+
substance_name: simple(:substance_name),
|
172
|
+
dose: simple(:dose)) { |dictionary|
|
173
|
+
puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
|
174
|
+
dose = dictionary[:dose].is_a?(ParseDose) ? dictionary[:dose] : nil
|
175
|
+
substance = ParseSubstance.new(dictionary[:substance_name].to_s, dose)
|
176
|
+
substance.more_info = dictionary[:more_info].to_s.strip.sub(/:$/, "") if dictionary[:more_info] && (dictionary[:more_info].to_s.length > 0)
|
177
|
+
CompositionTransformer.check_e_substance(substance)
|
178
|
+
@@substances << substance
|
179
|
+
substance
|
191
180
|
}
|
192
181
|
|
193
|
-
rule(:
|
194
|
-
:
|
195
|
-
:
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
substance
|
182
|
+
rule(lebensmittel_zusatz: simple(:lebensmittel_zusatz),
|
183
|
+
more_info: simple(:more_info),
|
184
|
+
digits: simple(:digits)) { |dictionary|
|
185
|
+
puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
|
186
|
+
substance = ParseSubstance.new("#{dictionary[:lebensmittel_zusatz]} #{dictionary[:digits]}")
|
187
|
+
substance.more_info = dictionary[:more_info].to_s.strip.sub(/:$/, "") if dictionary[:more_info] && (dictionary[:more_info].to_s.length > 0)
|
188
|
+
CompositionTransformer.check_e_substance(substance)
|
189
|
+
@@substances << substance
|
190
|
+
substance
|
203
191
|
}
|
204
|
-
rule(:
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
end
|
217
|
-
nil
|
192
|
+
rule(excipiens: subtree(:excipiens)) { |dictionary|
|
193
|
+
puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
|
194
|
+
info = dictionary[:excipiens].is_a?(Hash) ? dictionary[:excipiens] : dictionary[:excipiens].first
|
195
|
+
if info[:excipiens_description] ||
|
196
|
+
info[:dose] ||
|
197
|
+
info[:dose_corresp] ||
|
198
|
+
info[:more_info] ||
|
199
|
+
CompositionTransformer.get_ratio(dictionary)
|
200
|
+
CompositionTransformer.add_excipiens(info)
|
201
|
+
info
|
202
|
+
end
|
203
|
+
nil
|
218
204
|
}
|
219
|
-
rule(:
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
:
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
dictionary[:substance].salts << dictionary[:substance_ut].last
|
250
|
-
@@substances -= dictionary[:substance_ut]
|
251
|
-
end
|
252
|
-
dictionary[:substance]
|
205
|
+
rule(composition: subtree(:composition)) { |dictionary|
|
206
|
+
puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
|
207
|
+
info = dictionary[:composition].is_a?(Hash) ? dictionary[:composition] : dictionary[:composition].first
|
208
|
+
CompositionTransformer.add_excipiens(info) if info.is_a?(Hash)
|
209
|
+
info
|
210
|
+
}
|
211
|
+
rule(substance: simple(:substance),
|
212
|
+
chemical_substance: simple(:chemical_substance),
|
213
|
+
substance_ut: sequence(:substance_ut),
|
214
|
+
ratio: simple(:ratio)) { |dictionary|
|
215
|
+
puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
|
216
|
+
ratio = CompositionTransformer.get_ratio(dictionary)
|
217
|
+
if ratio && (ratio.length > 0)
|
218
|
+
if dictionary[:substance].more_info
|
219
|
+
dictionary[:substance].more_info += " " + ratio.strip
|
220
|
+
else
|
221
|
+
dictionary[:substance].more_info = ratio.strip
|
222
|
+
end
|
223
|
+
end
|
224
|
+
if dictionary[:chemical_substance]
|
225
|
+
dictionary[:substance].chemical_substance = dictionary[:chemical_substance]
|
226
|
+
@@substances -= [dictionary[:chemical_substance]]
|
227
|
+
end
|
228
|
+
if dictionary[:substance_ut].size > 0
|
229
|
+
dictionary[:substance].salts += dictionary[:substance_ut].last.salts
|
230
|
+
dictionary[:substance_ut].last.salts = []
|
231
|
+
dictionary[:substance].salts << dictionary[:substance_ut].last
|
232
|
+
@@substances -= dictionary[:substance_ut]
|
233
|
+
end
|
234
|
+
dictionary[:substance]
|
253
235
|
}
|
254
236
|
|
255
|
-
rule(:
|
256
|
-
rule(:
|
257
|
-
|
237
|
+
rule(int: simple(:int)) { IntLit.new(int) }
|
238
|
+
rule(number: simple(:nb)) {
|
239
|
+
/[eE.]/.match?(nb) ? Float(nb) : Integer(nb)
|
258
240
|
}
|
259
241
|
rule(
|
260
|
-
:
|
261
|
-
:
|
262
|
-
|
263
|
-
|
242
|
+
qty_range: simple(:qty_range),
|
243
|
+
unit: simple(:unit)
|
244
|
+
) {
|
245
|
+
ParseDose.new(qty_range, unit)
|
246
|
+
}
|
264
247
|
rule(
|
265
|
-
:
|
266
|
-
|
267
|
-
|
248
|
+
qty_range: simple(:qty_range)
|
249
|
+
) {
|
250
|
+
ParseDose.new(qty_range)
|
251
|
+
}
|
268
252
|
rule(
|
269
|
-
:
|
270
|
-
:
|
271
|
-
|
272
|
-
|
253
|
+
qty: simple(:qty),
|
254
|
+
unit: simple(:unit)
|
255
|
+
) {
|
256
|
+
ParseDose.new(qty, unit)
|
257
|
+
}
|
273
258
|
rule(
|
274
|
-
:
|
259
|
+
unit: simple(:unit)
|
260
|
+
) { ParseDose.new(nil, unit) }
|
275
261
|
rule(
|
276
|
-
:
|
262
|
+
qty: simple(:qty)
|
263
|
+
) { ParseDose.new(qty, nil) }
|
277
264
|
rule(
|
278
|
-
:
|
279
|
-
:
|
280
|
-
:
|
281
|
-
)
|
265
|
+
qty: simple(:qty),
|
266
|
+
unit: simple(:unit),
|
267
|
+
dose_right: simple(:dose_right)
|
268
|
+
) {
|
282
269
|
dose = ParseDose.new(qty, unit)
|
283
|
-
dose.unit = dose.unit.to_s +
|
270
|
+
dose.unit = dose.unit.to_s + " et " + ParseDose.new(dose_right).to_s
|
284
271
|
dose
|
285
272
|
}
|
286
273
|
|
287
274
|
@@substances ||= []
|
288
|
-
@@excipiens
|
289
|
-
def
|
290
|
-
@@more_info
|
275
|
+
@@excipiens = nil
|
276
|
+
def self.clear_substances
|
277
|
+
@@more_info = nil
|
291
278
|
@@substances = []
|
292
|
-
@@excipiens
|
293
|
-
@@corresp
|
279
|
+
@@excipiens = nil
|
280
|
+
@@corresp = nil
|
294
281
|
end
|
295
|
-
|
282
|
+
|
283
|
+
def self.substances
|
296
284
|
@@substances.clone
|
297
285
|
end
|
298
|
-
|
286
|
+
|
287
|
+
def self.excipiens
|
299
288
|
@@excipiens ? @@excipiens.clone : nil
|
300
289
|
end
|
301
|
-
|
290
|
+
|
291
|
+
def self.corresp
|
302
292
|
@@corresp ? @@corresp.clone : nil
|
303
293
|
end
|
304
294
|
end
|
@@ -306,29 +296,31 @@ end
|
|
306
296
|
class ParseDose
|
307
297
|
attr_reader :qty, :qty_range
|
308
298
|
attr_accessor :unit
|
309
|
-
def initialize(qty=nil, unit=nil)
|
299
|
+
def initialize(qty = nil, unit = nil)
|
310
300
|
puts "ParseDose.new from #{qty.inspect} #{unit.inspect} #{unit.inspect}" if VERBOSE_MESSAGES
|
311
|
-
if qty
|
312
|
-
string = qty.to_s.
|
313
|
-
if string.index(
|
301
|
+
if qty && (qty.is_a?(String) || qty.is_a?(Parslet::Slice))
|
302
|
+
string = qty.to_s.delete("'")
|
303
|
+
if string.index("-") && (string.index("-") > 0)
|
314
304
|
@qty_range = string
|
315
305
|
elsif string.index(/\^|\*|\//)
|
316
|
-
@qty
|
306
|
+
@qty = string
|
317
307
|
else
|
318
|
-
@qty
|
308
|
+
@qty = string.index(".") ? string.to_f : string.to_i
|
319
309
|
end
|
320
310
|
elsif qty
|
321
|
-
@qty
|
311
|
+
@qty = qty.eval
|
322
312
|
else
|
323
313
|
@qty = 1
|
324
314
|
end
|
325
315
|
@unit = unit ? unit.to_s : nil
|
326
316
|
end
|
317
|
+
|
327
318
|
def eval
|
328
319
|
self
|
329
320
|
end
|
321
|
+
|
330
322
|
def to_s
|
331
|
-
return @unit unless @qty
|
323
|
+
return @unit unless @qty || @qty_range
|
332
324
|
res = "#{@qty}#{@qty_range}"
|
333
325
|
res = "#{res} #{@unit}" if @unit
|
334
326
|
res
|
@@ -336,32 +328,36 @@ class ParseDose
|
|
336
328
|
end
|
337
329
|
|
338
330
|
class ParseSubstance
|
339
|
-
attr_accessor
|
340
|
-
attr_accessor
|
341
|
-
|
331
|
+
attr_accessor :name, :chemical_substance, :chemical_qty, :chemical_unit, :is_active_agent, :dose, :cdose, :is_excipiens
|
332
|
+
attr_accessor :description, :more_info, :salts
|
333
|
+
attr_writer :unit, :qty
|
334
|
+
def initialize(name, dose = nil)
|
342
335
|
puts "ParseSubstance.new from #{name.inspect} #{dose.inspect}" if VERBOSE_MESSAGES
|
343
336
|
@name = ParseUtil.capitalize(name.to_s)
|
344
|
-
@name.sub!(/\baqua\b/i,
|
345
|
-
@name.sub!(/\bDER\b/i,
|
346
|
-
@name.sub!(/\bad pulverem\b/i,
|
347
|
-
@name.sub!(/\bad iniectabilia\b/i,
|
348
|
-
@name.sub!(/\bad suspensionem\b/i,
|
349
|
-
@name.sub!(/\bad solutionem\b/i,
|
350
|
-
@name.sub!(/\bpro compresso\b/i,
|
351
|
-
@name.sub!(/\bpro\b/i,
|
352
|
-
@name.sub!(/ Q\.S\. /i,
|
353
|
-
@name.sub!(/\s+\bpro$/i,
|
337
|
+
@name.sub!(/\baqua\b/i, "aqua")
|
338
|
+
@name.sub!(/\bDER\b/i, "DER")
|
339
|
+
@name.sub!(/\bad pulverem\b/i, "ad pulverem")
|
340
|
+
@name.sub!(/\bad iniectabilia\b/i, "ad iniectabilia")
|
341
|
+
@name.sub!(/\bad suspensionem\b/i, "ad suspensionem")
|
342
|
+
@name.sub!(/\bad solutionem\b/i, "ad solutionem")
|
343
|
+
@name.sub!(/\bpro compresso\b/i, "pro compresso")
|
344
|
+
@name.sub!(/\bpro\b/i, "pro")
|
345
|
+
@name.sub!(/ Q\.S\. /i, " q.s. ")
|
346
|
+
@name.sub!(/\s+\bpro$/i, "")
|
354
347
|
@dose = dose if dose
|
355
348
|
@salts = []
|
356
349
|
end
|
350
|
+
|
357
351
|
def qty
|
358
|
-
return @dose.qty_range if @dose
|
352
|
+
return @dose.qty_range if @dose&.qty_range
|
359
353
|
@dose ? @dose.qty : @qty
|
360
354
|
end
|
355
|
+
|
361
356
|
def unit
|
362
357
|
return @unit if @unit
|
363
358
|
@dose ? @dose.unit : @unit
|
364
359
|
end
|
360
|
+
|
365
361
|
def to_string
|
366
362
|
s = "#{@name}:"
|
367
363
|
s = " #{@qty}" if @qty
|
@@ -372,51 +368,55 @@ class ParseSubstance
|
|
372
368
|
end
|
373
369
|
|
374
370
|
class ParseComposition
|
375
|
-
attr_accessor
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
371
|
+
attr_accessor :source, :label, :label_description, :substances, :galenic_form, :route_of_administration,
|
372
|
+
:corresp, :excipiens
|
373
|
+
|
374
|
+
ERRORS_TO_FIX = {
|
375
|
+
/(\d+)\s+-\s*(\d+)/ => '\1-\2',
|
376
|
+
"o.1" => "0.1",
|
377
|
+
/polymerisat(i|um) \d:\d/ => "polymerisatum",
|
378
|
+
/\s+(mg|g) DER:/ => ' \1, DER:',
|
379
|
+
" mind. " => " min. ",
|
380
|
+
" streptococci pyogen. " => " streptococci pyogen ",
|
381
|
+
" ut excipiens" => ", excipiens",
|
382
|
+
" Corresp. " => " corresp. ",
|
383
|
+
",," => ",",
|
384
|
+
"avena elatior,dactylis glomerata" => "avena elatior, dactylis glomerata",
|
385
|
+
" color.: corresp. " => " corresp.",
|
386
|
+
/ U\.: (excipiens) / => ' U. \1 ',
|
387
|
+
/ U\.: (alnus|betula|betula|betulae) / => ' U., \1 ',
|
388
|
+
/^(acari allergeni extractum (\(acarus siro\)|).+\s+U\.:)/ => 'A): \1',
|
389
|
+
"Solvens: alprostadilum" => "alprostadilum",
|
390
|
+
}
|
391
|
+
@@error_handler = ParseUtil::HandleSwissmedicErrors.new(ERRORS_TO_FIX)
|
394
392
|
|
395
393
|
def initialize(source)
|
396
394
|
@substances ||= []
|
397
395
|
puts "ParseComposition.new from #{source.inspect} @substances #{@substances.inspect}" if VERBOSE_MESSAGES
|
398
396
|
@source = source.to_s
|
399
397
|
end
|
400
|
-
|
401
|
-
|
398
|
+
|
399
|
+
def self.reset
|
400
|
+
@@error_handler = ParseUtil::HandleSwissmedicErrors.new(ERRORS_TO_FIX)
|
402
401
|
end
|
403
|
-
|
404
|
-
|
402
|
+
|
403
|
+
def self.report
|
404
|
+
@@error_handler.report
|
405
405
|
end
|
406
|
-
|
407
|
-
|
408
|
-
|
406
|
+
|
407
|
+
def self.from_string(string)
|
408
|
+
return nil if string.nil? || string.eql?(".") || string.eql?("")
|
409
|
+
stripped = string.gsub(/^"|["\n]+$/, "")
|
409
410
|
return nil unless stripped
|
410
|
-
if /(U\.I\.|U\.)$/.match(stripped)
|
411
|
-
|
411
|
+
cleaned = if /(U\.I\.|U\.)$/.match?(stripped)
|
412
|
+
stripped
|
412
413
|
else
|
413
|
-
|
414
|
+
stripped.sub(/\.+$/, "")
|
414
415
|
end
|
415
|
-
value = nil
|
416
416
|
puts "ParseComposition.from_string #{string}" if VERBOSE_MESSAGES # /ng-tr/.match(Socket.gethostbyname(Socket.gethostname).first)
|
417
417
|
|
418
|
-
cleaned = @@
|
419
|
-
puts "ParseComposition.new cleaned #{cleaned}" if VERBOSE_MESSAGES
|
418
|
+
cleaned = @@error_handler.apply_fixes(cleaned)
|
419
|
+
puts "ParseComposition.new cleaned #{cleaned}" if VERBOSE_MESSAGES && !cleaned.eql?(stripped)
|
420
420
|
CompositionTransformer.clear_substances
|
421
421
|
result = ParseComposition.new(cleaned)
|
422
422
|
parser = CompositionParser.new
|
@@ -430,8 +430,8 @@ class ParseComposition
|
|
430
430
|
ast = transf.apply(parser.parse(cleaned))
|
431
431
|
end
|
432
432
|
rescue Parslet::ParseFailed => error
|
433
|
-
@@
|
434
|
-
puts "#{File.basename(__FILE__)}:#{__LINE__}: failed parsing ==> #{cleaned}"
|
433
|
+
@@error_handler.nrParsingErrors += 1
|
434
|
+
puts "#{File.basename(__FILE__)}:#{__LINE__}: failed parsing ==> #{cleaned} #{error}"
|
435
435
|
return nil
|
436
436
|
end
|
437
437
|
result.source = string
|
@@ -441,50 +441,46 @@ class ParseComposition
|
|
441
441
|
result.substances = CompositionTransformer.substances
|
442
442
|
result.excipiens = CompositionTransformer.excipiens
|
443
443
|
result.corresp = CompositionTransformer.corresp if CompositionTransformer.corresp
|
444
|
-
if result
|
445
|
-
pro_qty = "/#{result.excipiens.qty} #{result.excipiens.unit}".sub(/\/1\s+/,
|
446
|
-
result.substances.each {
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
substance.dose.unit = "#{substance.dose.unit}#{pro_qty}" if substance.unit and not substance.unit.eql?(result.excipiens.unit)
|
444
|
+
if result&.excipiens&.unit
|
445
|
+
pro_qty = "/#{result.excipiens.qty} #{result.excipiens.unit}".sub(/\/1\s+/, "/")
|
446
|
+
result.substances.each { |substance|
|
447
|
+
next unless substance.is_a?(ParseSubstance)
|
448
|
+
substance.chemical_substance.unit = "#{substance.chemical_substance.unit}#{pro_qty}" if substance.chemical_substance
|
449
|
+
substance.dose.unit = "#{substance.dose.unit}#{pro_qty}" if substance.unit && !substance.unit.eql?(result.excipiens.unit)
|
451
450
|
}
|
452
451
|
end
|
453
|
-
if ast.is_a?(Array)
|
452
|
+
if ast.is_a?(Array) && ast.first.is_a?(Hash)
|
454
453
|
label = ast.first[:label].to_s if ast.first[:label]
|
455
454
|
label_description = ast.first[:label_description].to_s if ast.first[:label_description]
|
456
|
-
elsif ast
|
457
|
-
label = ast[:label].to_s if
|
455
|
+
elsif ast&.is_a?(Hash)
|
456
|
+
label = ast[:label].to_s if ast[:label]
|
458
457
|
label_description = ast[:label_description].to_s if ast[:label_description]
|
459
458
|
end
|
460
459
|
if label
|
461
|
-
if label
|
462
|
-
result.label
|
460
|
+
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)
|
461
|
+
result.label = label
|
463
462
|
end
|
464
|
-
result.label_description = label_description.gsub(/:+$/,
|
463
|
+
result.label_description = label_description.gsub(/:+$/, "").strip if label_description
|
465
464
|
end
|
466
|
-
result.corresp = ast[:corresp].to_s.sub(/:\s+/,
|
467
|
-
|
465
|
+
result.corresp = ast[:corresp].to_s.sub(/:\s+/, "") if !result.corresp && ast.is_a?(Hash) && ast[:corresp]
|
466
|
+
result
|
468
467
|
end
|
469
468
|
end
|
470
469
|
|
471
470
|
class GalenicFormTransformer < CompositionTransformer
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
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
|
471
|
+
rule(preparation_name: simple(:preparation_name),
|
472
|
+
galenic_form: simple(:preparation_name)) { |dictionary|
|
473
|
+
puts "#{File.basename(__FILE__)}:#{__LINE__}: dictionary #{dictionary}" if VERBOSE_MESSAGES
|
474
|
+
dictionary[:preparation_name] ? dictionary[:preparation_name].to_s : nil
|
475
|
+
dictionary[:galenic_form] ? dictionary[:galenic_form].to_s : nil
|
476
|
+
# name, form
|
481
477
|
}
|
482
478
|
end
|
483
479
|
|
484
480
|
class ParseGalenicForm
|
485
|
-
def
|
486
|
-
return nil if string
|
487
|
-
stripped = string.gsub(/^"|["\n]+$/,
|
481
|
+
def self.from_string(string)
|
482
|
+
return nil if string.nil?
|
483
|
+
stripped = string.gsub(/^"|["\n]+$/, "")
|
488
484
|
return nil unless stripped
|
489
485
|
puts "ParseGalenicForm.from_string #{string}" if VERBOSE_MESSAGES # /ng-tr/.match(Socket.gethostbyname(Socket.gethostname).first)
|
490
486
|
|
@@ -499,13 +495,13 @@ class ParseGalenicForm
|
|
499
495
|
ast = transf.apply(parser.parse(string))
|
500
496
|
end
|
501
497
|
rescue Parslet::ParseFailed => error
|
502
|
-
@@
|
503
|
-
puts "#{File.basename(__FILE__)}:#{__LINE__}: failed parsing ==> #{string}"
|
498
|
+
@@error_handler.nrParsingErrors += 1
|
499
|
+
puts "#{File.basename(__FILE__)}:#{__LINE__}: failed parsing ==> #{string} #{error}"
|
504
500
|
return nil
|
505
501
|
end
|
506
502
|
return [] unless ast
|
507
|
-
form = ast[:galenic_form] ? ast[:galenic_form].to_s.sub(/^\/\s+/,
|
503
|
+
form = ast[:galenic_form] ? ast[:galenic_form].to_s.sub(/^\/\s+/, "") : nil
|
508
504
|
name = ast[:prepation_name] ? ast[:prepation_name].to_s.strip : nil
|
509
|
-
|
505
|
+
[name, form]
|
510
506
|
end
|
511
507
|
end
|